Files
cromite/build/patches/eyeo-133.0.6943.49-base.patch
T

51824 lines
2.0 MiB
Plaintext

From: chromium-sdk <project_26591639_bot@noreply.gitlab.com>
Date: Fri, 14 Feb 2025 08:26:31 +0100
Subject: eyeo Browser Ad filtering Solution: Base Module
Based on Chromium 133.0.6943.49
---
DEPS | 10 +
LICENSE | 15 +
README.md | 12 +
base/threading/thread_restrictions.h | 9 +
base/trace_event/builtin_categories.h | 1 +
components/BUILD.gn | 7 +
components/adblock/BUILD.gn | 36 +
components/adblock/CHANGELOG.md | 746 +++++++++
components/adblock/LICENSE | 674 ++++++++
components/adblock/README.md | 80 +
components/adblock/content/BUILD.gn | 22 +
components/adblock/content/browser/BUILD.gn | 226 +++
.../browser/adblock_content_browser_client.h | 343 ++++
.../content/browser/adblock_context_data.cc | 87 +
.../content/browser/adblock_context_data.h | 63 +
.../content/browser/adblock_filter_match.h | 30 +
.../browser/adblock_internals_page_handler.cc | 171 ++
.../browser/adblock_internals_page_handler.h | 61 +
.../content/browser/adblock_internals_ui.cc | 67 +
.../content/browser/adblock_internals_ui.h | 49 +
.../browser/adblock_url_loader_factory.cc | 813 ++++++++++
.../browser/adblock_url_loader_factory.h | 100 ++
.../adblock_url_loader_factory_for_test.cc | 466 ++++++
.../adblock_url_loader_factory_for_test.h | 78 +
.../adblock_web_ui_controller_factory.cc | 62 +
.../adblock_web_ui_controller_factory.h | 58 +
.../browser/adblock_webcontents_observer.cc | 217 +++
.../browser/adblock_webcontents_observer.h | 93 ++
.../content_security_policy_injector.h | 61 +
.../content_security_policy_injector_impl.cc | 111 ++
.../content_security_policy_injector_impl.h | 65 +
.../adblock/content/browser/element_hider.h | 70 +
.../content/browser/element_hider_impl.cc | 367 +++++
.../content/browser/element_hider_impl.h | 55 +
.../content/browser/eyeo_document_info.cc | 50 +
.../content/browser/eyeo_document_info.h | 58 +
.../adblock/content/browser/eyeo_page_info.cc | 38 +
.../adblock/content/browser/eyeo_page_info.h | 53 +
.../adblock_request_throttle_factory.cc | 72 +
.../adblock_request_throttle_factory.h | 51 +
.../adblock_telemetry_service_factory.cc | 122 ++
.../adblock_telemetry_service_factory.h | 55 +
...ontent_security_policy_injector_factory.cc | 71 +
...content_security_policy_injector_factory.h | 49 +
.../factories/element_hider_factory.cc | 65 +
.../browser/factories/element_hider_factory.h | 47 +
.../browser/factories/embedding_utils.cc | 42 +
.../browser/factories/embedding_utils.h | 50 +
.../resource_classification_runner_factory.cc | 73 +
.../resource_classification_runner_factory.h | 49 +
.../factories/session_stats_factory.cc | 66 +
.../browser/factories/session_stats_factory.h | 47 +
.../factories/sitekey_storage_factory.cc | 61 +
.../factories/sitekey_storage_factory.h | 47 +
...ubscription_persistent_metadata_factory.cc | 66 +
...subscription_persistent_metadata_factory.h | 49 +
.../factories/subscription_service_factory.cc | 500 ++++++
.../factories/subscription_service_factory.h | 76 +
.../browser/frame_hierarchy_builder.cc | 137 ++
.../content/browser/frame_hierarchy_builder.h | 66 +
.../content/browser/frame_opener_info.cc | 37 +
.../content/browser/frame_opener_info.h | 46 +
.../adblock/content/browser/mojom/BUILD.gn | 22 +
.../browser/mojom/adblock_internals.mojom | 22 +
.../content/browser/page_view_stats.cc | 291 ++++
.../adblock/content/browser/page_view_stats.h | 131 ++
.../content/browser/request_initiator.cc | 58 +
.../content/browser/request_initiator.h | 69 +
.../browser/resource_classification_runner.h | 107 ++
.../resource_classification_runner_impl.cc | 431 +++++
.../resource_classification_runner_impl.h | 153 ++
.../content/browser/session_stats_impl.cc | 87 +
.../content/browser/session_stats_impl.h | 71 +
.../adblock_acceptable_ads_browsertest.cc | 211 +++
.../browser/test/adblock_browsertest_base.cc | 219 +++
.../browser/test/adblock_browsertest_base.h | 128 ++
...lock_content_browser_client_browsertest.cc | 110 ++
.../adblock_content_filters_browsertest.cc | 290 ++++
.../test/adblock_debug_url_browsertest.cc | 331 ++++
.../test/adblock_filter_list_browsertest.cc | 710 +++++++++
...ck_filtering_configurations_browsertest.cc | 666 ++++++++
.../test/adblock_non_ascii_browsertest.cc | 67 +
.../adblock_page_view_stats_browsertest.cc | 840 ++++++++++
.../adblock_request_throttle_browsertest.cc | 127 ++
.../adblock_service_workers_browsertest.cc | 208 +++
.../test/adblock_sitekey_browsertest.cc | 177 +++
.../test/adblock_snippets_browsertest.cc | 76 +
...dblock_subscription_service_browsertest.cc | 202 +++
.../adblock_telemetry_service_browsertest.cc | 280 ++++
.../adblock_trusted_events_browsertest.cc | 135 ++
.../test/adblock_url_loader_factory_test.cc | 512 ++++++
.../test/adblock_web_bundle_browsertest.cc | 425 +++++
.../test/adblock_web_ui_browsertest.cc | 58 +
.../test/adblock_webcontents_observer_test.cc | 189 +++
...tent_security_policy_injector_impl_test.cc | 218 +++
.../browser/test/element_hider_impl_test.cc | 433 +++++
.../test/frame_hierarchy_builder_test.cc | 131 ++
...dblock_content_security_policy_injector.cc | 28 +
...adblock_content_security_policy_injector.h | 44 +
.../browser/test/mock_element_hider.cc | 26 +
.../content/browser/test/mock_element_hider.h | 55 +
.../test/mock_frame_hierarchy_builder.cc | 26 +
.../test/mock_frame_hierarchy_builder.h | 46 +
.../mock_resource_classification_runner.cc | 49 +
.../mock_resource_classification_runner.h | 96 ++
.../browser/test/page_view_stats_test.cc | 334 ++++
...esource_classification_runner_impl_test.cc | 422 +++++
.../browser/test/session_stats_impl_test.cc | 74 +
.../test/subscription_service_factory_test.cc | 488 ++++++
.../resources/adblock_internals/BUILD.gn | 32 +
.../adblock_internals/adblock_internals.html | 45 +
.../adblock_internals/adblock_internals.ts | 65 +
components/adblock/core/BUILD.gn | 156 ++
.../activeping_telemetry_topic_provider.cc | 299 ++++
.../activeping_telemetry_topic_provider.h | 94 ++
.../adblock/core/adblock_telemetry_service.cc | 308 ++++
.../adblock/core/adblock_telemetry_service.h | 132 ++
components/adblock/core/classifier/BUILD.gn | 77 +
.../core/classifier/resource_classifier.cc | 24 +
.../core/classifier/resource_classifier.h | 90 ++
.../classifier/resource_classifier_impl.cc | 406 +++++
.../classifier/resource_classifier_impl.h | 64 +
.../test/mock_resource_classifier.cc | 26 +
.../test/mock_resource_classifier.h | 66 +
.../test/resource_classifier_impl_test.cc | 626 ++++++++
.../test/resource_classifier_perftest.cc | 394 +++++
components/adblock/core/common/BUILD.gn | 139 ++
.../adblock/core/common/adblock_constants.cc | 174 ++
.../adblock/core/common/adblock_constants.h | 54 +
.../adblock/core/common/adblock_prefs.cc | 167 ++
.../adblock/core/common/adblock_prefs.h | 55 +
.../adblock/core/common/adblock_switches.cc | 28 +
.../adblock/core/common/adblock_switches.h | 31 +
.../adblock/core/common/adblock_utils.cc | 80 +
.../adblock/core/common/adblock_utils.h | 43 +
components/adblock/core/common/app_info.cc | 46 +
components/adblock/core/common/app_info.h | 43 +
.../adblock/core/common/content_type.cc | 91 ++
components/adblock/core/common/content_type.h | 48 +
.../adblock/core/common/flatbuffer_data.cc | 105 ++
.../adblock/core/common/flatbuffer_data.h | 90 ++
.../adblock/core/common/header_filter_data.h | 38 +
.../core/common/keyword_extractor_utils.cc | 29 +
.../core/common/keyword_extractor_utils.h | 31 +
.../core/common/regex_filter_pattern.cc | 31 +
.../core/common/regex_filter_pattern.h | 34 +
components/adblock/core/common/sitekey.h | 29 +
.../adblock/core/common/task_scheduler.h | 35 +
.../core/common/task_scheduler_impl.cc | 59 +
.../adblock/core/common/task_scheduler_impl.h | 50 +
.../core/common/test/flatbuffer_data_test.cc | 80 +
.../core/common/test/mock_task_scheduler.cc | 25 +
.../core/common/test/mock_task_scheduler.h | 40 +
.../common/test/task_scheduler_impl_test.cc | 82 +
.../adblock/core/common/web_ui_constants.cc | 24 +
.../adblock/core/common/web_ui_constants.h | 27 +
.../adblock/core/configuration/BUILD.gn | 60 +
.../configuration/filtering_configuration.h | 91 ++
.../persistent_filtering_configuration.cc | 270 ++++
.../persistent_filtering_configuration.h | 84 +
.../test/fake_filtering_configuration.cc | 124 ++
.../test/fake_filtering_configuration.h | 68 +
.../test/mock_filtering_configuration.cc | 34 +
.../test/mock_filtering_configuration.h | 82 +
...persistent_filtering_configuration_test.cc | 255 +++
components/adblock/core/converter/BUILD.gn | 67 +
.../adblock/core/converter/converter_main.cc | 117 ++
.../core/converter/flatbuffer_converter.cc | 153 ++
.../core/converter/flatbuffer_converter.h | 63 +
.../adblock/core/converter/parser/BUILD.gn | 67 +
.../core/converter/parser/content_filter.cc | 100 ++
.../core/converter/parser/content_filter.h | 53 +
.../core/converter/parser/domain_option.cc | 146 ++
.../core/converter/parser/domain_option.h | 62 +
.../converter/parser/filter_classifier.cc | 52 +
.../core/converter/parser/filter_classifier.h | 49 +
.../adblock/core/converter/parser/metadata.cc | 145 ++
.../adblock/core/converter/parser/metadata.h | 64 +
.../core/converter/parser/snippet_filter.cc | 62 +
.../core/converter/parser/snippet_filter.h | 48 +
.../converter/parser/snippet_tokenizer.cc | 103 ++
.../core/converter/parser/snippet_tokenizer.h | 46 +
.../parser/test/content_filter_test.cc | 263 +++
.../parser/test/domain_option_test.cc | 214 +++
.../parser/test/filter_classifier_test.cc | 80 +
.../converter/parser/test/metadata_test.cc | 198 +++
.../parser/test/snippet_filter_test.cc | 79 +
.../parser/test/snippet_tokenizer_test.cc | 141 ++
.../parser/test/url_filter_options_test.cc | 258 +++
.../converter/parser/test/url_filter_test.cc | 163 ++
.../core/converter/parser/url_filter.cc | 232 +++
.../core/converter/parser/url_filter.h | 50 +
.../converter/parser/url_filter_options.cc | 267 ++++
.../converter/parser/url_filter_options.h | 122 ++
.../core/converter/serializer/BUILD.gn | 46 +
.../serializer/filter_keyword_extractor.cc | 63 +
.../serializer/filter_keyword_extractor.h | 59 +
.../serializer/flatbuffer_serializer.cc | 490 ++++++
.../serializer/flatbuffer_serializer.h | 161 ++
.../core/converter/serializer/serializer.h | 41 +
.../test/filter_keyword_extractor_test.cc | 68 +
.../test/flatbuffer_converter_perftest.cc | 83 +
components/adblock/core/features.cc | 26 +
components/adblock/core/features.h | 31 +
components/adblock/core/net/BUILD.gn | 68 +
.../core/net/adblock_request_throttle.h | 45 +
.../core/net/adblock_request_throttle_impl.cc | 71 +
.../core/net/adblock_request_throttle_impl.h | 54 +
.../core/net/adblock_resource_request.h | 61 +
.../core/net/adblock_resource_request_impl.cc | 217 +++
.../core/net/adblock_resource_request_impl.h | 78 +
.../adblock_request_throttle_impl_test.cc | 133 ++
.../adblock_resource_request_impl_test.cc | 414 +++++
.../net/test/mock_adblock_request_throttle.cc | 45 +
.../net/test/mock_adblock_request_throttle.h | 42 +
.../net/test/mock_adblock_resource_request.cc | 25 +
.../net/test/mock_adblock_resource_request.h | 49 +
components/adblock/core/resources/.gitignore | 1 +
components/adblock/core/resources/BUILD.gn | 95 ++
.../core/resources/adblock_resources.grd | 38 +
components/adblock/core/resources/elemhide.js | 43 +
.../core/resources/elemhide_for_selector.jst | 53 +
.../adblock/core/resources/elemhideemu.jst | 1410 +++++++++++++++++
.../adblock/core/resources/snippets_deps.py | 71 +
components/adblock/core/resources/update.sh | 33 +
.../core/schema/filter_list_schema.fbs | 190 +++
components/adblock/core/schema/schema_hash.h | 30 +
components/adblock/core/session_stats.h | 42 +
components/adblock/core/sitekey_storage.h | 53 +
.../adblock/core/sitekey_storage_impl.cc | 164 ++
.../adblock/core/sitekey_storage_impl.h | 59 +
components/adblock/core/subscription/BUILD.gn | 186 +++
.../core/subscription/conversion_executors.h | 50 +
.../core/subscription/domain_splitter.cc | 74 +
.../core/subscription/domain_splitter.h | 56 +
.../filtering_configuration_maintainer.h | 55 +
...filtering_configuration_maintainer_impl.cc | 516 ++++++
.../filtering_configuration_maintainer_impl.h | 124 ++
.../subscription/installed_subscription.cc | 46 +
.../subscription/installed_subscription.h | 147 ++
.../installed_subscription_impl.cc | 638 ++++++++
.../installed_subscription_impl.h | 147 ++
.../core/subscription/pattern_matcher.cc | 277 ++++
.../core/subscription/pattern_matcher.h | 35 +
.../preloaded_subscription_provider.h | 56 +
.../preloaded_subscription_provider_impl.cc | 121 ++
.../preloaded_subscription_provider_impl.h | 50 +
.../recommended_subscription_installer.h | 43 +
...recommended_subscription_installer_impl.cc | 141 ++
.../recommended_subscription_installer_impl.h | 73 +
.../recommended_subscription_parser.cc | 82 +
.../recommended_subscription_parser.h | 35 +
.../core/subscription/regex_matcher.cc | 162 ++
.../adblock/core/subscription/regex_matcher.h | 75 +
.../adblock/core/subscription/subscription.cc | 24 +
.../adblock/core/subscription/subscription.h | 81 +
.../subscription/subscription_collection.h | 98 ++
.../subscription_collection_impl.cc | 382 +++++
.../subscription_collection_impl.h | 98 ++
.../core/subscription/subscription_config.cc | 426 +++++
.../core/subscription/subscription_config.h | 131 ++
.../subscription/subscription_downloader.h | 60 +
.../subscription_downloader_impl.cc | 279 ++++
.../subscription_downloader_impl.h | 97 ++
.../subscription_persistent_metadata.h | 99 ++
.../subscription_persistent_metadata_impl.cc | 224 +++
.../subscription_persistent_metadata_impl.h | 68 +
.../subscription_persistent_storage.h | 60 +
.../subscription_persistent_storage_impl.cc | 237 +++
.../subscription_persistent_storage_impl.h | 80 +
.../core/subscription/subscription_service.h | 92 ++
.../subscription/subscription_service_impl.cc | 265 ++++
.../subscription/subscription_service_impl.h | 101 ++
.../subscription/subscription_validator.h | 59 +
.../subscription_validator_impl.cc | 144 ++
.../subscription_validator_impl.h | 53 +
.../subscription/test/domain_splitter_test.cc | 64 +
...ring_configuration_maintainer_impl_test.cc | 991 ++++++++++++
.../installed_subscription_impl_csp_test.cc | 236 +++
...stalled_subscription_impl_elemhide_test.cc | 566 +++++++
...installed_subscription_impl_header_test.cc | 297 ++++
...d_subscription_impl_list_converter_test.cc | 107 ++
...stalled_subscription_impl_metadata_test.cc | 129 ++
...nstalled_subscription_impl_rewrite_test.cc | 190 +++
...stalled_subscription_impl_snippets_test.cc | 196 +++
.../installed_subscription_impl_test_base.cc | 78 +
.../installed_subscription_impl_test_base.h | 54 +
.../installed_subscription_impl_url_test.cc | 1307 +++++++++++++++
.../test/load_gzipped_test_file.cc | 43 +
.../test/load_gzipped_test_file.h | 34 +
.../test/mock_conversion_executors.cc | 25 +
.../test/mock_conversion_executors.h | 46 +
...mock_filtering_configuration_maintainer.cc | 28 +
.../mock_filtering_configuration_maintainer.h | 48 +
.../test/mock_installed_subscription.cc | 26 +
.../test/mock_installed_subscription.h | 102 ++
.../subscription/test/mock_subscription.cc | 36 +
.../subscription/test/mock_subscription.h | 51 +
.../test/mock_subscription_collection.cc | 26 +
.../test/mock_subscription_collection.h | 102 ++
.../test/mock_subscription_downloader.cc | 25 +
.../test/mock_subscription_downloader.h | 48 +
.../mock_subscription_persistent_metadata.cc | 27 +
.../mock_subscription_persistent_metadata.h | 92 ++
.../test/mock_subscription_service.cc | 36 +
.../test/mock_subscription_service.h | 77 +
.../test/pattern_matcher_perftest.cc | 73 +
.../subscription/test/pattern_matcher_test.cc | 376 +++++
...eloaded_subscription_provider_impl_test.cc | 149 ++
...mended_subscription_installer_impl_test.cc | 325 ++++
.../recommended_subscription_parser_test.cc | 117 ++
.../test/regex_matcher_perftest.cc | 86 +
.../test/subscription_collection_impl_test.cc | 1021 ++++++++++++
.../test/subscription_downloader_impl_test.cc | 445 ++++++
...scription_persistent_metadata_impl_test.cc | 268 ++++
...bscription_persistent_storage_impl_test.cc | 346 ++++
.../test/subscription_service_impl_test.cc | 378 +++++
.../test/subscription_validator_impl_test.cc | 251 +++
.../test/url_keyword_extractor_test.cc | 74 +
.../subscription/url_keyword_extractor.cc | 66 +
.../core/subscription/url_keyword_extractor.h | 59 +
...ctiveping_telemetry_topic_provider_test.cc | 325 ++++
.../adblock_telemetry_service_unittest.cc | 460 ++++++
.../core/test/bundled_subscription_test.cc | 49 +
.../adblock/core/test/mock_sitekey_storage.cc | 26 +
.../adblock/core/test/mock_sitekey_storage.h | 49 +
.../core/test/sitekey_storage_impl_test.cc | 155 ++
components/adblock/features.gni | 51 +
.../loader/navigation_url_loader_impl.cc | 6 +-
.../renderer_host/render_frame_host_impl.cc | 10 +
.../renderer_host/render_frame_host_impl.h | 8 +
content/public/browser/render_frame_host.h | 7 +
content/public/common/isolated_world_ids.h | 8 +
content/public/test/fake_local_frame.cc | 7 +
content/public/test/fake_local_frame.h | 5 +
content/shell/BUILD.gn | 16 +
content/shell/app/shell_main_delegate.cc | 8 +-
.../adblock/adblock_shell_browser_context.cc | 69 +
.../adblock/adblock_shell_browser_context.h | 38 +
.../adblock_shell_content_browser_client.cc | 30 +
.../adblock_shell_content_browser_client.h | 35 +
content/shell/browser/shell.cc | 9 +
content/shell/browser/shell_browser_context.h | 7 +
.../shell/browser/shell_browser_main_parts.cc | 10 +-
...web_request_proxying_url_loader_factory.cc | 5 +
net/url_request/redirect_info.h | 8 +
.../network/public/cpp/net_ipc_param_traits.h | 5 +
.../blink/public/mojom/frame/frame.mojom | 8 +
third_party/blink/public/web/web_document.h | 12 +
.../renderer/core/dom/events/event_target.cc | 9 +-
.../renderer/core/exported/web_document.cc | 50 +
.../core/frame/local_frame_mojo_handler.cc | 12 +
.../core/frame/local_frame_mojo_handler.h | 5 +
.../blink/renderer/core/html/html_element.cc | 12 +-
.../url_loader/mojo_url_loader_client.cc | 6 +-
tools/eyeo/generate_interdiffs.sh | 150 ++
tools/eyeo/generate_modules.sh | 259 +++
tools/eyeo/git-grafts.txt | 60 +
tools/eyeo/update_git_grafts.py | 138 ++
tools/gritsettings/resource_ids.spec | 12 +
.../histograms/metadata/extensions/enums.xml | 8 +-
361 files changed, 48844 insertions(+), 8 deletions(-)
create mode 100644 components/adblock/BUILD.gn
create mode 100644 components/adblock/CHANGELOG.md
create mode 100644 components/adblock/LICENSE
create mode 100644 components/adblock/README.md
create mode 100644 components/adblock/content/BUILD.gn
create mode 100644 components/adblock/content/browser/BUILD.gn
create mode 100644 components/adblock/content/browser/adblock_content_browser_client.h
create mode 100644 components/adblock/content/browser/adblock_context_data.cc
create mode 100644 components/adblock/content/browser/adblock_context_data.h
create mode 100644 components/adblock/content/browser/adblock_filter_match.h
create mode 100644 components/adblock/content/browser/adblock_internals_page_handler.cc
create mode 100644 components/adblock/content/browser/adblock_internals_page_handler.h
create mode 100644 components/adblock/content/browser/adblock_internals_ui.cc
create mode 100644 components/adblock/content/browser/adblock_internals_ui.h
create mode 100644 components/adblock/content/browser/adblock_url_loader_factory.cc
create mode 100644 components/adblock/content/browser/adblock_url_loader_factory.h
create mode 100644 components/adblock/content/browser/adblock_url_loader_factory_for_test.cc
create mode 100644 components/adblock/content/browser/adblock_url_loader_factory_for_test.h
create mode 100644 components/adblock/content/browser/adblock_web_ui_controller_factory.cc
create mode 100644 components/adblock/content/browser/adblock_web_ui_controller_factory.h
create mode 100644 components/adblock/content/browser/adblock_webcontents_observer.cc
create mode 100644 components/adblock/content/browser/adblock_webcontents_observer.h
create mode 100644 components/adblock/content/browser/content_security_policy_injector.h
create mode 100644 components/adblock/content/browser/content_security_policy_injector_impl.cc
create mode 100644 components/adblock/content/browser/content_security_policy_injector_impl.h
create mode 100644 components/adblock/content/browser/element_hider.h
create mode 100644 components/adblock/content/browser/element_hider_impl.cc
create mode 100644 components/adblock/content/browser/element_hider_impl.h
create mode 100644 components/adblock/content/browser/eyeo_document_info.cc
create mode 100644 components/adblock/content/browser/eyeo_document_info.h
create mode 100644 components/adblock/content/browser/eyeo_page_info.cc
create mode 100644 components/adblock/content/browser/eyeo_page_info.h
create mode 100644 components/adblock/content/browser/factories/adblock_request_throttle_factory.cc
create mode 100644 components/adblock/content/browser/factories/adblock_request_throttle_factory.h
create mode 100644 components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc
create mode 100644 components/adblock/content/browser/factories/adblock_telemetry_service_factory.h
create mode 100644 components/adblock/content/browser/factories/content_security_policy_injector_factory.cc
create mode 100644 components/adblock/content/browser/factories/content_security_policy_injector_factory.h
create mode 100644 components/adblock/content/browser/factories/element_hider_factory.cc
create mode 100644 components/adblock/content/browser/factories/element_hider_factory.h
create mode 100644 components/adblock/content/browser/factories/embedding_utils.cc
create mode 100644 components/adblock/content/browser/factories/embedding_utils.h
create mode 100644 components/adblock/content/browser/factories/resource_classification_runner_factory.cc
create mode 100644 components/adblock/content/browser/factories/resource_classification_runner_factory.h
create mode 100644 components/adblock/content/browser/factories/session_stats_factory.cc
create mode 100644 components/adblock/content/browser/factories/session_stats_factory.h
create mode 100644 components/adblock/content/browser/factories/sitekey_storage_factory.cc
create mode 100644 components/adblock/content/browser/factories/sitekey_storage_factory.h
create mode 100644 components/adblock/content/browser/factories/subscription_persistent_metadata_factory.cc
create mode 100644 components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h
create mode 100644 components/adblock/content/browser/factories/subscription_service_factory.cc
create mode 100644 components/adblock/content/browser/factories/subscription_service_factory.h
create mode 100644 components/adblock/content/browser/frame_hierarchy_builder.cc
create mode 100644 components/adblock/content/browser/frame_hierarchy_builder.h
create mode 100644 components/adblock/content/browser/frame_opener_info.cc
create mode 100644 components/adblock/content/browser/frame_opener_info.h
create mode 100644 components/adblock/content/browser/mojom/BUILD.gn
create mode 100644 components/adblock/content/browser/mojom/adblock_internals.mojom
create mode 100644 components/adblock/content/browser/page_view_stats.cc
create mode 100644 components/adblock/content/browser/page_view_stats.h
create mode 100644 components/adblock/content/browser/request_initiator.cc
create mode 100644 components/adblock/content/browser/request_initiator.h
create mode 100644 components/adblock/content/browser/resource_classification_runner.h
create mode 100644 components/adblock/content/browser/resource_classification_runner_impl.cc
create mode 100644 components/adblock/content/browser/resource_classification_runner_impl.h
create mode 100644 components/adblock/content/browser/session_stats_impl.cc
create mode 100644 components/adblock/content/browser/session_stats_impl.h
create mode 100644 components/adblock/content/browser/test/adblock_acceptable_ads_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_browsertest_base.cc
create mode 100644 components/adblock/content/browser/test/adblock_browsertest_base.h
create mode 100644 components/adblock/content/browser/test/adblock_content_browser_client_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_content_filters_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_debug_url_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_filter_list_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_filtering_configurations_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_non_ascii_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_page_view_stats_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_request_throttle_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_service_workers_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_sitekey_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_snippets_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_subscription_service_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_telemetry_service_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_trusted_events_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_url_loader_factory_test.cc
create mode 100644 components/adblock/content/browser/test/adblock_web_bundle_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_web_ui_browsertest.cc
create mode 100644 components/adblock/content/browser/test/adblock_webcontents_observer_test.cc
create mode 100644 components/adblock/content/browser/test/content_security_policy_injector_impl_test.cc
create mode 100644 components/adblock/content/browser/test/element_hider_impl_test.cc
create mode 100644 components/adblock/content/browser/test/frame_hierarchy_builder_test.cc
create mode 100644 components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.cc
create mode 100644 components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h
create mode 100644 components/adblock/content/browser/test/mock_element_hider.cc
create mode 100644 components/adblock/content/browser/test/mock_element_hider.h
create mode 100644 components/adblock/content/browser/test/mock_frame_hierarchy_builder.cc
create mode 100644 components/adblock/content/browser/test/mock_frame_hierarchy_builder.h
create mode 100644 components/adblock/content/browser/test/mock_resource_classification_runner.cc
create mode 100644 components/adblock/content/browser/test/mock_resource_classification_runner.h
create mode 100644 components/adblock/content/browser/test/page_view_stats_test.cc
create mode 100644 components/adblock/content/browser/test/resource_classification_runner_impl_test.cc
create mode 100644 components/adblock/content/browser/test/session_stats_impl_test.cc
create mode 100644 components/adblock/content/browser/test/subscription_service_factory_test.cc
create mode 100644 components/adblock/content/resources/adblock_internals/BUILD.gn
create mode 100644 components/adblock/content/resources/adblock_internals/adblock_internals.html
create mode 100644 components/adblock/content/resources/adblock_internals/adblock_internals.ts
create mode 100644 components/adblock/core/BUILD.gn
create mode 100644 components/adblock/core/activeping_telemetry_topic_provider.cc
create mode 100644 components/adblock/core/activeping_telemetry_topic_provider.h
create mode 100644 components/adblock/core/adblock_telemetry_service.cc
create mode 100644 components/adblock/core/adblock_telemetry_service.h
create mode 100644 components/adblock/core/classifier/BUILD.gn
create mode 100644 components/adblock/core/classifier/resource_classifier.cc
create mode 100644 components/adblock/core/classifier/resource_classifier.h
create mode 100644 components/adblock/core/classifier/resource_classifier_impl.cc
create mode 100644 components/adblock/core/classifier/resource_classifier_impl.h
create mode 100644 components/adblock/core/classifier/test/mock_resource_classifier.cc
create mode 100644 components/adblock/core/classifier/test/mock_resource_classifier.h
create mode 100644 components/adblock/core/classifier/test/resource_classifier_impl_test.cc
create mode 100644 components/adblock/core/classifier/test/resource_classifier_perftest.cc
create mode 100644 components/adblock/core/common/BUILD.gn
create mode 100644 components/adblock/core/common/adblock_constants.cc
create mode 100644 components/adblock/core/common/adblock_constants.h
create mode 100644 components/adblock/core/common/adblock_prefs.cc
create mode 100644 components/adblock/core/common/adblock_prefs.h
create mode 100644 components/adblock/core/common/adblock_switches.cc
create mode 100644 components/adblock/core/common/adblock_switches.h
create mode 100644 components/adblock/core/common/adblock_utils.cc
create mode 100644 components/adblock/core/common/adblock_utils.h
create mode 100644 components/adblock/core/common/app_info.cc
create mode 100644 components/adblock/core/common/app_info.h
create mode 100644 components/adblock/core/common/content_type.cc
create mode 100644 components/adblock/core/common/content_type.h
create mode 100644 components/adblock/core/common/flatbuffer_data.cc
create mode 100644 components/adblock/core/common/flatbuffer_data.h
create mode 100644 components/adblock/core/common/header_filter_data.h
create mode 100644 components/adblock/core/common/keyword_extractor_utils.cc
create mode 100644 components/adblock/core/common/keyword_extractor_utils.h
create mode 100644 components/adblock/core/common/regex_filter_pattern.cc
create mode 100644 components/adblock/core/common/regex_filter_pattern.h
create mode 100644 components/adblock/core/common/sitekey.h
create mode 100644 components/adblock/core/common/task_scheduler.h
create mode 100644 components/adblock/core/common/task_scheduler_impl.cc
create mode 100644 components/adblock/core/common/task_scheduler_impl.h
create mode 100644 components/adblock/core/common/test/flatbuffer_data_test.cc
create mode 100644 components/adblock/core/common/test/mock_task_scheduler.cc
create mode 100644 components/adblock/core/common/test/mock_task_scheduler.h
create mode 100644 components/adblock/core/common/test/task_scheduler_impl_test.cc
create mode 100644 components/adblock/core/common/web_ui_constants.cc
create mode 100644 components/adblock/core/common/web_ui_constants.h
create mode 100644 components/adblock/core/configuration/BUILD.gn
create mode 100644 components/adblock/core/configuration/filtering_configuration.h
create mode 100644 components/adblock/core/configuration/persistent_filtering_configuration.cc
create mode 100644 components/adblock/core/configuration/persistent_filtering_configuration.h
create mode 100644 components/adblock/core/configuration/test/fake_filtering_configuration.cc
create mode 100644 components/adblock/core/configuration/test/fake_filtering_configuration.h
create mode 100644 components/adblock/core/configuration/test/mock_filtering_configuration.cc
create mode 100644 components/adblock/core/configuration/test/mock_filtering_configuration.h
create mode 100644 components/adblock/core/configuration/test/persistent_filtering_configuration_test.cc
create mode 100644 components/adblock/core/converter/BUILD.gn
create mode 100644 components/adblock/core/converter/converter_main.cc
create mode 100644 components/adblock/core/converter/flatbuffer_converter.cc
create mode 100644 components/adblock/core/converter/flatbuffer_converter.h
create mode 100644 components/adblock/core/converter/parser/BUILD.gn
create mode 100644 components/adblock/core/converter/parser/content_filter.cc
create mode 100644 components/adblock/core/converter/parser/content_filter.h
create mode 100644 components/adblock/core/converter/parser/domain_option.cc
create mode 100644 components/adblock/core/converter/parser/domain_option.h
create mode 100644 components/adblock/core/converter/parser/filter_classifier.cc
create mode 100644 components/adblock/core/converter/parser/filter_classifier.h
create mode 100644 components/adblock/core/converter/parser/metadata.cc
create mode 100644 components/adblock/core/converter/parser/metadata.h
create mode 100644 components/adblock/core/converter/parser/snippet_filter.cc
create mode 100644 components/adblock/core/converter/parser/snippet_filter.h
create mode 100644 components/adblock/core/converter/parser/snippet_tokenizer.cc
create mode 100644 components/adblock/core/converter/parser/snippet_tokenizer.h
create mode 100644 components/adblock/core/converter/parser/test/content_filter_test.cc
create mode 100644 components/adblock/core/converter/parser/test/domain_option_test.cc
create mode 100644 components/adblock/core/converter/parser/test/filter_classifier_test.cc
create mode 100644 components/adblock/core/converter/parser/test/metadata_test.cc
create mode 100644 components/adblock/core/converter/parser/test/snippet_filter_test.cc
create mode 100644 components/adblock/core/converter/parser/test/snippet_tokenizer_test.cc
create mode 100644 components/adblock/core/converter/parser/test/url_filter_options_test.cc
create mode 100644 components/adblock/core/converter/parser/test/url_filter_test.cc
create mode 100644 components/adblock/core/converter/parser/url_filter.cc
create mode 100644 components/adblock/core/converter/parser/url_filter.h
create mode 100644 components/adblock/core/converter/parser/url_filter_options.cc
create mode 100644 components/adblock/core/converter/parser/url_filter_options.h
create mode 100644 components/adblock/core/converter/serializer/BUILD.gn
create mode 100644 components/adblock/core/converter/serializer/filter_keyword_extractor.cc
create mode 100644 components/adblock/core/converter/serializer/filter_keyword_extractor.h
create mode 100644 components/adblock/core/converter/serializer/flatbuffer_serializer.cc
create mode 100644 components/adblock/core/converter/serializer/flatbuffer_serializer.h
create mode 100644 components/adblock/core/converter/serializer/serializer.h
create mode 100644 components/adblock/core/converter/serializer/test/filter_keyword_extractor_test.cc
create mode 100644 components/adblock/core/converter/test/flatbuffer_converter_perftest.cc
create mode 100644 components/adblock/core/features.cc
create mode 100644 components/adblock/core/features.h
create mode 100644 components/adblock/core/net/BUILD.gn
create mode 100644 components/adblock/core/net/adblock_request_throttle.h
create mode 100644 components/adblock/core/net/adblock_request_throttle_impl.cc
create mode 100644 components/adblock/core/net/adblock_request_throttle_impl.h
create mode 100644 components/adblock/core/net/adblock_resource_request.h
create mode 100644 components/adblock/core/net/adblock_resource_request_impl.cc
create mode 100644 components/adblock/core/net/adblock_resource_request_impl.h
create mode 100644 components/adblock/core/net/test/adblock_request_throttle_impl_test.cc
create mode 100644 components/adblock/core/net/test/adblock_resource_request_impl_test.cc
create mode 100644 components/adblock/core/net/test/mock_adblock_request_throttle.cc
create mode 100644 components/adblock/core/net/test/mock_adblock_request_throttle.h
create mode 100644 components/adblock/core/net/test/mock_adblock_resource_request.cc
create mode 100644 components/adblock/core/net/test/mock_adblock_resource_request.h
create mode 100644 components/adblock/core/resources/.gitignore
create mode 100644 components/adblock/core/resources/BUILD.gn
create mode 100644 components/adblock/core/resources/adblock_resources.grd
create mode 100644 components/adblock/core/resources/elemhide.js
create mode 100644 components/adblock/core/resources/elemhide_for_selector.jst
create mode 100644 components/adblock/core/resources/elemhideemu.jst
create mode 100644 components/adblock/core/resources/snippets_deps.py
create mode 100755 components/adblock/core/resources/update.sh
create mode 100644 components/adblock/core/schema/filter_list_schema.fbs
create mode 100644 components/adblock/core/schema/schema_hash.h
create mode 100644 components/adblock/core/session_stats.h
create mode 100644 components/adblock/core/sitekey_storage.h
create mode 100644 components/adblock/core/sitekey_storage_impl.cc
create mode 100644 components/adblock/core/sitekey_storage_impl.h
create mode 100644 components/adblock/core/subscription/BUILD.gn
create mode 100644 components/adblock/core/subscription/conversion_executors.h
create mode 100644 components/adblock/core/subscription/domain_splitter.cc
create mode 100644 components/adblock/core/subscription/domain_splitter.h
create mode 100644 components/adblock/core/subscription/filtering_configuration_maintainer.h
create mode 100644 components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc
create mode 100644 components/adblock/core/subscription/filtering_configuration_maintainer_impl.h
create mode 100644 components/adblock/core/subscription/installed_subscription.cc
create mode 100644 components/adblock/core/subscription/installed_subscription.h
create mode 100644 components/adblock/core/subscription/installed_subscription_impl.cc
create mode 100644 components/adblock/core/subscription/installed_subscription_impl.h
create mode 100644 components/adblock/core/subscription/pattern_matcher.cc
create mode 100644 components/adblock/core/subscription/pattern_matcher.h
create mode 100644 components/adblock/core/subscription/preloaded_subscription_provider.h
create mode 100644 components/adblock/core/subscription/preloaded_subscription_provider_impl.cc
create mode 100644 components/adblock/core/subscription/preloaded_subscription_provider_impl.h
create mode 100644 components/adblock/core/subscription/recommended_subscription_installer.h
create mode 100644 components/adblock/core/subscription/recommended_subscription_installer_impl.cc
create mode 100644 components/adblock/core/subscription/recommended_subscription_installer_impl.h
create mode 100644 components/adblock/core/subscription/recommended_subscription_parser.cc
create mode 100644 components/adblock/core/subscription/recommended_subscription_parser.h
create mode 100644 components/adblock/core/subscription/regex_matcher.cc
create mode 100644 components/adblock/core/subscription/regex_matcher.h
create mode 100644 components/adblock/core/subscription/subscription.cc
create mode 100644 components/adblock/core/subscription/subscription.h
create mode 100644 components/adblock/core/subscription/subscription_collection.h
create mode 100644 components/adblock/core/subscription/subscription_collection_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_collection_impl.h
create mode 100644 components/adblock/core/subscription/subscription_config.cc
create mode 100644 components/adblock/core/subscription/subscription_config.h
create mode 100644 components/adblock/core/subscription/subscription_downloader.h
create mode 100644 components/adblock/core/subscription/subscription_downloader_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_downloader_impl.h
create mode 100644 components/adblock/core/subscription/subscription_persistent_metadata.h
create mode 100644 components/adblock/core/subscription/subscription_persistent_metadata_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_persistent_metadata_impl.h
create mode 100644 components/adblock/core/subscription/subscription_persistent_storage.h
create mode 100644 components/adblock/core/subscription/subscription_persistent_storage_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_persistent_storage_impl.h
create mode 100644 components/adblock/core/subscription/subscription_service.h
create mode 100644 components/adblock/core/subscription/subscription_service_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_service_impl.h
create mode 100644 components/adblock/core/subscription/subscription_validator.h
create mode 100644 components/adblock/core/subscription/subscription_validator_impl.cc
create mode 100644 components/adblock/core/subscription/subscription_validator_impl.h
create mode 100644 components/adblock/core/subscription/test/domain_splitter_test.cc
create mode 100644 components/adblock/core/subscription/test/filtering_configuration_maintainer_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_csp_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_elemhide_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_header_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_list_converter_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_metadata_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_rewrite_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_snippets_test.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_test_base.cc
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_test_base.h
create mode 100644 components/adblock/core/subscription/test/installed_subscription_impl_url_test.cc
create mode 100644 components/adblock/core/subscription/test/load_gzipped_test_file.cc
create mode 100644 components/adblock/core/subscription/test/load_gzipped_test_file.h
create mode 100644 components/adblock/core/subscription/test/mock_conversion_executors.cc
create mode 100644 components/adblock/core/subscription/test/mock_conversion_executors.h
create mode 100644 components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.cc
create mode 100644 components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h
create mode 100644 components/adblock/core/subscription/test/mock_installed_subscription.cc
create mode 100644 components/adblock/core/subscription/test/mock_installed_subscription.h
create mode 100644 components/adblock/core/subscription/test/mock_subscription.cc
create mode 100644 components/adblock/core/subscription/test/mock_subscription.h
create mode 100644 components/adblock/core/subscription/test/mock_subscription_collection.cc
create mode 100644 components/adblock/core/subscription/test/mock_subscription_collection.h
create mode 100644 components/adblock/core/subscription/test/mock_subscription_downloader.cc
create mode 100644 components/adblock/core/subscription/test/mock_subscription_downloader.h
create mode 100644 components/adblock/core/subscription/test/mock_subscription_persistent_metadata.cc
create mode 100644 components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h
create mode 100644 components/adblock/core/subscription/test/mock_subscription_service.cc
create mode 100644 components/adblock/core/subscription/test/mock_subscription_service.h
create mode 100644 components/adblock/core/subscription/test/pattern_matcher_perftest.cc
create mode 100644 components/adblock/core/subscription/test/pattern_matcher_test.cc
create mode 100644 components/adblock/core/subscription/test/preloaded_subscription_provider_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/recommended_subscription_installer_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/recommended_subscription_parser_test.cc
create mode 100644 components/adblock/core/subscription/test/regex_matcher_perftest.cc
create mode 100644 components/adblock/core/subscription/test/subscription_collection_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/subscription_downloader_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/subscription_persistent_metadata_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/subscription_persistent_storage_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/subscription_service_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/subscription_validator_impl_test.cc
create mode 100644 components/adblock/core/subscription/test/url_keyword_extractor_test.cc
create mode 100644 components/adblock/core/subscription/url_keyword_extractor.cc
create mode 100644 components/adblock/core/subscription/url_keyword_extractor.h
create mode 100644 components/adblock/core/test/activeping_telemetry_topic_provider_test.cc
create mode 100644 components/adblock/core/test/adblock_telemetry_service_unittest.cc
create mode 100644 components/adblock/core/test/bundled_subscription_test.cc
create mode 100644 components/adblock/core/test/mock_sitekey_storage.cc
create mode 100644 components/adblock/core/test/mock_sitekey_storage.h
create mode 100644 components/adblock/core/test/sitekey_storage_impl_test.cc
create mode 100644 components/adblock/features.gni
create mode 100644 content/shell/browser/adblock/adblock_shell_browser_context.cc
create mode 100644 content/shell/browser/adblock/adblock_shell_browser_context.h
create mode 100644 content/shell/browser/adblock/adblock_shell_content_browser_client.cc
create mode 100644 content/shell/browser/adblock/adblock_shell_content_browser_client.h
create mode 100755 tools/eyeo/generate_interdiffs.sh
create mode 100755 tools/eyeo/generate_modules.sh
create mode 100644 tools/eyeo/git-grafts.txt
create mode 100755 tools/eyeo/update_git_grafts.py
diff --git a/DEPS b/DEPS
--- a/DEPS
+++ b/DEPS
@@ -282,6 +282,7 @@ vars = {
'download_libvpx_testdata': False,
'android_git': 'https://android.googlesource.com',
+ 'eyeo_gitlab': 'https://gitlab.com/eyeo',
'aomedia_git': 'https://aomedia.googlesource.com',
'boringssl_git': 'https://boringssl.googlesource.com',
'chrome_git': 'https://chrome-internal.googlesource.com',
@@ -529,6 +530,11 @@ vars = {
# and whatever else without interference from each other.
'compiler_rt_revision': '57196dd146582915c955f6d388e31aea93220c51',
+ # Three lines of non-changing comments so that
+ # the commit queue can handle CLs rolling feed
+ # and whatever else without interference from each other.
+ 'eyeo_snippets_revision': 'v2.0.0',
+
# If you change this, also update the libc++ revision in
# //buildtools/deps_revisions.gni.
'libcxx_revision': 'a01c02c9d4acbdae3b7e8a2f3ee58579a9c29f96',
@@ -1516,6 +1522,10 @@ deps = {
'condition': 'checkout_android and checkout_src_internal',
},
+ 'src/components/adblock/core/resources/snippets': {
+ 'url': Var('eyeo_gitlab') + '/anti-cv/snippets.git' + '@' + Var('eyeo_snippets_revision'),
+ },
+
'src/docs/website': {
'url': Var('chromium_git') + '/website.git' + '@' + 'd21d90790d8ea421b317c4cb52a0d94133422796',
},
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -25,3 +25,18 @@
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file is part of eyeo Chromium SDK,
+// Copyright (C) 2006-present eyeo GmbH
+//
+// eyeo Chromium SDK is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3 as
+// published by the Free Software Foundation.
+//
+// eyeo Chromium SDK is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -19,3 +19,15 @@ Android WebView, Ash). Even if these products have multiple executables, the
code should be in subdirectories of the product.
If you found a bug, please file it at https://crbug.com/new.
+
+
+## Eyeo Chromium SDK
+
+Eyeo Chromium SDK is a fork of the Chromium project that
+integrates ad-filtering capabilities. A big part of the functionality is
+implemented inside a component, to simplify the integration with other
+modifications to the browser.
+
+The [component folder](components/adblock) contains most of the source code,
+as well as the changelog, license and technical documentation about
+architecture and integration steps.
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -1,6 +1,10 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef BASE_THREADING_THREAD_RESTRICTIONS_H_
#define BASE_THREADING_THREAD_RESTRICTIONS_H_
@@ -170,6 +174,9 @@ class OverlayProcessorWebView;
class ScopedAllowInitGLBindings;
class VizCompositorThreadRunnerWebView;
} // namespace android_webview
+namespace adblock {
+class SubscriptionServiceFactory;
+} // namespace adblock
namespace ash {
class LoginEventRecorder;
class StartupCustomizationDocument;
@@ -661,6 +668,8 @@ class BASE_EXPORT ScopedAllowBlocking {
friend class ::BrowserStateDirectoryBuilder;
#endif
+ friend class adblock::SubscriptionServiceFactory;
+
// Sorted by function name (with namespace), ignoring the return type.
friend Profile* ::GetLastProfileMac(); // http://crbug.com/1176734
// Note: This function return syntax is required so the "::" doesn't get
diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h
--- a/base/trace_event/builtin_categories.h
+++ b/base/trace_event/builtin_categories.h
@@ -123,6 +123,7 @@ PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE_WITH_ATTRS(
perfetto::Category("exo"),
perfetto::Category("extensions"),
perfetto::Category("explore_sites"),
+ perfetto::Category("eyeo"),
perfetto::Category("FileSystem"),
perfetto::Category("file_system_provider"),
perfetto::Category("fledge"),
diff --git a/components/BUILD.gn b/components/BUILD.gn
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -1,6 +1,9 @@
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+#
+# This source code is a part of eyeo Chromium SDK.
+# Use of this source code is governed by the GPLv3 that can be found in the components/adblock/LICENSE file.
import("//base/debug/debug.gni")
import("//build/config/chrome_build.gni")
@@ -179,6 +182,7 @@ test("components_unittests") {
deps = [
"//base",
+ "//components/adblock:unit_tests",
"//components/affiliations/core/browser:unit_tests",
"//components/aggregation_service:unit_tests",
"//components/apdu:unit_tests",
@@ -918,6 +922,7 @@ test("components_unittests") {
repack("components_tests_pak") {
sources = [
+ "$root_gen_dir/components/adblock/core/resources/adblock_resources.pak",
"$root_gen_dir/components/arc/input_overlay_resources.pak",
"$root_gen_dir/components/autofill/core/browser/geo/autofill_address_rewriter_resources.pak",
"$root_gen_dir/components/components_resources.pak",
@@ -934,6 +939,7 @@ repack("components_tests_pak") {
# TODO(b/207518736): Input overlay resources will be changed to proto soon,
# thus not going to move this resource pak to under ash.
"//chromeos/ash/experiences/arc/input_overlay/resources:resources_grit",
+ "//components/adblock/core/resources:adblock_resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
"//components/metrics:server_urls_grd",
"//components/omnibox/resources:omnibox_pedal_synonyms",
@@ -1035,6 +1041,7 @@ if (use_blink) {
deps = [
"//base",
"//base/test:test_support",
+ "//components/adblock/content/browser:browser_tests",
"//components/autofill/content/browser",
"//components/autofill/content/browser:browser_tests",
"//components/autofill/content/renderer",
diff --git a/components/adblock/BUILD.gn b/components/adblock/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/BUILD.gn
@@ -0,0 +1,36 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//build/config/features.gni")
+
+group("unit_tests") {
+ testonly = true
+
+ deps = [
+ "//components/adblock/core:unit_tests",
+ "//components/adblock/core/classifier:unit_tests",
+ "//components/adblock/core/common:unit_tests",
+ "//components/adblock/core/configuration:unit_tests",
+ "//components/adblock/core/converter/parser:unit_tests",
+ "//components/adblock/core/converter/serializer:unit_tests",
+ "//components/adblock/core/net:unit_tests",
+ "//components/adblock/core/subscription:unit_tests",
+ ]
+
+ if (use_blink) {
+ deps += [ "//components/adblock/content/browser:unit_tests" ]
+ }
+}
diff --git a/components/adblock/CHANGELOG.md b/components/adblock/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/components/adblock/CHANGELOG.md
@@ -0,0 +1,746 @@
+# Release Notes
+
+## eyeo Browser Ad Filtering Solution 133.0
+* Updated to Chromium 133.0.6943.49
+* Updated snippet library to version 2.0.0 (DPD-3211)
+* Added rejecting of URL filters without any valid domain in domain option (DPD-3031)
+* Added filter list conversion optimization for parsing domain options (DPD-1760)
+* Refactoring: Made FlatbufferConverter non static (DPD-3152)
+* Refactoring: Split adblock_utils (DPD-1724)
+
+## eyeo Browser Ad Filtering Solution 132.0
+* Updated to Chromium 132.0.6834.5
+
+## eyeo Browser Ad Filtering Solution 131.0
+* Updated to Chromium 131.0.6778.39
+* Updated snippet library to version 1.7.0 (DPD-2944)
+* Avoid using Prefs to establish application locale (DPD-3071)
+* Fixed crash in InstalledSubscriptionImpl::GetElemhideData (DPD-2994)
+* Delay all eyeo-issued network requests for 30 seconds after startup (DPD-2992)
+* eyeometry schema extended to include AA stats (DPD-2954, DPD-2955)
+
+## eyeo Browser Ad Filtering Solution 130.0
+* Updated to Chromium 130.0.6723.5
+
+## eyeo Browser Ad Filtering Solution 129.0
+* Updated to Chromium 129.0.6668.54
+* Introduced Safe Filter lists in BAS (DPD-2795)
+* Updated snippets library to version 1.7.0 (DPD-2944)
+* Fixed parsing incorrect domain options (DPD-2789)
+* Fixed crash on opening preview frame (DPD-2884)
+* Fixed crash on loading pages from local files (DPD-2893)
+* Fixed crash on null response headers (DPD-2910)
+
+## eyeo Browser Ad Filtering Solution 128.0
+* Updated to Chromium 128.0.6613.7
+
+## eyeo Browser Ad Filtering Solution 127.0
+* Updated to Chromium 127.0.6533.4
+* Introduced Enhanced Filter List Delivery feature (DPD-2613)
+* Fixed document allowlisting miss sitekey check (DPD-2685)
+* Fixed ad-filtering request initiated by service workers (DPD-2589)
+* Fixed crash on WebSocket interception (DPD-2719)
+* Fixed misleading filter list removal log (DPD-2692)
+
+## eyeo Browser Ad Filtering Solution 126.0
+* Updated to Chromium 126.0.6465.0
+
+## eyeo Browser Ad Filtering Solution 125.0
+* Updated to Chromium 125.0.6422.53
+* New dependency for bundled ML inference service (DPD-2355)
+ See chrome/browser/resources/adblock_ml
+ This is experimental feature disabled by default.
+* Added stats for custom filters hits by means of listing "adblock:custom"
+ among filter list in chrome://adblock-internals (DPD-2713)
+* Allow priviliged filters from locally hosted filter lists (DPD-2625)
+* Fix eyeo performance benchmark arguments (DPD-2668)
+* Fix crash in pattern matcher (DPD-2644)
+
+## eyeo Browser Ad Filtering Solution 124.0
+* Updated to Chromium 124.0.6367.8
+
+## eyeo Browser Ad Filtering Solution 123.0
+* Updated to Chromium 123.0.6312.59
+* Added support for Content Shell (DPD-2433)
+* Added ContentShell JNI and Android tests (DPD-2542)
+* Fixed redundant rewrite filter check for navigation (DPD-2490)
+* Fixed potential crash in CSP injection (DPD-2494)
+* Fixed problem of allowing filtering configurations with duplicated name (DPD-2498)
+* Fixed generating the schema hash (DPD-2504)
+* Fixed race condition wrt building frame hierarchy which affected allowlisting (DPD-2291)
+* Improved element hiding script for blocked resource (DPD-2451)
+* Improved element hiding script to skip unsupported CSS selectors (DPD-2543)
+* Updated snippets library to version 1.2.0 (DPD-2567)
+* Reduced code duplication in AdblockContentBrowserClients (DPD-2435)
+* Removed redundant adblock prefs registration (DPD-2468)
+* Separated pack for adblock core resources (DPD-2477)
+* Limited our APIs to only allowlisted extensions (DPD-2465)
+* Moved implementation of chrome://adblock-internals under components (DPD-2475)
+* Enabled filtering and element hiding for localhost urls (DPD-2575)
+
+## eyeo Browser Ad Filtering Solution 122.0
+* Updated to Chromium 122.0.6261.5
+
+## eyeo Browser Ad Filtering Solution 121.0
+* Updated to Chromium 121.0.6167.101
+* Added support for domain wildcard filters (DPD-2287)
+* Fixed addonVersion value for FL downloads (DPD-2385)
+* Added support for remove filters (DPD-2294)
+* Added support for inline CSS filters (DPD-2295)
+* Fixed last_installation_time in HEAD requests (DPD-2378)
+* Removed deprecated AdblockContentType and AdblockCounters (DPD-2286)
+* Implemented sending trusted JS events via dispatchEvent() from adblock isolated world (DPD-2453)
+* Added support for trusted JS click events called from adblock isolated world (DPD-2429)
+* Fixed BrowserContext usage in eyeo factories (DPD-2458)
+
+## eyeo Browser Ad Filtering Solution 120.0
+* Updated to Chromium 120.0.6099.4
+* Remade JNI bindings (DPD-2231)
+* Resolved linker conflicst for monochrome targets (DPD-2232)
+* Snippets library updated to 0.10.0 (DPD-2324)
+* Component extension for AI snippets. It is active only if related flag is on. (DPD-2073)
+
+## eyeo Browser Ad Filtering Solution 119.0
+* Updated to Chromium 119.0.6045.66
+* Deprecated AdblockController class in Java (DPD-2239)
+* Renamed some C++ functions to use "resource" instead of "ad" more consistently (DPD-2246)
+* Removed a "Copying xpath3 deps code from .." log from the build output (DPD-2114)
+* WebView: started using the user space pref store instead of local space (DPD-2251)
+* Made the FilteringConfiguration Java and JavaScript APIs more consistent wrt naming (DPD-2252)
+
+Known issues:
+* monochrome_public_bundle target doesn't link. This is prioritized for fixing in 121.
+
+## eyeo Browser Ad Filtering Solution 118.0
+* Updated to Chromium 118.0.5993.48
+* Allow removing existing FilteringConfigurations via Java API (DPD-2090)
+* Extracting some common code shared between the WebView and Chrome integrations (DPD-2159)
+* Fixed xpath3 snippets for WebView integrations (DPD-2162)
+* Update snippets repository URL to reflect repo move (DPD-2149)
+* Removing references to C++ AdblockController class (DPD-2153). Use the FilteringConfiguration in C++ now.
+* AdblockController still remains in Java. Internally, it redirects methods to its FilteringConfiguration.
+* Eyeometry TopicProviders can now provide debug information asynchronously (DPD-2175). This debug info is shown on chrome://adblock-internals
+* Hopeful fix for a non-reproducible crash in IsActiveOnDomain (DPD-2101)
+* The following locales now install global-filters+easylist.txt by default: th, el, sl, hr, sr, bs (DPD-2180)
+* Uzbek and Kazakh locales now install ruadlist+easylist.txt by default (DPD-2228)
+* Simplify localization of filter list titles in Android UI (DPD-2227)
+
+Known issues:
+* monochrome_public_bundle target doesn't link. This is prioritized for fixing in 119.
+
+## eyeo Chromium SDK 117.0
+* Updated to Chromium 117.0.5938.44
+* Added support for hide-if-matches-xpath3 snippet (DPD-2065)
+* Added Japanese and Turkish filter lists (DPD-2096)
+* Fixed dump from dangling pointer detector in adblock::ElementHider (DPD-2122)
+* Revert changes introduced with DPD-2101 that caused a crash in chrome://flags
+
+## eyeo Chromium SDK 116.0
+* Updated to Chromium 116.0.5845.78
+* Added webview support (DPD-2036)
+* Added gn gen argument to disable eyeo filtering for first run (DPD-2063)
+* Added chrome://adblock-internals status page (DPD-1708)
+* Removed deprecated APIs from AdblockController (DPD-2060)
+* Improved blocking of popups (DPD-1977)
+* Fix bug that injects CSS/JS multiple times for the same document (DPD-1773)
+
+## eyeo Chromium SDK 115.0
+* Updated to Chromium 115.0.5790.98
+* Fixed bug with not allowlisting frames with 'about:blank...' urls (DPD-1946)
+* Fixed bug with logging telemetry authentication token in release builds (DPD-1951)
+* Fixed several problems with rewrite filters and refactored code (DPD-1657)
+* Fixed bug with handling domain matching filters with empty url pattern (DPD-1978)
+* Updated extensions JavaScript API and Java API by adding FilteringConfiguration name to blocked/allowed notifications (DPD-1909)
+* Updated snippets library version from v0.6.1 to v0.7.0 (DPD-1974)
+
+## eyeo Chromium SDK 114.0
+* Updated to Chromium 114.0.5735.53
+* Fixed bug of gn gen build settings for "eyeo_application_name" and "eyeo_application_version" being ignored (DPD-1937)
+* Fixed bug of not removing flatbuffer file after uninstalling subscription (DPD-1911)
+* Increased Telemetry ping interval from 8 hrs to 12 hrs
+* Removed ComposeFilterSuggestions on Android (DPD-699)
+* Refactored and simplified SnippetTokenizer (DPD-939)
+
+## eyeo Chromium SDK 113.0
+* Updated to Chromium 113.0.5672.76
+* Added new extension API which exposes filtering configurations, and which also supports promises (DPD-1719)
+ See chrome/common/extensions/api/eyeo_filtering_private.idl
+* Reduce JavaScript console output for element hiding emulation (DPD-1750)
+* Added TypeScript definitions for eyeo extension API (DPD-1870)
+* Restricted too generic filters that can affect many sites (DPD-1867)
+* Fix for filters containing | character in the middle of a string (DPD-1755)
+* Support for 'webbundle' filter content type (DPD-1876)
+* Ignore domain duplicates in filters (DPD-1795)
+* More events for extension API notifying about changes in allowed domains, custom filters, filters
+ lists and enable state (DPD-1871)
+* Filter lists downloads check network state and run only when connection is available (DPD-1762)
+* Relaxed base64 decoding for sitekey (DPD-1912)
+
+## eyeo Chromium SDK 112.0
+* Updated to Chromium 112.0.5615.37
+* Subscription for adblock_private events available in incognito mode (DPD-1868)
+* Restored deprecated subscriptions API removed in previous version. This methods will be kept until version 115 (DPD-1839, DPD-1771)
+* AdblockController::Observer is deprecated. Will be removed in version 115 (DPD-1754)
+* Java Subscription class now has method to report version (DPD-1794)
+* Regexp filters should not be converted to lowercase internally (DPD-1806)
+* Filter lists in repo now compressed (DPD-1774)
+
+## eyeo Chromium SDK 111.0
+* Updated to Chromium 111.0.5563.38
+* Updated snippets library from v0.5.5 to v0.6.1
+* Removed deprecated subscriptions API (DPD-1771)
+* Refactored filter lists converter (DPD-1355)
+* Rewritten pattern matching logic to improve url filtering performance (DPD-1745)
+* Added collecting frame hierarchy for popup filtering (DPD-1749)
+* Added Java API for multiple FilteringConfigurations (DPD-1661)
+* Fixed bug when AdblockController is created too late to register its FilteringConfiguration (DPD-1752)
+* Fixed sending redundant HEAD requests for Acceptable Ads when multiple FilteringConfigurations are enabled (DPD-1763)
+* Fixed problem of not removing downloaded filter list file from temp folder (DPD-1748)
+
+## eyeo Chromium SDK 110.0
+* Updated to Chromium 110.0.5481.50
+* Support multiple FilteringConfigurations (DPD-1568)
+* Fixed potential crash in SubscriptionValidator (DPD-1709)
+* Covered AdblockURLLoaderFactory with UT (DPD-1634)
+* Fixed desktop setting page (DPD-1663)
+* Added CRLF support in filter lists converter
+* Added support for multiple CSP filters per resource (DPD-1145)
+* Simplified resource type detection (DPD-1437)
+
+## eyeo Chromium SDK 109.0
+* Updated to Chromium 109.0.5414.86
+* Removed deprecated Allowed Connection Type API (DPD-1582)
+* Deprecated the distinction between installing Built-In vs Custom subscriptions (DPD-1441)
+ - Added API functions to install/uninstall/get subscriptions regardless of their provenance
+ - Deprecated API functions that select/unselect/get "built-in" subscriptions
+ - Deprecated API functions that add/remove/get "custom" subscriptions
+ - The deprecated API functions will disappear in version 111
+ - Identifying recommended filter lists is still possible via separate API functions
+* Fixed parsing URL filters with # symbol (DPD-1632)
+* Removed unneeded AdblockMojoInterface (DPD-1295)
+* Replaced some usage of render frame ID + render process ID with GlobalRenderFrameHostId (DPD-1130)
+* Fixed build issues on Windows
+* Initial work for enabling independent Filtering Configurations (DPD-1567)
+ - Filtering Configurations will allow supporting independently set up filter engine users, e.g.
+ an "ad-filtering" setting alongside a "privacy boosting" setting or "parental control" setting.
+ - Each may have independent filter lists, custom filters, allowed domains etc.
+ - The "ad-filtering" configuration remains the only possible configuration currently, maintaining
+ all existing semantics and APIs
+ - Support for multiple configurations is planned for a future release
+* Fixed invalid behavior when removing previously added custom filters and allowed domains
+
+## eyeo Chromium SDK 108.0
+* Updated to Chromium 108.0.5359.28
+* Allow Telemetry TopicProviders to collect payload asynchronously (DPD-1507)
+* Download filter list on any connection type (DPD-1418)
+ - Filter lists are now being compressed server-side and are very small (~400 kB)
+ - It's no longer advantageous to download them only on Wi-Fi
+ - APIs related to Allowed Connection Type are deprecated and non-functional
+ - Those APIs will be removed in version 109
+* Ensure Telemetry pings trigger correctly after the computer wakes up from sleep (DPD-1559)
+* Ensure filter list download requests attach Accept-Language header (DPD-1405)
+* Fixed a use-after-free when element hiding was applied on a closed tab (DPD-1600)
+* Fixed element hiding to apply within iframes served from Web Bundles (DPD-1510)
+* Improved page load times significantly by optimizing filter matching regular expressions (DPD-586)
+
+## eyeo Chromium SDK 107.0
+* Updated to Chromium 107.0.5304.54
+* Packaging a non-obfuscated version of snippets library for debug builds (DPD-1448)
+* Add GetCustomFilters call to web extensions API (DPD-849)
+* Add eyeo_intercept_debug_url build flag to hide internal testing feature (DPD-1407, DPD-1532)
+* Allow all requests from web extensions (DPD-1505)
+* Fixed a rare crash when tab is closed before websocket classification completes (DPD-1548)
+* Fixed a rare crash when network service disconnects during resource classification (DPD-1496)
+* Fixed Telemetry pings being sent too rarely when PC is suspended (DPD-1559)
+* Removed deprecated abp telemetry gn gen arguments
+
+Known issues:
+* Disabled AdblockMojoInterfaceImpl related unit tests. Will be removed in future releases
+
+## eyeo Chromium SDK 106.0
+* Updated to Chromium 106.0.5249.38
+* Updated snippets library from v0.5.1 to v0.5.5
+* Fixed faulty handling URL redirection by creating AdblockURLLoaderFactory as the last proxy (DPD-1492). Backported into 105.1
+* Fixed SiteKey validation for redirected urls (DPD-1452)
+* Improved logging related to subscription update (DPD-1359)
+* Simplified subscription first run update (DPD-1389)
+
+Known issues:
+* The following browser tests are failing and will be fixed in future releases:
+ - OutOfProcessPPAPITest.URLLoader3
+ - PasswordDialogViewTest.PopupAccountChooserWithMultipleCredentialsReturnEmpty
+ - CredentialManagerAvatarTest.AvatarFetchIsolatedPerOrigin
+
+## eyeo Chromium SDK 105.0
+* Updated to Chromium 105.0.5195.68
+* Rebranded to eyeo Chromium SDK (DPD-1322)
+* Moved snippet update to gclient sync (DPD-1283)
+* Updated extensions API:
+ - Added `onPopupAllowed` event which is fired when a popup is allowlisted
+ - Added `onPopupBlocked` event which is fired when a popup is blocked
+ - Added `onPageAllowed` event which is fired when a whole domain is allowlisted
+ - Fixed typo in `OnSubscriptionUpdated` which is now `onSubscriptionUpdated`
+* Updated Java AdBlockedObserver API:
+ - Removed `onAdMatched` and instead added `onAdAllowed` and `onAdBlocked`
+ - Removed `onPopupMatched` and instead added `onPopupAllowed` and `onPopupBlocked`
+ - Added `onPageAllowed` callback which is fired when a whole domain is allowlisted
+
+## Chromium 104.0 + ABP 2.1 (ABP Chromium 104 v1)
+* Updated to Chromium 104.0.5112.55
+* Added support for :not in element hiding (DPD-1106)
+* Fixed bug which caused that language specific list was not enabled by default in 1st run scenario (DPD-1302)
+* Moved waiting for SubscriptionService to be initialized from ResourceClassifier to mojo (DPD-1303)
+* Classification logic split into chrome-specific and chrome-agnostic part (DPD-1304)
+* Moved checking ABP enabled state to AdblockContentBrowserClient (DPD-1334)
+* Disabled flaky RecordNotificationDisplayedAndInteraction test
+
+## Chromium 103.0 + ABP 2.1 (ABP Chromium 103 v1)
+* Updated to Chromium 103.0.5060.53
+* Added rewrite filter type support (DPD-810)
+* Exposed filter list version, status and installation time and updated documentation in adblock_private.idl (DPD-870)
+* Fixed parsing regex filter as a filter option (DPD-1209)
+* Fixed parsing wildcard filters
+* Updated sequence diagrams in documentation (components/adblock/docs)
+* Fixed issues in AdblockController Java code which could lead to crashes when Profile is not yet initialized (DPD-1288)
+* Moved CSP injection and ad blocking into AdblockURLLoaderFactory and removed AdblockUrlLoaderThrottle (DPD-1238, DPD-1263)
+* Splitted ABP core codebase in namespaces (DPD-1258)
+* Several minor code cleanups and refactorings
+* Removed hardcoding fieldtrial_testing_enabled=false flag in BUILD.gn
+
+## Chromium 102.0 + ABP 2.1 (ABP Chromium 102 v1)
+* Updated to Chromium 102.0.5005.50
+* Added header filter support (DPD-1103)
+* Added Java API to get subscription version
+* Improved matching performance for long URLs (DPD-419)
+* Added support for `! Redirect` metadata in subscription header (DPD-965)
+* Fixed parsing of `! Expires` metadata in subscription header (DPD-965)
+
+## Chromium 101.0 + ABP 2.0 (ABP Chromium 101 v1)
+* Updated to Chromium 101.0.4951.41
+* Element hiding CSS sanitized before injection (DPD-1010)
+* Ping filter support (DPD-1102)
+* Allow defining default and privileged subscriptions via configuration file (DPD-1161, DPD-1205)
+* Restructure adblock component to fit Chromium Layered Components design (DPD-1165)
+* Updated in-repo documentation (DPD-1176)
+* Allow disabling adblocking via feature flag for testing (DPD-1172)
+* Add java API to get subscription version
+
+## Chromium 100.0 + ABP 2.0 (ABP Chromium 100 v1)
+* Updated to Chromium 100.0.4896.46
+* Replaced libadblockplus with a native, flatbuffer-based implementation of ad-filtering logic
+* Improved memory consumption considerably
+* Improved startup time considerably
+* Removed V8 dependency from browser process
+* Enabled preloaded filter lists for out-of-the-box ad filtering
+* Counting active users by sending periodic Telemetry pings with no user-identifiable data
+* Optimized injecting Snippets and Element Hiding Emulation JS
+* Added support for CSP filters
+* Updated in-repo documentation (components/adblock/docs)
+
+Known issues:
+* The following browser tests are failing and will be fixed in future releases:
+ - ClientHintsBrowserTest.DelegateAndMerge_HttpEquiv
+ - ClientHintsBrowserTest.DelegateAndMerge_MetaName
+ - ClientHintsBrowserTest.DelegateToBar_HttpEquiv
+ - ClientHintsBrowserTest.DelegateToBar_MetaName
+ - ClientHintsBrowserTest.DelegateToFoo_HttpEquiv
+ - ClientHintsBrowserTest.DelegateToFoo_MetaName
+ - ExtensionWebRequestApiTest.WebRequestBlocking
+
+## Chromium 99.0 + ABP 1.3 (ABP Chromium 99 v1)
+* Updated to Chromium 99.0.4844.35
+* Updated libadblockplus to version 13.1-c0.5.1
+* Enabled command line preferences for desktop builds (DPD-1088)
+* Added statistics API to expose runtime counters (DPC-610)
+* Extended JS API to receive notification when a subscription gets updated (DPC-694)
+
+Known issues:
+* Due to the V8 dependency, the following browser tests are still failing, and will be fixed in future releases:
+ - PageTextObserverSingleProcessBrowserTest.SameProcessAMPSubframe
+ - PageTextObserverSingleProcessBrowserTest.SameProcessIframe
+ - SingleProcessBrowserTest.Test
+
+## Chromium 98.0 + ABP 1.2 (ABP Chromium 98 v1)
+* Updated libadblockplus to version 12.1-c0.3.0
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/12.0-c0.3.0...12.1-c0.3.0)
+
+## Chromium 97.0 + ABP 1.1 (ABP Chromium 97 v1)
+* Updated to Chromium 97.0.4692.45
+* More fixes to make non-ABP test pass (DPD-866)
+* Moved ABP Java code to `components/adblock` to allow other components to declare dependencies from this code (DPD-837)
+ Package `org.chromium.chrome.browser.adblock` is changed to `org.chromium.components.adblock`
+* `.ci-scripts/v8.patch` introduced in 94v1 is no longer required (DPD-794)
+
+## Chromium 95.0 + ABP 1.0 (ABP Chromium 95 v1)
+* Updated to Chromium 95.0.4638.50
+* Removed unused AdblockTraceCall class and calls
+* Fixed majority of non-ABP unit tests and browser tests (DPC-568)
+
+Known issues:
+* The following browser tests are still failing, and will be fixed later:
+ - PersistentBackground/PermissionsApiTestWithContextType.OptionalPermissionsAutoConfirm/0
+ - PersistentBackground/PermissionsApiTestWithContextType.OptionalPermissionsGranted/0
+ - PageTextObserverSingleProcessBrowserTest.SameProcessAMPSubframe
+ - PageTextObserverSingleProcessBrowserTest.SameProcessIframe
+ - SingleProcessBrowserTest.Test
+
+## Chromium 94.0 + ABP 0.24 (ABP Chromium 94 v1)
+* Updated to Chromium 94.0.4606.50
+* Solved assertion issue detected in Chromium 92
+* Solved DCHECK issue detected in Chromium 93
+
+Known issues:
+* Debug builds sometimes hit DCHECK: https://bugs.chromium.org/p/chromium/issues/detail?id=1206694
+* Build is unsuccessful in certain environments due to warnings in V8: https://bugs.chromium.org/p/chromium/issues/detail?id=1251165
+
+ This issue can be circumvented by:
+
+ `cd v8 ; git reset --hard ; git apply ../.ci-scripts/v8.patch ; cd ..`
+
+## Chromium 93.0 + ABP 0.23 (ABP Chromium 93 v1)
+* Updated to Chromium 93.0.4577.62
+* Moved ABP-related translations to chrome/android/adblock to separate them from Chromium strings and avoid merging conflicts (DPD-696)
+* Fixed DCHECK for wrong ConversionMeasurementAPIAlternativeUsage feature configuration in upstream (DPD-749)
+* Fixed issue that blocked download PDF popup (DPD-742)
+* Added snippet filters support (DPD-648)
+* Added licensing header on chromium modified files (DPD-50)
+
+Known issues:
+* Debug builds running in specific x86 emulator hit DCHECK: https://bugs.chromium.org/p/chromium/issues/detail?id=1245583
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/10.0-c0.3.0...12.0-c0.3.0)
+
+## Chromium 92.0 + ABP 0.22 (ABP Chromium 92 v1)
+* Updated to Chromium 92.0.4515.105
+* Fixed crash when browser closed before FilterEngine has loaded (DPD-578)
+* Fixed crash when tab changed right after startup, before FilterEngine has loaded (DPD-367)
+* Fixed downloading the exceptions list on first run despite acceptable ads being off (DPD-53)
+* Replaced AdblockBridge with finer-grained classes: AdblockRequestClassifier and AdblockSitekeyStorage (DPD-611)
+* Added Extension API for testing and automation tasks on desktop (DPD-636)
+* Decrease priority for some adblocking-related background tasks for better startup experience (DPD-579)
+* Fixed redundant HEAD requests for disabled subscriptions (DPD-590)
+* Added class-level comments describing purpose and basic functionality description (DPD-400)
+* Added sequence diagrams for various ABP lifecycle scenarious to the docs_abp folder
+* Allow to override ABP application name & version from gn
+
+Known issues:
+* Debug builds running in x86 emulator crash due to spurious assertion in V8: https://bugs.chromium.org/p/chromium/issues/detail?id=1220335#c5
+
+ The assertion can be quenched by:
+
+ `cd v8 ; git reset --hard ; git apply ../.ci-scripts/v8.patch ; cd ..`
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/9.0-c0.3.0...10.0-c0.3.0)
+
+## Chromium 91.0 + ABP 0.21 (ABP Chromium 91 v1)
+* Updated to Chromium 91.0.4472.77
+* Moved ad-blocking logic from the network service to the browser and renderer(s) (DPD-368)
+* Moved all filter engine operations from AdblockController to AdblockBridge (DPD-284)
+* Added caching of JS and CSS generated for element hiding and element hiding emu purposes (DPD-7)
+* Updated libadblockplus to version 9.0-c0.3.0
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/8.1-c0.3.0...9.0-c0.3.0)
+
+## Chromium 90.0 + ABP 0.20 (ABP Chromium 90 v1)
+* Updated to Chromium 90.0.4430.66
+* Fixed a crash due to LegacyPrefsMigration sometimes starting without Profile (DPD-301)
+* Moved blocking/allowing WebSocket connections to ContentBrowserClient::CreateWebSocket (DPD-86)
+* Refactored AdblockBridgeImpl SendAdAllowed and SendAdBlocked into single method SendAdMatched (DPD-279)
+* Updated adblockpluscore to version 0.3.0
+* Updated libadblockplus to version 8.1-c0.3.0
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/8.0-c0.2.2...8.1-c0.3.0)
+
+## Chromium 89.0 + ABP 0.19 (ABP Chromium 89 v1)
+* Updated to Chromium 89.0.4389.72
+* Replaced deprecated base::ListValue with std::vector<base::Value> (DPD-26)
+* Created removeCustomFilter() method in AdblockController (DPD-58)
+* Removed ABP Network delegate (DPD-83)
+* Removed Android dependency from `components/adblock` (DPD-128)
+* Fixed Android Tests failing for builds with preloaded subscriptions (DPD-161)
+
+Known issues:
+* Some Android Tests are still failing for builds with preloaded subscriptions
+
+## Chromium 88.0 + ABP 0.18 (ABP Chromium 88 v1)
+* Updated to Chromium 88.0.4324.93
+* Fixed user counting when Acceptable Ads are disabled (DP-2118)
+* Fixed allowlisting in detached iframes (DP-2128)
+* Moved the bulk of the implementation to components/adblock (DP-1445)
+* Added dark-theme icons in Settings (DP-2054)
+* Reduced coupling between AdblockBridge and AdblockController (DP-2072)
+* Fixed potential random crashes in V8 (DP-2074)
+* Updated adblockpluscore to version 0.2.2
+* Updated libadblockplus to version 8.0-c0.2.2
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/3.1-c0.2.1...8.0-c0.2.2)
+
+## Chromium 87.0 + ABP 0.17 (ABP Chromium 87 v1)
+* Updated to Chromium 87.0.4280.66 (DP-1677)
+
+## Chromium 86.0 + ABP 0.17 (ABP Chromium 86 v2)
+* Allowlisting improvements:
+ - Generated allowlisted domain rule limited to the specific domain only (DP-1533)
+ - Adjusted allowlisting logic with Web Extension (DP-449)
+ - Fixed frame hierarchy not including browser-initiated loads (DP-1764)
+* AdblockController improvements:
+ - Added APIs allowing to add a custom filter and check if a filter matches (DP-1577)
+ - Made subscriptions-related APIs return `Subscription` object and aligned style with Java coding style (DP-1593, DP-1663)
+* Updated UI strings with translations (DP-1456, DP-1868, DP-1863)
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) updated to version 3.1-c0.2.1 (containing adblockplus core 0.2.1 equivalent to Web Extension version 3.10)
+ - Added Filter Engine enabled/disabled state, for cleaner prevention of subscription updates on startup (DP-1723)
+ - Aligning with Web Extension regarding blocking/allowing calls (DP-449)
+ - Made `Filter` and `Subscription` mockable via refactoring (DP-1490)
+ - Updated punycode.js to 2.1.0 (DP-1649)
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/35983c194d19afdf56847ee011c47df7b0fd7ff0...3b6d3934f1d85a09b9f33d374f56885e033950ca)
+
+Known issues:
+* Debug builds may crash due to failed assertions in vanilla Chromium source code; these crashes do not affect release builds (crbug.com/1129004, crbug.com/1041225)
+
+## Chromium 86.0 + ABP 0.16 (ABP Chromium 86 v1)
+* Updated to Chromium 86.0.4240.75 (DP-1669)
+
+Known issues:
+* Debug builds may crash due to failed assertions in vanilla Chromium source code; these crashes do not affect release builds (crbug.com/1129004, crbug.com/1041225)
+
+## Chromium 85.0 + ABP 0.16 (ABP Chromium 85 v2)
+* Introduced new architecture:
+ - Removed dependency on libadblockplus-android
+ - Removed the need for patches in v8/ and third_party/icu/
+ - Removed dependency on Android Support Library
+ - More responsive UI, especially on application startup
+ - Automatic migration of user settings from previous versions
+ - More how-to guides, including Migration FAQ
+ - Code better aligned with Chromium conventions
+ - Reduced the number of required changes in BUILD.gn files
+* Support for user-defined element blocking rules via core API (DP-1412)
+* Support for pre-loaded filter lists (DP-1430)
+* Filter lists will no longer be downloaded if ad-blocking is disabled (DP-1725)
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/-/compare/92c258681107db81c414083db7f64710642c7fec...35983c194d19afdf56847ee011c47df7b0fd7ff0)
+
+## Chromium 85.0 + ABP 0.15 (ABP Chromium 85 v1)
+* Updated to Chromium 85.0.4183.81 (DP-1471)
+* Reduced the verbosity of log messages containing debug information
+
+## Chromium 84.0 + ABP 0.15 (ABP Chromium 84 v1)
+* Updated to Chromium 84.0.4147.89 (DP-1334)
+* Switched to special version of [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) migrated to AndroidX, dependency points to branch `release-3.24-androidx-migrated`
+
+## Chromium 83.0 + ABP 0.15 (ABP Chromium 83 v2)
+* Upgraded ABP Core to version 3.9.1 (DP-1250, DP-1371)
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+* Fixed a bug causing some ads whitelisted by sitekey filters to be blocked on browser restart (DP-1268)
+* Reduced the number of ANRs (freezes) significantly due to moving more usages of the filter engine out of the UI thread (DP-1029)
+* Simplified code in order to make future Chromium updates easier (DP-871, DP-1266, DP-1189, DP-1323)
+* Fixed an ABP Core regression: Extended anchor does not matching a repeating pattern (DP-1208)
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/ffb27eeb2c61e3f951264485e435d03c0be5cd82...9f5579efbc774325c0b71978b775f96bf9fe64b1)
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/9436c424909ef9df9393ac48f0bae45c2c1dfa28...92c258681107db81c414083db7f64710642c7fec)
+
+## Chromium 83.0 + ABP 0.14 (ABP Chromium 83 v1)
+* Updated to Chromium 83.0.4103.96 (DP-1193)
+* Made ABP Settings UI more complying to other parts of chromium's settings UI which also mitigated chromium switching from the android support library to androidx (DP-1187)
+
+## Chromium 81.0 + ABP 0.14 (ABP Chromium 81 v2)
+* Upgraded ABP Core to version 3.6 (DP-1107)
+* Made ABP integration ready for Network Service running out of process (DP-1017)
+* Made the ABP popup blocker run first and disabled the builtin 'popups blocked' dialog (DP-1149)
+* Moved waiting for the filter engine out of the UI thread (DP-1249)
+* Fixed a bug causing the cache not being kept when changing subscriptions (DP-1061)
+* Fixed a bug leaving ad blocking still enabled when ABP and Acceptable Ads are both off (DP-1090)
+* Fixed a bug causing crashes due to the filter engine not being ready when entering Settings (DP-1173)
+* Fixed a crash caused by not checking if the filter engine is ready before the sitekey verification (DP-1267)
+* Adjusted to the threading changes introduced by libadblockplus-android 3.19 (DP-1281, DP-1263)
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/bf43c5313fbb4c39b91e84bc063cbeb448557461...ffb27eeb2c61e3f951264485e435d03c0be5cd82)
+
+Known regression:
+* Extended anchor does not match repeating pattern (DP-1208)
+
+## Chromium 81.0 + ABP 0.13 (ABP Chromium 81 v1)
+* Updated to Chromium 81.0.4044.96 (DP-939)
+* Fixed the detection if ABP library should not be loaded for other processes than the browser one so it does not raise an exception for the case the process is started by the Zygote preload.
+
+## Chromium 80.0 + ABP 0.13 (ABP Chromium 80 v2)
+* Updated to Chromium 80.0.3987.132 (DP-895)
+* Fixed incorrect JS escaping, convinience fix for strings manipulation (DP-843)
+* New way to collect frame heirarchy, improves filter matching for some cases (DP-923)
+* Various fixes aimed at improving stability and avoiding ANRs (DP-965, DP-887, DP-885, DP-831)
+
+## Chromium 80.0 + ABP 0.12 (ABP Chromium 80 v1)
+* Updated to Chromium 80.0.3987.99 (DP-751)
+
+## Chromium 79.0 + ABP 0.12 (ABP Chromium 79 v2)
+* Improved performance by distinguishing shared vs exclusive lock (rw locks) when setting/using g_adblock_provider (DP-736)
+
+## Chromium 79.0 + ABP 0.11 (ABP Chromium 79 v1)
+* Updated to Chromium 79.0.3945.93 (DP-724)
+
+## Chromium 78.0 + ABP 0.11 (ABP Chromium 78 v3)
+* Added API providing ad blocked and ad whitelisted notifications (DP-665, DP-553, DP-586)
+* Updated to Chromium 78.0.3904.108 (DP-658)
+* Fixed a regression bug introduced with ABPChromium 77, where subframes were not hidden after being blocked (DP-617)
+* Improved stability of multithreaded code (DP-422)
+
+## Chromium 78.0 + ABP 0.10 (ABP Chromium 78 v1)
+* Updated to Chromium 78.0.3904.62 (DP-630)
+
+## Chromium 77.0 + ABP 0.10
+* Updated to Chromium 77.0.3865.73 (DP-559)
+* Addressed the architectural changes of Chromium 77 by adapting our integration to NetworkService running in-process
+* Ported settings to use types from Android support library to be compatible with Chromium (DP-584)
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/4cf02b4b5f2837d45852234d9a1d2448e85f140c...9fff93a94836c9d31323c7d4a748c75f8bbc56c8)
+
+Known issues and notes:
+* ABP does not work with NetworkService running out of process
+* Subframes are not being hidden after being blocked (DP-601)
+
+## Chromium 76.0 + ABP 0.10
+* Updated to Chromium 76.0.3809.132 (DP-494)
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* Improved stability (DP-212, DP-482)
+* Improved performance (DP-469)
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/7a2ee1131ada85f9ec76186a1fe8ae5fa82cade3...4cf02b4b5f2837d45852234d9a1d2448e85f140c)
+
+## Chromium 76.0 + ABP 0.9
+* Updated to Chromium 76.0.3809.89
+* Disabled usage of NetworkService on top of upstream Chromium 76
+
+## Chromium 75.0 + ABP 0.9
+* Updated to Chromium 75.0.3770.101
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+* Added support for Genericblock filter option (DP-164)
+* Added support for Generichide filter option (DP-163)
+* Fixed a URL parsing bug causing sitekey whitelisting to not work on testpages.adblockplus.org (DP-229)
+* Fixed a bug which was blocking AAX sitekey ads when re-enabling ABP or restarting the browser (DP-242)
+* Fixed a bug causing anonymous iframe document not being blocked (DP-245)
+* Code cleanup: Reverted DP-222 changes - it was made redundant by DP-273 (DP-311)
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/468be7e41f58f035278d825a50f06aa04b4b9b02...9face67824818f9195d884dd4bc16e905937fcf8)
+
+## Chromium 75.0 + ABP 0.8
+* Updated to Chromium 75.0.3770.67
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/260136a5dffd457f3b964caa54bfcc7eeece4335...468be7e41f58f035278d825a50f06aa04b4b9b02)
+
+## Chromium 74.0 + ABP 0.8
+* Updated to Chromium 74.0.3729.136
+* Completely disabled field trial in order to disable lite page notifications (DP-66,DP-273)
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/d5c01bb06dffe21f7dcd634f65f224596d2094b4...260136a5dffd457f3b964caa54bfcc7eeece4335)
+
+## Chromium 73.0 + ABP 0.8
+* Updated to Chromium 73.0.3683.90
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* Improved whitelisting (DP-190, DP-145)
+* Fixed a bug with websocket support (DP-93)
+* Fixed a bug where request blocking was disabled in Production Builds due to a field trial NetworkService feature (DP-222)
+* Added support for compilation for arm64 architecture (DP-181)
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/10266f56a6cc8b18deb497adc9f566d15d38ea87...4794faabf5a743085983a7f0bb5dcaed7afe2249)
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/9ee5526e35cc651542082cbf61d70b9ae813457c...7a2ee1131ada85f9ec76186a1fe8ae5fa82cade3)
+
+## Chromium 73.0 + ABP 0.7
+* Updated to Chromium 73.0.3683.75
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/925ad81b5c81829b341b06fc98980912b1429b6d...9ee5526e35cc651542082cbf61d70b9ae813457c)
+
+## Chromium 72.0 + ABP 0.7
+* Updated a minor version of Chromium in order to include fix of CVE-2019-5786 (DP-90)
+
+## Chromium 72.0 + ABP 0.7
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* Fixed a bug where domain whitelisting is not working (DP-7)
+* Added support for element hiding emulation (DP-17)
+* Added support for sitekey (DP-28)
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/ec692c663b60e76c4a02a30323fac73686a18c14...10266f56a6cc8b18deb497adc9f566d15d38ea87)
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/f2e1a80624bb5280121eb07a726e5417af227ba7...925ad81b5c81829b341b06fc98980912b1429b6d)
+
+## Chromium 72.0 + ABP 0.6
+* Updated to Chromium 72.0.3626.76
+
+## Chromium 71.0 + ABP 0.6
+* [libadblockplus](https://gitlab.com/eyeo/adblockplus/libadblockplus) was updated
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/71) where chromium would crash in incognito mode
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/29) in blocking of popups
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/55) that caused chromium to crash on android 4.4.4
+
+[Full list of `libadblockplus` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus/compare/9fb96255de4d62e8a29f6d7e889d54d1fad9feb4...d0a4727ac3c21c8551eb392d2571122d1f88dead)
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/d150f08d5d72de8938c7ebbdccd9b0c4e06b4070...c803f858ca2c7ab572acf333042e254f41de3b94)
+
+## Chromium 71.0 + ABP 0.5
+* Updated to Chromium 71.0.3578.83
+
+## Chromium 70.0 + ABP 0.5
+* [libadblockplus-android](https://gitlab.com/eyeo/adblockplus/libadblockplus-android) was updated
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/33) with websocket requests not being blocked
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/35) with !important css styles not being blocked
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/36) where element hiding was not applied to content added via document.write() to anonymous iFrames
+* [Added support](https://gitlab.com/eyeo/adblockplus/chromium/issues/39) for allowing custom filter list subscriptions
+
+[Full list of `libadblockplus-android` changes](https://gitlab.com/eyeo/adblockplus/libadblockplus-android/compare/da7f888d7ef0c4a9fb798a972e5132612730b740...d150f08d5d72de8938c7ebbdccd9b0c4e06b4070)
+
+[Full list of `adblockpluschromium` changes](https://gitlab.com/eyeo/adblockplus/chromium/compare/cd317a965431966844f8d25f4e13dd352a6e1340...dev-70.0.3538.64_2)
+
+## Chromium 70.0 + ABP 0.4
+* Updated to Chromium 70.0.3538.64
+
+## Chromium 69.0 + ABP 0.4
+* Updated to Chromium 69.0.3497.100
+
+## Chromium 69.0 + ABP 0.4
+* Updated to Chromium 69.0.3497.91
+
+## Chromium 68.0 + ABP 0.4
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/31) where type of some network requests was incorrectly labeled as FAVICON instead of IMAGE, causing some images to not be blocked.
+* [Fixed a bug](https://gitlab.com/eyeo/adblockplus/chromium/issues/29) where pop-up blocking was not working.
+* [Improved ad blocking](https://gitlab.com/eyeo/adblockplus/chromium/issues/23) experience by hiding empty spaces left from elements which were blocked on a network level.
+
+## Chromium 68.0 + ABP 0.3.4
+* Updated to Chromium `68.0.3440.70`
+* [Use Chromium Android NDK](https://gitlab.com/eyeo/adblockplus/chromium/issues/26) when building, instead of downloading official Google Android NDK
+* [Fixed a bug](https://issues.adblockplus.org/ticket/6799) where Hebrew subscription was not installed for Hebrew locale
+
+## Chromium 67.0 + ABP 0.3.3
+* Updated to Chromium `67.0.3396.87`
+* [Update](https://gitlab.com/eyeo/adblockplus/chromium/issues/7) the way `libadblockplus` and `libadblockplus-android` are built
+* [Added support](https://issues.adblockplus.org/ticket/6632) for:
+ - `am-ET - Amharic (Ethiopia)`
+ - `fr-CA - French (Canada)`
+ - `fil-PH - Filipino (Philippines)`
+ - `sw-KE - Kiswahili (Kenya)`
+* [Initialize](https://gitlab.com/eyeo/adblockplus/chromium/issues/9) filter engine asynchronously, and [wait](https://gitlab.com/eyeo/adblockplus/chromium/issues/14) for it in Settings.
+* Stop using [deprecated libadblockplus-android API](https://gitlab.com/eyeo/adblockplus/chromium/issues/13)
+* [Disable](https://gitlab.com/eyeo/adblockplus/chromium/issues/20) Chromium's built-in ad blocking
+* [Support](https://gitlab.com/eyeo/adblockplus/chromium/issues/22) configuring of android package name filter list network requests
+* [Correctly detect](https://gitlab.com/eyeo/adblockplus/chromium/issues/24) the type of image resources
+* Added a requirement to have 7zip installed on building machine
+
+[Full list of `libadblockplus` changes](https://github.com/adblockplus/libadblockplus/compare/ea5309a0a6f3c5ab1e378b79c09d930ac3fbcfd0...40f5d4d2d00abe3f94ce69210267bcce908cd748).
+
+[Full list of `libadblockplus-android` changes](https://github.com/adblockplus/libadblockplus-android/compare/7ef63f2b8458a5b23595bd22c180c0e6f2398801...5342ea7697a7a55a3f649aadb6a976a0799e7922)
+
+[Full list of `adblockpluschromium` changes](https://github.com/adblockplus/chromium/compare/483c3b509b0f032a7e92bd4fce339e70c452d2aa...6b18f01e06da9bcb1f5ffdd3c9cfb1e1e485cbf2)
+
+## Chromium 65.0 + ABP 0.3.2
+* Base release of ad blocking integration
diff --git a/components/adblock/LICENSE b/components/adblock/LICENSE
new file mode 100644
--- /dev/null
+++ b/components/adblock/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year>-present eyeo GmbH
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year>-present eyeo GmbH
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
diff --git a/components/adblock/README.md b/components/adblock/README.md
new file mode 100644
--- /dev/null
+++ b/components/adblock/README.md
@@ -0,0 +1,80 @@
+# Eyeo Chromium SDK integration
+
+Eyeo Chromium SDK is a fork of the [Chromium project](https://chromium.googlesource.com/chromium/src) that integrates ad-filtering capabilities. This fork serves as a Software Development Kit (SDK) for extending browsers based on Chromium.
+
+For a detailed reasoning on why we implement our own ad-filtering solution instead of basing it in Chromium's Subresource Filter, check the corresponding [decision record](docs/adr/not-extending-subresource-filter.md).
+
+Please send any questions or report any issues to distribution-partners@eyeo.com.
+
+## General information
+
+The [ad-filtering documentation](docs/ad-filtering) describes the available ad-blocking functionalities and how they relate to filter lists. This is done not only from a functional perspective, but also providing more in-depth details, like sequence diagrams illustrating the ad-blocking flow.
+
+The [data collection documentation](docs/data-collection) describes what information is sent to the eyeo services, and our commitment to preserving user's privacy.
+
+The [settings documentation](docs/settings) describes the interfaces to configure the ad-blocking integration.
+
+Finally, the [Architecture Decision Record](docs/adr) documents the reasoning behind certain decisions taken during the development of eyeo Chromium SDK.
+
+
+## Partner integration steps
+
+### Check dependencies and prerequisites
+
+Eyeo Chromium SDK depends on several parts of Chromium, such as the following:
+
+* [KeyedService](/components/keyed_service/core/keyed_service.h)
+* [Network Service](/services/network/)
+* [PrefService](/components/prefs/pref_service.h)
+* [Profile](/chrome/browser/profiles/profile.h)
+* [Resources](/components/resources/)
+* [Version Info](/components/version_info/)
+
+If you cannot include these or any other parts of Chromium in your browser, you will have to re-implement them or work around them.
+
+### Understand the available interfaces
+
+The eyeo Chromium SDK APIs allow to interact with the ad-filtering engine:
+- to enable/disable it,
+- configure selected filter lists and filters,
+- receive notifications about blocking or allowing events.
+
+The SDK can be controlled by:
+- C++ API
+- Java API (on Android)
+- JavaScript Extension API (on Linux, Windows, MacOS)
+
+The SDK extends the browser's Settings UI with an "Ad blocking" section on:
+- Android
+- Linux, Windows, MacOS
+
+In order to understand what settings are available and which API is best for your use case, check the [settings documentation](docs/settings/README.md).
+
+### Prepare your application
+
+Follow the [integration how-to](docs/integration-how-to.md) to configure the ad-filtering engine. You will also find information about how to set up your application name and version.
+
+### Upgrade scenario: Find out what has changed between eyeo Chromium SDK releases
+
+Differences across versions are listed in [the changelog](components/adblock/CHANGELOG.md).
+
+You can also use our [interdiff script](tools/eyeo/generate_interdiffs.sh) to compare two git revision ranges. You can find more information in the [integration how-to](docs/integration-how-to.md).
+
+
+## For eyeo Chromium SDK contributors
+
+Adblock strives to follow the [layered component design](https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-design)
+
+You will find most of eyeo Chromium SDK specific code in the following places:
+
+* `components/adblock/core`: Platform-agnostic ad filtering integration
+* `components/adblock/content`: `content` dependent ad filtering integration
+* `chrome/browser/adblock`: OS-agnostic but Chrome-specific integration
+* `components/adblock/android`: Android-specific JNI code for UI
+* `chrome/renderer/adblock`: Hooks in the Renderer process to observe Renderer-issued resource loads
+* `chrome/common/extensions`: Implementation of the `adblockPrivate` Extension API
+* `components/adblock/android/java/src/org/chromium/components/adblock`: Android implementation of Settings UI
+
+You can find how our implementation maps to Chromium's design in the [design overview](docs/design-overview.md).
+
+General information for developers, like options for logging, testing, etc, can be found in the [developer notes](docs/developer-notes.md).
diff --git a/components/adblock/content/BUILD.gn b/components/adblock/content/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/BUILD.gn
@@ -0,0 +1,22 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+assert(!is_ios)
+
+# External targets should depend on this
+group("browser") {
+ public_deps = [ "//components/adblock/content/browser:browser_impl" ]
+}
diff --git a/components/adblock/content/browser/BUILD.gn b/components/adblock/content/browser/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/BUILD.gn
@@ -0,0 +1,226 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//components/adblock/features.gni")
+
+config("adblock_content_common_config") {
+ defines = []
+
+ if (eyeo_intercept_debug_url) {
+ print("WARNING! Enabled intercepting eyeo debug domain \"test.data\"")
+ defines += [ "EYEO_INTERCEPT_DEBUG_URL=1" ]
+ }
+}
+
+source_set("browser_impl") {
+ visibility = [
+ ":*",
+ "//components/adblock/content:*",
+ ]
+ sources = [
+ "adblock_context_data.cc",
+ "adblock_context_data.h",
+ "adblock_filter_match.h",
+ "adblock_internals_page_handler.cc",
+ "adblock_internals_page_handler.h",
+ "adblock_internals_ui.cc",
+ "adblock_internals_ui.h",
+ "adblock_url_loader_factory.cc",
+ "adblock_url_loader_factory.h",
+ "adblock_web_ui_controller_factory.cc",
+ "adblock_web_ui_controller_factory.h",
+ "adblock_webcontents_observer.cc",
+ "adblock_webcontents_observer.h",
+ "content_security_policy_injector.h",
+ "content_security_policy_injector_impl.cc",
+ "content_security_policy_injector_impl.h",
+ "element_hider.h",
+ "element_hider_impl.cc",
+ "element_hider_impl.h",
+ "eyeo_document_info.cc",
+ "eyeo_document_info.h",
+ "eyeo_page_info.cc",
+ "eyeo_page_info.h",
+ "factories/adblock_request_throttle_factory.cc",
+ "factories/adblock_request_throttle_factory.h",
+ "factories/adblock_telemetry_service_factory.cc",
+ "factories/adblock_telemetry_service_factory.h",
+ "factories/content_security_policy_injector_factory.cc",
+ "factories/content_security_policy_injector_factory.h",
+ "factories/element_hider_factory.cc",
+ "factories/element_hider_factory.h",
+ "factories/embedding_utils.cc",
+ "factories/embedding_utils.h",
+ "factories/resource_classification_runner_factory.cc",
+ "factories/resource_classification_runner_factory.h",
+ "factories/session_stats_factory.cc",
+ "factories/session_stats_factory.h",
+ "factories/sitekey_storage_factory.cc",
+ "factories/sitekey_storage_factory.h",
+ "factories/subscription_persistent_metadata_factory.cc",
+ "factories/subscription_persistent_metadata_factory.h",
+ "factories/subscription_service_factory.cc",
+ "factories/subscription_service_factory.h",
+ "frame_hierarchy_builder.cc",
+ "frame_hierarchy_builder.h",
+ "frame_opener_info.cc",
+ "frame_opener_info.h",
+ "page_view_stats.cc",
+ "page_view_stats.h",
+ "request_initiator.cc",
+ "request_initiator.h",
+ "resource_classification_runner.h",
+ "resource_classification_runner_impl.cc",
+ "resource_classification_runner_impl.h",
+ "session_stats_impl.cc",
+ "session_stats_impl.h",
+ ]
+
+ if (eyeo_intercept_debug_url) {
+ sources += [
+ "adblock_url_loader_factory_for_test.cc",
+ "adblock_url_loader_factory_for_test.h",
+ ]
+ }
+
+ deps = [
+ "//base",
+ "//components/adblock/content/browser/mojom:adblock_internals",
+ "//components/adblock/content/resources/adblock_internals:resources",
+ "//components/adblock/core/converter:converter",
+ "//components/adblock/core/resources:adblock_resources",
+ "//components/keyed_service/content:content",
+ "//components/language/core/browser:browser",
+ "//components/language/core/common:common",
+ "//components/user_prefs:user_prefs",
+ "//ui/webui:webui",
+ "//url:url",
+ ]
+
+ public_deps = [
+ "//components/adblock/core",
+ "//content/public/browser",
+ "//third_party/blink/public/common:headers",
+ ]
+
+ all_dependent_configs = [ ":adblock_content_common_config" ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/mock_adblock_content_security_policy_injector.cc",
+ "test/mock_adblock_content_security_policy_injector.h",
+ "test/mock_element_hider.cc",
+ "test/mock_element_hider.h",
+ "test/mock_frame_hierarchy_builder.cc",
+ "test/mock_frame_hierarchy_builder.h",
+ "test/mock_resource_classification_runner.cc",
+ "test/mock_resource_classification_runner.h",
+ ]
+
+ public_deps = [
+ ":browser_impl",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/adblock_url_loader_factory_test.cc",
+ "test/adblock_webcontents_observer_test.cc",
+ "test/content_security_policy_injector_impl_test.cc",
+ "test/element_hider_impl_test.cc",
+ "test/frame_hierarchy_builder_test.cc",
+ "test/page_view_stats_test.cc",
+ "test/resource_classification_runner_impl_test.cc",
+ "test/session_stats_impl_test.cc",
+ "test/subscription_service_factory_test.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ "//base/test:test_support",
+ "//components/adblock/core:test_support",
+ "//components/adblock/core:test_support",
+ "//components/adblock/core/classifier:test_support",
+ "//components/adblock/core/resources:adblock_resources",
+ "//components/adblock/core/subscription:test_support",
+ "//components/prefs:test_support",
+ "//components/sync_preferences:test_support",
+ "//content/test:test_support",
+ "//net:test_support",
+ "//services/network:test_support",
+ "//ui/base:test_support",
+ ]
+}
+
+source_set("browser_tests_support") {
+ testonly = true
+
+ sources = [
+ "test/adblock_browsertest_base.cc",
+ "test/adblock_browsertest_base.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//base/test:test_support",
+ "//components/adblock/content:browser",
+ "//components/adblock/core/common",
+ "//components/adblock/core/subscription:test_support",
+ "//components/user_prefs:user_prefs",
+ "//components/web_package:web_package",
+ "//content/shell:content_shell_app",
+ "//content/shell:content_shell_lib",
+ "//content/test:browsertest_support",
+ "//content/test:test_support",
+ "//net:test_support",
+ ]
+}
+
+source_set("browser_tests") {
+ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+ testonly = true
+
+ sources = [
+ "test/adblock_acceptable_ads_browsertest.cc",
+ "test/adblock_content_browser_client_browsertest.cc",
+ "test/adblock_content_filters_browsertest.cc",
+ "test/adblock_filter_list_browsertest.cc",
+ "test/adblock_filtering_configurations_browsertest.cc",
+ "test/adblock_non_ascii_browsertest.cc",
+ "test/adblock_page_view_stats_browsertest.cc",
+ "test/adblock_request_throttle_browsertest.cc",
+ "test/adblock_service_workers_browsertest.cc",
+ "test/adblock_sitekey_browsertest.cc",
+ "test/adblock_snippets_browsertest.cc",
+ "test/adblock_subscription_service_browsertest.cc",
+ "test/adblock_telemetry_service_browsertest.cc",
+ "test/adblock_trusted_events_browsertest.cc",
+ "test/adblock_web_bundle_browsertest.cc",
+ "test/adblock_web_ui_browsertest.cc",
+ ]
+
+ if (eyeo_intercept_debug_url) {
+ sources += [ "test/adblock_debug_url_browsertest.cc" ]
+ }
+
+ deps = [ ":browser_tests_support" ]
+}
diff --git a/components/adblock/content/browser/adblock_content_browser_client.h b/components/adblock/content/browser/adblock_content_browser_client.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_content_browser_client.h
@@ -0,0 +1,343 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTENT_BROWSER_CLIENT_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTENT_BROWSER_CLIENT_H_
+
+#include "build/buildflag.h"
+#include "components/adblock/content/browser/adblock_context_data.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/adblock_web_ui_controller_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "services/network/public/cpp/url_loader_factory_builder.h"
+#include "services/network/public/mojom/websocket.mojom.h"
+#include "url/url_util.h"
+
+#ifdef EYEO_INTERCEPT_DEBUG_URL
+#include "components/adblock/content/browser/adblock_url_loader_factory_for_test.h"
+#endif
+
+#include "extensions/buildflags/buildflags.h"
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/extension_util.h"
+#endif
+
+#include "components/adblock/content/browser/adblock_internals_ui.h"
+#include "content/public/browser/web_ui_controller_interface_binder.h"
+
+namespace adblock {
+
+/**
+ * @brief Intercepts network and UI events to inject ad-filtering.
+ * Provides ad-filtering implementations of URLLoaderThrottles.
+ * Binds a mojo connection between Renderer processes and the
+ * Browser-process-based ResourceClassificationRunner.
+ * Lives in browser process UI thread.
+ */
+template <class ContentBrowserClientBase>
+class AdblockContentBrowserClient : public ContentBrowserClientBase {
+ public:
+ template <typename... Args>
+ explicit AdblockContentBrowserClient(Args&&... args)
+ : ContentBrowserClientBase(std::forward<Args>(args)...) {}
+
+ AdblockContentBrowserClient(const AdblockContentBrowserClient&) = delete;
+ AdblockContentBrowserClient& operator=(const AdblockContentBrowserClient&) =
+ delete;
+ ~AdblockContentBrowserClient() override = default;
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ // Enable ad filtering also for requests initiated by extensions.
+ // This allows implementing extension-driven browser tests.
+ // In production code, requests from extensions are not blocked.
+ static void ForceAdblockProxyForTesting();
+#endif
+
+ bool WillInterceptWebSocket(content::RenderFrameHost* frame) override;
+ void CreateWebSocket(
+ content::RenderFrameHost* frame,
+ content::ContentBrowserClient::WebSocketFactory factory,
+ const GURL& url,
+ const net::SiteForCookies& site_for_cookies,
+ const absl::optional<std::string>& user_agent,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ handshake_client) override;
+ void WillCreateURLLoaderFactory(
+ content::BrowserContext* browser_context,
+ content::RenderFrameHost* frame,
+ int render_process_id,
+ content::ContentBrowserClient::URLLoaderFactoryType type,
+ const url::Origin& request_initiator,
+ const net::IsolationInfo& isolation_info,
+ absl::optional<int64_t> navigation_id,
+ ukm::SourceIdObj ukm_source_id,
+ network::URLLoaderFactoryBuilder& factory_builder,
+ mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+ header_client,
+ bool* bypass_redirect_checks,
+ bool* disable_secure_dns,
+ network::mojom::URLLoaderFactoryOverridePtr* factory_override,
+ scoped_refptr<base::SequencedTaskRunner> navigation_response_task_runner)
+ override;
+
+ void RegisterBrowserInterfaceBindersForFrame(
+ content::RenderFrameHost* render_frame_host,
+ mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override;
+
+ protected:
+ static bool IsFilteringNeeded(content::BrowserContext* browser_context);
+
+ // current_browser_context is the BrowserContext relevant for the currently
+ // processed request. It might be an off-the-record browser context. This
+ // method allows implementing embedder-specific logic for getting the "right"
+ // BrowserContext for eyeo services, which might be the "default" or the
+ // "original" BrowserContext, depending on platform.
+ virtual content::BrowserContext* GetBrowserContextForEyeoFactories(
+ content::BrowserContext* current_browser_context) = 0;
+
+ private:
+ content::BrowserContext* GetBrowserContext(content::RenderFrameHost* frame) {
+ DCHECK(frame);
+ return GetBrowserContextForEyeoFactories(
+ frame->GetProcess()->GetBrowserContext());
+ }
+
+ void OnWebSocketFilterCheckCompleted(
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ content::ContentBrowserClient::WebSocketFactory factory,
+ const GURL& url,
+ const net::SiteForCookies& site_for_cookies,
+ const absl::optional<std::string>& user_agent,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ handshake_client,
+ adblock::FilterMatchResult result);
+
+ base::WeakPtrFactory<AdblockContentBrowserClient<ContentBrowserClientBase>>
+ weak_factory_{this};
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ static bool force_adblock_proxy_for_testing_;
+#endif
+};
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+// static
+template <class ContentBrowserClientBase>
+bool AdblockContentBrowserClient<
+ ContentBrowserClientBase>::force_adblock_proxy_for_testing_ = false;
+
+// static
+template <class ContentBrowserClientBase>
+void AdblockContentBrowserClient<
+ ContentBrowserClientBase>::ForceAdblockProxyForTesting() {
+ force_adblock_proxy_for_testing_ = true;
+}
+#endif
+
+template <class ContentBrowserClientBase>
+bool AdblockContentBrowserClient<ContentBrowserClientBase>::
+ WillInterceptWebSocket(content::RenderFrameHost* frame) {
+ if (frame && IsFilteringNeeded(GetBrowserContext(frame))) {
+ return true;
+ }
+ return ContentBrowserClientBase::WillInterceptWebSocket(frame);
+}
+
+template <class ContentBrowserClientBase>
+void AdblockContentBrowserClient<ContentBrowserClientBase>::CreateWebSocket(
+ content::RenderFrameHost* frame,
+ content::ContentBrowserClient::WebSocketFactory factory,
+ const GURL& url,
+ const net::SiteForCookies& site_for_cookies,
+ const absl::optional<std::string>& user_agent,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ handshake_client) {
+ if (frame && IsFilteringNeeded(GetBrowserContext(frame))) {
+ auto* subscription_service =
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(
+ GetBrowserContext(frame));
+ auto* classification_runner =
+ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext(
+ GetBrowserContext(frame));
+ classification_runner->CheckRequestFilterMatch(
+ subscription_service->GetCurrentSnapshot(), url, ContentType::Websocket,
+ RequestInitiator(frame),
+ base::BindOnce(
+ &AdblockContentBrowserClient<
+ ContentBrowserClientBase>::OnWebSocketFilterCheckCompleted,
+ weak_factory_.GetWeakPtr(), frame->GetGlobalId(),
+ std::move(factory), url, site_for_cookies, user_agent,
+ std::move(handshake_client)));
+ } else {
+ DCHECK(ContentBrowserClientBase::WillInterceptWebSocket(frame));
+ ContentBrowserClientBase::CreateWebSocket(frame, std::move(factory), url,
+ site_for_cookies, user_agent,
+ std::move(handshake_client));
+ }
+}
+
+template <class ContentBrowserClientBase>
+void AdblockContentBrowserClient<ContentBrowserClientBase>::
+ RegisterBrowserInterfaceBindersForFrame(
+ content::RenderFrameHost* render_frame_host,
+ mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
+ ContentBrowserClientBase::RegisterBrowserInterfaceBindersForFrame(
+ render_frame_host, map);
+ content::RegisterWebUIControllerInterfaceBinder<
+ ::mojom::adblock_internals::AdblockInternalsPageHandler,
+ adblock::AdblockInternalsUI>(map);
+}
+
+template <class ContentBrowserClientBase>
+void AdblockContentBrowserClient<ContentBrowserClientBase>::
+ WillCreateURLLoaderFactory(
+ content::BrowserContext* browser_context,
+ content::RenderFrameHost* frame,
+ int render_process_id,
+ content::ContentBrowserClient::URLLoaderFactoryType type,
+ const url::Origin& request_initiator,
+ const net::IsolationInfo& isolation_info,
+ absl::optional<int64_t> navigation_id,
+ ukm::SourceIdObj ukm_source_id,
+ network::URLLoaderFactoryBuilder& factory_builder,
+ mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+ header_client,
+ bool* bypass_redirect_checks,
+ bool* disable_secure_dns,
+ network::mojom::URLLoaderFactoryOverridePtr* factory_override,
+ scoped_refptr<base::SequencedTaskRunner>
+ navigation_response_task_runner) {
+ // Create Chromium proxy first as WebRequestProxyingURLLoaderFactory logic
+ // depends on being first proxy
+ ContentBrowserClientBase::WillCreateURLLoaderFactory(
+ browser_context, frame, render_process_id, type, request_initiator,
+ isolation_info, navigation_id, ukm_source_id, factory_builder,
+ header_client, bypass_redirect_checks, disable_secure_dns,
+ factory_override, navigation_response_task_runner);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ if (!force_adblock_proxy_for_testing_ &&
+ request_initiator.scheme() == extensions::kExtensionScheme) {
+ VLOG(1) << "[eyeo] Do not use adblock proxy for extensions requests "
+ "[extension id:"
+ << request_initiator.host() << "].";
+ return;
+ }
+#endif
+ auto* eyeo_browser_context =
+ GetBrowserContextForEyeoFactories(browser_context);
+ bool use_adblock_proxy =
+ (type == content::ContentBrowserClient::URLLoaderFactoryType::
+ kDocumentSubResource ||
+ type ==
+ content::ContentBrowserClient::URLLoaderFactoryType::kNavigation ||
+ type == content::ContentBrowserClient::URLLoaderFactoryType::
+ kServiceWorkerSubResource ||
+ type == content::ContentBrowserClient::URLLoaderFactoryType::
+ kServiceWorkerScript) &&
+ IsFilteringNeeded(eyeo_browser_context);
+
+ bool use_test_loader = false;
+#ifdef EYEO_INTERCEPT_DEBUG_URL
+ if (frame) {
+ content::WebContents* wc = content::WebContents::FromRenderFrameHost(frame);
+ use_test_loader =
+ (type ==
+ content::ContentBrowserClient::URLLoaderFactoryType::kNavigation) &&
+ wc->GetVisibleURL().is_valid() &&
+ url::DomainIs(wc->GetVisibleURL().host_piece(),
+ AdblockURLLoaderFactoryForTest::kEyeoDebugDataHostName);
+ use_adblock_proxy |= use_test_loader;
+ }
+#endif
+
+ if (use_adblock_proxy) {
+ auto [proxied_receiver, target_factory_remote] = factory_builder.Append();
+ const RequestInitiator initiator =
+ frame ? RequestInitiator(frame)
+ : RequestInitiator(request_initiator.GetURL());
+ AdblockContextData::StartProxying(
+ eyeo_browser_context, initiator, std::move(proxied_receiver),
+ std::move(target_factory_remote),
+ ContentBrowserClientBase::GetUserAgent(), use_test_loader);
+ }
+}
+
+template <class ContentBrowserClientBase>
+void AdblockContentBrowserClient<ContentBrowserClientBase>::
+ OnWebSocketFilterCheckCompleted(
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ content::ContentBrowserClient::WebSocketFactory factory,
+ const GURL& url,
+ const net::SiteForCookies& site_for_cookies,
+ const absl::optional<std::string>& user_agent,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ handshake_client,
+ adblock::FilterMatchResult result) {
+ auto* frame = content::RenderFrameHost::FromID(render_frame_host_id);
+ if (!frame) {
+ return;
+ }
+ const bool has_blocking_filter =
+ result == adblock::FilterMatchResult::kBlockRule;
+ if (!has_blocking_filter) {
+ VLOG(1) << "[eyeo] Web socket allowed for " << url;
+ if (ContentBrowserClientBase::WillInterceptWebSocket(frame)) {
+ ContentBrowserClientBase::CreateWebSocket(frame, std::move(factory), url,
+ site_for_cookies, user_agent,
+ std::move(handshake_client));
+ return;
+ }
+
+ std::vector<network::mojom::HttpHeaderPtr> headers;
+ if (user_agent) {
+ headers.push_back(network::mojom::HttpHeader::New(
+ net::HttpRequestHeaders::kUserAgent, *user_agent));
+ }
+ std::move(factory).Run(url, std::move(headers), std::move(handshake_client),
+ mojo::NullRemote(), mojo::NullRemote());
+ }
+
+ VLOG(1) << "[eyeo] Web socket blocked for " << url;
+}
+
+// static
+template <class ContentBrowserClientBase>
+bool AdblockContentBrowserClient<ContentBrowserClientBase>::IsFilteringNeeded(
+ content::BrowserContext* browser_context) {
+ if (browser_context) {
+ return base::ranges::any_of(
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(
+ browser_context)
+ ->GetInstalledFilteringConfigurations(),
+ &adblock::FilteringConfiguration::IsEnabled);
+ }
+ return false;
+}
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTENT_BROWSER_CLIENT_H_
diff --git a/components/adblock/content/browser/adblock_context_data.cc b/components/adblock/content/browser/adblock_context_data.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_context_data.cc
@@ -0,0 +1,87 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_context_data.h"
+
+#include "components/adblock/content/browser/factories/content_security_policy_injector_factory.h"
+#include "components/adblock/content/browser/factories/element_hider_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/sitekey_storage_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/request_initiator.h"
+
+#ifdef EYEO_INTERCEPT_DEBUG_URL
+#include "components/adblock/content/browser/adblock_url_loader_factory_for_test.h"
+#endif
+
+namespace adblock {
+
+AdblockContextData::AdblockContextData() = default;
+AdblockContextData::~AdblockContextData() = default;
+
+// static
+void AdblockContextData::StartProxying(
+ content::BrowserContext* browser_context,
+ RequestInitiator initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ const std::string& user_agent,
+ bool use_test_loader) {
+ const void* const kAdblockContextUserDataKey = &kAdblockContextUserDataKey;
+ auto* self = static_cast<AdblockContextData*>(
+ browser_context->GetUserData(kAdblockContextUserDataKey));
+ if (!self) {
+ self = new AdblockContextData();
+ browser_context->SetUserData(kAdblockContextUserDataKey,
+ base::WrapUnique(self));
+ }
+ adblock::AdblockURLLoaderFactoryConfig config{
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(
+ browser_context),
+ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext(
+ browser_context),
+ adblock::ElementHiderFactory::GetForBrowserContext(browser_context),
+ adblock::SitekeyStorageFactory::GetForBrowserContext(browser_context),
+ adblock::ContentSecurityPolicyInjectorFactory::GetForBrowserContext(
+ browser_context)};
+#ifdef EYEO_INTERCEPT_DEBUG_URL
+ if (use_test_loader) {
+ auto proxy = std::make_unique<adblock::AdblockURLLoaderFactoryForTest>(
+ std::move(config), std::move(initiator), std::move(receiver),
+ std::move(target_factory), user_agent,
+ base::BindOnce(&AdblockContextData::RemoveProxy,
+ self->weak_factory_.GetWeakPtr()),
+ browser_context);
+ self->proxies_.emplace(std::move(proxy));
+ return;
+ }
+#endif
+ auto proxy = std::make_unique<adblock::AdblockURLLoaderFactory>(
+ std::move(config), std::move(initiator), std::move(receiver),
+ std::move(target_factory), user_agent,
+ base::BindOnce(&AdblockContextData::RemoveProxy,
+ self->weak_factory_.GetWeakPtr()));
+ self->proxies_.emplace(std::move(proxy));
+}
+
+void AdblockContextData::RemoveProxy(AdblockURLLoaderFactory* proxy) {
+ auto it = proxies_.find(proxy);
+ DCHECK(it != proxies_.end());
+ proxies_.erase(it);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_context_data.h b/components/adblock/content/browser/adblock_context_data.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_context_data.h
@@ -0,0 +1,63 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTEXT_DATA_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTEXT_DATA_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "components/adblock/content/browser/adblock_url_loader_factory.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_frame_host.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+
+namespace adblock {
+
+// Owns all of the AdblockURLLoaderFactory for a given BrowserContext.
+class AdblockContextData : public base::SupportsUserData::Data {
+ public:
+ AdblockContextData(const AdblockContextData&) = delete;
+ AdblockContextData& operator=(const AdblockContextData&) = delete;
+ ~AdblockContextData() override;
+
+ static void StartProxying(
+ content::BrowserContext* browser_context,
+ RequestInitiator initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ const std::string& user_agent,
+ bool use_test_loader);
+
+ private:
+ AdblockContextData();
+
+ void RemoveProxy(AdblockURLLoaderFactory* proxy);
+
+ std::set<std::unique_ptr<AdblockURLLoaderFactory>, base::UniquePtrComparator>
+ proxies_;
+
+ base::WeakPtrFactory<AdblockContextData> weak_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTEXT_DATA_H_
diff --git a/components/adblock/content/browser/adblock_filter_match.h b/components/adblock/content/browser/adblock_filter_match.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_filter_match.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_FILTER_MATCH_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_FILTER_MATCH_H_
+
+#include "base/functional/callback_forward.h"
+
+namespace adblock {
+
+enum class FilterMatchResult { kNoRule, kBlockRule, kAllowRule, kDisabled };
+
+using CheckFilterMatchCallback = base::OnceCallback<void(FilterMatchResult)>;
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_FILTER_MATCH_H_
diff --git a/components/adblock/content/browser/adblock_internals_page_handler.cc b/components/adblock/content/browser/adblock_internals_page_handler.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_internals_page_handler.cc
@@ -0,0 +1,171 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_internals_page_handler.h"
+
+#include "base/i18n/time_formatting.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+#include "components/adblock/content/browser/factories/session_stats_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/core/adblock_telemetry_service.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/session_stats.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+
+namespace adblock {
+
+namespace {
+
+std::string SubscriptionInstallationStateToString(
+ adblock::Subscription::InstallationState state) {
+ using State = adblock::Subscription::InstallationState;
+ switch (state) {
+ case State::Installed:
+ return "Installed";
+ case State::AutoInstalled:
+ return "AutoInstalled";
+ case State::Preloaded:
+ return "Preloaded";
+ case State::Installing:
+ return "Installing";
+ case State::Unknown:
+ return "Unknown";
+ }
+ return "";
+}
+
+std::string DebugLine(std::string name, std::string value, int level) {
+ return std::string(2 * level, ' ') + (name.empty() ? "" : name + ": ") +
+ value + '\n';
+}
+
+std::string DebugLine(std::string name, int value, int level) {
+ return DebugLine(name, std::to_string(value), level);
+}
+
+std::string FormatInstallationTime(base::Time time) {
+ if (time.is_null()) {
+ return "Never";
+ }
+ return base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(time));
+}
+
+} // namespace
+
+AdblockInternalsPageHandler::AdblockInternalsPageHandler(
+ content::BrowserContext* context,
+ mojo::PendingReceiver<mojom::adblock_internals::AdblockInternalsPageHandler>
+ receiver)
+ : context_(context), receiver_(this, std::move(receiver)) {}
+
+AdblockInternalsPageHandler::~AdblockInternalsPageHandler() = default;
+
+void AdblockInternalsPageHandler::GetDebugInfo(GetDebugInfoCallback callback) {
+ CHECK(context_);
+ auto* service =
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(context_);
+ auto* stats = adblock::SessionStatsFactory::GetForBrowserContext(context_);
+ auto allowed = stats->GetSessionAllowedResourcesCount();
+ auto blocked = stats->GetSessionBlockedResourcesCount();
+ std::string content;
+ for (auto* config : service->GetInstalledFilteringConfigurations()) {
+ content += DebugLine("Configuration", config->GetName(), 0);
+ content += DebugLine("Enabled", (config->IsEnabled() ? "Yes" : "No"), 1);
+ if (!config->GetAllowedDomains().empty()) {
+ content += DebugLine("Allowed domains", "", 1);
+ for (const auto& it : config->GetAllowedDomains()) {
+ content += DebugLine("", it, 2);
+ }
+ }
+ if (!config->GetCustomFilters().empty()) {
+ content += DebugLine("Custom filters", "", 1);
+ for (const auto& it : config->GetCustomFilters()) {
+ content += DebugLine("", it, 2);
+ }
+ }
+ content += DebugLine("Filter lists", "", 1);
+ for (auto it : service->GetCurrentSubscriptions(config)) {
+ auto url = it->GetSourceUrl();
+ content += DebugLine("", url.spec(), 2);
+ content += DebugLine(
+ "State",
+ SubscriptionInstallationStateToString(it->GetInstallationState()), 3);
+ content += DebugLine("Title", it->GetTitle(), 3);
+ content += DebugLine("Version", it->GetCurrentVersion(), 3);
+ content += DebugLine(
+ "Last update", FormatInstallationTime(it->GetInstallationTime()), 3);
+ content += DebugLine("Total allowed", allowed[url], 3);
+ content += DebugLine("Total blocked", blocked[url], 3);
+ }
+ // Add stats for custom filters
+ content += DebugLine("", CustomFiltersUrl().spec(), 2);
+ content += DebugLine("Title", "Internal filter list for custom filters", 3);
+ content += DebugLine("Total allowed", allowed[CustomFiltersUrl()], 3);
+ content += DebugLine("Total blocked", blocked[CustomFiltersUrl()], 3);
+ }
+
+ auto* telemetry_service =
+ adblock::AdblockTelemetryServiceFactory::GetForBrowserContext(context_);
+ telemetry_service->GetTopicProvidersDebugInfo(base::BindOnce(
+ &AdblockInternalsPageHandler::OnTelemetryServiceInfoArrived,
+ std::move(callback), std::move(content)));
+}
+
+void AdblockInternalsPageHandler::ToggleTestpagesFLSubscription(
+ ToggleTestpagesFLSubscriptionCallback callback) {
+ auto* adblock_configuration =
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(context_)
+ ->GetFilteringConfiguration(
+ adblock::kAdblockFilteringConfigurationName);
+ if (IsSubscribedToTestpagesFL()) {
+ adblock_configuration->RemoveFilterList(
+ adblock::TestPagesSubscriptionUrl());
+ } else {
+ adblock_configuration->AddFilterList(adblock::TestPagesSubscriptionUrl());
+ }
+ std::move(callback).Run(IsSubscribedToTestpagesFL());
+}
+
+void AdblockInternalsPageHandler::IsSubscribedToTestpagesFL(
+ IsSubscribedToTestpagesFLCallback callback) {
+ std::move(callback).Run(IsSubscribedToTestpagesFL());
+}
+
+void AdblockInternalsPageHandler::OnTelemetryServiceInfoArrived(
+ GetDebugInfoCallback callback,
+ std::string content,
+ std::vector<std::string> topic_provider_content) {
+ for (auto& topic_provider_debug_info : topic_provider_content) {
+ content +=
+ DebugLine("Eyeometry topic provider", topic_provider_debug_info, 0);
+ }
+ std::move(callback).Run(std::move(content));
+}
+
+bool AdblockInternalsPageHandler::IsSubscribedToTestpagesFL() const {
+ auto* adblock_configuration =
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(context_)
+ ->GetFilteringConfiguration(
+ adblock::kAdblockFilteringConfigurationName);
+ auto filter_lists = adblock_configuration->GetFilterLists();
+ return std::find(filter_lists.begin(), filter_lists.end(),
+ adblock::TestPagesSubscriptionUrl()) != filter_lists.end();
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_internals_page_handler.h b/components/adblock/content/browser/adblock_internals_page_handler.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_internals_page_handler.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_PAGE_HANDLER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_PAGE_HANDLER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/content/browser/mojom/adblock_internals.mojom.h"
+#include "content/public/browser/browser_context.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+namespace adblock {
+
+class AdblockInternalsPageHandler
+ : public mojom::adblock_internals::AdblockInternalsPageHandler {
+ public:
+ explicit AdblockInternalsPageHandler(
+ content::BrowserContext* context,
+ mojo::PendingReceiver<
+ mojom::adblock_internals::AdblockInternalsPageHandler> receiver);
+ AdblockInternalsPageHandler(const AdblockInternalsPageHandler&) = delete;
+ AdblockInternalsPageHandler& operator=(const AdblockInternalsPageHandler&) =
+ delete;
+ ~AdblockInternalsPageHandler() override;
+
+ // mojom::adblock_internals::AdblockInternalsPageHandler:
+ void GetDebugInfo(GetDebugInfoCallback callback) override;
+ void ToggleTestpagesFLSubscription(
+ ToggleTestpagesFLSubscriptionCallback callback) override;
+ void IsSubscribedToTestpagesFL(
+ IsSubscribedToTestpagesFLCallback callback) override;
+
+ private:
+ static void OnTelemetryServiceInfoArrived(
+ GetDebugInfoCallback callback,
+ std::string content,
+ std::vector<std::string> topic_provider_content);
+ bool IsSubscribedToTestpagesFL() const;
+
+ raw_ptr<content::BrowserContext> context_;
+ mojo::Receiver<mojom::adblock_internals::AdblockInternalsPageHandler>
+ receiver_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_PAGE_HANDLER_H_
diff --git a/components/adblock/content/browser/adblock_internals_ui.cc b/components/adblock/content/browser/adblock_internals_ui.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_internals_ui.cc
@@ -0,0 +1,67 @@
+
+/* \
+ * This file is part of eyeo Chromium SDK, \
+ * Copyright (C) 2006-present eyeo GmbH \
+ * \
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify \
+ * it under the terms of the GNU General Public License version 3 as \
+ * published by the Free Software Foundation. \
+ * \
+ * eyeo Chromium SDK is distributed in the hope that it will be useful, \
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of \
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \
+ * GNU General Public License for more details. \
+ * \
+ * You should have received a copy of the GNU General Public License \
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>. \
+ */
+
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO DPD-2881: Remove this and convert code to safer constructs.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "components/adblock/content/browser/adblock_internals_ui.h"
+
+#include "components/adblock/content/browser/adblock_internals_page_handler.h"
+#include "components/adblock/core/common/web_ui_constants.h"
+#include "components/grit/adblock_internals_resources.h"
+#include "components/grit/adblock_internals_resources_map.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace adblock {
+
+AdblockInternalsUI::AdblockInternalsUI(content::WebUI* web_ui)
+ : ui::MojoWebUIController(web_ui) {
+ content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
+ this->web_ui()->GetWebContents()->GetBrowserContext(),
+ adblock::kChromeUIAdblockInternalsHost);
+ // see webui::SetupWebUIDataSource for reference
+ source->OverrideContentSecurityPolicy(
+ network::mojom::CSPDirectiveName::ScriptSrc,
+ "script-src chrome://resources 'self';");
+ source->OverrideContentSecurityPolicy(
+ network::mojom::CSPDirectiveName::RequireTrustedTypesFor,
+ "require-trusted-types-for 'script';");
+ source->OverrideContentSecurityPolicy(
+ network::mojom::CSPDirectiveName::TrustedTypes,
+ "trusted-types static-types "
+ // Add TrustedTypes policies necessary for using Polymer.
+ "polymer-html-literal polymer-template-event-attribute-policy;");
+ source->AddResourcePaths(kAdblockInternalsResources);
+ source->SetDefaultResource(IDR_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_HTML);
+}
+
+AdblockInternalsUI::~AdblockInternalsUI() = default;
+
+WEB_UI_CONTROLLER_TYPE_IMPL(AdblockInternalsUI)
+
+void AdblockInternalsUI::BindInterface(
+ mojo::PendingReceiver<mojom::adblock_internals::AdblockInternalsPageHandler>
+ receiver) {
+ handler_ = std::make_unique<AdblockInternalsPageHandler>(
+ web_ui()->GetWebContents()->GetBrowserContext(), std::move(receiver));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_internals_ui.h b/components/adblock/content/browser/adblock_internals_ui.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_internals_ui.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_UI_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_UI_H_
+
+#include "components/adblock/content/browser/mojom/adblock_internals.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "ui/webui/mojo_web_ui_controller.h"
+
+namespace adblock {
+
+class AdblockInternalsUI : public ui::MojoWebUIController {
+ public:
+ explicit AdblockInternalsUI(content::WebUI* web_ui);
+
+ AdblockInternalsUI(const AdblockInternalsUI&) = delete;
+ AdblockInternalsUI& operator=(const AdblockInternalsUI&) = delete;
+
+ ~AdblockInternalsUI() override;
+
+ void BindInterface(
+ mojo::PendingReceiver<
+ mojom::adblock_internals::AdblockInternalsPageHandler> receiver);
+
+ private:
+ WEB_UI_CONTROLLER_TYPE_DECL();
+
+ std::unique_ptr<mojom::adblock_internals::AdblockInternalsPageHandler>
+ handler_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_INTERNALS_UI_H_
diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/components/adblock/content/browser/adblock_url_loader_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_url_loader_factory.cc
@@ -0,0 +1,813 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_url_loader_factory.h"
+
+#include "base/barrier_closure.h"
+#include "base/strings/stringprintf.h"
+#include "base/supports_user_data.h"
+#include "components/adblock/content/browser/content_security_policy_injector.h"
+#include "components/adblock/content/browser/element_hider.h"
+#include "components/adblock/content/browser/eyeo_document_info.h"
+#include "components/adblock/content/browser/frame_opener_info.h"
+#include "components/adblock/content/browser/page_view_stats.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/features.h"
+#include "components/adblock/core/sitekey_storage.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/url_util.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/early_hints.mojom.h"
+#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+namespace {
+
+// Without USER_BLOCKING priority - in session restore case - posted
+// callbacks can take over a minute to trigger. This is why such high
+// priority is applied everywhere.
+const base::TaskPriority kTaskResponsePriority =
+ base::TaskPriority::USER_BLOCKING;
+
+bool IsDocumentRequest(const network::ResourceRequest& request) {
+ return !request.url.SchemeIsWSOrWSS() && !request.is_fetch_like_api &&
+ request.destination == network::mojom::RequestDestination::kDocument;
+}
+
+bool ShouldIgnoreRequest(const GURL& url) {
+ return !url.SchemeIsHTTPOrHTTPS() && !url.SchemeIsWSOrWSS();
+}
+
+ContentType ToAdblockResourceType(const network::ResourceRequest& request) {
+ if (request.url.SchemeIsWSOrWSS()) {
+ return ContentType::Websocket;
+ }
+ if (request.is_fetch_like_api) {
+ // See https://crbug.com/611453
+ return ContentType::Xmlhttprequest;
+ }
+
+ switch (request.destination) {
+ case network::mojom::RequestDestination::kDocument:
+ case network::mojom::RequestDestination::kIframe:
+ case network::mojom::RequestDestination::kFrame:
+ case network::mojom::RequestDestination::kFencedframe:
+ return ContentType::Subdocument;
+ case network::mojom::RequestDestination::kStyle:
+ case network::mojom::RequestDestination::kXslt:
+ return ContentType::Stylesheet;
+ case network::mojom::RequestDestination::kScript:
+ case network::mojom::RequestDestination::kWorker:
+ case network::mojom::RequestDestination::kSharedWorker:
+ case network::mojom::RequestDestination::kServiceWorker:
+ case network::mojom::RequestDestination::kSharedStorageWorklet:
+ return ContentType::Script;
+ case network::mojom::RequestDestination::kImage:
+ return ContentType::Image;
+ case network::mojom::RequestDestination::kFont:
+ return ContentType::Font;
+ case network::mojom::RequestDestination::kObject:
+ case network::mojom::RequestDestination::kEmbed:
+ return ContentType::Object;
+ case network::mojom::RequestDestination::kAudio:
+ case network::mojom::RequestDestination::kTrack:
+ case network::mojom::RequestDestination::kVideo:
+ return ContentType::Media;
+ case network::mojom::RequestDestination::kEmpty:
+ // https://fetch.spec.whatwg.org/#concept-request-destination
+ if (request.keepalive) {
+ return ContentType::Ping;
+ }
+ return ContentType::Other;
+ case network::mojom::RequestDestination::kWebBundle:
+ return ContentType::WebBundle;
+ case network::mojom::RequestDestination::kAudioWorklet:
+ case network::mojom::RequestDestination::kDictionary:
+ case network::mojom::RequestDestination::kJson:
+ case network::mojom::RequestDestination::kManifest:
+ case network::mojom::RequestDestination::kPaintWorklet:
+ case network::mojom::RequestDestination::kReport:
+ case network::mojom::RequestDestination::kSpeculationRules:
+ case network::mojom::RequestDestination::kWebIdentity:
+ return ContentType::Other;
+ }
+ return ContentType::Other;
+}
+
+bool IsPopup(const RequestInitiator& initiator) {
+ DCHECK(initiator.IsFrame());
+ auto* host = initiator.GetRenderFrameHost();
+ DCHECK(host);
+ auto* wc = content::WebContents::FromRenderFrameHost(host);
+ DCHECK(wc);
+ auto* info = FrameOpenerInfo::FromWebContents(wc);
+ // We could use WebContents::HasLiveOriginalOpenerChain() to recognize a
+ // popup here. The problem is that its companion method
+ // WebContents::GetFirstWebContentsInLiveOriginalOpenerChain() returns not a
+ // direct opener of a popup (which can be an embedded iframe) but the main
+ // frame of the page which opened popup which is not enough for correct
+ // allowlisting. Because of that we need to track an opener via
+ // content::WebContentsUserData (see
+ // AdblockWebContentObserver::DidOpenRequestedURL()), so for
+ // consistency we everywhere check content::WebContentsUserData to find out
+ // whether a request is a popup or to obtain its opener.
+ return info && content::RenderFrameHost::FromID(info->GetOpener());
+}
+
+// We recognize Acceptable Ads Blockthrough filter(s) hit on a page by the fact
+// that url btloader.com/recovery?w={{websiteID}} is loaded. We relax here check
+// to allow any port to make this code working with our browser tests which run
+// a custom http(s)s server.
+bool AcceptableAdsBlockthroughFiltersHitDetected(const GURL& request_url) {
+ return request_url.host() == "btloader.com" &&
+ base::StartsWith(request_url.path(), "/recovery");
+}
+
+} // namespace
+
+class AdblockURLLoaderFactory::InProgressRequest
+ : public network::mojom::URLLoader,
+ public network::mojom::URLLoaderClient {
+ public:
+ InProgressRequest(
+ AdblockURLLoaderFactory* factory,
+ mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
+
+ void FollowRedirect(
+ const std::vector<std::string>& removed_headers,
+ const net::HttpRequestHeaders& modified_headers,
+ const net::HttpRequestHeaders& modified_cors_exempt_headers,
+ const absl::optional<GURL>& new_url) override;
+ void SetPriority(net::RequestPriority priority,
+ int32_t intra_priority_value) override;
+
+ void OnReceiveEarlyHints(
+ ::network::mojom::EarlyHintsPtr early_hints) override;
+ void OnReceiveResponse(
+ ::network::mojom::URLResponseHeadPtr head,
+ ::mojo::ScopedDataPipeConsumerHandle body,
+ absl::optional<mojo_base::BigBuffer> cached_metadata) override;
+ void OnReceiveRedirect(const ::net::RedirectInfo& redirect_info,
+ ::network::mojom::URLResponseHeadPtr head) override;
+ void OnUploadProgress(int64_t current_position,
+ int64_t total_size,
+ OnUploadProgressCallback callback) override;
+ void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
+ void OnComplete(const ::network::URLLoaderCompletionStatus& status) override;
+
+ private:
+ using ProcessResponseHeadersCallback =
+ base::OnceCallback<void(FilterMatchResult,
+ network::mojom::ParsedHeadersPtr)>;
+ using CheckRewriteFilterMatchCallback =
+ base::OnceCallback<void(const absl::optional<GURL>&)>;
+
+ void OnBindingsClosed();
+ void OnClientDisconnected();
+ void Start(uint32_t options,
+ network::ResourceRequest request,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+ mojo::PendingReceiver<network::mojom::URLLoader> target_loader,
+ mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client,
+ const absl::optional<GURL>& rewrite);
+ void OnRequestFilterMatchResult(
+ ::mojo::PendingReceiver<network::mojom::URLLoader> loader,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+ FilterMatchResult result);
+ void OnRedirectFilterMatchResult(const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head,
+ FilterMatchResult result);
+ void OnProcessHeadersResult(
+ ::network::mojom::URLResponseHeadPtr head,
+ ::mojo::ScopedDataPipeConsumerHandle body,
+ absl::optional<mojo_base::BigBuffer> cached_metadata,
+ FilterMatchResult result,
+ network::mojom::ParsedHeadersPtr parsed_headers);
+ void OnRequestError(int error_code);
+ void CheckFilterMatch(CheckFilterMatchCallback callback);
+ void ProcessResponseHeaders(
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ ProcessResponseHeadersCallback callback);
+ void CheckRewriteFilterMatch(CheckRewriteFilterMatchCallback callback);
+ void OnRequestUrlClassified(CheckFilterMatchCallback callback,
+ FilterMatchResult result);
+ void OnResponseHeadersClassified(
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ ProcessResponseHeadersCallback callback,
+ FilterMatchResult result);
+ void PostFilterMatchCallbackToUI(CheckFilterMatchCallback callback,
+ FilterMatchResult result);
+ void PostResponseHeadersCallbackToUI(
+ ProcessResponseHeadersCallback callback,
+ FilterMatchResult result,
+ network::mojom::ParsedHeadersPtr parsed_headers);
+ void PostRewriteCallbackToUI(
+ base::OnceCallback<void(const absl::optional<GURL>&)> callback,
+ const absl::optional<GURL>& url);
+ void SetPreCommitUrlForFrame();
+ bool IsRequestInitiatedByFrame() const;
+ bool IsRequestInitiatorDestroyed() const;
+ void ApplyPostBlockingBehavior() const;
+
+ GURL request_url_;
+ int request_id_;
+ bool is_document_request_;
+ ContentType adblock_resource_type_;
+ const raw_ptr<AdblockURLLoaderFactory> factory_;
+ // There are the mojo pipe endpoints between this proxy and the renderer.
+ // Messages received by |client_receiver_| are forwarded to
+ // |target_client_|.
+ mojo::Remote<network::mojom::URLLoaderClient> target_client_;
+ mojo::Receiver<network::mojom::URLLoader> loader_receiver_;
+ base::RepeatingCallback<void(content::RenderFrameHost*)>
+ aa_bt_page_view_counter_;
+ // These are the mojo pipe endpoints between this proxy and the network
+ // process. Messages received by |loader_receiver_| are forwarded to
+ // |target_loader_|.
+ mojo::Remote<network::mojom::URLLoader> target_loader_;
+ mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this};
+ base::WeakPtrFactory<AdblockURLLoaderFactory::InProgressRequest>
+ weak_factory_{this};
+};
+
+AdblockURLLoaderFactory::InProgressRequest::InProgressRequest(
+ AdblockURLLoaderFactory* factory,
+ mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+ : request_url_(request.url),
+ request_id_(request_id),
+ is_document_request_(IsDocumentRequest(request)),
+ adblock_resource_type_(ToAdblockResourceType(request)),
+ factory_(factory),
+ target_client_(std::move(client)),
+ loader_receiver_(this, std::move(loader_receiver)),
+ aa_bt_page_view_counter_(CountAcceptableAdsBlockthrougCallback()) {
+ if (!is_document_request_ && !ShouldIgnoreRequest(request_url_)) {
+ // Subresource requests may be rewritten (redirected to a local resource).
+ // Check this before sending the request to the network process.
+ CheckRewriteFilterMatch(base::BindOnce(
+ &InProgressRequest::Start, weak_factory_.GetWeakPtr(), options, request,
+ traffic_annotation, target_loader_.BindNewPipeAndPassReceiver(),
+ client_receiver_.BindNewPipeAndPassRemote()));
+ if (AcceptableAdsBlockthroughFiltersHitDetected(request_url_)) {
+ aa_bt_page_view_counter_.Run(
+ factory_->request_initiator_.GetRenderFrameHost());
+ }
+ } else {
+ // Main frame navigation requests are never rewritten, start immediately.
+ Start(options, request, traffic_annotation,
+ target_loader_.BindNewPipeAndPassReceiver(),
+ client_receiver_.BindNewPipeAndPassRemote(), absl::nullopt);
+ }
+
+ // Calls |OnBindingsClosed| only after both disconnect handlers have been run.
+ base::RepeatingClosure closure = base::BarrierClosure(
+ 2, base::BindOnce(&InProgressRequest::OnBindingsClosed,
+ weak_factory_.GetWeakPtr()));
+ loader_receiver_.set_disconnect_handler(closure);
+ client_receiver_.set_disconnect_handler(closure);
+ target_client_.set_disconnect_handler(base::BindOnce(
+ &InProgressRequest::OnClientDisconnected, weak_factory_.GetWeakPtr()));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::FollowRedirect(
+ const std::vector<std::string>& removed_headers,
+ const net::HttpRequestHeaders& modified_headers,
+ const net::HttpRequestHeaders& modified_cors_exempt_headers,
+ const absl::optional<GURL>& new_url) {
+ if (target_loader_.is_bound()) {
+ target_loader_->FollowRedirect(removed_headers, modified_headers,
+ modified_cors_exempt_headers, new_url);
+ }
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::SetPriority(
+ net::RequestPriority priority,
+ int32_t intra_priority_value) {
+ if (target_loader_.is_bound()) {
+ target_loader_->SetPriority(priority, intra_priority_value);
+ }
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnReceiveEarlyHints(
+ network::mojom::EarlyHintsPtr early_hints) {
+ target_client_->OnReceiveEarlyHints(std::move(early_hints));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnReceiveResponse(
+ network::mojom::URLResponseHeadPtr head,
+ mojo::ScopedDataPipeConsumerHandle body,
+ absl::optional<mojo_base::BigBuffer> cached_metadata) {
+ bool needs_early_exit = false;
+ if (ShouldIgnoreRequest(request_url_)) {
+ VLOG(1) << "[eyeo] Unsupported scheme, allowing to load.";
+ needs_early_exit = true;
+ } else if (!head->headers) {
+ VLOG(1) << "[eyeo] Missing headers, skipping response processing.";
+ needs_early_exit = true;
+ }
+
+ if (needs_early_exit) {
+ target_client_->OnReceiveResponse(std::move(head), std::move(body),
+ std::move(cached_metadata));
+ return;
+ }
+
+ if (IsRequestInitiatedByFrame()) {
+ SetPreCommitUrlForFrame();
+ }
+
+ VLOG(1) << "[eyeo] Sending headers for processing: " << request_url_;
+ client_receiver_.Pause();
+ const scoped_refptr<net::HttpResponseHeaders>& headers = head->headers;
+ ProcessResponseHeaders(
+ headers, base::BindOnce(&InProgressRequest::OnProcessHeadersResult,
+ weak_factory_.GetWeakPtr(), std::move(head),
+ std::move(body), std::move(cached_metadata)));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnProcessHeadersResult(
+ ::network::mojom::URLResponseHeadPtr head,
+ ::mojo::ScopedDataPipeConsumerHandle body,
+ absl::optional<mojo_base::BigBuffer> cached_metadata,
+ FilterMatchResult result,
+ network::mojom::ParsedHeadersPtr parsed_headers) {
+ if (result == FilterMatchResult::kBlockRule) {
+ OnRequestError(net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ return;
+ }
+ if (parsed_headers) {
+ // Headers were modified by ProcessResponseHeaders(). Raw headers must match
+ // parsed headers.
+ // |new_response_head| already contains the modified raw headers, update the
+ // parsed headers.
+ head->parsed_headers = std::move(parsed_headers);
+ }
+
+ target_client_->OnReceiveResponse(std::move(head), std::move(body),
+ std::move(cached_metadata));
+ client_receiver_.Resume();
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnRequestError(
+ int error_code) {
+ target_client_->OnComplete(network::URLLoaderCompletionStatus(error_code));
+ factory_->RemoveRequest(this);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch(
+ CheckFilterMatchCallback callback) {
+ const RequestInitiator& initiator = factory_->request_initiator_;
+ if (IsRequestInitiatorDestroyed()) {
+ PostFilterMatchCallbackToUI(std::move(callback),
+ FilterMatchResult::kNoRule);
+ return;
+ }
+
+ auto subscription_service = factory_->config_.subscription_service;
+ if (is_document_request_) {
+ if (IsPopup(initiator)) {
+ auto* host = initiator.GetRenderFrameHost();
+ factory_->config_.resource_classifier->CheckPopupFilterMatch(
+ subscription_service->GetCurrentSnapshot(), request_url_, *host,
+ base::BindOnce(
+ &AdblockURLLoaderFactory::InProgressRequest::
+ OnRequestUrlClassified,
+ weak_factory_.GetWeakPtr(),
+ base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest::
+ PostFilterMatchCallbackToUI,
+ weak_factory_.GetWeakPtr(), std::move(callback))));
+ } else {
+ PostFilterMatchCallbackToUI(std::move(callback),
+ FilterMatchResult::kNoRule);
+ }
+ } else {
+ factory_->config_.resource_classifier->CheckRequestFilterMatch(
+ subscription_service->GetCurrentSnapshot(), request_url_,
+ adblock_resource_type_, initiator,
+ base::BindOnce(
+ &AdblockURLLoaderFactory::InProgressRequest::OnRequestUrlClassified,
+ weak_factory_.GetWeakPtr(),
+ base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest::
+ PostFilterMatchCallbackToUI,
+ weak_factory_.GetWeakPtr(), std::move(callback))));
+ }
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::ProcessResponseHeaders(
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ ProcessResponseHeadersCallback callback) {
+ if (IsRequestInitiatorDestroyed()) {
+ PostResponseHeadersCallbackToUI(std::move(callback),
+ FilterMatchResult::kNoRule, nullptr);
+ return;
+ }
+
+ auto subscription_service = factory_->config_.subscription_service;
+ factory_->config_.resource_classifier->CheckResponseFilterMatch(
+ subscription_service->GetCurrentSnapshot(), request_url_,
+ adblock_resource_type_, factory_->request_initiator_, headers,
+ base::BindOnce(
+ &AdblockURLLoaderFactory::InProgressRequest::
+ OnResponseHeadersClassified,
+ weak_factory_.GetWeakPtr(), headers,
+ base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest::
+ PostResponseHeadersCallbackToUI,
+ weak_factory_.GetWeakPtr(), std::move(callback))));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::CheckRewriteFilterMatch(
+ CheckRewriteFilterMatchCallback callback) {
+ if (IsRequestInitiatorDestroyed()) {
+ PostRewriteCallbackToUI(std::move(callback), absl::optional<GURL>{});
+ return;
+ }
+
+ auto subscription_service = factory_->config_.subscription_service;
+ factory_->config_.resource_classifier->CheckRewriteFilterMatch(
+ subscription_service->GetCurrentSnapshot(), request_url_,
+ factory_->request_initiator_,
+ base::BindOnce(
+ &AdblockURLLoaderFactory::InProgressRequest::PostRewriteCallbackToUI,
+ weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnRequestUrlClassified(
+ CheckFilterMatchCallback callback,
+ FilterMatchResult result) {
+ if (result == FilterMatchResult::kBlockRule && IsRequestInitiatedByFrame()) {
+ ApplyPostBlockingBehavior();
+ }
+ PostFilterMatchCallbackToUI(std::move(callback), result);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnResponseHeadersClassified(
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ ProcessResponseHeadersCallback callback,
+ FilterMatchResult result) {
+ if (IsRequestInitiatorDestroyed() || result == FilterMatchResult::kDisabled) {
+ PostResponseHeadersCallbackToUI(std::move(callback), result, nullptr);
+ return;
+ }
+
+ if (result == FilterMatchResult::kBlockRule) {
+ if (IsRequestInitiatedByFrame()) {
+ ApplyPostBlockingBehavior();
+ }
+ PostResponseHeadersCallbackToUI(std::move(callback), result, nullptr);
+ return;
+ }
+
+ if (adblock_resource_type_ == ContentType::Subdocument) {
+ factory_->config_.sitekey_storage->ProcessResponseHeaders(
+ request_url_, headers, factory_->user_agent_string_);
+
+ const RequestInitiator& initiator = factory_->request_initiator_;
+ if (is_document_request_ && !IsPopup(initiator)) {
+ factory_->config_.resource_classifier->CheckDocumentAllowlisted(
+ factory_->config_.subscription_service->GetCurrentSnapshot(),
+ request_url_, initiator);
+ }
+
+ factory_->config_.csp_injector
+ ->InsertContentSecurityPolicyHeadersIfApplicable(
+ request_url_, initiator, headers,
+ base::BindOnce(std::move(callback), result));
+ } else {
+ PostResponseHeadersCallbackToUI(std::move(callback), result, nullptr);
+ }
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::PostFilterMatchCallbackToUI(
+ CheckFilterMatchCallback callback,
+ FilterMatchResult result) {
+ content::GetUIThreadTaskRunner({kTaskResponsePriority})
+ ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::
+ PostResponseHeadersCallbackToUI(
+ ProcessResponseHeadersCallback callback,
+ FilterMatchResult result,
+ network::mojom::ParsedHeadersPtr parsed_headers) {
+ content::GetUIThreadTaskRunner({kTaskResponsePriority})
+ ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), result,
+ std::move(parsed_headers)));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::PostRewriteCallbackToUI(
+ base::OnceCallback<void(const absl::optional<GURL>&)> callback,
+ const absl::optional<GURL>& url) {
+ content::GetUIThreadTaskRunner({kTaskResponsePriority})
+ ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), url));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
+ const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head) {
+ VLOG(1)
+ << "[eyeo] AdblockURLLoaderFactory::InProgressRequest::OnReceiveRedirect "
+ "from "
+ << request_url_ << " to " << redirect_info.new_url;
+ request_url_ = redirect_info.new_url;
+ if (ShouldIgnoreRequest(request_url_)) {
+ VLOG(1) << "[eyeo] Unsupported scheme, allowing to load.";
+ target_client_->OnReceiveRedirect(redirect_info, std::move(head));
+ return;
+ }
+ CheckFilterMatch(base::BindOnce(
+ &InProgressRequest::OnRedirectFilterMatchResult,
+ weak_factory_.GetWeakPtr(), redirect_info, std::move(head)));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnRedirectFilterMatchResult(
+ const net::RedirectInfo& redirect_info,
+ network::mojom::URLResponseHeadPtr head,
+ FilterMatchResult result) {
+ if (!factory_->target_factory_.is_bound()) {
+ DLOG(WARNING) << "[eyeo] "
+ "AdblockURLLoaderFactory::InProgressRequest::"
+ "OnRedirectFilterMatchResult: target_factory_ not bound";
+ return;
+ }
+ if (result == FilterMatchResult::kBlockRule) {
+ if (IsRequestInitiatedByFrame()) {
+ ApplyPostBlockingBehavior();
+ }
+ OnRequestError(net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ return;
+ }
+ target_client_->OnReceiveRedirect(redirect_info, std::move(head));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnUploadProgress(
+ int64_t current_position,
+ int64_t total_size,
+ OnUploadProgressCallback callback) {
+ target_client_->OnUploadProgress(current_position, total_size,
+ std::move(callback));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnTransferSizeUpdated(
+ int32_t transfer_size_diff) {
+ target_client_->OnTransferSizeUpdated(transfer_size_diff);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnComplete(
+ const network::URLLoaderCompletionStatus& status) {
+ target_client_->OnComplete(status);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnBindingsClosed() {
+ factory_->RemoveRequest(this);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnClientDisconnected() {
+ OnRequestError(net::ERR_ABORTED);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::Start(
+ uint32_t options,
+ network::ResourceRequest request,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+ mojo::PendingReceiver<network::mojom::URLLoader> target_loader,
+ mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client,
+ const absl::optional<GURL>& rewrite) {
+ if (rewrite) {
+ constexpr int kInternalRedirectStatusCode = net::HTTP_TEMPORARY_REDIRECT;
+ net::RedirectInfo redirect_info = net::RedirectInfo::ComputeRedirectInfo(
+ request.method, request.url, request.site_for_cookies,
+ request.update_first_party_url_on_redirect
+ ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
+ : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL,
+ request.referrer_policy, request.referrer.spec(),
+ kInternalRedirectStatusCode, rewrite.value(),
+ absl::nullopt /* referrer_policy_header */,
+ false /* insecure_scheme_was_upgraded */, false /* copy_fragment */,
+ false /* is_signed_exchange_fallback_redirect */);
+ redirect_info.bypass_redirect_checks = true;
+
+ auto head = network::mojom::URLResponseHead::New();
+ std::string headers = base::StringPrintf(
+ "HTTP/1.1 %i Internal Redirect\n"
+ "Location: %s\n"
+ "Non-Authoritative-Reason: ABPC\n\n",
+ kInternalRedirectStatusCode, rewrite.value().spec().c_str());
+ head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ net::HttpUtil::AssembleRawHeaders(headers));
+ head->encoded_data_length = 0;
+
+ // Close the connection with the current URLLoader and inform the
+ // client about redirect. New URLLoader will be recreated after redirect.
+ client_receiver_.reset();
+ target_loader_.reset();
+ target_client_->OnReceiveRedirect(redirect_info, std::move(head));
+ return;
+ }
+
+ if (!factory_->target_factory_.is_bound()) {
+ DLOG(WARNING)
+ << "[eyeo] AdblockURLLoaderFactory::InProgressRequest::Start: "
+ "target_factory_ not bound";
+ return;
+ }
+
+ if (ShouldIgnoreRequest(request_url_)) {
+ VLOG(1) << "[eyeo] Unsupported scheme, allowing to load.";
+ factory_->target_factory_->CreateLoaderAndStart(
+ std::move(target_loader), request_id_, options, request,
+ std::move(proxy_client), traffic_annotation);
+ return;
+ }
+
+ VLOG(1) << "[eyeo] Checking filter match for: " << request.url << " ("
+ << request.resource_type << ")";
+
+ CheckFilterMatch(base::BindOnce(
+ &InProgressRequest::OnRequestFilterMatchResult,
+ weak_factory_.GetWeakPtr(), std::move(target_loader), options, request,
+ std::move(proxy_client), traffic_annotation));
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::OnRequestFilterMatchResult(
+ ::mojo::PendingReceiver<network::mojom::URLLoader> target_loader,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+ FilterMatchResult result) {
+ if (!factory_->target_factory_.is_bound()) {
+ DLOG(WARNING) << "[eyeo] "
+ "AdblockURLLoaderFactory::InProgressRequest::"
+ "OnRequestFilterMatchResult: target_factory_ not bound";
+ return;
+ }
+ if (result == FilterMatchResult::kBlockRule) {
+ OnRequestError(net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ return;
+ }
+ factory_->target_factory_->CreateLoaderAndStart(
+ std::move(target_loader), request_id_, options, request,
+ std::move(proxy_client), traffic_annotation);
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::SetPreCommitUrlForFrame() {
+ DCHECK(IsRequestInitiatedByFrame());
+ content::RenderFrameHost* host =
+ factory_->request_initiator_.GetRenderFrameHost();
+ if (adblock_resource_type_ == ContentType::Subdocument && host) {
+ // Frames with HTML may trigger fetches of subresources from the renderer
+ // process before the browser process (this one) receives a notification
+ // about the navigation becoming committed. This could lead to a situation
+ // where the FrameHierarchyBuilder cannot establish the URL of this frame
+ // via GetLastCommittedURL(). We therefore set the "temporary" pre-commit
+ // URL into an EyeoDocumentInfo instance associated with this frame.
+ // This instance of EyeoDocumentInfo will be destroyed and re-created once
+ // the navigation becomes committed, so it may be very short-lived and only
+ // matter for the few subresource loads that happen during the HTML preload
+ // phase.
+ EyeoDocumentInfo* document_info =
+ EyeoDocumentInfo::GetOrCreateForCurrentDocument(host);
+ document_info->SetPreCommitURL(request_url_);
+ }
+}
+
+bool AdblockURLLoaderFactory::InProgressRequest::IsRequestInitiatedByFrame()
+ const {
+ return factory_->request_initiator_.IsFrame();
+}
+
+bool AdblockURLLoaderFactory::InProgressRequest::IsRequestInitiatorDestroyed()
+ const {
+ return IsRequestInitiatedByFrame() &&
+ !factory_->request_initiator_.GetRenderFrameHost();
+}
+
+void AdblockURLLoaderFactory::InProgressRequest::ApplyPostBlockingBehavior()
+ const {
+ DCHECK(IsRequestInitiatedByFrame());
+ auto* frame = factory_->request_initiator_.GetRenderFrameHost();
+ // For blocked requests triggered by rendered frames, we might need to do
+ // some cleanup to preserve good user experience.
+ if (frame) {
+ if (is_document_request_) {
+ // This path means we classified popup - close the window.
+ auto* wc = content::WebContents::FromRenderFrameHost(frame);
+ DCHECK(wc);
+ wc->ClosePage();
+ } else {
+ // We blocked a subresource request. Collapse whitespace around the
+ // blocked element.
+ ElementHider* element_hider = factory_->config_.element_hider;
+ if (element_hider->IsElementTypeHideable(adblock_resource_type_)) {
+ element_hider->HideBlockedElement(request_url_, frame);
+ }
+ }
+ }
+}
+
+AdblockURLLoaderFactory::AdblockURLLoaderFactory(
+ AdblockURLLoaderFactoryConfig config,
+ RequestInitiator request_initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ std::string user_agent_string,
+ DisconnectCallback on_disconnect)
+ : config_(std::move(config)),
+ request_initiator_(std::move(request_initiator)),
+ user_agent_string_(std::move(user_agent_string)),
+ on_disconnect_(std::move(on_disconnect)) {
+ DCHECK(config_.subscription_service);
+ DCHECK(config_.resource_classifier);
+ DCHECK(config_.element_hider);
+ DCHECK(config_.sitekey_storage);
+ DCHECK(config_.csp_injector);
+ target_factory_.Bind(std::move(target_factory));
+ target_factory_.set_disconnect_handler(base::BindOnce(
+ &AdblockURLLoaderFactory::OnTargetFactoryError, base::Unretained(this)));
+ proxy_receivers_.Add(this, std::move(receiver));
+ proxy_receivers_.set_disconnect_handler(base::BindRepeating(
+ &AdblockURLLoaderFactory::OnProxyBindingError, base::Unretained(this)));
+}
+
+AdblockURLLoaderFactory::~AdblockURLLoaderFactory() = default;
+
+void AdblockURLLoaderFactory::CreateLoaderAndStart(
+ ::mojo::PendingReceiver<network::mojom::URLLoader> loader,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
+ requests_.insert(std::make_unique<InProgressRequest>(
+ this, std::move(loader), request_id, options, request, std::move(client),
+ traffic_annotation));
+}
+
+void AdblockURLLoaderFactory::Clone(
+ ::mojo::PendingReceiver<URLLoaderFactory> factory) {
+ proxy_receivers_.Add(this, std::move(factory));
+}
+
+void AdblockURLLoaderFactory::OnTargetFactoryError() {
+ target_factory_.reset();
+ proxy_receivers_.Clear();
+ MaybeDestroySelf();
+}
+
+void AdblockURLLoaderFactory::OnProxyBindingError() {
+ MaybeDestroySelf();
+}
+
+void AdblockURLLoaderFactory::RemoveRequest(InProgressRequest* request) {
+ auto it = requests_.find(request);
+ DCHECK(it != requests_.end());
+ requests_.erase(it);
+ MaybeDestroySelf();
+}
+
+void AdblockURLLoaderFactory::MaybeDestroySelf() {
+ if (proxy_receivers_.empty() && requests_.empty()) {
+ std::move(on_disconnect_).Run(this);
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_url_loader_factory.h b/components/adblock/content/browser/adblock_url_loader_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_url_loader_factory.h
@@ -0,0 +1,100 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_H_
+
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "content/public/browser/global_routing_id.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class SubscriptionService;
+class ResourceClassificationRunner;
+class ElementHider;
+class SitekeyStorage;
+class ContentSecurityPolicyInjector;
+
+struct AdblockURLLoaderFactoryConfig {
+ // These raw_ptrs have DisableDanglingPtrDetection because they point to
+ // KeyedServices that will be removed with the BrowserContext. This object has
+ // no good method to find out when the BrowserContext is destroyed, so it
+ // can't null the pointers.
+ raw_ptr<SubscriptionService, DisableDanglingPtrDetection>
+ subscription_service = nullptr;
+ raw_ptr<ResourceClassificationRunner, DisableDanglingPtrDetection>
+ resource_classifier = nullptr;
+ raw_ptr<ElementHider, DisableDanglingPtrDetection> element_hider = nullptr;
+ raw_ptr<SitekeyStorage, DisableDanglingPtrDetection> sitekey_storage =
+ nullptr;
+ raw_ptr<ContentSecurityPolicyInjector, DisableDanglingPtrDetection>
+ csp_injector = nullptr;
+};
+
+// Processing network requests and responses.
+class AdblockURLLoaderFactory : public network::mojom::URLLoaderFactory {
+ public:
+ using DisconnectCallback = base::OnceCallback<void(AdblockURLLoaderFactory*)>;
+
+ AdblockURLLoaderFactory(
+ AdblockURLLoaderFactoryConfig config,
+ RequestInitiator request_initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ std::string user_agent_string,
+ DisconnectCallback on_disconnect);
+ ~AdblockURLLoaderFactory() override;
+
+ void CreateLoaderAndStart(
+ ::mojo::PendingReceiver<::network::mojom::URLLoader> loader,
+ int32_t request_id,
+ uint32_t options,
+ const ::network::ResourceRequest& request,
+ ::mojo::PendingRemote<::network::mojom::URLLoaderClient> client,
+ const ::net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+ override;
+ void Clone(::mojo::PendingReceiver<URLLoaderFactory> factory) override;
+
+ private:
+ class InProgressRequest;
+ friend class InProgressRequest;
+
+ void OnTargetFactoryError();
+ void OnProxyBindingError();
+ void RemoveRequest(InProgressRequest* request);
+ void MaybeDestroySelf();
+
+ AdblockURLLoaderFactoryConfig config_;
+ RequestInitiator request_initiator_;
+ mojo::ReceiverSet<network::mojom::URLLoaderFactory> proxy_receivers_;
+ std::set<std::unique_ptr<InProgressRequest>, base::UniquePtrComparator>
+ requests_;
+ mojo::Remote<network::mojom::URLLoaderFactory> target_factory_;
+ const std::string user_agent_string_;
+ DisconnectCallback on_disconnect_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_H_
diff --git a/components/adblock/content/browser/adblock_url_loader_factory_for_test.cc b/components/adblock/content/browser/adblock_url_loader_factory_for_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_url_loader_factory_for_test.cc
@@ -0,0 +1,466 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_url_loader_factory_for_test.h"
+
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/user_prefs/user_prefs.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "url/url_canon.h"
+#include "url/url_util.h"
+
+namespace adblock {
+
+namespace {
+
+constexpr char kResponseOk[] = "OK";
+constexpr char kResponseInvalidCommand[] = "INVALID_COMMAND";
+constexpr char kResponseInvalidConfiguration[] =
+ "Configuration '%s' does not exist!";
+
+constexpr char kTopicFilters[] = "filters";
+constexpr char kTopicDomains[] = "domains";
+constexpr char kTopicSubscriptions[] = "subscriptions";
+constexpr char kTopicConfiguration[] = "configuration";
+constexpr char kTopicAcceptableAds[] = "aa";
+constexpr char kTopicHelp[] = "help";
+
+constexpr char kActionAdd[] = "add";
+constexpr char kActionClear[] = "clear";
+constexpr char kActionList[] = "list";
+constexpr char kActionRemove[] = "remove";
+constexpr char kActionEnable[] = "enable";
+constexpr char kActionDisable[] = "disable";
+constexpr char kActionState[] = "state";
+
+constexpr char kManageConfigurations[] = "test.data/configurations/";
+
+constexpr char kTheConfigurationsCommandHelp[] =
+ "Type %slist to see all configurations.\n\n"
+ "Type %s{add|remove}/[configuration_name] to "
+ "add/remove configuration with specified name.";
+
+constexpr char kAConfigurationCommandHelp[] = R"(
+ Command syntax `%s.test.data/[topic]/[action]/[payload]` where:
+ - `topic` is either `domains` (allowed domains), `filters`, `subscriptions`,
+ `configuration`, `aa` (Acceptable Ads)
+ - `action` is either:
+ - `add`, `clear` (remove all), `list`, `remove` valid for `domains`,
+ `filters` and `subscriptions`
+ - `enable`, `disable` or `state` valid for `aa` and `configuration`
+ - `payload` is url encoded string required for action `add` and `remove`.
+ When adding or removing filter/domain/subscription one can encode several
+ entries splitting them by a new line character.
+)";
+
+// Prints help for managing a configuration
+std::string GetAConfigurationCommandsHelpMessage(
+ const char* configuration_name) {
+ return base::StringPrintf(kAConfigurationCommandHelp, configuration_name);
+}
+
+// Prints help for managing all configurations
+std::string GetTheConfigurationsCommandsHelpMessage() {
+ return base::StringPrintf(kTheConfigurationsCommandHelp,
+ kManageConfigurations, kManageConfigurations);
+}
+
+std::string GetInvalidConfigurationWithHelpMessage(
+ const char* configuration_name) {
+ return base::StringPrintf(kResponseInvalidConfiguration, configuration_name) +
+ "\n\n" + GetTheConfigurationsCommandsHelpMessage();
+}
+
+std::string GetCompleteHelpMessage() {
+ return GetAConfigurationCommandsHelpMessage("[configuration]") + "\n\n" +
+ GetTheConfigurationsCommandsHelpMessage();
+}
+
+std::string GetInvalidCommandWithAConfigurationHelpMessage(
+ const char* configuration_name) {
+ return std::string(kResponseInvalidCommand) + "\n\n" +
+ GetAConfigurationCommandsHelpMessage(configuration_name);
+}
+
+std::string GetInvalidCommandWithTheConfigurationsHelpMessage() {
+ return std::string(kResponseInvalidCommand) + "\n\n" +
+ GetTheConfigurationsCommandsHelpMessage();
+}
+
+constexpr char payload_delimiter[] = "\n";
+
+std::string DecodePayload(const std::string& encoded) {
+ // Example how to encode:
+ // url::RawCanonOutputT<char> buffer;
+ // url::EncodeURIComponent(base.c_str(), base.size(), &buffer);
+ // std::string encoded(buffer.data(), buffer.length());
+ VLOG(2) << "[eyeo] Encoded payload: " << encoded;
+ url::RawCanonOutputT<char16_t> output;
+ url::DecodeURLEscapeSequences(encoded, url::DecodeURLMode::kUTF8OrIsomorphic,
+ &output);
+ std::string decoded =
+ base::UTF16ToUTF8(std::u16string(output.data(), output.length()));
+ VLOG(2) << "[eyeo] Decoded payload: " << decoded;
+ return decoded;
+}
+
+std::vector<std::string> GetCommandElements(const GURL& url) {
+ std::vector<std::string> command_elements;
+ auto path = url.path();
+ if (path.length() > 1) {
+ path.erase(0, 1);
+ command_elements = base::SplitString(path, "/", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ }
+ return command_elements;
+}
+
+void Clear(FilteringConfiguration* configuration,
+ std::vector<std::string> (FilteringConfiguration::*getter)() const,
+ void (FilteringConfiguration::*action)(const std::string&)) {
+ for (const auto& item : (configuration->*getter)()) {
+ (configuration->*action)(item);
+ }
+}
+
+std::vector<std::string> List(
+ FilteringConfiguration* configuration,
+ std::vector<std::string> (FilteringConfiguration::*getter)() const) {
+ return (configuration->*getter)();
+}
+
+void AddOrRemove(FilteringConfiguration* configuration,
+ void (FilteringConfiguration::*action)(const std::string&),
+ std::string& items) {
+ auto items_list = base::SplitString(items, payload_delimiter,
+ base::WhitespaceHandling::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+ for (const auto& item : items_list) {
+ (configuration->*action)(item);
+ }
+}
+
+void ClearSubscriptions(FilteringConfiguration* configuration) {
+ for (const auto& subscription : configuration->GetFilterLists()) {
+ configuration->RemoveFilterList(subscription);
+ }
+}
+
+std::vector<std::string> ListSubscriptions(
+ FilteringConfiguration* configuration) {
+ std::vector<std::string> subscriptions;
+ base::ranges::transform(
+ configuration->GetFilterLists(), std::back_inserter(subscriptions),
+ [](const auto& subscription) { return subscription.spec(); });
+ return subscriptions;
+}
+
+void AddOrRemoveSubscription(
+ FilteringConfiguration* configuration,
+ void (FilteringConfiguration::*action)(const GURL&),
+ std::string& items) {
+ auto items_list = base::SplitString(items, payload_delimiter,
+ base::WhitespaceHandling::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+ for (const auto& item : items_list) {
+ (configuration->*action)(GURL{item});
+ }
+}
+
+bool IsAdblockEnabled(FilteringConfiguration* configuration) {
+ return configuration->IsEnabled();
+}
+
+void SetAdblockEnabled(FilteringConfiguration* configuration, bool enabled) {
+ configuration->SetEnabled(enabled);
+}
+
+bool IsAAEnabled(FilteringConfiguration* configuration) {
+ return base::ranges::any_of(
+ configuration->GetFilterLists(),
+ [&](const auto& url) { return url == AcceptableAdsUrl(); });
+}
+
+void SetAAEnabled(FilteringConfiguration* configuration, bool enabled) {
+ enabled ? configuration->AddFilterList(AcceptableAdsUrl())
+ : configuration->RemoveFilterList(AcceptableAdsUrl());
+}
+
+} // namespace
+
+// static
+const char AdblockURLLoaderFactoryForTest::kEyeoDebugDataHostName[] =
+ "test.data";
+
+AdblockURLLoaderFactoryForTest::AdblockURLLoaderFactoryForTest(
+ AdblockURLLoaderFactoryConfig config,
+ RequestInitiator request_initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ std::string user_agent_string,
+ DisconnectCallback on_disconnect,
+ content::BrowserContext* context)
+ : AdblockURLLoaderFactory(config,
+ std::move(request_initiator),
+ std::move(receiver),
+ std::move(target_factory),
+ user_agent_string,
+ std::move(on_disconnect)),
+ subscription_service_(config.subscription_service.get()),
+ prefs_(user_prefs::UserPrefs::Get(context)) {}
+
+AdblockURLLoaderFactoryForTest::~AdblockURLLoaderFactoryForTest() = default;
+
+void AdblockURLLoaderFactoryForTest::CreateLoaderAndStart(
+ ::mojo::PendingReceiver<network::mojom::URLLoader> loader,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& request,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> client,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
+ DCHECK(subscription_service_);
+ if (!url::DomainIs(request.url.host_piece(), kEyeoDebugDataHostName)) {
+ DLOG(WARNING)
+ << "[eyeo] AdblockURLLoaderFactoryForTest got unexpected url: "
+ << request.url;
+ AdblockURLLoaderFactory::CreateLoaderAndStart(
+ std::move(loader), request_id, options, request, std::move(client),
+ traffic_annotation);
+ return;
+ }
+ VLOG(2) << "[eyeo] AdblockURLLoaderFactoryForTest handles: " << request.url;
+ std::string response_body;
+ if (!base::StartsWith(request.url.host_piece(), kEyeoDebugDataHostName)) {
+ configuration_name_ = request.url.host_piece().substr(
+ 0, request.url.host_piece().rfind(kEyeoDebugDataHostName) - 1);
+ response_body = HandleCommand(request.url);
+ } else if (request.url.spec().find(kManageConfigurations) !=
+ std::string::npos) {
+ response_body = HandleConfigurations(request.url);
+ } else {
+ response_body = GetCompleteHelpMessage();
+ }
+ SendResponse(std::move(response_body), std::move(client));
+}
+
+void AdblockURLLoaderFactoryForTest::SendResponse(
+ std::string response_body,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> client) const {
+ auto response_head = network::mojom::URLResponseHead::New();
+ response_head->mime_type = "text/plain";
+ mojo::Remote<network::mojom::URLLoaderClient> client_remote(
+ std::move(client));
+ mojo::ScopedDataPipeProducerHandle producer;
+ mojo::ScopedDataPipeConsumerHandle consumer;
+ if (CreateDataPipe(nullptr, producer, consumer) != MOJO_RESULT_OK) {
+ DLOG(ERROR)
+ << "[eyeo] AdblockURLLoaderFactoryForTest fails to call CreateDataPipe";
+ client_remote->OnComplete(
+ network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
+ return;
+ }
+ size_t write_size = response_body.size();
+ producer->WriteData(base::as_byte_span(response_body),
+ MOJO_WRITE_DATA_FLAG_NONE, write_size);
+ client_remote->OnReceiveResponse(std::move(response_head),
+ std::move(consumer), absl::nullopt);
+ network::URLLoaderCompletionStatus status;
+ status.error_code = net::OK;
+ status.decoded_body_length = write_size;
+ client_remote->OnComplete(status);
+}
+
+std::string AdblockURLLoaderFactoryForTest::HandleConfigurations(
+ const GURL& url) const {
+ std::string response = kResponseOk;
+ response += "\n\n";
+ auto command_elements = GetCommandElements(url);
+ if (command_elements.size() == 2 && command_elements[1] == kActionList) {
+ for (const auto* config :
+ subscription_service_->GetInstalledFilteringConfigurations()) {
+ response += config->GetName() + "\n";
+ }
+ return response;
+ } else if (command_elements.size() == 3) {
+ const auto& action = command_elements[1];
+ const auto& config_name = command_elements[2];
+ if (action == kActionAdd) {
+ subscription_service_->InstallFilteringConfiguration(
+ std::make_unique<PersistentFilteringConfiguration>(prefs_,
+ config_name));
+ return response;
+ } else if (action == kActionRemove) {
+ if (!subscription_service_->GetFilteringConfiguration(config_name)) {
+ response = GetInvalidConfigurationWithHelpMessage(config_name.c_str());
+ } else {
+ subscription_service_->UninstallFilteringConfiguration(config_name);
+ }
+ return response;
+ }
+ }
+
+ return GetInvalidCommandWithTheConfigurationsHelpMessage();
+}
+
+std::string AdblockURLLoaderFactoryForTest::HandleCommand(
+ const GURL& url) const {
+ auto* configuration = GetConfiguration();
+ if (!configuration) {
+ return GetInvalidConfigurationWithHelpMessage(configuration_name_.c_str());
+ }
+ auto command_elements = GetCommandElements(url);
+ if (command_elements.size() == 1 && command_elements[0] == kTopicHelp) {
+ return GetAConfigurationCommandsHelpMessage(configuration_name_.c_str());
+ }
+ // There needs to be at least topic and action, plus optional payload
+ if (command_elements.size() > 1) {
+ const auto& topic = command_elements[0];
+ const auto& action = command_elements[1];
+ if (topic == kTopicSubscriptions) {
+ if (command_elements.size() == 3) {
+ // This can be either add or remove with custom payload
+ void (FilteringConfiguration::*action_ptr)(const GURL&) = nullptr;
+ std::string payload = DecodePayload(command_elements[2]);
+ if (action == kActionAdd) {
+ action_ptr = &FilteringConfiguration::AddFilterList;
+ } else if (action == kActionRemove) {
+ action_ptr = &FilteringConfiguration::RemoveFilterList;
+ }
+ if (action_ptr) {
+ VLOG(2) << "[eyeo] Handling subscription payload: " << payload;
+ AddOrRemoveSubscription(configuration, action_ptr, payload);
+ return kResponseOk;
+ }
+ } else if (action == kActionClear) {
+ ClearSubscriptions(configuration);
+ return kResponseOk;
+ } else if (action == kActionList) {
+ std::string response = kResponseOk;
+ auto subscriptions = ListSubscriptions(configuration);
+ if (!subscriptions.empty()) {
+ response += "\n\n";
+ for (const auto& subscription : subscriptions) {
+ response += subscription + "\n";
+ }
+ }
+ return response;
+ }
+ } else if (topic == kTopicFilters || topic == kTopicDomains) {
+ if (command_elements.size() == 3) {
+ // This can be either add or remove with custom payload
+ void (FilteringConfiguration::*action_ptr)(const std::string&) =
+ nullptr;
+ std::string payload = DecodePayload(command_elements[2]);
+ if (topic == kTopicFilters) {
+ if (action == kActionAdd) {
+ action_ptr = &FilteringConfiguration::AddCustomFilter;
+ } else if (action == kActionRemove) {
+ action_ptr = &FilteringConfiguration::RemoveCustomFilter;
+ }
+ } else {
+ if (action == kActionAdd) {
+ action_ptr = &FilteringConfiguration::AddAllowedDomain;
+ } else if (action == kActionRemove) {
+ action_ptr = &FilteringConfiguration::RemoveAllowedDomain;
+ }
+ }
+ if (action_ptr) {
+ VLOG(2) << "[eyeo] Handling payload: " << payload;
+ AddOrRemove(configuration, action_ptr, payload);
+ return kResponseOk;
+ }
+ } else {
+ std::vector<std::string> (FilteringConfiguration::*getter)() const =
+ nullptr;
+ void (FilteringConfiguration::*deleter)(const std::string&) = nullptr;
+ if (action == kActionClear) {
+ if (topic == kTopicFilters) {
+ deleter = &FilteringConfiguration::RemoveCustomFilter;
+ getter = &FilteringConfiguration::GetCustomFilters;
+ } else {
+ deleter = &FilteringConfiguration::RemoveAllowedDomain;
+ getter = &FilteringConfiguration::GetAllowedDomains;
+ }
+ } else if (action == kActionList) {
+ if (topic == kTopicFilters) {
+ getter = &FilteringConfiguration::GetCustomFilters;
+ } else {
+ getter = &FilteringConfiguration::GetAllowedDomains;
+ }
+ }
+ if (deleter && getter) {
+ Clear(configuration, getter, deleter);
+ return kResponseOk;
+ } else if (getter) {
+ std::string response = kResponseOk;
+ auto items = List(configuration, getter);
+ if (!items.empty()) {
+ response += "\n\n";
+ for (const auto& item : items) {
+ response += item + "\n";
+ }
+ }
+ return response;
+ }
+ }
+ } else if (topic == kTopicConfiguration || topic == kTopicAcceptableAds) {
+ if (action == kActionState) {
+ std::string response = kResponseOk;
+ response += "\n\n";
+ bool enabled = topic == kTopicConfiguration
+ ? IsAdblockEnabled(configuration)
+ : IsAAEnabled(configuration);
+ response += enabled ? "enabled" : "disabled";
+ return response;
+ }
+ absl::optional<bool> value = absl::nullopt;
+ if (action == kActionEnable) {
+ value = true;
+ } else if (action == kActionDisable) {
+ value = false;
+ }
+ if (value.has_value()) {
+ if (topic == kTopicConfiguration) {
+ SetAdblockEnabled(configuration, value.value());
+ } else {
+ SetAAEnabled(configuration, value.value());
+ }
+ return kResponseOk;
+ }
+ }
+ }
+ return GetInvalidCommandWithAConfigurationHelpMessage(
+ configuration_name_.c_str());
+}
+
+FilteringConfiguration* AdblockURLLoaderFactoryForTest::GetConfiguration()
+ const {
+ return subscription_service_->GetFilteringConfiguration(configuration_name_);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_url_loader_factory_for_test.h b/components/adblock/content/browser/adblock_url_loader_factory_for_test.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_url_loader_factory_for_test.h
@@ -0,0 +1,78 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_FOR_TEST_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_FOR_TEST_H_
+
+#include "components/adblock/content/browser/adblock_url_loader_factory.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// A simple class which handles following commands passed via intercepted url
+// in a format `adblock.test.data/[topic]/[action]/[payload]` where:
+// - `topic` is either `domains` (allowed domains), `filters`, `subscriptions`,
+// `adblock`, `aa` (Acceptable Ads)
+// - `action` is either:
+// - `add`, `clear` (remove all), `list`, `remove` valid for `domains`,
+// `filters` and `subscriptions`
+// - `enable`, `disable` or `state` valid for `aa` and `adblock`
+// - `payload` is url encoded string required for action `add` and `remove`.
+// When adding or removing filter/domain/subscription one can encode several
+// entries splitting them by a new line character.
+class AdblockURLLoaderFactoryForTest final : public AdblockURLLoaderFactory {
+ public:
+ AdblockURLLoaderFactoryForTest(
+ AdblockURLLoaderFactoryConfig config,
+ RequestInitiator request_initiator,
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
+ mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
+ std::string user_agent_string,
+ DisconnectCallback on_disconnect,
+ content::BrowserContext* context);
+ ~AdblockURLLoaderFactoryForTest() final;
+
+ void CreateLoaderAndStart(
+ ::mojo::PendingReceiver<::network::mojom::URLLoader> loader,
+ int32_t request_id,
+ uint32_t options,
+ const ::network::ResourceRequest& request,
+ ::mojo::PendingRemote<::network::mojom::URLLoaderClient> client,
+ const ::net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
+ final;
+
+ static const char kEyeoDebugDataHostName[];
+
+ private:
+ std::string HandleCommand(const GURL& url) const;
+ std::string HandleConfigurations(const GURL& url) const;
+ FilteringConfiguration* GetConfiguration() const;
+ void SendResponse(
+ std::string response_body,
+ ::mojo::PendingRemote<network::mojom::URLLoaderClient> client) const;
+ raw_ptr<SubscriptionService> subscription_service_;
+ raw_ptr<PrefService> prefs_;
+ std::string configuration_name_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_FOR_TEST_H_
diff --git a/components/adblock/content/browser/adblock_web_ui_controller_factory.cc b/components/adblock/content/browser/adblock_web_ui_controller_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_web_ui_controller_factory.cc
@@ -0,0 +1,62 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_web_ui_controller_factory.h"
+
+#include "components/adblock/content/browser/adblock_internals_ui.h"
+#include "components/adblock/core/common/web_ui_constants.h"
+#include "content/public/common/url_utils.h"
+
+namespace adblock {
+
+// static
+AdblockWebUIControllerFactory* AdblockWebUIControllerFactory::GetInstance() {
+ return base::Singleton<AdblockWebUIControllerFactory>::get();
+}
+
+content::WebUI::TypeID AdblockWebUIControllerFactory::GetWebUIType(
+ content::BrowserContext* browser_context,
+ const GURL& url) {
+ if (!content::HasWebUIScheme(url)) {
+ return content::WebUI::kNoWebUI;
+ }
+
+ if (url.host() == adblock::kChromeUIAdblockInternalsHost) {
+ return kAdblockInternalsID;
+ }
+
+ return content::WebUI::kNoWebUI;
+}
+
+bool AdblockWebUIControllerFactory::UseWebUIForURL(
+ content::BrowserContext* browser_context,
+ const GURL& url) {
+ return GetWebUIType(browser_context, url) != content::WebUI::kNoWebUI;
+}
+
+std::unique_ptr<content::WebUIController>
+AdblockWebUIControllerFactory::CreateWebUIControllerForURL(
+ content::WebUI* web_ui,
+ const GURL& url) {
+ if (url.host() == adblock::kChromeUIAdblockInternalsHost) {
+ return std::make_unique<adblock::AdblockInternalsUI>(web_ui);
+ }
+
+ return nullptr;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_web_ui_controller_factory.h b/components/adblock/content/browser/adblock_web_ui_controller_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_web_ui_controller_factory.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEB_UI_CONTROLLER_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEB_UI_CONTROLLER_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "content/public/browser/web_ui_controller_factory.h"
+
+namespace adblock {
+
+namespace {
+
+const content::WebUI::TypeID kAdblockInternalsID = &kAdblockInternalsID;
+
+} // namespace
+
+// Owned by WebUIConfigMap. Used to hook up with the existing WebUI infra.
+class AdblockWebUIControllerFactory : public content::WebUIControllerFactory {
+ public:
+ static AdblockWebUIControllerFactory* GetInstance();
+
+ AdblockWebUIControllerFactory(const AdblockWebUIControllerFactory&) = delete;
+ AdblockWebUIControllerFactory& operator=(
+ const AdblockWebUIControllerFactory&) = delete;
+
+ content::WebUI::TypeID GetWebUIType(content::BrowserContext* browser_context,
+ const GURL& url) override;
+ bool UseWebUIForURL(content::BrowserContext* browser_context,
+ const GURL& url) override;
+ std::unique_ptr<content::WebUIController> CreateWebUIControllerForURL(
+ content::WebUI* web_ui,
+ const GURL& url) override;
+
+ private:
+ friend struct base::DefaultSingletonTraits<AdblockWebUIControllerFactory>;
+
+ AdblockWebUIControllerFactory() = default;
+ ~AdblockWebUIControllerFactory() override = default;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEB_UI_CONTROLLER_FACTORY_H_
diff --git a/components/adblock/content/browser/adblock_webcontents_observer.cc b/components/adblock/content/browser/adblock_webcontents_observer.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_webcontents_observer.cc
@@ -0,0 +1,217 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_webcontents_observer.h"
+
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/content/browser/frame_opener_info.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/navigation_handle.h"
+#include "net/base/url_util.h"
+#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
+
+namespace {
+const char* WindowOpenDispositionToString(WindowOpenDisposition value) {
+ switch (value) {
+ case WindowOpenDisposition::UNKNOWN:
+ return "UNKNOWN";
+ case WindowOpenDisposition::CURRENT_TAB:
+ return "CURRENT_TAB";
+ case WindowOpenDisposition::SINGLETON_TAB:
+ return "SINGLETON_TAB";
+ case WindowOpenDisposition::NEW_FOREGROUND_TAB:
+ return "NEW_FOREGROUND_TAB";
+ case WindowOpenDisposition::NEW_BACKGROUND_TAB:
+ return "NEW_BACKGROUND_TAB";
+ case WindowOpenDisposition::NEW_POPUP:
+ return "NEW_POPUP";
+ case WindowOpenDisposition::NEW_WINDOW:
+ return "NEW_WINDOW";
+ case WindowOpenDisposition::SAVE_TO_DISK:
+ return "SAVE_TO_DISK";
+ case WindowOpenDisposition::OFF_THE_RECORD:
+ return "OFF_THE_RECORD";
+ case WindowOpenDisposition::IGNORE_ACTION:
+ return "IGNORE_ACTION";
+ case WindowOpenDisposition::SWITCH_TO_TAB:
+ return "SWITCH_TO_TAB";
+ case WindowOpenDisposition::NEW_PICTURE_IN_PICTURE:
+ return "NEW_PICTURE_IN_PICTURE";
+ default:
+ return "";
+ }
+}
+
+void TraceHandleLoadComplete(
+ intptr_t rfh_trace_id,
+ const adblock::ElementHider::ElemhideInjectionData&) {
+ TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo",
+ "AdblockWebContentObserver::HandleOnLoad",
+ TRACE_ID_LOCAL(rfh_trace_id));
+}
+
+bool IsOrdinaryNavigation(content::NavigationHandle* navigation_handle) {
+ const GURL& url = navigation_handle->GetURL();
+ return !navigation_handle->IsErrorPage() && !url.IsAboutBlank() &&
+ url.SchemeIsHTTPOrHTTPS();
+}
+
+bool IsBlockedIframe(content::NavigationHandle* navigation_handle) {
+ return navigation_handle->GetNetErrorCode() ==
+ net::ERR_BLOCKED_BY_ADMINISTRATOR &&
+ navigation_handle->GetRenderFrameHost()->GetFrameOwnerElementType() ==
+ blink::FrameOwnerElementType::kIframe;
+}
+
+bool ShouldSkipElementHiding(const GURL& url) {
+ return !url.SchemeIsHTTPOrHTTPS() && !url.SchemeIsWSOrWSS() &&
+ !url.IsAboutBlank();
+}
+
+} // namespace
+
+namespace adblock {
+
+AdblockWebContentObserver::AdblockWebContentObserver(
+ content::WebContents* web_contents,
+ SubscriptionService* subscription_service,
+ ElementHider* element_hider,
+ SitekeyStorage* sitekey_storage,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder,
+ base::RepeatingCallback<void(content::RenderFrameHost*)> navigation_counter)
+ : content::WebContentsObserver(web_contents),
+ content::WebContentsUserData<AdblockWebContentObserver>(*web_contents),
+ subscription_service_(subscription_service),
+ element_hider_(element_hider),
+ sitekey_storage_(sitekey_storage),
+ frame_hierarchy_builder_(std::move(frame_hierarchy_builder)),
+ navigation_counter_(std::move(navigation_counter)) {}
+
+AdblockWebContentObserver::~AdblockWebContentObserver() = default;
+
+void AdblockWebContentObserver::DidOpenRequestedURL(
+ content::WebContents* new_contents,
+ content::RenderFrameHost* source_render_frame_host,
+ const GURL& url,
+ const content::Referrer& referrer,
+ WindowOpenDisposition disposition,
+ ui::PageTransition transition,
+ bool started_from_context_menu,
+ bool renderer_initiated) {
+ if (!IsAdblockEnabled()) {
+ return;
+ }
+ VLOG(1) << "[eyeo] DidOpenRequestedURL: URL=" << url
+ << ", disposition=" << WindowOpenDispositionToString(disposition)
+ << ", renderer_initiated=" << renderer_initiated;
+ if (disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
+ disposition == WindowOpenDisposition::NEW_POPUP) {
+ // WindowOpenDisposition::NEW_WINDOW is excluded as it is set when user
+ // opens a link in a new window from context menu.
+ // We use content::WebContentsUserData instead of content::DocumentUserData
+ // because the latter is reset when there is a client side redirection.
+ FrameOpenerInfo::CreateForWebContents(new_contents);
+ auto* info = FrameOpenerInfo::FromWebContents(new_contents);
+ info->SetOpener(source_render_frame_host->GetGlobalId());
+ }
+}
+
+void AdblockWebContentObserver::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!IsAdblockEnabled()) {
+ return;
+ }
+ const GURL& url = navigation_handle->GetURL();
+ VLOG(1) << "[eyeo] Finished navigation: URL=" << url
+ << ", has_commited=" << navigation_handle->HasCommitted()
+ << ", is_error=" << navigation_handle->IsErrorPage()
+ << ", isInMainFrame=" << navigation_handle->IsInMainFrame();
+ content::RenderFrameHost* frame = nullptr;
+ if (navigation_handle->HasCommitted()) {
+ frame = navigation_handle->GetRenderFrameHost();
+ }
+ if (!frame) {
+ return;
+ }
+ if (ShouldSkipElementHiding(url)) {
+ VLOG(1) << "[eyeo] Unsupported scheme, skipping injection.";
+ return;
+ }
+ if (!navigation_handle->IsErrorPage()) {
+ // Element hiding for ordinary main frame (or iframe)
+ DVLOG(3) << "[eyeo] Ready to inject element hiding to " << url.spec();
+ HandleOnLoad(frame);
+ }
+
+ if (!IsOrdinaryNavigation(navigation_handle)) {
+ DVLOG(3) << "[eyeo] Not suitable URL " << url.spec()
+ << ", error = " << navigation_handle->GetNetErrorCode()
+ << ", IsAboutSrcdoc = " << url.IsAboutSrcdoc();
+ if (IsBlockedIframe(navigation_handle)) {
+ // Element hiding for blocked element, this collapses empty space.
+ DVLOG(3) << "[eyeo] Subframe url=" << url.spec()
+ << ", isAboutSrcDoc = " << url.IsAboutSrcdoc();
+ element_hider_->HideBlockedElement(url, frame->GetParent());
+ }
+ } else {
+ // Count navigation for AA stats.
+ if (navigation_handle->IsInMainFrame()) {
+ navigation_counter_.Run(frame);
+ }
+ }
+}
+
+void AdblockWebContentObserver::HandleOnLoad(
+ content::RenderFrameHost* frame_host) {
+ DCHECK(frame_host);
+ const GURL url =
+ frame_hierarchy_builder_->FindUrlForFrame(frame_host, web_contents());
+ const auto rfh_trace_id = reinterpret_cast<intptr_t>(frame_host);
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
+ "eyeo", "AdblockWebContentObserver::HandleOnLoad",
+ TRACE_ID_LOCAL(rfh_trace_id), "url", url.spec());
+
+ auto frame_hierarchy = frame_hierarchy_builder_->BuildFrameHierarchy(
+ RequestInitiator(frame_host));
+ DVLOG(1) << "[eyeo] Got " << frame_hierarchy.size()
+ << " frame hierarchy items for " << url.spec();
+
+ SiteKey site_key;
+ auto url_key_pair = sitekey_storage_->FindSiteKeyForAnyUrl(frame_hierarchy);
+ if (url_key_pair.has_value()) {
+ site_key = url_key_pair->second;
+ DVLOG(2) << "[eyeo] Element hiding found siteKey: " << url_key_pair->second
+ << " for url: " << url_key_pair->first;
+ }
+
+ element_hider_->ApplyElementHidingEmulationOnPage(
+ std::move(url), std::move(frame_hierarchy), frame_host,
+ std::move(site_key),
+ base::BindOnce(&TraceHandleLoadComplete, rfh_trace_id));
+}
+
+bool AdblockWebContentObserver::IsAdblockEnabled() const {
+ return std::ranges::any_of(
+ subscription_service_->GetInstalledFilteringConfigurations(),
+ &FilteringConfiguration::IsEnabled);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(AdblockWebContentObserver);
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/adblock_webcontents_observer.h b/components/adblock/content/browser/adblock_webcontents_observer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/adblock_webcontents_observer.h
@@ -0,0 +1,93 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEBCONTENTS_OBSERVER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEBCONTENTS_OBSERVER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/content/browser/element_hider.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/core/sitekey_storage.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace content {
+class NavigationHandle;
+class RenderFrameHost;
+} // namespace content
+
+class GURL;
+
+namespace adblock {
+
+/**
+ * @brief Listens to page load events to trigger frame-wide element hiding.
+ * Responds to notifications about blocked resource loads to collapse the
+ * empty space around them. Lives in browser process UI thread.
+ *
+ */
+class AdblockWebContentObserver
+ : public content::WebContentsObserver,
+ public content::WebContentsUserData<AdblockWebContentObserver> {
+ public:
+ AdblockWebContentObserver(
+ content::WebContents* web_contents,
+ SubscriptionService* subscription_service,
+ ElementHider* element_hider,
+ SitekeyStorage* sitekey_storage,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder,
+ base::RepeatingCallback<void(content::RenderFrameHost*)>
+ navigation_counter);
+ ~AdblockWebContentObserver() override;
+ AdblockWebContentObserver(const AdblockWebContentObserver&) = delete;
+ AdblockWebContentObserver& operator=(const AdblockWebContentObserver&) =
+ delete;
+
+ // WebContentsObserver overrides.
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+
+ void DidOpenRequestedURL(content::WebContents* new_contents,
+ content::RenderFrameHost* source_render_frame_host,
+ const GURL& url,
+ const content::Referrer& referrer,
+ WindowOpenDisposition disposition,
+ ui::PageTransition transition,
+ bool started_from_context_menu,
+ bool renderer_initiated) override;
+
+ private:
+ explicit AdblockWebContentObserver(content::WebContents* web_contents);
+ void HandleOnLoad(content::RenderFrameHost* render_frame_host);
+ bool IsAdblockEnabled() const;
+
+ friend class content::WebContentsUserData<AdblockWebContentObserver>;
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ raw_ptr<SubscriptionService> subscription_service_;
+ raw_ptr<ElementHider> element_hider_;
+ raw_ptr<SitekeyStorage> sitekey_storage_;
+
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder_;
+ base::RepeatingCallback<void(content::RenderFrameHost*)> navigation_counter_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEBCONTENTS_OBSERVER_H_
diff --git a/components/adblock/content/browser/content_security_policy_injector.h b/components/adblock/content/browser/content_security_policy_injector.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/content_security_policy_injector.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_
+
+#include <string>
+
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/global_routing_id.h"
+#include "net/http/http_response_headers.h"
+#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+using InsertContentSecurityPolicyHeadersCallback =
+ base::OnceCallback<void(network::mojom::ParsedHeadersPtr)>;
+
+// Implements CSP filter application.
+//
+// CSP filters are an anti-circumvention technique that allows injecting a
+// Content Security Policy header into a HTTP response.
+// For example, a "Content-Security-Policy: script-src: 'none'" header blocks
+// all scripts, including inline, in the received document.
+// This will not block downloading the resource, but may stop it from executing
+// further actions once it has downloaded.
+class ContentSecurityPolicyInjector : public KeyedService {
+ public:
+ // If a CSP filter exists for this URL in any of currently installed filter
+ // lists, inserts its payload into |headers| as a Content-Security-Policy
+ // header type.
+ // If |headers| were changed, |callback| will receive a ParsedHeaders object
+ // that matches the new state of the response headers - otherwise |callback|
+ // will receive nullptr.
+ virtual void InsertContentSecurityPolicyHeadersIfApplicable(
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ InsertContentSecurityPolicyHeadersCallback callback) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_
diff --git a/components/adblock/content/browser/content_security_policy_injector_impl.cc b/components/adblock/content/browser/content_security_policy_injector_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/content_security_policy_injector_impl.cc
@@ -0,0 +1,111 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/content_security_policy_injector_impl.h"
+
+#include <string_view>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/thread_pool.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/render_frame_host.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace adblock {
+namespace {
+
+std::set<std::string> GetCspInjections(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy_chain) {
+ TRACE_EVENT1("eyeo", "GetCspInjection", "url", request_url.spec());
+ std::set<std::string> injections;
+ for (const auto& collection : subscription_collections) {
+ for (const auto& injection :
+ collection->GetCspInjections(request_url, frame_hierarchy_chain)) {
+ injections.emplace(injection);
+ }
+ }
+ if (!injections.empty()) {
+ VLOG(1) << "[eyeo] Will attempt to inject CSP header/s " << " for "
+ << request_url;
+ DVLOG(2) << "[eyeo] CSP headers for " << request_url << ":";
+ for (const auto& filter : injections) {
+ DVLOG(2) << "[eyeo] " << filter;
+ }
+ }
+ return injections;
+}
+
+} // namespace
+
+ContentSecurityPolicyInjectorImpl::ContentSecurityPolicyInjectorImpl(
+ SubscriptionService* subscription_service,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder)
+ : subscription_service_(subscription_service),
+ frame_hierarchy_builder_(std::move(frame_hierarchy_builder)) {}
+
+ContentSecurityPolicyInjectorImpl::~ContentSecurityPolicyInjectorImpl() =
+ default;
+
+void ContentSecurityPolicyInjectorImpl::
+ InsertContentSecurityPolicyHeadersIfApplicable(
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ InsertContentSecurityPolicyHeadersCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // GetCspInjection might take a while, let it run in the background.
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(
+ &GetCspInjections, subscription_service_->GetCurrentSnapshot(),
+ request_url,
+ frame_hierarchy_builder_->BuildFrameHierarchy(request_initiator)),
+ base::BindOnce(
+ &ContentSecurityPolicyInjectorImpl::OnCspInjectionsSearchFinished,
+ weak_ptr_factory.GetWeakPtr(), request_url, std::move(headers),
+ std::move(callback)));
+}
+
+void ContentSecurityPolicyInjectorImpl::OnCspInjectionsSearchFinished(
+ const GURL request_url,
+ const scoped_refptr<net::HttpResponseHeaders> headers,
+ InsertContentSecurityPolicyHeadersCallback callback,
+ std::set<std::string> csp_injections) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!csp_injections.empty()) {
+ for (const auto& c_i : csp_injections) {
+ // Set the CSP header according to |csp_injection|.
+ headers->AddHeader("Content-Security-Policy", c_i);
+ }
+ // We need to ensure parsed headers match raw headers. Send the updated
+ // raw headers to NetworkService for parsing.
+ content::GetNetworkService()->ParseHeaders(request_url, headers,
+ std::move(callback));
+ } else {
+ // No headers are injected, no need to update parsed headers.
+ std::move(callback).Run(nullptr);
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/content_security_policy_injector_impl.h b/components/adblock/content/browser/content_security_policy_injector_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/content_security_policy_injector_impl.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/content/browser/content_security_policy_injector.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace adblock {
+
+class ContentSecurityPolicyInjectorImpl final
+ : public ContentSecurityPolicyInjector {
+ public:
+ ContentSecurityPolicyInjectorImpl(
+ SubscriptionService* subscription_service,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder);
+
+ ~ContentSecurityPolicyInjectorImpl() final;
+
+ void InsertContentSecurityPolicyHeadersIfApplicable(
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ InsertContentSecurityPolicyHeadersCallback callback) final;
+
+ private:
+ void OnCspInjectionsSearchFinished(
+ const GURL request_url,
+ const scoped_refptr<net::HttpResponseHeaders> headers,
+ InsertContentSecurityPolicyHeadersCallback callback,
+ std::set<std::string> csp_injections);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ raw_ptr<SubscriptionService> subscription_service_;
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder_;
+ base::WeakPtrFactory<ContentSecurityPolicyInjectorImpl> weak_ptr_factory{
+ this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_
diff --git a/components/adblock/content/browser/element_hider.h b/components/adblock/content/browser/element_hider.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/element_hider.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class GURL;
+
+namespace content {
+class RenderFrameHost;
+} // namespace content
+
+namespace adblock {
+/**
+ * @brief Implements element hiding logic.
+ * Element hiding includes injecting JavaScript code, CSS stylesheets and
+ * predefined snippets into the context of a loaded page to hide unwanted
+ * objects that could not be blocked from loading earlier.
+ * Element hiding also collapses visible elements blocked during resource load.
+ * Lives in browser process, UI thread.
+ *
+ */
+class ElementHider : public KeyedService {
+ public:
+ struct ElemhideInjectionData {
+ std::string stylesheet;
+ std::string elemhide_js;
+ std::string snippet_js;
+ };
+
+ virtual void ApplyElementHidingEmulationOnPage(
+ GURL url,
+ std::vector<GURL> frame_hierarchy,
+ content::RenderFrameHost* render_frame_host,
+ SiteKey sitekey,
+ base::OnceCallback<void(const ElemhideInjectionData&)> on_finished) = 0;
+
+ virtual bool IsElementTypeHideable(
+ ContentType adblock_resource_type) const = 0;
+
+ virtual void HideBlockedElement(
+ const GURL& url,
+ content::RenderFrameHost* render_frame_host) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_
diff --git a/components/adblock/content/browser/element_hider_impl.cc b/components/adblock/content/browser/element_hider_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/element_hider_impl.cc
@@ -0,0 +1,367 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/element_hider_impl.h"
+
+#include <string_view>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task/thread_pool.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/content/browser/eyeo_document_info.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/common/isolated_world_ids.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace adblock {
+namespace {
+
+using ContentFiltersData = InstalledSubscription::ContentFiltersData;
+using SelectorWithCss =
+ InstalledSubscription::ContentFiltersData::SelectorWithCss;
+
+std::string GenerateBlockedElemhideJavaScript(
+ const std::string& url,
+ const std::string& filename_with_query) {
+ TRACE_EVENT1("eyeo", "GenerateBlockedElemhideJavaScript", "url", url);
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // basically copy-pasted JS and saved to resources from
+ // https://github.com/adblockplus/adblockpluschrome/blob/master/include.preload.js#L546
+ std::string result =
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_ADBLOCK_ELEMHIDE_JS);
+
+ result.append("\n");
+
+ // the file is template with tokens to be replaced with actual variable values
+ std::string query_jst =
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_ADBLOCK_ELEMHIDE_FOR_SELECTOR_JS);
+
+ std::string build_string;
+ base::EscapeJSONString(filename_with_query, false, &build_string);
+
+ base::ReplaceSubstringsAfterOffset(&query_jst, 0, "{{url}}", url);
+ base::ReplaceSubstringsAfterOffset(&query_jst, 0, "{{filename_with_query}}",
+ build_string);
+ result.append(query_jst);
+
+ return result;
+}
+
+void GenerateStylesheet(const GURL& url,
+ ContentFiltersData& eh_input_data,
+ std::string& output) {
+ TRACE_EVENT1("eyeo", "GenerateStylesheet", "url", url.spec());
+
+ const base::span<std::string_view> selectors(
+ eh_input_data.elemhide_selectors);
+
+ // Chromium's Blink engine supports only up to 8,192 simple selectors, and
+ // even fewer compound selectors, in a rule. The exact number of selectors
+ // that would work depends on their sizes (e.g. "#foo .bar" has a size of 2).
+ // Since we don't know the sizes of the selectors here, we simply split them
+ // into groups of 1,024, based on the reasonable assumption that the average
+ // selector won't have a size greater than 8. The alternative would be to
+ // calculate the sizes of the selectors and divide them up accordingly, but
+ // this approach is more efficient and has worked well in practice. In theory
+ // this could still lead to some selectors not working on Chromium, but it is
+ // highly unlikely.
+ const size_t max_selector_count = 1024u;
+ for (size_t i = 0; i < eh_input_data.elemhide_selectors.size();
+ i += max_selector_count) {
+ const size_t batch_size = std::min(
+ max_selector_count, eh_input_data.elemhide_selectors.size() - i);
+ output += base::JoinString(selectors.subspan(i, batch_size), ", ") +
+ " {display: none !important;}\n";
+ }
+}
+
+void GenerateElemHidingEmuJavaScript(const GURL& url,
+ ContentFiltersData& ehe_input_data,
+ std::string& output) {
+ TRACE_EVENT1("eyeo", "GenerateElemHidingEmuJavaScript", "url", url.spec());
+ // build the string with selectors
+ std::string build_string;
+ for (const auto& selector : ehe_input_data.elemhide_selectors) {
+ build_string.append("{selector:");
+ base::EscapeJSONString(selector, true, &build_string);
+ build_string.append("}, \n");
+ }
+ for (const auto& selector : ehe_input_data.remove_selectors) {
+ build_string.append("{selector:");
+ base::EscapeJSONString(selector, true, &build_string);
+ build_string.append(", text: \"remove()\"}, \n");
+ }
+ for (const auto& selector_with_css : ehe_input_data.selectors_to_inline_css) {
+ build_string.append("{selector:");
+ base::EscapeJSONString(selector_with_css.first, true, &build_string);
+ build_string.append(", text:");
+ base::EscapeJSONString(selector_with_css.second, true, &build_string);
+ build_string.append("}, \n");
+ }
+ output = ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_ADBLOCK_ELEMHIDE_EMU_JS);
+
+ base::ReplaceSubstringsAfterOffset(
+ &output, 0, "{{elemHidingEmulatedPatternsDef}}", build_string);
+}
+
+std::string GenerateXpath3Dep() {
+ static std::string xpath3_dep =
+ "(" +
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_ADBLOCK_SNIPPETS_XPATH3_DEP_JS) +
+ ")();";
+ if (xpath3_dep == "()();") {
+ LOG(WARNING) << "[eyeo] Snippets library does not support xpath3!";
+ return "";
+ }
+ return xpath3_dep;
+}
+
+void GenerateSnippetScript(const GURL& url,
+ base::Value::List input,
+ std::string& output) {
+ TRACE_EVENT1("eyeo", "GenerateSnippetScript", "url", url.spec());
+ // snippets must be JSON representation of the array of arrays of snippets
+ std::string serialized;
+ JSONStringValueSerializer serializer(&serialized);
+ serializer.Serialize(std::move(input));
+ // snippets_lib should be the library as-is, without any escaping or JSON
+ // parsing.
+ static std::string snippets_lib =
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_ADBLOCK_SNIPPETS_JS);
+
+ output = "{{xpath3}}({{callback}})({}, ...{{snippets}});";
+ bool require_xpath3 =
+ serialized.find("hide-if-matches-xpath3") != std::string::npos;
+ base::ReplaceSubstringsAfterOffset(&output, 0, "{{xpath3}}",
+ require_xpath3 ? GenerateXpath3Dep() : "");
+ base::ReplaceSubstringsAfterOffset(&output, 0, "{{callback}}", snippets_lib);
+ base::ReplaceSubstringsAfterOffset(&output, 0, "{{snippets}}", serialized);
+}
+
+void AppendContentFiltersData(ContentFiltersData& target,
+ const ContentFiltersData& source) {
+ std::ranges::copy(source.elemhide_selectors,
+ std::back_inserter(target.elemhide_selectors));
+ std::ranges::copy(source.remove_selectors,
+ std::back_inserter(target.remove_selectors));
+ std::ranges::copy(source.selectors_to_inline_css,
+ std::back_inserter(target.selectors_to_inline_css));
+}
+
+ElementHider::ElemhideInjectionData PrepareElemhideEmulationData(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL url,
+ const std::vector<GURL> frame_hierarchy,
+ const SiteKey sitekey) {
+ TRACE_EVENT1("eyeo", "PrepareElemhideEmulationData", "url", url.spec());
+
+ ContentFiltersData eh_data, eh_emu_data;
+ base::Value::List snippet_js;
+ for (const auto& collection : subscription_collections) {
+ bool doc_allowlisted = !!collection->FindBySpecialFilter(
+ SpecialFilterType::Document, url, frame_hierarchy, sitekey);
+ bool ehe_allowlisted =
+ doc_allowlisted ||
+ collection->FindBySpecialFilter(SpecialFilterType::Elemhide, url,
+ frame_hierarchy, sitekey);
+ if (!ehe_allowlisted) {
+ auto collection_eh_data =
+ collection->GetElementHideData(url, frame_hierarchy, sitekey);
+ auto collection_eh_emu_data =
+ collection->GetElementHideEmulationData(url);
+ AppendContentFiltersData(eh_data, collection_eh_data);
+ AppendContentFiltersData(eh_emu_data, collection_eh_emu_data);
+ }
+ if (!doc_allowlisted) {
+ std::ranges::for_each(
+ collection->GenerateSnippets(url, frame_hierarchy),
+ [&snippet_js](auto& item) { snippet_js.Append(std::move(item)); });
+ }
+ }
+ ElementHider::ElemhideInjectionData result;
+ if (!eh_data.elemhide_selectors.empty()) {
+ DVLOG(2) << "[eyeo] Got EH " << eh_data.elemhide_selectors.size()
+ << " hide selectors for url " << url;
+ GenerateStylesheet(url, eh_data, result.stylesheet);
+ }
+ if (!eh_emu_data.elemhide_selectors.empty() ||
+ !eh_emu_data.remove_selectors.empty() ||
+ !eh_emu_data.selectors_to_inline_css.empty()) {
+ DVLOG(2) << "[eyeo] Got EH emulation "
+ << eh_emu_data.elemhide_selectors.size() << " hide selectors, "
+ << eh_emu_data.remove_selectors.size() << " remove selectors and "
+ << eh_emu_data.selectors_to_inline_css.size()
+ << " inline CSS selectors and for url" << url;
+ GenerateElemHidingEmuJavaScript(url, eh_emu_data, result.elemhide_js);
+ }
+ if (!snippet_js.empty()) {
+ DVLOG(2) << "[eyeo] Got " << snippet_js.size() << " snippets for url "
+ << url;
+ GenerateSnippetScript(url, std::move(snippet_js), result.snippet_js);
+ }
+ return result;
+}
+
+void InsertUserCSSAndApplyElemHidingEmuJS(
+ content::GlobalRenderFrameHostId frame_host_id,
+ base::OnceCallback<void(const ElementHider::ElemhideInjectionData&)>
+ on_finished,
+ ElementHider::ElemhideInjectionData input) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ auto* frame_host = content::RenderFrameHost::FromID(frame_host_id);
+ if (!frame_host) {
+ // Render frame host was destroyed before element hiding could be applied.
+ // This is not a bug, just legitimate a race condition.
+ std::move(on_finished).Run(std::move(input));
+ return;
+ }
+ auto* info = EyeoDocumentInfo::GetOrCreateForCurrentDocument(frame_host);
+ if (info->IsElementHidingDone()) {
+ std::move(on_finished).Run(ElementHider::ElemhideInjectionData{});
+ return;
+ } else {
+ info->SetElementHidingDone();
+ }
+
+ if (!input.stylesheet.empty()) {
+ frame_host->InsertAbpElemhideStylesheet(input.stylesheet);
+ DVLOG(1) << "[eyeo] Element hiding - inserted stylesheet in frame" << " '"
+ << frame_host->GetFrameName() << "'";
+ }
+
+ if (!input.elemhide_js.empty()) {
+ frame_host->ExecuteJavaScriptInIsolatedWorld(
+ base::UTF8ToUTF16(input.elemhide_js),
+ content::RenderFrameHost::JavaScriptResultCallback(),
+ content::ISOLATED_WORLD_ID_ADBLOCK);
+
+ DVLOG(1) << "[eyeo] Element hiding emulation - executed JS in frame" << " '"
+ << frame_host->GetFrameName() << "'";
+ }
+
+ if (!input.snippet_js.empty()) {
+ // PK: Extension API ends up generating isolated world for injected script
+ // execution. See GetIsolatedWorldIdForInstance in
+ // extensions/renderer/script_injection.cc. Why not to reuse adblock space?
+ frame_host->ExecuteJavaScriptInIsolatedWorld(
+ base::UTF8ToUTF16(input.snippet_js),
+ content::RenderFrameHost::JavaScriptResultCallback(),
+ content::ISOLATED_WORLD_ID_ADBLOCK);
+
+ DVLOG(1) << "[eyeo] Snippet - executed JS in frame" << " '"
+ << frame_host->GetFrameName() << "'";
+ }
+
+ std::move(on_finished).Run(std::move(input));
+}
+
+} // namespace
+
+ElementHiderImpl::ElementHiderImpl(SubscriptionService* subscription_service)
+ : subscription_service_(subscription_service) {}
+
+ElementHiderImpl::~ElementHiderImpl() = default;
+
+void ElementHiderImpl::ApplyElementHidingEmulationOnPage(
+ GURL url,
+ std::vector<GURL> frame_hierarchy,
+ content::RenderFrameHost* render_frame_host,
+ SiteKey sitekey,
+ base::OnceCallback<void(const ElemhideInjectionData&)> on_finished) {
+ auto* info = EyeoDocumentInfo::GetForCurrentDocument(render_frame_host);
+ if (info && info->IsElementHidingDone()) {
+ std::move(on_finished).Run(ElementHider::ElemhideInjectionData{});
+ return;
+ }
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(&PrepareElemhideEmulationData,
+ subscription_service_->GetCurrentSnapshot(),
+ std::move(url), std::move(frame_hierarchy),
+ std::move(sitekey)),
+ base::BindOnce(&InsertUserCSSAndApplyElemHidingEmuJS,
+ render_frame_host->GetGlobalId(), std::move(on_finished)));
+}
+
+bool ElementHiderImpl::IsElementTypeHideable(
+ ContentType adblock_resource_type) const {
+ switch (adblock_resource_type) {
+ case ContentType::Image:
+ case ContentType::Object:
+ case ContentType::Media:
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+void ElementHiderImpl::HideBlockedElement(
+ const GURL& url,
+ content::RenderFrameHost* render_frame_host) {
+ TRACE_EVENT1("eyeo", "ElementHiderFlatbufferImpl::HideBlockedElemenet", "url",
+ url.spec());
+ if (!render_frame_host->IsInLifecycleState(
+ content::RenderFrameHost::LifecycleState::kActive)) {
+ // The frame is not active, so we can't execute JS in it, otherwise we
+ // trigger an assertion in RenderFrameHostImpl::AssertFrameWasCommitted():
+ // see DPD-2884.
+ // TODO(mpawlowski): Instead of ignoring the hide blocked element request,
+ // we should instead defer it until the frame becomes active. This requires
+ // more changes to the code and automated tests. See DPD-2890.
+ return;
+ }
+ // we can't get relative URL from URLRequest
+ // so the hack is to select in JS with filename_with_query selector and then
+ // check every found element's full absolute URL
+ std::string filename_with_query = url.ExtractFileName();
+ if (url.has_query()) {
+ filename_with_query.append("?");
+ filename_with_query.append(url.query());
+ }
+
+ const std::string js =
+ GenerateBlockedElemhideJavaScript(url.spec(), filename_with_query);
+
+ // elemhide resource by element hide rules
+ render_frame_host->ExecuteJavaScriptInIsolatedWorld(
+ base::UTF8ToUTF16(js),
+ content::RenderFrameHost::JavaScriptResultCallback(),
+ content::ISOLATED_WORLD_ID_ADBLOCK);
+
+ DVLOG(1) << "[eyeo] Element hiding - executed JS in frame" << " '"
+ << render_frame_host->GetFrameName() << "'";
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/element_hider_impl.h b/components/adblock/content/browser/element_hider_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/element_hider_impl.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/adblock/content/browser/element_hider.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/browser/global_routing_id.h"
+
+namespace adblock {
+
+class ElementHiderImpl final : public ElementHider {
+ public:
+ explicit ElementHiderImpl(SubscriptionService* subscription_service);
+ ~ElementHiderImpl() final;
+
+ void ApplyElementHidingEmulationOnPage(
+ GURL url,
+ std::vector<GURL> frame_hierarchy,
+ content::RenderFrameHost* render_frame_host,
+ SiteKey sitekey,
+ base::OnceCallback<void(const ElemhideInjectionData&)> on_finished) final;
+
+ bool IsElementTypeHideable(ContentType adblock_resource_type) const final;
+
+ void HideBlockedElement(const GURL& url,
+ content::RenderFrameHost* render_frame_host) final;
+
+ private:
+ raw_ptr<SubscriptionService> subscription_service_;
+ base::WeakPtrFactory<ElementHiderImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_
diff --git a/components/adblock/content/browser/eyeo_document_info.cc b/components/adblock/content/browser/eyeo_document_info.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/eyeo_document_info.cc
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/eyeo_document_info.h"
+
+#include "content/public/browser/render_frame_host.h"
+
+namespace adblock {
+
+DOCUMENT_USER_DATA_KEY_IMPL(EyeoDocumentInfo);
+
+EyeoDocumentInfo::EyeoDocumentInfo(content::RenderFrameHost* rfh)
+ : content::DocumentUserData<EyeoDocumentInfo>(rfh) {}
+
+EyeoDocumentInfo::~EyeoDocumentInfo() = default;
+
+const GURL& EyeoDocumentInfo::GetURL() const {
+ if (!pre_commit_url_.is_empty()) {
+ return pre_commit_url_;
+ }
+ return render_frame_host().GetLastCommittedURL();
+}
+
+void EyeoDocumentInfo::SetPreCommitURL(GURL url) {
+ pre_commit_url_ = std::move(url);
+}
+
+bool EyeoDocumentInfo::IsElementHidingDone() const {
+ return element_hiding_done_;
+}
+
+void EyeoDocumentInfo::SetElementHidingDone() {
+ element_hiding_done_ = true;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/eyeo_document_info.h b/components/adblock/content/browser/eyeo_document_info.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/eyeo_document_info.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_DOCUMENT_INFO_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_DOCUMENT_INFO_H_
+
+#include "content/public/browser/document_user_data.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class EyeoDocumentInfo final
+ : public content::DocumentUserData<EyeoDocumentInfo> {
+ public:
+ ~EyeoDocumentInfo() final;
+
+ // Returns a URL usable for building the frame hierarchy.
+ // Returns the pre-commit URL if it was set, otherwise the last committed URL
+ // of the RenderFrameHost. This resolves a race condition where depending on
+ // RenderFrameHost::GetLastCommitedURL() alone could return an empty URL if
+ // the navigation was still in progress.
+ const GURL& GetURL() const;
+ // Set the URL of the document, as soon as it becomes known - accounting for
+ // possible redirects. This EyeoDocumentInfo will be destroyed and re-created
+ // when the navigation commits, so the value set here is temporary - like the
+ // entire object.
+ void SetPreCommitURL(GURL url);
+
+ bool IsElementHidingDone() const;
+ void SetElementHidingDone();
+
+ private:
+ explicit EyeoDocumentInfo(content::RenderFrameHost* rfh);
+
+ bool element_hiding_done_ = false;
+ GURL pre_commit_url_;
+
+ friend DocumentUserData;
+ DOCUMENT_USER_DATA_KEY_DECL();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_DOCUMENT_INFO_H_
diff --git a/components/adblock/content/browser/eyeo_page_info.cc b/components/adblock/content/browser/eyeo_page_info.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/eyeo_page_info.cc
@@ -0,0 +1,38 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/eyeo_page_info.h"
+
+namespace adblock {
+
+PAGE_USER_DATA_KEY_IMPL(EyeoPageInfo);
+
+EyeoPageInfo::EyeoPageInfo(content::Page& page)
+ : content::PageUserData<EyeoPageInfo>(page) {}
+
+EyeoPageInfo::~EyeoPageInfo() = default;
+
+bool EyeoPageInfo::SetMatchedPageView(PageViewStats::Metric metric) {
+ auto result = page_views_.insert(metric);
+ return result.second;
+}
+
+bool EyeoPageInfo::HasMatchedPageView(PageViewStats::Metric metric) {
+ return page_views_.count(metric);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/eyeo_page_info.h b/components/adblock/content/browser/eyeo_page_info.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/eyeo_page_info.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_PAGE_INFO_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_PAGE_INFO_H_
+
+#include <set>
+
+#include "components/adblock/content/browser/page_view_stats.h"
+#include "content/public/browser/page.h"
+#include "content/public/browser/page_user_data.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class EyeoPageInfo final : public content::PageUserData<EyeoPageInfo> {
+ public:
+ ~EyeoPageInfo() final;
+
+ // Marks that document has matched specified page view metric.
+ // |key| is a base::PersistentHash() of a metric name.
+ // Returns true if |key| was not yet stored for this document.
+ bool SetMatchedPageView(PageViewStats::Metric metric);
+
+ // Checks whether |key| is already stored.
+ bool HasMatchedPageView(PageViewStats::Metric metric);
+
+ private:
+ explicit EyeoPageInfo(content::Page& page);
+
+ std::set<PageViewStats::Metric> page_views_ = {};
+
+ friend PageUserData;
+ PAGE_USER_DATA_KEY_DECL();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_PAGE_INFO_H_
diff --git a/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc b/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc
@@ -0,0 +1,72 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/net/adblock_request_throttle_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+AdblockRequestThrottle* AdblockRequestThrottleFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<AdblockRequestThrottle*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+AdblockRequestThrottleFactory* AdblockRequestThrottleFactory::GetInstance() {
+ static base::NoDestructor<AdblockRequestThrottleFactory> instance;
+ return instance.get();
+}
+
+AdblockRequestThrottleFactory::AdblockRequestThrottleFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AdblockRequestThrottle",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+AdblockRequestThrottleFactory::~AdblockRequestThrottleFactory() = default;
+
+std::unique_ptr<KeyedService>
+AdblockRequestThrottleFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ auto throttle = std::make_unique<AdblockRequestThrottleImpl>();
+ const auto initial_delay =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ adblock::switches::kDisableEyeoRequestThrottling)
+ ? base::TimeDelta()
+ : base::Seconds(30);
+ throttle->AllowRequestsAfter(initial_delay);
+ return std::move(throttle);
+}
+
+content::BrowserContext* AdblockRequestThrottleFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/adblock_request_throttle_factory.h b/components/adblock/content/browser/factories/adblock_request_throttle_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/adblock_request_throttle_factory.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_REQUEST_THROTTLE_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_REQUEST_THROTTLE_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "base/time/time.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class AdblockRequestThrottle;
+class AdblockRequestThrottleFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static AdblockRequestThrottle* GetForBrowserContext(
+ content::BrowserContext* context);
+ static AdblockRequestThrottleFactory* GetInstance();
+
+ static void SetInitialDelay(base::TimeDelta delay);
+
+ private:
+ friend class base::NoDestructor<AdblockRequestThrottleFactory>;
+ AdblockRequestThrottleFactory();
+ ~AdblockRequestThrottleFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_REQUEST_THROTTLE_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc
@@ -0,0 +1,122 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/page_view_stats.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "components/adblock/core/adblock_telemetry_service.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+
+namespace adblock {
+namespace {
+
+std::optional<base::TimeDelta> g_check_interval_for_testing;
+
+base::TimeDelta GetCheckInterval() {
+ static base::TimeDelta kCheckInterval =
+ g_check_interval_for_testing ? g_check_interval_for_testing.value()
+ : base::Minutes(5);
+ return kCheckInterval;
+}
+
+} // namespace
+
+// static
+AdblockTelemetryService* AdblockTelemetryServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<AdblockTelemetryService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+AdblockTelemetryServiceFactory* AdblockTelemetryServiceFactory::GetInstance() {
+ static base::NoDestructor<AdblockTelemetryServiceFactory> instance;
+ return instance.get();
+}
+
+AdblockTelemetryServiceFactory::AdblockTelemetryServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AdblockTelemetryService",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ResourceClassificationRunnerFactory::GetInstance());
+ DependsOn(SubscriptionServiceFactory::GetInstance());
+ DependsOn(AdblockRequestThrottleFactory::GetInstance());
+}
+
+AdblockTelemetryServiceFactory::~AdblockTelemetryServiceFactory() = default;
+
+content::BrowserContext* AdblockTelemetryServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+std::unique_ptr<KeyedService>
+AdblockTelemetryServiceFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ // Need to use a URLLoaderFactory specific to the browser context, not from
+ // system_network_context_manager(), because the required Accept-Language
+ // header depends on user's language settings and is not present in requests
+ // made from the System network context.
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
+ context->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess();
+ auto* subscription_service =
+ SubscriptionServiceFactory::GetForBrowserContext(context);
+ auto telemetry_service = std::make_unique<AdblockTelemetryService>(
+ subscription_service, url_loader_factory,
+ AdblockRequestThrottleFactory::GetForBrowserContext(context),
+ GetCheckInterval());
+ telemetry_service->AddTopicProvider(
+ std::make_unique<ActivepingTelemetryTopicProvider>(
+ user_prefs::UserPrefs::Get(context), subscription_service,
+ ActivepingTelemetryTopicProvider::DefaultBaseUrl(),
+ ActivepingTelemetryTopicProvider::DefaultAuthToken(),
+ std::make_unique<PageViewStats>(
+ ResourceClassificationRunnerFactory::GetForBrowserContext(
+ context),
+ user_prefs::UserPrefs::Get(context))));
+
+ if (url_loader_factory) {
+ telemetry_service->Start();
+ }
+
+ return std::move(telemetry_service);
+}
+
+bool AdblockTelemetryServiceFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
+
+void AdblockTelemetryServiceFactory::SetCheckIntervalForTesting(
+ base::TimeDelta check_interval) {
+ g_check_interval_for_testing = check_interval;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/adblock_telemetry_service_factory.h b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_TELEMETRY_SERVICE_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_TELEMETRY_SERVICE_FACTORY_H_
+
+#include "base/time/time.h"
+#include "components/adblock/core/adblock_telemetry_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/prefs/pref_service.h"
+
+namespace adblock {
+class SubscriptionService;
+class AdblockTelemetryServiceFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static AdblockTelemetryService* GetForBrowserContext(
+ content::BrowserContext* context);
+ static AdblockTelemetryServiceFactory* GetInstance();
+
+ // Sets the check interval, required for browser tests.
+ // Must be called before BuildServiceInstanceFor().
+ void SetCheckIntervalForTesting(base::TimeDelta check_interval);
+
+ private:
+ friend class base::NoDestructor<AdblockTelemetryServiceFactory>;
+ AdblockTelemetryServiceFactory();
+ ~AdblockTelemetryServiceFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ bool ServiceIsNULLWhileTesting() const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ADBLOCK_TELEMETRY_SERVICE_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/content_security_policy_injector_factory.cc b/components/adblock/content/browser/factories/content_security_policy_injector_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/content_security_policy_injector_factory.cc
@@ -0,0 +1,71 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/content_security_policy_injector_factory.h"
+
+#include <memory>
+
+#include "components/adblock/content/browser/content_security_policy_injector_impl.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+ContentSecurityPolicyInjector*
+ContentSecurityPolicyInjectorFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ContentSecurityPolicyInjector*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+ContentSecurityPolicyInjectorFactory*
+ContentSecurityPolicyInjectorFactory::GetInstance() {
+ static base::NoDestructor<ContentSecurityPolicyInjectorFactory> instance;
+ return instance.get();
+}
+
+ContentSecurityPolicyInjectorFactory::ContentSecurityPolicyInjectorFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ContentSecurityPolicyInjector",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(SubscriptionServiceFactory::GetInstance());
+}
+
+ContentSecurityPolicyInjectorFactory::~ContentSecurityPolicyInjectorFactory() =
+ default;
+
+std::unique_ptr<KeyedService>
+ContentSecurityPolicyInjectorFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<ContentSecurityPolicyInjectorImpl>(
+ SubscriptionServiceFactory::GetForBrowserContext(context),
+ std::make_unique<FrameHierarchyBuilder>());
+}
+
+content::BrowserContext*
+ContentSecurityPolicyInjectorFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/content_security_policy_injector_factory.h b/components/adblock/content/browser/factories/content_security_policy_injector_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/content_security_policy_injector_factory.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_CONTENT_SECURITY_POLICY_INJECTOR_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_CONTENT_SECURITY_POLICY_INJECTOR_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class ContentSecurityPolicyInjector;
+class ContentSecurityPolicyInjectorFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static ContentSecurityPolicyInjector* GetForBrowserContext(
+ content::BrowserContext* context);
+ static ContentSecurityPolicyInjectorFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<ContentSecurityPolicyInjectorFactory>;
+ ContentSecurityPolicyInjectorFactory();
+ ~ContentSecurityPolicyInjectorFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_CONTENT_SECURITY_POLICY_INJECTOR_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/element_hider_factory.cc b/components/adblock/content/browser/factories/element_hider_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/element_hider_factory.cc
@@ -0,0 +1,65 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/element_hider_factory.h"
+
+#include <memory>
+
+#include "components/adblock/content/browser/element_hider_impl.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+ElementHider* ElementHiderFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ElementHider*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+ElementHiderFactory* ElementHiderFactory::GetInstance() {
+ static base::NoDestructor<ElementHiderFactory> instance;
+ return instance.get();
+}
+
+ElementHiderFactory::ElementHiderFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ElementHider",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(adblock::SubscriptionServiceFactory::GetInstance());
+}
+
+ElementHiderFactory::~ElementHiderFactory() = default;
+
+std::unique_ptr<KeyedService>
+ElementHiderFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<ElementHiderImpl>(
+ adblock::SubscriptionServiceFactory::GetForBrowserContext(context));
+}
+
+content::BrowserContext* ElementHiderFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/element_hider_factory.h b/components/adblock/content/browser/factories/element_hider_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/element_hider_factory.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ELEMENT_HIDER_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ELEMENT_HIDER_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class ElementHider;
+class ElementHiderFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static ElementHider* GetForBrowserContext(content::BrowserContext* context);
+ static ElementHiderFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<ElementHiderFactory>;
+ ElementHiderFactory();
+ ~ElementHiderFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_ELEMENT_HIDER_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/embedding_utils.cc b/components/adblock/content/browser/factories/embedding_utils.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/embedding_utils.cc
@@ -0,0 +1,42 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/embedding_utils.h"
+
+#include <memory>
+
+#include "components/adblock/content/browser/adblock_webcontents_observer.h"
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+#include "components/adblock/content/browser/factories/content_security_policy_injector_factory.h"
+#include "components/adblock/content/browser/factories/element_hider_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/session_stats_factory.h"
+#include "components/adblock/content/browser/factories/sitekey_storage_factory.h"
+#include "components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+
+namespace adblock {
+
+void EnsureBackgroundServicesStarted(content::BrowserContext* browser_context) {
+ ResourceClassificationRunnerFactory::GetForBrowserContext(browser_context);
+ AdblockTelemetryServiceFactory::GetForBrowserContext(browser_context);
+ SessionStatsFactory::GetForBrowserContext(browser_context);
+ SitekeyStorageFactory::GetForBrowserContext(browser_context);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/embedding_utils.h b/components/adblock/content/browser/factories/embedding_utils.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/embedding_utils.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_EMBEDDING_UTILS_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_EMBEDDING_UTILS_H_
+
+#include "components/adblock/content/browser/factories/element_hider_factory.h"
+#include "components/adblock/content/browser/factories/sitekey_storage_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/content/browser/page_view_stats.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+
+namespace adblock {
+
+// Registers an AdblockWebContentObserver for the given WebContents.
+template <typename ObserverClass>
+void RegisterAdblockWebContentObserver(
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context) {
+ ObserverClass::CreateForWebContents(
+ web_contents,
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context),
+ ElementHiderFactory::GetForBrowserContext(browser_context),
+ SitekeyStorageFactory::GetForBrowserContext(browser_context),
+ std::make_unique<FrameHierarchyBuilder>(), CountNavigationsCallback());
+}
+
+// Ensures that all background services are started for the given browser
+// context.
+void EnsureBackgroundServicesStarted(content::BrowserContext* browser_context);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_EMBEDDING_UTILS_H_
diff --git a/components/adblock/content/browser/factories/resource_classification_runner_factory.cc b/components/adblock/content/browser/factories/resource_classification_runner_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/resource_classification_runner_factory.cc
@@ -0,0 +1,73 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+
+#include <memory>
+
+#include "components/adblock/content/browser/factories/sitekey_storage_factory.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/content/browser/resource_classification_runner_impl.h"
+#include "components/adblock/core/classifier/resource_classifier_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+ResourceClassificationRunner*
+ResourceClassificationRunnerFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<ResourceClassificationRunner*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+ResourceClassificationRunnerFactory*
+ResourceClassificationRunnerFactory::GetInstance() {
+ static base::NoDestructor<ResourceClassificationRunnerFactory> instance;
+ return instance.get();
+}
+
+ResourceClassificationRunnerFactory::ResourceClassificationRunnerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "ResourceClassificationRunner",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(SitekeyStorageFactory::GetInstance());
+}
+
+ResourceClassificationRunnerFactory::~ResourceClassificationRunnerFactory() =
+ default;
+
+std::unique_ptr<KeyedService>
+ResourceClassificationRunnerFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<ResourceClassificationRunnerImpl>(
+ base::MakeRefCounted<ResourceClassifierImpl>(),
+ std::make_unique<FrameHierarchyBuilder>(),
+ SitekeyStorageFactory::GetForBrowserContext(context));
+}
+
+content::BrowserContext*
+ResourceClassificationRunnerFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/resource_classification_runner_factory.h b/components/adblock/content/browser/factories/resource_classification_runner_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/resource_classification_runner_factory.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_RESOURCE_CLASSIFICATION_RUNNER_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_RESOURCE_CLASSIFICATION_RUNNER_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class ResourceClassificationRunner;
+class ResourceClassificationRunnerFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static ResourceClassificationRunner* GetForBrowserContext(
+ content::BrowserContext* context);
+ static ResourceClassificationRunnerFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<ResourceClassificationRunnerFactory>;
+ ResourceClassificationRunnerFactory();
+ ~ResourceClassificationRunnerFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_RESOURCE_CLASSIFICATION_RUNNER_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/session_stats_factory.cc b/components/adblock/content/browser/factories/session_stats_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/session_stats_factory.cc
@@ -0,0 +1,66 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/session_stats_factory.h"
+
+#include <memory>
+
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/session_stats_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+SessionStats* SessionStatsFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<SessionStats*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+SessionStatsFactory* SessionStatsFactory::GetInstance() {
+ static base::NoDestructor<SessionStatsFactory> instance;
+ return instance.get();
+}
+
+SessionStatsFactory::SessionStatsFactory()
+ : BrowserContextKeyedServiceFactory(
+ "SessionStats",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(ResourceClassificationRunnerFactory::GetInstance());
+}
+
+SessionStatsFactory::~SessionStatsFactory() = default;
+
+std::unique_ptr<KeyedService>
+SessionStatsFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<SessionStatsImpl>(
+ ResourceClassificationRunnerFactory::GetForBrowserContext(context));
+}
+
+content::BrowserContext* SessionStatsFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/session_stats_factory.h b/components/adblock/content/browser/factories/session_stats_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/session_stats_factory.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SESSION_STATS_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SESSION_STATS_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class SessionStats;
+class SessionStatsFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static SessionStats* GetForBrowserContext(content::BrowserContext* context);
+ static SessionStatsFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<SessionStatsFactory>;
+ SessionStatsFactory();
+ ~SessionStatsFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SESSION_STATS_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/sitekey_storage_factory.cc b/components/adblock/content/browser/factories/sitekey_storage_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/sitekey_storage_factory.cc
@@ -0,0 +1,61 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/sitekey_storage_factory.h"
+
+#include <memory>
+
+#include "components/adblock/core/sitekey_storage_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+SitekeyStorage* SitekeyStorageFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<SitekeyStorage*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+SitekeyStorageFactory* SitekeyStorageFactory::GetInstance() {
+ static base::NoDestructor<SitekeyStorageFactory> instance;
+ return instance.get();
+}
+
+SitekeyStorageFactory::SitekeyStorageFactory()
+ : BrowserContextKeyedServiceFactory(
+ "SitekeyStorage",
+ BrowserContextDependencyManager::GetInstance()) {}
+
+SitekeyStorageFactory::~SitekeyStorageFactory() = default;
+
+std::unique_ptr<KeyedService>
+SitekeyStorageFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<SitekeyStorageImpl>();
+}
+
+content::BrowserContext* SitekeyStorageFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/sitekey_storage_factory.h b/components/adblock/content/browser/factories/sitekey_storage_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/sitekey_storage_factory.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SITEKEY_STORAGE_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SITEKEY_STORAGE_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class SitekeyStorage;
+class SitekeyStorageFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ static SitekeyStorage* GetForBrowserContext(content::BrowserContext* context);
+ static SitekeyStorageFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<SitekeyStorageFactory>;
+ SitekeyStorageFactory();
+ ~SitekeyStorageFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SITEKEY_STORAGE_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.cc b/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.cc
@@ -0,0 +1,66 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h"
+
+#include <memory>
+
+#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+// static
+SubscriptionPersistentMetadata*
+SubscriptionPersistentMetadataFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<SubscriptionPersistentMetadata*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+SubscriptionPersistentMetadataFactory*
+SubscriptionPersistentMetadataFactory::GetInstance() {
+ static base::NoDestructor<SubscriptionPersistentMetadataFactory> instance;
+ return instance.get();
+}
+
+SubscriptionPersistentMetadataFactory::SubscriptionPersistentMetadataFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AdblockSubscriptionPersistentMetadata",
+ BrowserContextDependencyManager::GetInstance()) {}
+SubscriptionPersistentMetadataFactory::
+ ~SubscriptionPersistentMetadataFactory() = default;
+
+std::unique_ptr<KeyedService>
+SubscriptionPersistentMetadataFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ return std::make_unique<SubscriptionPersistentMetadataImpl>(
+ user_prefs::UserPrefs::Get(context));
+}
+
+content::BrowserContext*
+SubscriptionPersistentMetadataFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h b/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_PERSISTENT_METADATA_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_PERSISTENT_METADATA_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class SubscriptionPersistentMetadata;
+class SubscriptionPersistentMetadataFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static SubscriptionPersistentMetadata* GetForBrowserContext(
+ content::BrowserContext* context);
+ static SubscriptionPersistentMetadataFactory* GetInstance();
+
+ private:
+ friend class base::NoDestructor<SubscriptionPersistentMetadataFactory>;
+ SubscriptionPersistentMetadataFactory();
+ ~SubscriptionPersistentMetadataFactory() override;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_PERSISTENT_METADATA_FACTORY_H_
diff --git a/components/adblock/content/browser/factories/subscription_service_factory.cc b/components/adblock/content/browser/factories/subscription_service_factory.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/subscription_service_factory.cc
@@ -0,0 +1,500 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+
+#include <fstream>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h"
+#include "components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/common/task_scheduler_impl.h"
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/net/adblock_resource_request_impl.h"
+#include "components/adblock/core/subscription/filtering_configuration_maintainer_impl.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "components/adblock/core/subscription/preloaded_subscription_provider_impl.h"
+#include "components/adblock/core/subscription/recommended_subscription_installer_impl.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_downloader_impl.h"
+#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h"
+#include "components/adblock/core/subscription/subscription_service_impl.h"
+#include "components/adblock/core/subscription/subscription_validator_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/language/core/common/locale_util.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace adblock {
+namespace {
+
+std::optional<base::TimeDelta> g_update_check_interval_for_testing;
+
+base::TimeDelta GetUpdateCheckInterval() {
+ static base::TimeDelta kCheckInterval =
+ g_update_check_interval_for_testing
+ ? g_update_check_interval_for_testing.value()
+ : base::Hours(1);
+ return kCheckInterval;
+}
+
+constexpr net::BackoffEntry::Policy kRetryBackoffPolicy = {
+ 0, // Number of initial errors to ignore.
+ 5000, // Initial delay in ms.
+ 2.0, // Factor by which the waiting time will be multiplied.
+ 0.2, // Fuzzing percentage.
+ 60 * 60 * 1000, // Maximum delay in ms.
+ -1, // Never discard the entry.
+ false, // Use initial delay.
+};
+
+std::unique_ptr<AdblockResourceRequest> MakeSubscriptionRequest(
+ content::BrowserContext* context) {
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
+ context->GetDefaultStoragePartition()
+ ->GetURLLoaderFactoryForBrowserProcess();
+ AdblockRequestThrottle* request_throttle =
+ AdblockRequestThrottleFactory::GetForBrowserContext(context);
+ return std::make_unique<AdblockResourceRequestImpl>(
+ &kRetryBackoffPolicy, url_loader_factory, request_throttle);
+}
+
+ConversionResult ConvertFilterFile(
+ const scoped_refptr<FlatbufferConverter>& converter,
+ const GURL& subscription_url,
+ const base::FilePath& path) {
+ TRACE_EVENT1("eyeo", "ConvertFileToFlatbuffer", "url",
+ subscription_url.spec());
+ ConversionResult result;
+ std::ifstream input_stream(path.AsUTF8Unsafe());
+ if (!input_stream.is_open() || !input_stream.good()) {
+ result = ConversionError("Could not open filter file");
+ } else if (!converter) {
+ result = ConversionError("Converter is not initialized");
+ } else {
+ result =
+ converter->Convert(input_stream, subscription_url,
+ config::AllowPrivilegedFilters(subscription_url));
+ }
+ base::DeleteFile(path);
+ return result;
+}
+
+std::unique_ptr<TaskScheduler> MakeTaskScheduler() {
+ return std::make_unique<TaskSchedulerImpl>(GetUpdateCheckInterval());
+}
+
+std::unique_ptr<FilteringConfigurationMaintainer>
+MakeFilterConfigurationMaintainer(
+ content::BrowserContext* context,
+ PrefService* prefs,
+ SubscriptionPersistentMetadata* persistent_metadata,
+ const ConversionExecutors* conversion_executors,
+ FilteringConfiguration* configuration,
+ FilteringConfigurationMaintainerImpl::SubscriptionUpdatedCallback
+ observer) {
+ auto main_thread_task_runner = base::SequencedTaskRunner::GetCurrentDefault();
+
+ const std::string storage_dir = configuration->GetName() + "_subscriptions";
+
+ auto storage = std::make_unique<SubscriptionPersistentStorageImpl>(
+ context->GetPath().AppendASCII(storage_dir),
+ std::make_unique<SubscriptionValidatorImpl>(prefs,
+ CurrentSchemaVersion()),
+ persistent_metadata);
+
+ auto downloader = std::make_unique<SubscriptionDownloaderImpl>(
+ base::BindRepeating(&MakeSubscriptionRequest, context),
+ const_cast<ConversionExecutors*>(conversion_executors),
+ persistent_metadata);
+
+ std::unique_ptr<RecommendedSubscriptionInstallerImpl> recommended_installer =
+ nullptr;
+ if (configuration->GetName() == kAdblockFilteringConfigurationName) {
+ recommended_installer =
+ std::make_unique<RecommendedSubscriptionInstallerImpl>(
+ prefs, configuration, persistent_metadata,
+ base::BindRepeating(&MakeSubscriptionRequest, context));
+ }
+
+ auto maintainer = std::make_unique<FilteringConfigurationMaintainerImpl>(
+ configuration, std::move(storage), std::move(downloader),
+ std::move(recommended_installer),
+ std::make_unique<PreloadedSubscriptionProviderImpl>(),
+ MakeTaskScheduler(),
+ const_cast<ConversionExecutors*>(conversion_executors),
+ persistent_metadata, observer);
+ maintainer->InitializeStorage();
+ return maintainer;
+}
+
+void CleanupPersistedConfiguration(PrefService* prefs,
+ FilteringConfiguration* configuration) {
+ PersistentFilteringConfiguration::RemovePersistedData(
+ prefs, configuration->GetName());
+}
+
+void InstallFirstRunDefaultAdblockSubscription(
+ std::unique_ptr<FilteringConfiguration>& adblock_filtering_configuration) {
+ if (std::ranges::any_of(config::GetKnownSubscriptions(),
+ [&](const KnownSubscriptionInfo& subscription) {
+ return subscription.url ==
+ DefaultSubscriptionUrl() &&
+ subscription.first_run !=
+ SubscriptionFirstRunBehavior::Ignore;
+ })) {
+ VLOG(1) << "[eyeo] Using the default subscription";
+ adblock_filtering_configuration->AddFilterList(DefaultSubscriptionUrl());
+ } else {
+ VLOG(1) << "[eyeo] No default subscription found, neither "
+ "language-specific, nor generic.";
+ }
+}
+
+bool InstallFirstRunAdblockSubscriptionsCheckingLocale(
+ std::string_view language,
+ std::unique_ptr<FilteringConfiguration>& adblock_filtering_configuration) {
+ bool language_specific_subscription_installed = false;
+ // On first run, install additional subscriptions.
+ for (const auto& subscription : adblock::config::GetKnownSubscriptions()) {
+ if (subscription.first_run == SubscriptionFirstRunBehavior::Subscribe) {
+ adblock_filtering_configuration->AddFilterList(subscription.url);
+ } else if (subscription.first_run ==
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch &&
+ std::find(subscription.languages.begin(),
+ subscription.languages.end(),
+ language) != subscription.languages.end()) {
+ VLOG(1) << "[eyeo] Using recommended subscription for language \""
+ << language << "\": " << subscription.title;
+ language_specific_subscription_installed = true;
+ adblock_filtering_configuration->AddFilterList(subscription.url);
+ }
+ }
+ return language_specific_subscription_installed;
+}
+
+void CheckAndRunFirstRunLogic(
+ std::string_view locale,
+ PrefService* prefs,
+ std::unique_ptr<FilteringConfiguration>& adblock_filtering_configuration) {
+ // If the state of installed subscriptions in SubscriptionService is different
+ // than the state in prefs, prefs take precedence.
+ if (prefs->GetBoolean(common::prefs::kInstallFirstStartSubscriptions)) {
+ adblock_filtering_configuration =
+ std::make_unique<PersistentFilteringConfiguration>(
+ prefs, kAdblockFilteringConfigurationName);
+ std::string_view language = language::ExtractBaseLanguage(locale);
+ if (language.size() != 2u) {
+ language = "en";
+ }
+ if (!InstallFirstRunAdblockSubscriptionsCheckingLocale(
+ language, adblock_filtering_configuration)) {
+ InstallFirstRunDefaultAdblockSubscription(
+ adblock_filtering_configuration);
+ }
+ if (IsEyeoFilteringDisabledByDefault()) {
+ adblock_filtering_configuration->SetEnabled(false);
+ }
+ if (IsAcceptableAdsDisabledByDefault()) {
+ adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl());
+ }
+ prefs->SetBoolean(common::prefs::kInstallFirstStartSubscriptions, false);
+ }
+}
+
+template <typename T>
+std::vector<T> MigrateItemsFromList(PrefService* pref_service,
+ const std::string& pref_name) {
+ std::vector<T> results;
+ if (pref_service->FindPreference(pref_name)->HasUserSetting()) {
+ const auto& list = pref_service->GetList(pref_name);
+ for (const auto& item : list) {
+ if (item.is_string()) {
+ results.emplace_back(item.GetString());
+ }
+ }
+ pref_service->ClearPref(pref_name);
+ }
+ return results;
+}
+
+absl::optional<bool> MigrateBoolFromPrefs(PrefService* pref_service,
+ const std::string& pref_name) {
+ if (pref_service->FindPreference(pref_name)->HasUserSetting()) {
+ bool value = pref_service->GetBoolean(pref_name);
+ pref_service->ClearPref(pref_name);
+ return value;
+ }
+ return absl::nullopt;
+}
+
+void CheckAndMigrateSettings(
+ PrefService* prefs,
+ std::unique_ptr<FilteringConfiguration>& adblock_filtering_configuration) {
+ auto enable_value =
+ MigrateBoolFromPrefs(prefs, common::prefs::kEnableAdblockLegacy);
+ auto aa_value =
+ MigrateBoolFromPrefs(prefs, common::prefs::kEnableAcceptableAdsLegacy);
+ auto subscriptions = MigrateItemsFromList<GURL>(
+ prefs, common::prefs::kAdblockSubscriptionsLegacy);
+ auto custom_subscriptions = MigrateItemsFromList<GURL>(
+ prefs, common::prefs::kAdblockCustomSubscriptionsLegacy);
+ auto domains = MigrateItemsFromList<std::string>(
+ prefs, common::prefs::kAdblockAllowedDomainsLegacy);
+ auto filters = MigrateItemsFromList<std::string>(
+ prefs, common::prefs::kAdblockCustomFiltersLegacy);
+
+ if (!enable_value && !aa_value && subscriptions.empty() &&
+ custom_subscriptions.empty() && domains.empty() && filters.empty()) {
+ return;
+ }
+
+ if (!adblock_filtering_configuration) {
+ adblock_filtering_configuration =
+ std::make_unique<PersistentFilteringConfiguration>(
+ prefs, kAdblockFilteringConfigurationName);
+ }
+
+ if (enable_value) {
+ adblock_filtering_configuration->SetEnabled(*enable_value);
+ VLOG(1) << "[eyeo] Migrated kEnableAdblockLegacy pref";
+ }
+
+ if (aa_value) {
+ if (*aa_value) {
+ adblock_filtering_configuration->AddFilterList(AcceptableAdsUrl());
+ } else {
+ adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl());
+ }
+ VLOG(1) << "[eyeo] Migrated kEnableAcceptableAdsLegacy pref";
+ } else if (!subscriptions.empty()) {
+ // In old version AA setting could never be touched but AA was in use.
+ // So if we see no value for AA but kAdblockSubscriptionsLegacy pref was
+ // touched (kAdblockSubscriptionsLegacy is touched during 1st run scenario)
+ // we need to assume AA was enabled. See DPD-2219.
+ adblock_filtering_configuration->AddFilterList(AcceptableAdsUrl());
+ }
+
+ for (const auto& url : subscriptions) {
+ adblock_filtering_configuration->AddFilterList(url);
+ VLOG(1) << "[eyeo] Migrated " << url
+ << " from kAdblockSubscriptionsLegacy pref";
+ }
+
+ for (const auto& url : custom_subscriptions) {
+ adblock_filtering_configuration->AddFilterList(url);
+ VLOG(1) << "[eyeo] Migrated " << url
+ << " from kAdblockCustomSubscriptionsLegacy pref";
+ }
+
+ for (const auto& domain : domains) {
+ adblock_filtering_configuration->AddAllowedDomain(domain);
+ VLOG(1) << "[eyeo] Migrated " << domain
+ << " from kAdblockAllowedDomainsLegacy pref";
+ }
+
+ for (const auto& filter : filters) {
+ adblock_filtering_configuration->AddCustomFilter(filter);
+ VLOG(1) << "[eyeo] Migrated " << filter
+ << " from kAdblockCustomFiltersLegacy pref";
+ }
+}
+
+void TranslateCombinedFilterListsToStandalone(
+ FilteringConfiguration* adblock_filtering_configuration) {
+ for (const auto& url : adblock_filtering_configuration->GetFilterLists()) {
+ const auto standalone_lists = config::MaybeSplitCombinedAdblockList(url);
+ if (standalone_lists.size() > 0) {
+ VLOG(1) << "[eyeo] Removing combined list: " << url;
+ adblock_filtering_configuration->RemoveFilterList(url);
+ for (const auto& standalone_list : standalone_lists) {
+ // Note: we resolve using `AdblockBaseFilterListUrl()` not `url` as in
+ // browser tests `url` can be invalid referring to previous instance
+ // (port) of test http server
+ auto filter_list_url =
+ AdblockBaseFilterListUrl().Resolve(standalone_list);
+ VLOG(1) << "[eyeo] Adding standalone list: " << filter_list_url;
+ adblock_filtering_configuration->AddFilterList(filter_list_url);
+ }
+ }
+ }
+}
+
+void CheckAdblockCliSwitches(
+ FilteringConfiguration* adblock_filtering_configuration) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ adblock::switches::kDisableAcceptableAds)) {
+ adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl());
+ }
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAdblock)) {
+ adblock_filtering_configuration->SetEnabled(false);
+ }
+}
+
+} // namespace
+
+SubscriptionServiceFactory::SubscriptionServiceFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AdblockSubscriptionService",
+ BrowserContextDependencyManager::GetInstance()) {
+ flatbuffer_converter_ = base::MakeRefCounted<FlatbufferConverter>();
+ DependsOn(SubscriptionPersistentMetadataFactory::GetInstance());
+}
+
+SubscriptionServiceFactory::~SubscriptionServiceFactory() = default;
+
+// static
+SubscriptionService* SubscriptionServiceFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<SubscriptionService*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+// static
+SubscriptionServiceFactory* SubscriptionServiceFactory::GetInstance() {
+ static base::NoDestructor<SubscriptionServiceFactory> instance;
+ return instance.get();
+}
+
+std::unique_ptr<KeyedService>
+SubscriptionServiceFactory::BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const {
+ auto* prefs = GetPrefs(context);
+ auto subscription_service = std::make_unique<SubscriptionServiceImpl>(
+ prefs,
+ base::BindRepeating(&MakeFilterConfigurationMaintainer, context, prefs,
+ GetSubscriptionPersistentMetadata(context),
+ static_cast<const ConversionExecutors*>(this)),
+ base::BindRepeating(&CleanupPersistedConfiguration, prefs));
+ auto persisted_configs =
+ PersistentFilteringConfiguration::GetPersistedConfigurations(prefs);
+ bool start_disabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableEyeoFiltering);
+ for (auto& persisted_configuration : persisted_configs) {
+ if (start_disabled) {
+ persisted_configuration->SetEnabled(false);
+ }
+ if (persisted_configuration->GetName() ==
+ kAdblockFilteringConfigurationName) {
+ CheckAdblockCliSwitches(persisted_configuration.get());
+ TranslateCombinedFilterListsToStandalone(persisted_configuration.get());
+ }
+ subscription_service->InstallFilteringConfiguration(
+ std::move(persisted_configuration));
+ }
+ if (persisted_configs.empty()) {
+ std::unique_ptr<FilteringConfiguration> adblock_filtering_configuration;
+ // Check if we have a 1st run scenario, if yes then "adblock"
+ // FilterConfiguration will be created and configured.
+ {
+ CheckAndRunFirstRunLogic(GetLocale(), prefs,
+ adblock_filtering_configuration);
+ }
+ // Check and migrate if there are any pre-configuration settings.
+ // "adblock" FilterConfiguration will be created if needed.
+ CheckAndMigrateSettings(prefs, adblock_filtering_configuration);
+ if (adblock_filtering_configuration) {
+ CheckAdblockCliSwitches(adblock_filtering_configuration.get());
+ if (start_disabled) {
+ adblock_filtering_configuration->SetEnabled(false);
+ }
+ TranslateCombinedFilterListsToStandalone(
+ adblock_filtering_configuration.get());
+ subscription_service->InstallFilteringConfiguration(
+ std::move(adblock_filtering_configuration));
+ }
+ }
+ return subscription_service;
+}
+
+scoped_refptr<InstalledSubscription>
+SubscriptionServiceFactory::ConvertCustomFilters(
+ const std::vector<std::string>& filters) const {
+ auto raw_data =
+ flatbuffer_converter_->Convert(filters, CustomFiltersUrl(), true);
+ return base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(raw_data), Subscription::InstallationState::Installed,
+ base::Time());
+}
+
+void SubscriptionServiceFactory::ConvertFilterListFile(
+ const GURL& subscription_url,
+ const base::FilePath& path,
+ base::OnceCallback<void(ConversionResult)> result_callback) const {
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&ConvertFilterFile, flatbuffer_converter_,
+ subscription_url, path),
+ std::move(result_callback));
+}
+
+void SubscriptionServiceFactory::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ adblock::common::prefs::RegisterProfilePrefs(registry);
+}
+
+// static
+void SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting(
+ base::TimeDelta check_interval) {
+ g_update_check_interval_for_testing = check_interval;
+}
+
+SubscriptionPersistentMetadata*
+SubscriptionServiceFactory::GetSubscriptionPersistentMetadata(
+ content::BrowserContext* context) const {
+ return SubscriptionPersistentMetadataFactory::GetForBrowserContext(context);
+}
+
+content::BrowserContext* SubscriptionServiceFactory::GetBrowserContextToUse(
+ content::BrowserContext* context) const {
+ if (context->IsOffTheRecord()) {
+ return nullptr;
+ }
+ return context;
+}
+
+PrefService* SubscriptionServiceFactory::GetPrefs(
+ content::BrowserContext* context) const {
+ return user_prefs::UserPrefs::Get(context);
+}
+
+std::string SubscriptionServiceFactory::GetLocale() const {
+ base::ScopedAllowBlocking allow_blocking;
+ // Provide no initial guess (which normally would come from local state),
+ // read the locale from the system. Do not save the locale in ICU, this
+ // would create a side effect.
+ return l10n_util::GetApplicationLocale("", /*set_icu_locale=*/false);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/factories/subscription_service_factory.h b/components/adblock/content/browser/factories/subscription_service_factory.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/factories/subscription_service_factory.h
@@ -0,0 +1,76 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_SERVICE_FACTORY_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_SERVICE_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/conversion_executors.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+
+namespace adblock {
+
+class SubscriptionPersistentMetadata;
+class SubscriptionServiceFactory : public BrowserContextKeyedServiceFactory,
+ public ConversionExecutors {
+ public:
+ static SubscriptionService* GetForBrowserContext(
+ content::BrowserContext* context);
+ static SubscriptionServiceFactory* GetInstance();
+ static void SetUpdateCheckIntervalForTesting(base::TimeDelta check_interval);
+
+ // ConversionExecutors:
+ scoped_refptr<InstalledSubscription> ConvertCustomFilters(
+ const std::vector<std::string>& filters) const override;
+ void ConvertFilterListFile(
+ const GURL& subscription_url,
+ const base::FilePath& path,
+ base::OnceCallback<void(ConversionResult)>) const override;
+
+ protected:
+ SubscriptionServiceFactory();
+ ~SubscriptionServiceFactory() override;
+
+ // virtual just for unit tests:
+ virtual SubscriptionPersistentMetadata* GetSubscriptionPersistentMetadata(
+ content::BrowserContext* context) const;
+ virtual PrefService* GetPrefs(content::BrowserContext* context) const;
+ virtual std::string GetLocale() const;
+
+ // BrowserContextKeyedServiceFactory:
+ std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+ content::BrowserContext* context) const override;
+
+ private:
+ friend class base::NoDestructor<SubscriptionServiceFactory>;
+
+ // BrowserContextKeyedServiceFactory:
+ content::BrowserContext* GetBrowserContextToUse(
+ content::BrowserContext* context) const override;
+ void RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) override;
+
+ scoped_refptr<FlatbufferConverter> flatbuffer_converter_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FACTORIES_SUBSCRIPTION_SERVICE_FACTORY_H_
diff --git a/components/adblock/content/browser/frame_hierarchy_builder.cc b/components/adblock/content/browser/frame_hierarchy_builder.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/frame_hierarchy_builder.cc
@@ -0,0 +1,137 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+
+#include <string_view>
+#include <vector>
+
+#include "base/logging.h"
+#include "components/adblock/content/browser/eyeo_document_info.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+namespace {
+
+// Do not use GURL::IsAboutBlank for frame hierarchy, it returns true for valid
+// src like `about:blank?eyeo=true`
+bool IsExactlyAboutBlank(const GURL url) {
+ static const std::string_view kAboutBlankUrl = "about:blank";
+ return url == kAboutBlankUrl;
+}
+
+bool IsValidForFrameHierarchy(const GURL& url) {
+ return !url.is_empty() && !IsExactlyAboutBlank(url) &&
+ url != content::kBlockedURL;
+}
+
+// Basically calling `GetAsReferrer()` on url strips elements that are not
+// supposed to be sent as HTTP referrer: username, password and ref fragment,
+// and this is what we want for frame hierarchy urls.
+// But for urls like `about:blank?eyeo=true` calling `GetAsReferrer()` returns
+// an empty url which excludes it from frame hierarchy, so for `about:blank...`
+// we fallback to the original URL.
+// Also `GetAsReferrer()` returns an empty url for file scheme urls.
+GURL GetUrlAsReferrer(content::RenderFrameHost* frame_host) {
+ EyeoDocumentInfo* document_info =
+ EyeoDocumentInfo::GetOrCreateForCurrentDocument(frame_host);
+ const GURL& frame_url = document_info->GetURL();
+ if ((frame_url.IsAboutBlank() && IsValidForFrameHierarchy(frame_url)) ||
+ frame_url.SchemeIsFile()) {
+ return frame_url;
+ }
+ return frame_url.GetAsReferrer();
+}
+
+std::vector<GURL> BuildFrameHierarchyForRenderFrameHost(
+ content::RenderFrameHost* frame_host) {
+ std::vector<GURL> frame_hierarchy;
+ for (auto* iter = frame_host; iter; iter = iter->GetParent()) {
+ auto last_commited_referrer = GetUrlAsReferrer(iter);
+ if (IsValidForFrameHierarchy(last_commited_referrer)) {
+ VLOG(2) << "[eyeo] FrameHierarchy item: "
+ << last_commited_referrer.spec();
+ frame_hierarchy.emplace_back(last_commited_referrer);
+ }
+ }
+ return frame_hierarchy;
+}
+
+std::vector<GURL> BuildFrameHierarchyForRequestInitiator(
+ const GURL& initiator_url) {
+ std::vector<GURL> frame_hierarchy;
+ if (IsValidForFrameHierarchy(initiator_url)) {
+ frame_hierarchy.emplace_back(initiator_url);
+ }
+ return frame_hierarchy;
+}
+
+} // namespace
+
+FrameHierarchyBuilder::FrameHierarchyBuilder() = default;
+
+FrameHierarchyBuilder::~FrameHierarchyBuilder() = default;
+
+std::vector<GURL> FrameHierarchyBuilder::BuildFrameHierarchy(
+ const RequestInitiator& request_initiator) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // frame_hierarchy is used for allowlisting, so we can check if any of the
+ // parents of the requesting entity are allowlisted, in the event that the
+ // request gets blocked. For "parentless" requests originating from service
+ // workers, we fall back to their "initiator" URLs. This will be the URL of
+ // the service worker script, not necessarily the website that hosts it, so
+ // it's not ideal, but it's the best we can do.
+ if (request_initiator.IsFrame()) {
+ return BuildFrameHierarchyForRenderFrameHost(
+ request_initiator.GetRenderFrameHost());
+ } else {
+ return BuildFrameHierarchyForRequestInitiator(
+ request_initiator.GetInitiatorUrl());
+ }
+}
+
+GURL FrameHierarchyBuilder::FindUrlForFrame(
+ content::RenderFrameHost* frame_host,
+ content::WebContents* web_contents) const {
+ GURL url = frame_host->GetLastCommittedURL();
+ // Anonymous frames have "about:blank" source so we use a parent frame URL
+ // Do not use GURL::IsAboutBlank (DPD-1946).
+ if (IsExactlyAboutBlank(url)) {
+ for (auto* parent = frame_host->GetParent(); parent;
+ parent = parent->GetParent()) {
+ GURL parent_url = parent->GetLastCommittedURL();
+ if (!parent_url.spec().empty() && !IsExactlyAboutBlank(parent_url)) {
+ url = parent_url;
+ break;
+ }
+ }
+ // If we still cannot find parent url let's use top level navigation url
+ if (IsExactlyAboutBlank(url)) {
+ url = web_contents->GetLastCommittedURL();
+ }
+ }
+ return url;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/frame_hierarchy_builder.h b/components/adblock/content/browser/frame_hierarchy_builder.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/frame_hierarchy_builder.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_
+
+#include <vector>
+
+#include "base/sequence_checker.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "content/public/browser/render_frame_host.h"
+
+class GURL;
+
+namespace content {
+class RenderFrameHost;
+class WebContents;
+} // namespace content
+
+namespace adblock {
+/**
+ * @brief Builds the frame hierarchy based on the RenderFrameHost.
+ * A frame hierarchy is an ordered list of URLs of frames containing
+ * an element, beginning with the direct parent frame and ending with
+ * the top-level URL of the page.
+ * Frame hierarchies are traversed to find any allow rules that apply to
+ * parent frames of blocked resource to override the blocking rule.
+ * Used in browser process UI main thread.
+ *
+ */
+class FrameHierarchyBuilder {
+ public:
+ FrameHierarchyBuilder();
+ virtual ~FrameHierarchyBuilder();
+
+ // For request initiated by a frame, traverses the parents of the frame and
+ // returns a filtered list of frame URLs. For detached requests, like the ones
+ // issued by service workers, creates an approximation based on the URL of the
+ // initiator of the request.
+ virtual std::vector<GURL> BuildFrameHierarchy(
+ const RequestInitiator& request_initiator) const;
+
+ virtual GURL FindUrlForFrame(content::RenderFrameHost* host,
+ content::WebContents* web_contents) const;
+
+ private:
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_
diff --git a/components/adblock/content/browser/frame_opener_info.cc b/components/adblock/content/browser/frame_opener_info.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/frame_opener_info.cc
@@ -0,0 +1,37 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/frame_opener_info.h"
+
+namespace adblock {
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(FrameOpenerInfo);
+
+FrameOpenerInfo::FrameOpenerInfo(content::WebContents* contents)
+ : content::WebContentsUserData<FrameOpenerInfo>(*contents) {}
+
+FrameOpenerInfo::~FrameOpenerInfo() = default;
+
+content::GlobalRenderFrameHostId FrameOpenerInfo::GetOpener() const {
+ return render_frame_host_id_;
+}
+void FrameOpenerInfo::SetOpener(
+ content::GlobalRenderFrameHostId render_frame_host_id) {
+ render_frame_host_id_ = render_frame_host_id;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/frame_opener_info.h b/components/adblock/content/browser/frame_opener_info.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/frame_opener_info.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_OPENER_INFO_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_OPENER_INFO_H_
+
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace adblock {
+
+class FrameOpenerInfo final
+ : public content::WebContentsUserData<FrameOpenerInfo> {
+ public:
+ ~FrameOpenerInfo() final;
+
+ content::GlobalRenderFrameHostId GetOpener() const;
+ void SetOpener(content::GlobalRenderFrameHostId render_frame_host_id);
+
+ private:
+ explicit FrameOpenerInfo(content::WebContents* contents);
+
+ content::GlobalRenderFrameHostId render_frame_host_id_ =
+ content::GlobalRenderFrameHostId{MSG_ROUTING_NONE, MSG_ROUTING_NONE};
+
+ friend WebContentsUserData;
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_OPENER_INFO_H_
diff --git a/components/adblock/content/browser/mojom/BUILD.gn b/components/adblock/content/browser/mojom/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/mojom/BUILD.gn
@@ -0,0 +1,22 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("adblock_internals") {
+ sources = [ "adblock_internals.mojom" ]
+ webui_module_path = "/"
+}
diff --git a/components/adblock/content/browser/mojom/adblock_internals.mojom b/components/adblock/content/browser/mojom/adblock_internals.mojom
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/mojom/adblock_internals.mojom
@@ -0,0 +1,22 @@
+// This file is part of eyeo Chromium SDK,
+// Copyright (C) 2006-present eyeo GmbH
+//
+// eyeo Chromium SDK is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3 as
+// published by the Free Software Foundation.
+//
+// eyeo Chromium SDK is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+module mojom.adblock_internals;
+
+interface AdblockInternalsPageHandler {
+ GetDebugInfo() => (string debug_info);
+ ToggleTestpagesFLSubscription() => (bool is_subscribed);
+ IsSubscribedToTestpagesFL() => (bool is_subscribed);
+};
diff --git a/components/adblock/content/browser/page_view_stats.cc b/components/adblock/content/browser/page_view_stats.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/page_view_stats.cc
@@ -0,0 +1,291 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/page_view_stats.h"
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/adblock/content/browser/eyeo_page_info.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace adblock {
+namespace {
+
+// The key name for the kTelemetryPageViewStats dict for storing the number of
+// Acceptable Ads page views.
+const char kAcceptableAdsStatsCountKey[] = "aa_pageviews";
+
+// The key name for the kTelemetryPageViewStats dict for storing the number of
+// Acceptable Ads page views for Blockhthrough specific allowlisting filters.
+// We recognize this case by seeing that page loads script from url
+// https://btloader.com/recovery?w={{page_id}}&upapi=true which is requested by
+// a page only when AA and Easylist are on. And to get notification about this
+// script being loaded we add a fake blocking filter (the asterisk is for our
+// browser tests which are using custom port)
+// `|https://btloader.com*/recovery?w=` which is overruled by another fake
+// allowing filter `@@|https://btloader.com*/recovery?w=`.
+const char kAcceptableAdsBlockthroughStatsCountKey[] = "aa_bt_pageviews";
+
+// The key name for the kTelemetryPageViewStats dict for storing the number of
+// allowing filter page views.
+const char kAllowedStatsCountKey[] = "allowed_pageviews";
+
+// The key name for the kTelemetryPageViewStats dict for storing the number of
+// blocking filter page views.
+const char kBlockedStatsCountKey[] = "blocked_pageviews";
+
+// This stores the total number of all page views (finished navigations) rather
+// than AA page views.
+const char kTotalPagesStatsCountKey[] = "pageviews";
+
+std::string_view GetReportedNameForMetric(PageViewStats::Metric metric) {
+ switch (metric) {
+ case PageViewStats::Metric::AcceptableAds:
+ return kAcceptableAdsStatsCountKey;
+ case PageViewStats::Metric::AcceptableAdsBlockThrough:
+ return kAcceptableAdsBlockthroughStatsCountKey;
+ case PageViewStats::Metric::Allowing:
+ return kAllowedStatsCountKey;
+ case PageViewStats::Metric::Blocking:
+ return kBlockedStatsCountKey;
+ case PageViewStats::Metric::TotalPages:
+ return kTotalPagesStatsCountKey;
+ }
+}
+
+base::WeakPtr<PageViewStats> g_last_used_instance;
+
+void RegisterNavigationWithLastUsedPageViewStats(
+ content::RenderFrameHost* render_frame_host) {
+ if (g_last_used_instance) {
+ g_last_used_instance->RegisterMainFrameNavigation(render_frame_host);
+ }
+}
+
+void RegisterAcceptableAdsBlockthroughtHitWithLastUsedPageViewStats(
+ content::RenderFrameHost* render_frame_host) {
+ if (g_last_used_instance) {
+ g_last_used_instance->RegisterAcceptableAdsBlockthroughtHit(
+ render_frame_host);
+ }
+}
+
+inline bool WasNavigationCommitted(PageViewStats::Metric metric,
+ EyeoPageInfo* page_info) {
+ return page_info->HasMatchedPageView(PageViewStats::Metric::TotalPages);
+}
+
+inline bool IsNavigationCommittingNow(PageViewStats::Metric metric) {
+ return metric == PageViewStats::Metric::TotalPages;
+}
+
+} // namespace
+
+PageViewStats::PageViewStats(
+ ResourceClassificationRunner* classification_runner,
+ PrefService* prefs)
+ : classification_runner_(classification_runner), prefs_(prefs) {
+ DCHECK(classification_runner_);
+ DCHECK(prefs_);
+ classification_runner_->AddObserver(this);
+ g_last_used_instance = weak_factory_.GetWeakPtr();
+}
+
+PageViewStats::~PageViewStats() {
+ classification_runner_->RemoveObserver(this);
+}
+
+void PageViewStats::OnRequestMatched(
+ const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ OnMatchedInternal(url, match_result, content_type, render_frame_host,
+ subscription);
+}
+
+void PageViewStats::OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ OnMatchedInternal(url, match_result, ContentType::Other, render_frame_host,
+ subscription);
+}
+
+void PageViewStats::OnMatchedInternal(
+ const GURL& url,
+ FilterMatchResult match_result,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription) {
+ if (render_frame_host == nullptr) {
+ return;
+ }
+
+ if (match_result == FilterMatchResult::kAllowRule) {
+ if (subscription == AcceptableAdsUrl()) {
+ RecordPageView(render_frame_host->GetPage(), Metric::AcceptableAds);
+ }
+ // We increment general (any) allowlisting metric despite incrementing
+ // specific, AA related metrics.
+ RecordPageView(render_frame_host->GetPage(), Metric::Allowing);
+ } else {
+ DCHECK(match_result == FilterMatchResult::kBlockRule);
+ RecordPageView(render_frame_host->GetPage(), Metric::Blocking);
+ }
+}
+
+base::Value::Dict PageViewStats::GetPayload() const {
+ base::Value::Dict payload;
+ payload.Set(kAcceptableAdsStatsCountKey,
+ GetPageViewsCount(Metric::AcceptableAds));
+ payload.Set(kAcceptableAdsBlockthroughStatsCountKey,
+ GetPageViewsCount(Metric::AcceptableAdsBlockThrough));
+ payload.Set(kAllowedStatsCountKey, GetPageViewsCount(Metric::Allowing));
+ payload.Set(kBlockedStatsCountKey, GetPageViewsCount(Metric::Blocking));
+ payload.Set(kTotalPagesStatsCountKey, GetPageViewsCount(Metric::TotalPages));
+ return payload;
+}
+
+void PageViewStats::ResetStats() {
+ ResetPageViewsCount(Metric::AcceptableAds);
+ ResetPageViewsCount(Metric::AcceptableAdsBlockThrough);
+ ResetPageViewsCount(Metric::Allowing);
+ ResetPageViewsCount(Metric::Blocking);
+ ResetPageViewsCount(Metric::TotalPages);
+}
+
+void PageViewStats::RegisterMainFrameNavigation(
+ content::RenderFrameHost* render_frame_host) {
+ if (render_frame_host == nullptr) {
+ return;
+ }
+
+ RecordPageView(render_frame_host->GetPage(), Metric::TotalPages);
+}
+
+void PageViewStats::RegisterAcceptableAdsBlockthroughtHit(
+ content::RenderFrameHost* render_frame_host) {
+ if (render_frame_host == nullptr) {
+ return;
+ }
+
+ RecordPageView(render_frame_host->GetPage(),
+ Metric::AcceptableAdsBlockThrough);
+}
+
+void PageViewStats::ParkMetric(content::Page& page, Metric metric) {
+ auto main_frame_id = page.GetMainDocument().GetGlobalId();
+ parked_metrics_before_main_navigation_[main_frame_id].insert(metric);
+}
+
+void PageViewStats::RecordParkedMetrics(content::Page& page) {
+ auto main_frame_id = page.GetMainDocument().GetGlobalId();
+ for (auto it = parked_metrics_before_main_navigation_.cbegin();
+ it != parked_metrics_before_main_navigation_.cend();) {
+ auto* rfh = content::RenderFrameHost::FromID(it->first);
+ // Check if the parked entry represents a dead RFH, if so erase it
+ if (!rfh) {
+ it = parked_metrics_before_main_navigation_.erase(it);
+ continue;
+ }
+ // If this is the entry matching Page we are looking for...
+ if (main_frame_id == it->first) {
+ // ...and it contains some parked metrics...
+ if (!it->second.empty()) {
+ //...then record them
+ auto* page_info = EyeoPageInfo::GetOrCreateForPage(page);
+ ScopedDictPrefUpdate update(prefs_,
+ common::prefs::kTelemetryPageViewStats);
+ for (auto parked_metric : it->second) {
+ auto parked_metric_key = GetReportedNameForMetric(parked_metric);
+ const auto current_count_for_parked =
+ update->FindInt(parked_metric_key);
+ update->Set(parked_metric_key,
+ current_count_for_parked.value_or(0) + 1);
+ // Now with "final" EyeoPageInfo we can mark metric as recorded
+ page_info->SetMatchedPageView(parked_metric);
+ }
+ }
+ it = parked_metrics_before_main_navigation_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void PageViewStats::RecordPageView(content::Page& page, Metric metric) {
+ auto dict_child_key = GetReportedNameForMetric(metric);
+ auto* page_info = EyeoPageInfo::GetOrCreateForPage(page);
+ if (!IsNavigationCommittingNow(metric) &&
+ !WasNavigationCommitted(metric, page_info)) {
+ ParkMetric(page, metric);
+ return;
+ }
+ // We don't count stats metrics for individual requests but for a whole page.
+ // If this is the first request matched a metric for this page, we increment
+ // the counter. We store previous matches in EyeoPageInfo, so we can check if
+ // this is the first metric match for this page.
+ if (!page_info->SetMatchedPageView(metric)) {
+ // metric was already counted
+ return;
+ }
+ ScopedDictPrefUpdate update(prefs_, common::prefs::kTelemetryPageViewStats);
+ const auto current_count = update->FindInt(dict_child_key);
+ update->Set(dict_child_key, current_count.value_or(0) + 1);
+ // Check parked metrics and record
+ if (WasNavigationCommitted(metric, page_info)) {
+ RecordParkedMetrics(page);
+ }
+}
+
+int PageViewStats::GetPageViewsCount(Metric metric) const {
+ auto dict_child_key = GetReportedNameForMetric(metric);
+ const base::Value::Dict& dict =
+ prefs_->GetDict(common::prefs::kTelemetryPageViewStats);
+ const auto current_count = dict.FindInt(dict_child_key);
+ return current_count.value_or(0);
+}
+
+void PageViewStats::ResetPageViewsCount(Metric metric) {
+ auto dict_child_key = GetReportedNameForMetric(metric);
+ ScopedDictPrefUpdate update(prefs_, common::prefs::kTelemetryPageViewStats);
+ update->Set(dict_child_key, 0);
+}
+
+base::RepeatingCallback<void(content::RenderFrameHost*)>
+CountNavigationsCallback() {
+ return base::BindRepeating(&RegisterNavigationWithLastUsedPageViewStats);
+}
+
+base::RepeatingCallback<void(content::RenderFrameHost*)>
+CountAcceptableAdsBlockthrougCallback() {
+ return base::BindRepeating(
+ &RegisterAcceptableAdsBlockthroughtHitWithLastUsedPageViewStats);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/page_view_stats.h b/components/adblock/content/browser/page_view_stats.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/page_view_stats.h
@@ -0,0 +1,131 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_PAGE_VIEW_STATS_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_PAGE_VIEW_STATS_H_
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/page.h"
+#include "content/public/browser/render_frame_host.h"
+#include "partition_alloc/pointers/raw_ptr.h"
+
+namespace adblock {
+
+// Collects anonymous statistics about the frequency of showing acceptable ads.
+// Used for evaluating the effectiveness of the Acceptable Ads program.
+class PageViewStats final
+ : public ResourceClassificationRunner::Observer,
+ public ActivepingTelemetryTopicProvider::StatsPayloadProvider {
+ public:
+ enum Metric {
+ AcceptableAds,
+ AcceptableAdsBlockThrough,
+ Allowing,
+ Blocking,
+ TotalPages
+ };
+
+ PageViewStats(ResourceClassificationRunner* classification_runner,
+ PrefService* prefs);
+
+ ~PageViewStats() final;
+
+ // ResourceClassificationRunner::Observer:
+ void OnRequestMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final;
+
+ // OnPageAllowed is redundant with respect to OnRequestMatched, so we can
+ // ignore it.
+ void OnPageAllowed(const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final {}
+
+ void OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final;
+
+ // ActivepingTelemetryTopicProvider::StatsPayloadProvider:
+ base::Value::Dict GetPayload() const final;
+ void ResetStats() final;
+
+ // Counts a page view, whether it's an AA page view or not. Increments the
+ // total count.
+ void RegisterMainFrameNavigation(content::RenderFrameHost* render_frame_host);
+
+ void RegisterAcceptableAdsBlockthroughtHit(
+ content::RenderFrameHost* render_frame_host);
+
+ private:
+ void OnMatchedInternal(const GURL& url,
+ FilterMatchResult match_result,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription);
+ // Increments count for given metric from 0 to 1 for current page, if already
+ // 1 then noop.
+ void RecordPageView(content::Page& page, Metric metric);
+ // We need to park metric before main navigation is committed because we need
+ // wait for a "final" EyeoPageInfo object. For some unknown reason for the
+ // very same RFH or Page object UserData (Document or Page) is reset when
+ // navigation commits. So when page starts to load subresources before
+ // navigation is committed it uses one UserData object, and then after
+ // navigation is comitted there is a new empty UserData created. And by that
+ // we lose tracking which metrics were already counted and count too much for
+ // the same page. So when main navigation is commited we can go though parked
+ // metrics and record them.
+ void ParkMetric(content::Page& page, Metric metric);
+ void RecordParkedMetrics(content::Page& page);
+ int GetPageViewsCount(Metric metric) const;
+ void ResetPageViewsCount(Metric metric);
+
+ raw_ptr<ResourceClassificationRunner> classification_runner_;
+ raw_ptr<PrefService> prefs_;
+ const GURL endpoint_url_;
+ std::map<content::GlobalRenderFrameHostId, std::set<Metric>>
+ parked_metrics_before_main_navigation_;
+ base::WeakPtrFactory<PageViewStats> weak_factory_{this};
+};
+
+// Returns a closures, calling which will increment the total page view count or
+// the Acceptable Ads Blockthrough count respectively in the last used
+// PageViewStats instance. NOP if there's no such instance. This is a simpler
+// way to expose access to PageViewStats to external callers than to convert it
+// to a KeyedService.
+base::RepeatingCallback<void(content::RenderFrameHost*)>
+CountNavigationsCallback();
+base::RepeatingCallback<void(content::RenderFrameHost*)>
+CountAcceptableAdsBlockthrougCallback();
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_PAGE_VIEW_STATS_H_
diff --git a/components/adblock/content/browser/request_initiator.cc b/components/adblock/content/browser/request_initiator.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/request_initiator.cc
@@ -0,0 +1,58 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/request_initiator.h"
+
+#include "base/check.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace adblock {
+
+RequestInitiator::RequestInitiator(content::RenderFrameHost* render_frame_host)
+ : initiator_(render_frame_host->GetGlobalId()) {}
+
+RequestInitiator::RequestInitiator(GURL initiator_url)
+ : initiator_(std::move(initiator_url)) {}
+
+RequestInitiator::~RequestInitiator() = default;
+
+RequestInitiator::RequestInitiator(const RequestInitiator& other) = default;
+
+RequestInitiator& RequestInitiator::operator=(const RequestInitiator& other) =
+ default;
+
+RequestInitiator::RequestInitiator(RequestInitiator&& other) = default;
+
+RequestInitiator& RequestInitiator::operator=(RequestInitiator&& other) =
+ default;
+
+bool RequestInitiator::IsFrame() const {
+ return absl::holds_alternative<content::GlobalRenderFrameHostId>(initiator_);
+}
+
+content::RenderFrameHost* RequestInitiator::GetRenderFrameHost() const {
+ DCHECK(IsFrame());
+ return content::RenderFrameHost::FromID(
+ absl::get<content::GlobalRenderFrameHostId>(initiator_));
+}
+
+const GURL& RequestInitiator::GetInitiatorUrl() const {
+ DCHECK(!IsFrame());
+ return absl::get<GURL>(initiator_);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/request_initiator.h b/components/adblock/content/browser/request_initiator.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/request_initiator.h
@@ -0,0 +1,69 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_REQUEST_INITIATOR_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_REQUEST_INITIATOR_H_
+
+#include "absl/types/variant.h"
+#include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/render_frame_host.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// RequestInitiator is used to create a frame hierarchy for allowlisting that's
+// relevant for a network request. It's the GlobalRenderFrameHostId that
+// represents a frame/page that made the request, or the initiator URL of a
+// request triggered from a detached service worker.
+class RequestInitiator {
+ public:
+ // Appropriate for requests triggered from a frame, like a website loading an
+ // image, or an iframe loading a script.
+ explicit RequestInitiator(content::RenderFrameHost* render_frame_host);
+
+ // Appropriate for requests triggered from a detached service worker. The
+ // initiator URL is the URL of the service worker script. There is no
+ // associated frame, a single service worker script can make requests on
+ // behalf of multiple frames or even tabs.
+ // |initiator_url| must be valid.
+ explicit RequestInitiator(GURL initiator_url);
+
+ ~RequestInitiator();
+ RequestInitiator(const RequestInitiator&);
+ RequestInitiator& operator=(const RequestInitiator&);
+ RequestInitiator(RequestInitiator&&);
+ RequestInitiator& operator=(RequestInitiator&&);
+
+ bool operator==(const RequestInitiator& other) const = default;
+
+ // Returns true if the initiator is a frame, false if it's a URL.
+ bool IsFrame() const;
+
+ // Asserts IsFrame().
+ // The returned pointer may be null if the frame has been destroyed.
+ content::RenderFrameHost* GetRenderFrameHost() const;
+
+ // Asserts !IsFrame()
+ const GURL& GetInitiatorUrl() const;
+
+ private:
+ absl::variant<content::GlobalRenderFrameHostId, GURL> initiator_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_REQUEST_INITIATOR_H_
diff --git a/components/adblock/content/browser/resource_classification_runner.h b/components/adblock/content/browser/resource_classification_runner.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/resource_classification_runner.h
@@ -0,0 +1,107 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/observer_list.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "services/network/public/mojom/network_param.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+class RenderFrameHost;
+} // namespace content
+namespace adblock {
+
+/**
+ * @brief Declares whether network requests should be blocked or allowed to
+ * load by comparing their URLs and other metadata against filters defined
+ * in active subscriptions.
+ * Lives in the UI thread.
+ */
+class ResourceClassificationRunner : public KeyedService {
+ public:
+ class Observer : public base::CheckedObserver {
+ public:
+ // Will only be called when |match_result| is BLOCK_RULE or ALLOW_RULE.
+ virtual void OnRequestMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) = 0;
+ virtual void OnPageAllowed(const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) = 0;
+ virtual void OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) = 0;
+ };
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // Performs a *synchronous* check, this can block the UI for a while!
+ virtual FilterMatchResult ShouldBlockPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost* render_frame_host) = 0;
+ virtual void CheckPopupFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost& render_frame_host,
+ CheckFilterMatchCallback callback) = 0;
+ virtual void CheckRequestFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ CheckFilterMatchCallback callback) = 0;
+ // No callback, just notify observers
+ virtual void CheckDocumentAllowlisted(
+ SubscriptionService::Snapshot subscription_collection,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator) = 0;
+ virtual void CheckResponseFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& response_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ CheckFilterMatchCallback callback) = 0;
+ virtual void CheckRewriteFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ base::OnceCallback<void(const absl::optional<GURL>&)> result) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_
diff --git a/components/adblock/content/browser/resource_classification_runner_impl.cc b/components/adblock/content/browser/resource_classification_runner_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/resource_classification_runner_impl.cc
@@ -0,0 +1,431 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/resource_classification_runner_impl.h"
+
+#include "base/functional/bind.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/content/browser/frame_opener_info.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace adblock {
+
+using ClassificationDecision =
+ ResourceClassifier::ClassificationResult::Decision;
+
+ResourceClassificationRunnerImpl::ResourceClassificationRunnerImpl(
+ scoped_refptr<ResourceClassifier> resource_classifier,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder,
+ SitekeyStorage* sitekey_storage)
+ : resource_classifier_(std::move(resource_classifier)),
+ frame_hierarchy_builder_(std::move(frame_hierarchy_builder)),
+ sitekey_storage_(sitekey_storage),
+ weak_ptr_factory_(this) {}
+
+ResourceClassificationRunnerImpl::~ResourceClassificationRunnerImpl() = default;
+
+void ResourceClassificationRunnerImpl::AddObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.AddObserver(observer);
+}
+
+void ResourceClassificationRunnerImpl::RemoveObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ observers_.RemoveObserver(observer);
+}
+
+void ResourceClassificationRunnerImpl::OnCheckPopupFilterMatchComplete(
+ const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ absl::optional<CheckFilterMatchCallback> callback,
+ const ResourceClassifier::ClassificationResult& classification_result) {
+ if (classification_result.decision != ClassificationDecision::Ignored) {
+ FilterMatchResult result =
+ classification_result.decision == ClassificationDecision::Allowed
+ ? FilterMatchResult::kAllowRule
+ : FilterMatchResult::kBlockRule;
+ if (callback) {
+ std::move(*callback).Run(result);
+ }
+ auto* frame_host = content::RenderFrameHost::FromID(render_frame_host_id);
+ if (frame_host) {
+ const auto& opener_url =
+ frame_hierarchy.empty() ? GURL() : frame_hierarchy.front();
+ if (result == FilterMatchResult::kBlockRule) {
+ VLOG(1) << "[eyeo] Prevented loading of pop-up " << popup_url.spec()
+ << ", opener: " << opener_url.spec();
+ } else {
+ VLOG(1) << "[eyeo] Pop-up allowed " << popup_url.spec()
+ << ", opener: " << opener_url.spec();
+ }
+ for (auto& observer : observers_) {
+ observer.OnPopupMatched(
+ popup_url, result, opener_url, frame_host,
+ classification_result.decisive_subscription,
+ classification_result.decisive_configuration_name);
+ }
+ }
+ } else if (callback) {
+ std::move(*callback).Run(FilterMatchResult::kNoRule);
+ }
+}
+
+FilterMatchResult ResourceClassificationRunnerImpl::ShouldBlockPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost* frame_host) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(1) << "[eyeo] ShouldBlockPopup for " << popup_url.spec();
+ DCHECK(frame_host);
+ TRACE_EVENT1("eyeo", "ResourceClassificationRunnerImpl::ShouldBlockPopup",
+ "popup_url", popup_url.spec());
+
+ const auto frame_hierarchy = frame_hierarchy_builder_->BuildFrameHierarchy(
+ RequestInitiator(frame_host));
+ const auto sitekey = sitekey_storage_->FindSiteKeyForAnyUrl(frame_hierarchy);
+
+ auto classification_result = resource_classifier_->ClassifyPopup(
+ subscription_collections, popup_url, frame_hierarchy,
+ sitekey ? sitekey->second : SiteKey());
+ if (classification_result.decision == ClassificationDecision::Ignored) {
+ return FilterMatchResult::kNoRule;
+ }
+ OnCheckPopupFilterMatchComplete(popup_url, frame_hierarchy,
+ frame_host->GetGlobalId(), absl::nullopt,
+ classification_result);
+ return classification_result.decision == ClassificationDecision::Allowed
+ ? FilterMatchResult::kAllowRule
+ : FilterMatchResult::kBlockRule;
+}
+
+void ResourceClassificationRunnerImpl::CheckPopupFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost& render_frame_host,
+ CheckFilterMatchCallback callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(1) << "[eyeo] CheckPopupFilterMatch for " << popup_url.spec();
+
+ auto* wc = content::WebContents::FromRenderFrameHost(&render_frame_host);
+ DCHECK(wc);
+ auto* info = FrameOpenerInfo::FromWebContents(wc);
+ DCHECK(info);
+ auto* frame_host_opener = content::RenderFrameHost::FromID(info->GetOpener());
+ if (!frame_host_opener) {
+ // We are unable to check allowlisting
+ VLOG(1) << "[eyeo] CheckPopupFilterMatch for " << popup_url.spec()
+ << " no frame host for opener!";
+ std::move(callback).Run(FilterMatchResult::kNoRule);
+ return;
+ }
+
+ const auto frame_hierarchy = frame_hierarchy_builder_->BuildFrameHierarchy(
+ RequestInitiator(frame_host_opener));
+ auto sitekey = sitekey_storage_->FindSiteKeyForAnyUrl(frame_hierarchy);
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(&ResourceClassifier::ClassifyPopup, resource_classifier_,
+ std::move(subscription_collections), popup_url,
+ frame_hierarchy, sitekey ? sitekey->second : SiteKey()),
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::OnCheckPopupFilterMatchComplete,
+ weak_ptr_factory_.GetWeakPtr(), popup_url, frame_hierarchy,
+ render_frame_host.GetGlobalId(), std::move(callback)));
+}
+
+void ResourceClassificationRunnerImpl::CheckDocumentAllowlisted(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator) {
+ // We pass main frame no matter what
+ DVLOG(1) << "[eyeo] Main document. Passing it through: " << request_url;
+ auto* render_frame_host = request_initiator.GetRenderFrameHost();
+ DCHECK(render_frame_host);
+ if (!render_frame_host) {
+ // The frame has been destroyed, so we can't notify observers.
+ return;
+ }
+
+ auto site_key_pair = sitekey_storage_->FindSiteKeyForAnyUrl(
+ frame_hierarchy_builder_->BuildFrameHierarchy(request_initiator));
+ SiteKey site_key;
+ if (site_key_pair.has_value()) {
+ site_key = site_key_pair->second;
+ DVLOG(1) << "[eyeo] Found site key: " << site_key.value()
+ << " for url: " << site_key_pair->first;
+ }
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(&CheckDocumentAllowlistedInternal,
+ std::move(subscription_collections), request_url,
+ std::move(site_key)),
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::ProcessDocumentAllowlistedResponse,
+ weak_ptr_factory_.GetWeakPtr(), request_url,
+ render_frame_host->GetGlobalId()));
+}
+
+void ResourceClassificationRunnerImpl::ProcessDocumentAllowlistedResponse(
+ const GURL request_url,
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ CheckResourceFilterMatchResult result) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ content::RenderFrameHost* host =
+ content::RenderFrameHost::FromID(render_frame_host_id);
+ if (result.status == FilterMatchResult::kAllowRule && host) {
+ VLOG(1) << "[eyeo] Calling OnPageAllowed() for " << request_url;
+ for (auto& observer : observers_) {
+ observer.OnPageAllowed(request_url, host, result.subscription,
+ result.configuration_name);
+ }
+ }
+}
+
+void ResourceClassificationRunnerImpl::CheckRequestFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ CheckFilterMatchCallback callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(1) << "[eyeo] CheckRequestFilterMatchImpl for " << request_url.spec();
+
+ const auto frame_hierarchy =
+ frame_hierarchy_builder_->BuildFrameHierarchy(request_initiator);
+
+ DVLOG(1) << "[eyeo] Got " << frame_hierarchy.size() << " frame_hierarchy for "
+ << request_url.spec();
+
+ auto site_key_pair = sitekey_storage_->FindSiteKeyForAnyUrl(frame_hierarchy);
+ SiteKey site_key;
+ if (site_key_pair.has_value()) {
+ site_key = site_key_pair->second;
+ DVLOG(1) << "[eyeo] Found site key: " << site_key.value()
+ << " for url: " << site_key_pair->first;
+ }
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal,
+ resource_classifier_, std::move(subscription_collections),
+ request_url, frame_hierarchy, adblock_resource_type,
+ std::move(site_key)),
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::OnCheckResourceFilterMatchComplete,
+ weak_ptr_factory_.GetWeakPtr(), request_url, frame_hierarchy,
+ adblock_resource_type, request_initiator, std::move(callback)));
+}
+
+// static
+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult
+ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal(
+ const scoped_refptr<ResourceClassifier>& resource_classifier,
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const SiteKey sitekey) {
+ TRACE_EVENT1("eyeo",
+ "ResourceClassificationRunnerImpl::"
+ "CheckRequestFilterMatchInternal",
+ "url", request_url.spec());
+
+ DVLOG(1) << "[eyeo] CheckRequestFilterMatchInternal start";
+
+ auto classification_result = resource_classifier->ClassifyRequest(
+ std::move(subscription_collections), request_url, frame_hierarchy,
+ adblock_resource_type, sitekey);
+
+ if (classification_result.decision == ClassificationDecision::Allowed) {
+ VLOG(1) << "[eyeo] Resource allowed due to allowing filter " << request_url;
+ return CheckResourceFilterMatchResult{
+ FilterMatchResult::kAllowRule,
+ classification_result.decisive_subscription,
+ classification_result.decisive_configuration_name};
+ }
+
+ if (classification_result.decision == ClassificationDecision::Blocked) {
+ VLOG(1) << "[eyeo] Resource blocked " << request_url;
+ return CheckResourceFilterMatchResult{
+ FilterMatchResult::kBlockRule,
+ classification_result.decisive_subscription,
+ classification_result.decisive_configuration_name};
+ }
+
+ return CheckResourceFilterMatchResult{FilterMatchResult::kNoRule, {}, {}};
+}
+
+void ResourceClassificationRunnerImpl::OnCheckResourceFilterMatchComplete(
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ CheckFilterMatchCallback callback,
+ const CheckResourceFilterMatchResult result) {
+ // Notify |callback| as soon as we know whether we should block, as this
+ // unblocks loading of network resources.
+ std::move(callback).Run(result.status);
+ // Only notify the UI if we explicitly blocked or allowed the resource, not
+ // when there was NO_RULE.
+ if (result.status == FilterMatchResult::kAllowRule ||
+ result.status == FilterMatchResult::kBlockRule) {
+ NotifyResourceMatched(request_url, result.status, frame_hierarchy,
+ adblock_resource_type, request_initiator,
+ result.subscription, result.configuration_name);
+ }
+}
+
+void ResourceClassificationRunnerImpl::NotifyResourceMatched(
+ const GURL& url,
+ FilterMatchResult result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ const RequestInitiator& request_initiator,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ VLOG(1) << "[eyeo] NotifyResourceMatched() called for " << url;
+ if (!request_initiator.IsFrame()) {
+ // We don't have a frame to notify about, so we can't notify observers.
+ return;
+ }
+ auto* render_frame_host = request_initiator.GetRenderFrameHost();
+ if (!render_frame_host) {
+ // The frame has been destroyed, so we can't notify observers.
+ return;
+ }
+
+ for (auto& observer : observers_) {
+ observer.OnRequestMatched(
+ url, result, parent_frame_urls, static_cast<ContentType>(content_type),
+ render_frame_host, subscription, configuration_name);
+ }
+}
+
+// static
+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult
+ResourceClassificationRunnerImpl::CheckDocumentAllowlistedInternal(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const SiteKey sitekey) {
+ CheckResourceFilterMatchResult result{FilterMatchResult::kNoRule, {}, {}};
+ // It is required for all configurations to have an allowing Document filter
+ // to consider a page allowlisted.
+ for (const auto& collection : subscription_collections) {
+ auto subscription_url = collection->FindBySpecialFilter(
+ SpecialFilterType::Document, request_url, std::vector<GURL>(), sitekey);
+ if (!subscription_url) {
+ return {FilterMatchResult::kNoRule, {}, {}};
+ } else {
+ result = {FilterMatchResult::kAllowRule, subscription_url.value(),
+ collection->GetFilteringConfigurationName()};
+ }
+ }
+ return result;
+}
+
+void ResourceClassificationRunnerImpl::CheckResponseFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& response_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ CheckFilterMatchCallback callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DVLOG(1) << "[eyeo] CheckResponseFilterMatch for " << response_url.spec();
+
+ const auto frame_hierarchy =
+ frame_hierarchy_builder_->BuildFrameHierarchy(request_initiator);
+ // ResponseFilterMatch might take a while, let it run in the background.
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::CheckResponseFilterMatchInternal,
+ resource_classifier_, std::move(subscription_collections),
+ response_url, frame_hierarchy, adblock_resource_type,
+ std::move(headers)),
+ base::BindOnce(
+ &ResourceClassificationRunnerImpl::OnCheckResourceFilterMatchComplete,
+ weak_ptr_factory_.GetWeakPtr(), response_url, frame_hierarchy,
+ adblock_resource_type, request_initiator, std::move(callback)));
+}
+
+// static
+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult
+ResourceClassificationRunnerImpl::CheckResponseFilterMatchInternal(
+ const scoped_refptr<ResourceClassifier> resource_classifier,
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL response_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const scoped_refptr<net::HttpResponseHeaders> response_headers) {
+ auto classification_result = resource_classifier->ClassifyResponse(
+ std::move(subscription_collections), response_url, frame_hierarchy,
+ adblock_resource_type, response_headers);
+
+ if (classification_result.decision == ClassificationDecision::Allowed) {
+ VLOG(1) << "[eyeo] Resource allowed due to allowing filter "
+ << response_url;
+ return CheckResourceFilterMatchResult{
+ FilterMatchResult::kAllowRule,
+ classification_result.decisive_subscription,
+ classification_result.decisive_configuration_name};
+ }
+
+ if (classification_result.decision == ClassificationDecision::Blocked) {
+ VLOG(1) << "[eyeo] Resource blocked " << response_url;
+ return CheckResourceFilterMatchResult{
+ FilterMatchResult::kBlockRule,
+ classification_result.decisive_subscription,
+ classification_result.decisive_configuration_name};
+ }
+
+ return CheckResourceFilterMatchResult{FilterMatchResult::kNoRule, {}, {}};
+}
+
+void ResourceClassificationRunnerImpl::CheckRewriteFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ base::OnceCallback<void(const absl::optional<GURL>&)> callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ DVLOG(1) << "[eyeo] CheckRewriteFilterMatch for " << request_url.spec();
+
+ const auto frame_hierarchy =
+ frame_hierarchy_builder_->BuildFrameHierarchy(request_initiator);
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {},
+ base::BindOnce(&ResourceClassifier::CheckRewrite, resource_classifier_,
+ std::move(subscription_collections), request_url,
+ frame_hierarchy),
+ std::move(callback));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/resource_classification_runner_impl.h b/components/adblock/content/browser/resource_classification_runner_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/resource_classification_runner_impl.h
@@ -0,0 +1,153 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/classifier/resource_classifier.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/sitekey_storage.h"
+#include "content/public/browser/global_routing_id.h"
+
+namespace adblock {
+
+class ResourceClassificationRunnerImpl final
+ : public ResourceClassificationRunner {
+ public:
+ ResourceClassificationRunnerImpl(
+ scoped_refptr<ResourceClassifier> resource_classifier,
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder,
+ SitekeyStorage* sitekey_storage);
+ ~ResourceClassificationRunnerImpl() final;
+
+ void AddObserver(Observer* observer) final;
+ void RemoveObserver(Observer* observer) final;
+
+ // Performs a *synchronous* check, this can block the UI for a while!
+ FilterMatchResult ShouldBlockPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost* render_frame_host) final;
+ void CheckPopupFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& popup_url,
+ content::RenderFrameHost& render_frame_host,
+ CheckFilterMatchCallback callback) final;
+ void CheckRequestFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ CheckFilterMatchCallback callback) final;
+ // No callback, just notify observers
+ void CheckDocumentAllowlisted(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator) final;
+ void CheckResponseFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& response_url,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ CheckFilterMatchCallback callback) final;
+ void CheckRewriteFilterMatch(
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator,
+ base::OnceCallback<void(const absl::optional<GURL>&)> result) final;
+
+ private:
+ struct CheckResourceFilterMatchResult {
+ FilterMatchResult status;
+ GURL subscription;
+ std::string configuration_name;
+ };
+
+ static CheckResourceFilterMatchResult CheckRequestFilterMatchInternal(
+ const scoped_refptr<ResourceClassifier>& resource_classifier,
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const SiteKey sitekey);
+
+ void OnCheckResourceFilterMatchComplete(
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const RequestInitiator& request_initiator,
+ CheckFilterMatchCallback callback,
+ const CheckResourceFilterMatchResult result);
+
+ void OnCheckPopupFilterMatchComplete(
+ const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ absl::optional<CheckFilterMatchCallback> callback,
+ const ResourceClassifier::ClassificationResult& result);
+
+ static CheckResourceFilterMatchResult CheckResponseFilterMatchInternal(
+ const scoped_refptr<ResourceClassifier> resource_classifier,
+ SubscriptionService::Snapshot subscription_collections,
+ const GURL response_url,
+ const std::vector<GURL> frame_hierarchy,
+ ContentType adblock_resource_type,
+ const scoped_refptr<net::HttpResponseHeaders> response_headers);
+
+ void NotifyResourceMatched(const GURL& url,
+ FilterMatchResult result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ const RequestInitiator& request_initiator,
+ const GURL& subscription,
+ const std::string& configuration_name);
+
+ void PostFilterMatchCallbackToUI(CheckFilterMatchCallback callback,
+ FilterMatchResult result);
+
+ void PostRewriteCallbackToUI(
+ base::OnceCallback<void(const absl::optional<GURL>&)> callback,
+ absl::optional<GURL> url);
+
+ static CheckResourceFilterMatchResult CheckDocumentAllowlistedInternal(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const SiteKey sitekey);
+
+ void ProcessDocumentAllowlistedResponse(
+ const GURL request_url,
+ content::GlobalRenderFrameHostId render_frame_host_id,
+ CheckResourceFilterMatchResult result);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ scoped_refptr<ResourceClassifier> resource_classifier_;
+ std::unique_ptr<FrameHierarchyBuilder> frame_hierarchy_builder_;
+ raw_ptr<SitekeyStorage> sitekey_storage_;
+ base::ObserverList<Observer> observers_;
+ base::WeakPtrFactory<ResourceClassificationRunnerImpl> weak_ptr_factory_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_
diff --git a/components/adblock/content/browser/session_stats_impl.cc b/components/adblock/content/browser/session_stats_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/session_stats_impl.cc
@@ -0,0 +1,87 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/session_stats_impl.h"
+
+#include "components/adblock/core/common/adblock_constants.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace adblock {
+
+SessionStatsImpl::SessionStatsImpl(
+ ResourceClassificationRunner* classification_runner)
+ : classification_runner_(classification_runner) {
+ DCHECK(classification_runner_);
+ classification_runner_->AddObserver(this);
+}
+
+SessionStatsImpl::~SessionStatsImpl() {
+ classification_runner_->RemoveObserver(this);
+}
+
+std::map<GURL, long> SessionStatsImpl::GetSessionAllowedResourcesCount() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return allowed_map_;
+}
+
+std::map<GURL, long> SessionStatsImpl::GetSessionBlockedResourcesCount() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return blocked_map_;
+}
+
+void SessionStatsImpl::OnRequestMatched(
+ const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ OnMatchedInternal(match_result, subscription);
+}
+
+void SessionStatsImpl::OnPageAllowed(
+ const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ OnMatchedInternal(FilterMatchResult::kAllowRule, subscription);
+}
+
+void SessionStatsImpl::OnPopupMatched(
+ const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ OnMatchedInternal(match_result, subscription);
+}
+
+void SessionStatsImpl::OnMatchedInternal(FilterMatchResult match_result,
+ const GURL& subscription) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!subscription.is_empty());
+ if (match_result == adblock::FilterMatchResult::kBlockRule) {
+ blocked_map_[subscription]++;
+ } else {
+ DCHECK(match_result == adblock::FilterMatchResult::kAllowRule);
+ allowed_map_[subscription]++;
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/session_stats_impl.h b/components/adblock/content/browser/session_stats_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/session_stats_impl.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_SESSION_STATS_IMPL_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_SESSION_STATS_IMPL_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/session_stats.h"
+
+namespace adblock {
+
+class SessionStatsImpl final : public SessionStats,
+ public ResourceClassificationRunner::Observer {
+ public:
+ explicit SessionStatsImpl(
+ ResourceClassificationRunner* classification_runner);
+
+ ~SessionStatsImpl() final;
+
+ std::map<GURL, long> GetSessionAllowedResourcesCount() const final;
+
+ std::map<GURL, long> GetSessionBlockedResourcesCount() const final;
+
+ // ResourceClassificationRunner::Observer:
+ void OnRequestMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final;
+ void OnPageAllowed(const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final;
+ void OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) final;
+
+ private:
+ void OnMatchedInternal(FilterMatchResult match_result,
+ const GURL& subscription);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ raw_ptr<ResourceClassificationRunner> classification_runner_;
+ std::map<GURL, long> allowed_map_;
+ std::map<GURL, long> blocked_map_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_SESSION_STATS_IMPL_H_
diff --git a/components/adblock/content/browser/test/adblock_acceptable_ads_browsertest.cc b/components/adblock/content/browser/test/adblock_acceptable_ads_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_acceptable_ads_browsertest.cc
@@ -0,0 +1,211 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockAcceptableAdsTest
+ : public AdblockBrowserTestBase,
+ public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
+ public:
+ AdblockAcceptableAdsTest()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ setenv("LANGUAGE", "en_US", 1);
+#endif
+ https_server_.RegisterRequestHandler(base::BindRepeating(
+ &AdblockAcceptableAdsTest::RequestHandler, base::Unretained(this)));
+ net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+ cert_config.dns_names = {"easylist-downloads.adblockplus.org", kTestDomain};
+ https_server_.SetSSLConfig(cert_config);
+ EXPECT_TRUE(https_server_.Start());
+ SetFilterListServerPortForTesting(https_server_.port());
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("easylist-downloads.adblockplus.org", "127.0.0.1");
+ host_resolver()->AddRule(kTestDomain, "127.0.0.1");
+ if (DomainAllowlisted()) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration);
+ adblock_configuration->AddAllowedDomain(kTestDomain);
+ }
+ InitResourceClassificationObserver();
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ if (!AcceptableAdsEnabled()) {
+ command_line->AppendSwitch(switches::kDisableAcceptableAds);
+ }
+ if (IncognitoMode()) {
+ command_line->AppendSwitch("incognito");
+ }
+ }
+
+ void WaitUntilSubscriptionsInstalled() {
+ std::vector<GURL> subscriptions = {DefaultSubscriptionUrl()};
+ if (AcceptableAdsEnabled()) {
+ subscriptions.emplace_back(AcceptableAdsUrl());
+ }
+ auto waiter = GetSubscriptionInstalledWaiter();
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ virtual std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (request.GetURL().path() == AcceptableAdsUrl().path()) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(
+ "[Adblock Plus 2.0]\n\n"
+ "@@iframe_image.png");
+ return std::move(http_response);
+ } else if (request.GetURL().path() == DefaultSubscriptionUrl().path()) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(
+ "[Adblock Plus 2.0]\n\n"
+ "iframe_image.png");
+ http_response->set_content_type("text/plain");
+ return std::move(http_response);
+ } else if (base::StartsWith(request.relative_url, "/test_page.html")) {
+ static constexpr char kMainFrame[] =
+ R"(
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <iframe src="sitekey_iframe.html?query"></iframe>
+ </body>
+ </html>)";
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(kMainFrame);
+ http_response->set_content_type("text/html");
+ return std::move(http_response);
+ } else if (base::StartsWith(request.relative_url, "/sitekey_iframe.html")) {
+ static constexpr char kIframe[] =
+ R"(
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <img src="/iframe_image.png" />
+ </body>
+ </html>)";
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(kIframe);
+ http_response->set_content_type("text/html");
+ return std::move(http_response);
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404. This
+ // is fine for the purpose of this test.
+ return nullptr;
+ }
+
+ GURL GetPageUrl(const std::string& path = "/test_page.html") {
+ return https_server_.GetURL(kTestDomain, path);
+ }
+
+ void NavigateToPage() {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ }
+
+ void VerifyExpectedNotifications() {
+ if (AcceptableAdsEnabled() || DomainAllowlisted()) {
+ ASSERT_EQ(observer_.allowed_ads_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.allowed_ads_notifications.front() ==
+ GetPageUrl("/iframe_image.png"))
+ << "Request not allowed!";
+ if (DomainAllowlisted()) {
+ ASSERT_EQ(observer_.allowed_pages_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.allowed_pages_notifications.front() ==
+ GetPageUrl())
+ << "Page not allowed!";
+ }
+ } else {
+ ASSERT_EQ(observer_.blocked_ads_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.blocked_ads_notifications.front() ==
+ GetPageUrl("/iframe_image.png"))
+ << "Request not blocked!";
+ EXPECT_TRUE(observer_.allowed_ads_notifications.empty());
+ EXPECT_TRUE(observer_.allowed_pages_notifications.empty());
+ }
+ }
+
+ bool AcceptableAdsEnabled() { return std::get<0>(GetParam()); }
+
+ bool DomainAllowlisted() { return std::get<1>(GetParam()); }
+
+ bool IncognitoMode() { return std::get<2>(GetParam()); }
+
+ private:
+ net::EmbeddedTestServer https_server_;
+ static constexpr char kTestDomain[] = "test.org";
+};
+
+IN_PROC_BROWSER_TEST_P(AdblockAcceptableAdsTest, VerifyAcceptableAds) {
+ LOG(INFO) << "AA on: " << AcceptableAdsEnabled();
+ LOG(INFO) << "Domain allowed: " << DomainAllowlisted();
+ LOG(INFO) << "Incognito: " << IncognitoMode();
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration);
+ auto subscriptions = adblock_configuration->GetFilterLists();
+ // This remove/add dance is required to avoid race when we are not sure
+ // if subscriptions were already installed or not. It's difficult to set
+ // SubscriptionObserver for built-in subscriptions in right time during
+ // test setup so we do it here and by Remove() then Add() we trigger
+ // filter lists installations which we then observe and block until done.
+ for (const auto& subscription : subscriptions) {
+ adblock_configuration->RemoveFilterList(subscription);
+ adblock_configuration->AddFilterList(subscription);
+ }
+ WaitUntilSubscriptionsInstalled();
+ NavigateToPage();
+ VerifyExpectedNotifications();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ AdblockAcceptableAdsTest,
+ testing::Combine(/* AA on/off */ testing::Bool(),
+ /* Allowlist domain */ testing::Bool(),
+ /* Incognito on/off */ testing::Bool()));
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_browsertest_base.cc b/components/adblock/content/browser/test/adblock_browsertest_base.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_browsertest_base.cc
@@ -0,0 +1,219 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+
+#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/net/adblock_request_throttle.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+
+namespace adblock {
+
+TestResourceClassificationRunnerObserver::
+ TestResourceClassificationRunnerObserver() = default;
+
+TestResourceClassificationRunnerObserver::
+ ~TestResourceClassificationRunnerObserver() = default;
+
+void TestResourceClassificationRunnerObserver::OnRequestMatched(
+ const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ if (match_result == FilterMatchResult::kAllowRule) {
+ allowed_ads_notifications.push_back(url);
+ } else {
+ blocked_ads_notifications.push_back(url);
+ }
+}
+
+void TestResourceClassificationRunnerObserver::OnPageAllowed(
+ const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ allowed_pages_notifications.push_back(url);
+}
+
+void TestResourceClassificationRunnerObserver::OnPopupMatched(
+ const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) {
+ if (match_result == FilterMatchResult::kAllowRule) {
+ allowed_popups_notifications.push_back(url);
+ } else {
+ blocked_popups_notifications.push_back(url);
+ }
+}
+
+SubscriptionInstalledWaiter::SubscriptionInstalledWaiter(
+ SubscriptionService* subscription_service)
+ : subscription_service_(subscription_service) {
+ subscription_service_->AddObserver(this);
+}
+
+SubscriptionInstalledWaiter::~SubscriptionInstalledWaiter() {
+ subscription_service_->RemoveObserver(this);
+}
+
+void SubscriptionInstalledWaiter::WaitUntilSubscriptionsInstalled(
+ std::vector<GURL> subscriptions) {
+ awaited_subscriptions_ = std::move(subscriptions);
+ run_loop_.Run();
+}
+
+void SubscriptionInstalledWaiter::OnSubscriptionInstalled(
+ const GURL& subscription_url) {
+ awaited_subscriptions_.erase(
+ base::ranges::remove(awaited_subscriptions_, subscription_url),
+ awaited_subscriptions_.end());
+ if (awaited_subscriptions_.empty()) {
+ run_loop_.Quit();
+ }
+}
+
+AdblockBrowserTestBase::AdblockBrowserTestBase() = default;
+
+AdblockBrowserTestBase::~AdblockBrowserTestBase() = default;
+
+content::ContentMainDelegate*
+AdblockBrowserTestBase::GetOptionalContentMainDelegateOverride() {
+ return new content::ShellMainDelegate(true);
+}
+
+content::WebContents* AdblockBrowserTestBase::web_contents() {
+ return shell()->web_contents();
+}
+
+content::BrowserContext* AdblockBrowserTestBase::browser_context() {
+ return web_contents()->GetBrowserContext();
+}
+
+PrefService* AdblockBrowserTestBase::GetPrefs() {
+ return user_prefs::UserPrefs::Get(browser_context());
+}
+
+SubscriptionInstalledWaiter
+AdblockBrowserTestBase::GetSubscriptionInstalledWaiter() {
+ return SubscriptionInstalledWaiter(
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context()));
+}
+
+void AdblockBrowserTestBase::SetUpOnMainThread() {
+ content::ContentBrowserTest::SetUpOnMainThread();
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ // Some tests remove "adblock" configuration so let's check before using.
+ if (adblock_configuration) {
+ adblock_configuration->RemoveCustomFilter(kAllowlistEverythingFilter);
+ }
+
+ // Allow network requests immediately, otherwise tests that expect e.g. filter
+ // list downloads will hang for 30 seconds.
+ AdblockRequestThrottleFactory::GetForBrowserContext(browser_context())
+ ->AllowRequestsAfter(base::Seconds(0));
+}
+
+void AdblockBrowserTestBase::TearDownOnMainThread() {
+ // RemoveObserver is harmless even if AddObserver was not called.
+ auto* classification_runner =
+ ResourceClassificationRunnerFactory::GetForBrowserContext(
+ browser_context());
+ classification_runner->RemoveObserver(&observer_);
+}
+
+bool AdblockBrowserTestBase::WaitAndVerifyCondition(const char* condition) {
+ std::string script = base::StringPrintf(
+ R"(
+ (async () => {
+ let count = 10;
+ function waitFor(condition) {
+ const poll = resolve => {
+ if(condition() || !count--) resolve();
+ else setTimeout(_ => poll(resolve), 300);
+ }
+ return new Promise(poll);
+ }
+ // Waits up to 3 seconds
+ await waitFor(_ => %s);
+ return %s;
+ })()
+ )",
+ condition, condition);
+ return content::EvalJs(web_contents(), script) == true;
+}
+
+void AdblockBrowserTestBase::NotifyTestFinished() {
+ finish_condition_met_ = true;
+ // If the test is currently waiting for the finish condition to be met, we
+ // need to quit the run loop.
+ if (quit_closure_) {
+ quit_closure_.Run();
+ }
+}
+
+void AdblockBrowserTestBase::RunUntilTestFinished() {
+ // If the finish condition is already met, we don't need to run the run
+ // loop.
+ if (finish_condition_met_) {
+ return;
+ }
+ // Wait until NotifyTestFinished() gets called.
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitClosure();
+ std::move(run_loop).Run();
+}
+
+void AdblockBrowserTestBase::SetFilters(
+ const std::vector<std::string>& filters) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration) << "Test expects \"adblock\" configuration";
+ for (auto& filter : filters) {
+ adblock_configuration->AddCustomFilter(filter);
+ }
+}
+
+void AdblockBrowserTestBase::InitResourceClassificationObserver() {
+ auto* classification_runner =
+ ResourceClassificationRunnerFactory::GetForBrowserContext(
+ browser_context());
+ DCHECK(classification_runner);
+ classification_runner->AddObserver(&observer_);
+}
+
+std::string AdblockBrowserTestBase::GetTelemetryDomain() {
+ static std::string domain =
+ GURL(ActivepingTelemetryTopicProvider::DefaultBaseUrl()).host();
+ return domain;
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_browsertest_base.h b/components/adblock/content/browser/test/adblock_browsertest_base.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_browsertest_base.h
@@ -0,0 +1,128 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_ADBLOCK_BROWSERTEST_BASE_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_ADBLOCK_BROWSERTEST_BASE_H_
+
+#include "base/run_loop.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/shell/app/shell_main_delegate.h"
+
+namespace adblock {
+
+class TestResourceClassificationRunnerObserver
+ : public ResourceClassificationRunner::Observer {
+ public:
+ TestResourceClassificationRunnerObserver();
+
+ ~TestResourceClassificationRunnerObserver() override;
+
+ // ResourceClassificationRunner::Observer:
+ void OnRequestMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override;
+
+ void OnPageAllowed(const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override;
+ void OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override;
+
+ std::vector<GURL> blocked_ads_notifications;
+ std::vector<GURL> allowed_ads_notifications;
+ std::vector<GURL> blocked_popups_notifications;
+ std::vector<GURL> allowed_popups_notifications;
+ std::vector<GURL> allowed_pages_notifications;
+};
+
+class SubscriptionInstalledWaiter
+ : public SubscriptionService::SubscriptionObserver {
+ public:
+ explicit SubscriptionInstalledWaiter(
+ SubscriptionService* subscription_service);
+
+ ~SubscriptionInstalledWaiter() override;
+
+ void WaitUntilSubscriptionsInstalled(std::vector<GURL> subscriptions);
+
+ void OnSubscriptionInstalled(const GURL& subscription_url) override;
+
+ protected:
+ raw_ptr<SubscriptionService> subscription_service_;
+ base::RunLoop run_loop_;
+ std::vector<GURL> awaited_subscriptions_;
+};
+
+class AdblockBrowserTestBase : public content::ContentBrowserTest {
+ public:
+ AdblockBrowserTestBase();
+
+ ~AdblockBrowserTestBase() override;
+
+ // Without this override there is no AdblockShellContentBrowserClient
+ // (created by ShellMainDelegate) but default ShellContentBrowserClient.
+ content::ContentMainDelegate* GetOptionalContentMainDelegateOverride()
+ override;
+
+ content::WebContents* web_contents();
+
+ content::BrowserContext* browser_context();
+
+ PrefService* GetPrefs();
+
+ SubscriptionInstalledWaiter GetSubscriptionInstalledWaiter();
+
+ void SetUpOnMainThread() override;
+
+ void TearDownOnMainThread() override;
+
+ bool WaitAndVerifyCondition(const char* condition);
+
+ void NotifyTestFinished();
+
+ void RunUntilTestFinished();
+
+ // Sets custom filters in "adblock" configuration
+ void SetFilters(const std::vector<std::string>& filters);
+
+ void InitResourceClassificationObserver();
+
+ std::string GetTelemetryDomain();
+
+ protected:
+ bool finish_condition_met_ = false;
+ base::RepeatingClosure quit_closure_;
+ TestResourceClassificationRunnerObserver observer_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_ADBLOCK_BROWSERTEST_BASE_H_
diff --git a/components/adblock/content/browser/test/adblock_content_browser_client_browsertest.cc b/components/adblock/content/browser/test/adblock_content_browser_client_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_content_browser_client_browsertest.cc
@@ -0,0 +1,110 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/strings/utf_string_conversions.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
+#include "net/test/test_data_directory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockContentBrowserClientBrowserTest : public AdblockBrowserTestBase {
+ public:
+ AdblockContentBrowserClientBrowserTest()
+ : ws_server_(net::EmbeddedTestServer::TYPE_HTTP) {
+ net::test_server::InstallDefaultWebSocketHandlers(
+ &ws_server_, /*serve_websocket_test_data=*/true);
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ watcher_ = std::make_unique<content::TitleWatcher>(web_contents(), u"PASS");
+ watcher_->AlsoWaitForTitle(u"FAIL");
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ net::GetWebSocketTestDataDirectory());
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ void TearDownOnMainThread() override { watcher_.reset(); }
+
+ void NavigateToHTTP(const std::string& path) {
+ // Visit a HTTP page for testing.
+ const GURL url = ws_server_.GetURL(path);
+ ASSERT_TRUE(url.SchemeIs("http"));
+ ASSERT_TRUE(content::NavigateToURL(shell(), url));
+ }
+
+ std::string WaitAndGetTitle() {
+ return base::UTF16ToUTF8(watcher_->WaitAndGetTitle());
+ }
+
+ net::EmbeddedTestServer ws_server_;
+
+ private:
+ std::unique_ptr<content::TitleWatcher> watcher_;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockContentBrowserClientBrowserTest,
+ WebSocketConnectionNotIntercepted) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ // Disable ad-filtering.
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ adblock_configuration->SetEnabled(false);
+
+ NavigateToHTTP("/split_packet_check.html");
+
+ // WebSocket connected.
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentBrowserClientBrowserTest,
+ WebSocketConnectionInterceptedButNotBlocked) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ NavigateToHTTP("/split_packet_check.html");
+
+ // WebSocket connected, there were no blocking filters.
+ EXPECT_EQ("PASS", WaitAndGetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentBrowserClientBrowserTest,
+ WebSocketConnectionInterceptedAndBlocked) {
+ // Launch a WebSocket server.
+ ASSERT_TRUE(ws_server_.Start());
+
+ // Intercept WebSocket and block connection via a filter.
+ SetFilters({"*$websocket"});
+
+ NavigateToHTTP("/split_packet_check.html");
+
+ // WebSocket did not connect.
+ EXPECT_EQ("FAIL", WaitAndGetTitle());
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_content_filters_browsertest.cc b/components/adblock/content/browser/test/adblock_content_filters_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_content_filters_browsertest.cc
@@ -0,0 +1,290 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <vector>
+
+#include "absl/strings/str_format.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockContentFiltersBrowserTest : public AdblockBrowserTestBase {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("example.com", "127.0.0.1");
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ "components/test/data/adblock");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ GURL GetUrl(const std::string& path) {
+ return embedded_test_server()->GetURL("example.com", path);
+ }
+
+ void WaitForDynamicContentLoaded() {
+ std::string dynamic_content_loaded =
+ "window.dynamic_content_loaded == true";
+ ASSERT_TRUE(WaitAndVerifyCondition(dynamic_content_loaded.c_str()));
+ }
+
+ void VerifyTargetsRemoved(bool removed, const std::string& class_id) {
+ std::string is_removed_js =
+ base::StringPrintf("document.getElementsByClassName('%s').length == %d",
+ class_id.c_str(), removed ? 0 : 2);
+ EXPECT_TRUE(WaitAndVerifyCondition(is_removed_js.c_str()));
+ }
+
+ void VerifyTargetHidden(bool hidden, const std::string& id) {
+ std::string expected_visibility = (hidden ? "none" : "inline");
+ std::string is_hidden_js = base::StringPrintf(
+ "window.getComputedStyle(document.getElementById('%s'))."
+ "display == '%s'",
+ id.c_str(), expected_visibility.c_str());
+ EXPECT_TRUE(WaitAndVerifyCondition(is_hidden_js.c_str()));
+ }
+
+ void VerifyTargetsHidden(bool hidden, const std::string& class_id) {
+ std::string expected_visibility = (hidden ? "none" : "inline");
+ std::string is_hidden_js = base::StringPrintf(
+ "window.getComputedStyle(document.getElementsByClassName('%s')[0])."
+ "display == '%s' && "
+ "window.getComputedStyle(document.getElementsByClassName('%s')[1])."
+ "display == '%s'",
+ class_id.c_str(), expected_visibility.c_str(), class_id.c_str(),
+ expected_visibility.c_str());
+ EXPECT_TRUE(WaitAndVerifyCondition(is_hidden_js.c_str()));
+ }
+
+ void VerifyCssAppliedForTarget(bool applied, const std::string& id) {
+ std::string expected_css = (applied ? "rgb(0, 255, 0)" : "rgb(255, 0, 0)");
+ std::string is_css_applied_js = base::StringPrintf(
+ "window.getComputedStyle(document.getElementById('%s'))."
+ "backgroundColor == '%s'",
+ id.c_str(), expected_css.c_str());
+ EXPECT_TRUE(WaitAndVerifyCondition(is_css_applied_js.c_str()));
+ }
+
+ void VerifyCssAppliedForTargets(bool applied, const std::string& class_id) {
+ std::string expected_css = (applied ? "rgb(0, 255, 0)" : "rgb(255, 0, 0)");
+ std::string is_css_applied_js = base::StringPrintf(
+ "window.getComputedStyle(document.getElementsByClassName('%s')[0])."
+ "backgroundColor == '%s' && "
+ "window.getComputedStyle(document.getElementsByClassName('%s')[1])."
+ "backgroundColor == '%s'",
+ class_id.c_str(), expected_css.c_str(), class_id.c_str(),
+ expected_css.c_str());
+ EXPECT_TRUE(WaitAndVerifyCondition(is_css_applied_js.c_str()));
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyNoFilters) {
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyHide) {
+ SetFilters({"example.com##.id_to_elem_hide",
+ "example.com#?#span:-abp-contains(id_to_elem_hide_emu)"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(true, "id_to_elem_hide");
+ VerifyTargetsHidden(true, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyHideException) {
+ SetFilters({"example.com##.id_to_elem_hide",
+ "example.com#?#span:-abp-contains(id_to_elem_hide_emu)",
+ "example.com#@#.id_to_elem_hide",
+ "example.com#@#span:-abp-contains(id_to_elem_hide_emu)"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyRemove) {
+ SetFilters({"example.com##.id_to_remove_by_eh {remove: true;}",
+ "example.com#?#span:-abp-contains(id_to_remove_by_ehe) {remove: "
+ "true;}"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(true, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(true, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest,
+ VerifyRemoveException) {
+ SetFilters({"example.com##.id_to_remove_by_eh {remove: true;}",
+ "example.com#?#span:-abp-contains(id_to_remove_by_ehe\"]"
+ ") {remove: true;}",
+ "example.com#@#.id_to_remove_by_eh",
+ "example.com#@#span:-abp-contains(id_to_remove_by_ehe)"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyInlineCss) {
+ SetFilters(
+ {"example.com##.id_to_apply_style_by_eh {background-color: "
+ "#00FF00!important;}",
+ "example.com#?#span:-abp-contains(id_to_apply_style_by_ehe) "
+ "{background-color: #00FF00!important;}"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(true, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(true, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest,
+ VerifyInlineCssException) {
+ SetFilters(
+ {"example.com##.id_to_apply_style_by_eh {background-color: "
+ "#00FF00!important;}",
+ "example.com#?#span:-abp-contains(id_to_apply_style_by_ehe) "
+ "{background-color: #00FF00!important;}",
+ "example.com#@#.id_to_apply_style_by_eh",
+ "example.com#@#span:-abp-contains(id_to_apply_style_by_ehe)"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(false, "id_to_elem_hide");
+ VerifyTargetsHidden(false, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(false, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(false, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(false, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest, VerifyAllFilters) {
+ SetFilters({"example.com##.id_to_elem_hide",
+ "example.com#?#span:-abp-contains(id_to_elem_hide_emu)",
+ "example.com##.id_to_remove_by_eh {remove: true;}",
+ "example.com#?#span:-abp-contains(id_to_remove_by_ehe) {"
+ "remove: true;}",
+ "example.com##.id_to_apply_style_by_eh {background-color: "
+ "#00FF00!important;}",
+ "example.com#?#span:-abp-contains(id_to_apply_style_by_ehe) "
+ "{background-color: #00FF00!important;}"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ WaitForDynamicContentLoaded();
+ VerifyTargetsHidden(true, "id_to_elem_hide");
+ VerifyTargetsHidden(true, "id_to_elem_hide_emu");
+ VerifyTargetsRemoved(true, "id_to_remove_by_eh");
+ VerifyTargetsRemoved(true, "id_to_remove_by_ehe");
+ VerifyCssAppliedForTargets(true, "id_to_apply_style_by_eh");
+ VerifyCssAppliedForTargets(true, "id_to_apply_style_by_ehe");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest,
+ VerifyHideToInlineCssSelectorChange) {
+ SetFilters({"example.com#?#span:-abp-contains(hide_selector)",
+ "example.com#?#span:-abp-contains(inline_css_selector) "
+ "{background-color: #00FF00!important;}"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ VerifyTargetHidden(false, "changing_element");
+ VerifyCssAppliedForTarget(false, "changing_element");
+ EXPECT_EQ(
+ "hide_selector",
+ content::EvalJs(web_contents(),
+ "document.getElementById('changing_element').innerHTML = "
+ "'hide_selector'"));
+ VerifyTargetHidden(true, "changing_element");
+ VerifyCssAppliedForTarget(false, "changing_element");
+ EXPECT_EQ(
+ "inline_css_selector",
+ content::EvalJs(web_contents(),
+ "document.getElementById('changing_element').innerHTML = "
+ "'inline_css_selector'"));
+ VerifyTargetHidden(false, "changing_element");
+ VerifyCssAppliedForTarget(true, "changing_element");
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockContentFiltersBrowserTest,
+ VerifyInlineCssStyleModificationLogic) {
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ static constexpr char get_expected_style_property[] =
+ "document.getElementById('changing_element').style['%s'] === '%s'";
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(get_expected_style_property, "background-color",
+ "rgb(255, 0, 0)")
+ .c_str()));
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(get_expected_style_property, "width", "").c_str()));
+ SetFilters(
+ {"example.com###changing_element {background-color: #00FF00!important;}",
+ "example.com###changing_element {width: 100px;}"});
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), GetUrl("/content_type_filters.html")));
+ // "background-color" is now overwritten (update logic for existing property)
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(get_expected_style_property, "background-color",
+ "rgb(0, 255, 0)")
+ .c_str()));
+ // "width" is now set (add logic for not yet set property)
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(get_expected_style_property, "width", "100px")
+ .c_str()));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_debug_url_browsertest.cc b/components/adblock/content/browser/test/adblock_debug_url_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_debug_url_browsertest.cc
@@ -0,0 +1,331 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+
+#include "components/adblock/content/browser/adblock_url_loader_factory_for_test.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::HasSubstr;
+using testing::Mock;
+using testing::Return;
+using testing::StartsWith;
+
+namespace adblock {
+
+class AdblockDebugUrlTest : public AdblockBrowserTestBase {
+ public:
+ AdblockDebugUrlTest() {}
+ ~AdblockDebugUrlTest() override = default;
+ AdblockDebugUrlTest(const AdblockDebugUrlTest&) = delete;
+ AdblockDebugUrlTest& operator=(const AdblockDebugUrlTest&) = delete;
+
+ protected:
+ std::string ExecuteScriptAndExtractString(const std::string& js_code) {
+ return content::EvalJs(web_contents(), js_code).ExtractString();
+ }
+
+ bool IsAdblockEnabled() {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration) << "Test expects \"adblock\" configuration";
+ return adblock_configuration->IsEnabled();
+ }
+
+ bool IsAAEnabled() {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration) << "Test expects \"adblock\" configuration";
+ return base::ranges::any_of(
+ adblock_configuration->GetFilterLists(),
+ [&](const auto& url) { return url == AcceptableAdsUrl(); });
+ }
+
+ std::string GetUrlForAdblockConfiguration() {
+ return std::string("chrome://") + kAdblockFilteringConfigurationName + "." +
+ AdblockURLLoaderFactoryForTest::kEyeoDebugDataHostName;
+ }
+
+ std::string GetUrlForListingConfigurations() {
+ return std::string("chrome://") +
+ AdblockURLLoaderFactoryForTest::kEyeoDebugDataHostName +
+ "/configurations";
+ }
+
+ const std::string kReadPageBodyScript =
+ "document.getElementsByTagName('body')[0].firstChild.innerHTML";
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestInvalidUrls) {
+ GURL no_command1(GetUrlForAdblockConfiguration());
+ ASSERT_TRUE(content::NavigateToURL(shell(), no_command1));
+ ASSERT_TRUE(base::StartsWith(
+ ExecuteScriptAndExtractString(kReadPageBodyScript), "INVALID_COMMAND"));
+
+ GURL no_command2(GetUrlForAdblockConfiguration() + "/");
+ ASSERT_TRUE(content::NavigateToURL(shell(), no_command2));
+ ASSERT_TRUE(base::StartsWith(
+ ExecuteScriptAndExtractString(kReadPageBodyScript), "INVALID_COMMAND"));
+
+ GURL invalid_command_url(GetUrlForAdblockConfiguration() +
+ "/some_invalid_command");
+ ASSERT_TRUE(content::NavigateToURL(shell(), invalid_command_url));
+ ASSERT_TRUE(base::StartsWith(
+ ExecuteScriptAndExtractString(kReadPageBodyScript), "INVALID_COMMAND"));
+
+ GURL invalid_topic(GetUrlForAdblockConfiguration() +
+ "/filter/add/%2Fadsponsor.");
+ ASSERT_TRUE(content::NavigateToURL(shell(), invalid_topic));
+ ASSERT_TRUE(base::StartsWith(
+ ExecuteScriptAndExtractString(kReadPageBodyScript), "INVALID_COMMAND"));
+
+ GURL invalid_command(GetUrlForAdblockConfiguration() +
+ "/filters/ad/%2Fadsponsor.");
+ ASSERT_TRUE(content::NavigateToURL(shell(), invalid_command));
+ ASSERT_TRUE(base::StartsWith(
+ ExecuteScriptAndExtractString(kReadPageBodyScript), "INVALID_COMMAND"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestFilterCommands) {
+ GURL clear_filters_url(GetUrlForAdblockConfiguration() + "/filters/clear");
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_filters_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL list_filters_url(GetUrlForAdblockConfiguration() + "/filters/list");
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_filters_url));
+ std::string expected_no_filters = "OK";
+ ASSERT_EQ(expected_no_filters,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL add_filters_url(GetUrlForAdblockConfiguration() +
+ "/filters/add/%2FadsPlugin%2F%2A%0A%2Fadsponsor.");
+ ASSERT_TRUE(content::NavigateToURL(shell(), add_filters_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_filters_url));
+ auto response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_THAT(response, StartsWith("OK\n\n"));
+ ASSERT_THAT(response, HasSubstr("adsPlugin/*"));
+ ASSERT_THAT(response, HasSubstr("adsponsor."));
+
+ GURL remove_filter_url(GetUrlForAdblockConfiguration() +
+ "/filters/remove/%2Fadsponsor.");
+ ASSERT_TRUE(content::NavigateToURL(shell(), remove_filter_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_filters_url));
+ std::string expected_one_filter = "OK\n\n/adsPlugin/*\n";
+ ASSERT_EQ(expected_one_filter,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_filters_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_filters_url));
+ ASSERT_EQ(expected_no_filters,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestDomainCommands) {
+ GURL clear_domains_url(GetUrlForAdblockConfiguration() + "/domains/clear");
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_domains_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL list_domains_url(GetUrlForAdblockConfiguration() + "/domains/list");
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_domains_url));
+ std::string expected_no_domains = "OK";
+ ASSERT_EQ(expected_no_domains,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL add_domain_url(GetUrlForAdblockConfiguration() +
+ "/domains/add/example.com%0Adomain.org");
+ ASSERT_TRUE(content::NavigateToURL(shell(), add_domain_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_domains_url));
+ auto response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_THAT(response, StartsWith("OK\n\n"));
+ ASSERT_THAT(response, HasSubstr("example.com"));
+ ASSERT_THAT(response, HasSubstr("domain.org"));
+
+ GURL remove_domain_url(GetUrlForAdblockConfiguration() +
+ "/domains/remove/example.com");
+ ASSERT_TRUE(content::NavigateToURL(shell(), remove_domain_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_domains_url));
+ std::string expected_one_domain = "OK\n\ndomain.org\n";
+ ASSERT_EQ(expected_one_domain,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_domains_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_domains_url));
+ ASSERT_EQ(expected_no_domains,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestSubscriptionCommands) {
+ GURL clear_subscriptions_url(GetUrlForAdblockConfiguration() +
+ "/subscriptions/clear");
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_subscriptions_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL list_subscriptions_url(GetUrlForAdblockConfiguration() +
+ "/subscriptions/list");
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_subscriptions_url));
+ std::string expected_no_subscriptions = "OK";
+ ASSERT_EQ(expected_no_subscriptions,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ GURL add_subscription_url(GetUrlForAdblockConfiguration() +
+ "/subscriptions/add/"
+ "https%3A%2F%2Fexample.com%2Flist1.txt%0Ahttps%3A%"
+ "2F%2Fwww.domain.org%2Flist2.txt");
+ ASSERT_TRUE(content::NavigateToURL(shell(), add_subscription_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_subscriptions_url));
+ auto response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_THAT(response, StartsWith("OK\n\n"));
+ ASSERT_THAT(response, HasSubstr("https://example.com/list1.txt"));
+ ASSERT_THAT(response, HasSubstr("https://www.domain.org/list2.txt"));
+
+ GURL remove_subscription_url(
+ GetUrlForAdblockConfiguration() +
+ "/subscriptions/remove/https%3A%2F%2Fwww.domain.org%2Flist2.txt");
+ ASSERT_TRUE(content::NavigateToURL(shell(), remove_subscription_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_subscriptions_url));
+ std::string expected_one_subscription =
+ "OK\n\nhttps://example.com/list1.txt\n";
+ ASSERT_EQ(expected_one_subscription,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), clear_subscriptions_url));
+ ASSERT_EQ("OK", ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_subscriptions_url));
+ ASSERT_EQ(expected_no_subscriptions,
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestEnableConfigurationCommands) {
+ GURL enable_adblock__url(GetUrlForAdblockConfiguration() +
+ "/configuration/enable");
+ GURL disable_adblock_url(GetUrlForAdblockConfiguration() +
+ "/configuration/disable");
+ GURL adblock_state_url(GetUrlForAdblockConfiguration() +
+ "/configuration/state");
+
+ ASSERT_TRUE(IsAdblockEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), adblock_state_url));
+ ASSERT_EQ("OK\n\nenabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), disable_adblock_url));
+ ASSERT_FALSE(IsAdblockEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), adblock_state_url));
+ ASSERT_EQ("OK\n\ndisabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), enable_adblock__url));
+ ASSERT_TRUE(IsAdblockEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), adblock_state_url));
+ ASSERT_EQ("OK\n\nenabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestEnableAACommands) {
+ GURL enable_aa_url(GetUrlForAdblockConfiguration() + "/aa/enable");
+ GURL disable_aa_url(GetUrlForAdblockConfiguration() + "/aa/disable");
+ GURL aa_state_url(GetUrlForAdblockConfiguration() + "/aa/state");
+
+ ASSERT_TRUE(IsAAEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), aa_state_url));
+ ASSERT_EQ("OK\n\nenabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), disable_aa_url));
+ ASSERT_FALSE(IsAAEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), aa_state_url));
+ ASSERT_EQ("OK\n\ndisabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), enable_aa_url));
+ ASSERT_TRUE(IsAAEnabled());
+ ASSERT_TRUE(content::NavigateToURL(shell(), aa_state_url));
+ ASSERT_EQ("OK\n\nenabled",
+ ExecuteScriptAndExtractString(kReadPageBodyScript));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestHandleConfigurationsCommands) {
+ GURL list_configurations_url(GetUrlForListingConfigurations() + "/list");
+ GURL add_configuration_url(GetUrlForListingConfigurations() + "/add/adblock");
+ GURL remove_configuration_url(GetUrlForListingConfigurations() +
+ "/remove/adblock");
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_configurations_url));
+ auto response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_THAT(response, StartsWith("OK\n\n"));
+ ASSERT_THAT(response, HasSubstr("adblock"));
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), remove_configuration_url));
+ response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_EQ(response, "OK\n\n");
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_configurations_url));
+ response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_EQ(response, "OK\n\n");
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), add_configuration_url));
+ response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_EQ(response, "OK\n\n");
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), list_configurations_url));
+ response = ExecuteScriptAndExtractString(kReadPageBodyScript);
+ ASSERT_THAT(response, StartsWith("OK\n\n"));
+ ASSERT_THAT(response, HasSubstr("adblock"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockDebugUrlTest, TestUrlsInterception) {
+ std::vector<GURL> invalid_urls = {
+ GURL{"https://adblocktest.data"}, GURL{"https://adblock.testdata"},
+ GURL{"https://adblock.test.data.eyeo"}, GURL{"https://test.data.eyeo"}};
+ std::vector<GURL> valid_urls = {GURL{"https://adblock.test.data"},
+ GURL{"https://ad.block.test.data"}};
+ for (const auto& url : invalid_urls) {
+ ASSERT_FALSE(content::NavigateToURL(shell(), url));
+ }
+ for (const auto& url : valid_urls) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), url));
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_filter_list_browsertest.cc b/components/adblock/content/browser/test/adblock_filter_list_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_filter_list_browsertest.cc
@@ -0,0 +1,710 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/check.h"
+#include "base/environment.h"
+#include "base/functional/callback_forward.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time_override.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/subscription/recommended_subscription_installer_impl.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/version_info/version_info.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+using base::subtle::TimeNowIgnoringOverride;
+
+class AdblockFilterListDownloadTestBase : public AdblockBrowserTestBase {
+ public:
+ AdblockFilterListDownloadTestBase()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+ https_server_.RegisterRequestHandler(
+ base::BindRepeating(&AdblockFilterListDownloadTestBase::RequestHandler,
+ base::Unretained(this)));
+ net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+ cert_config.dns_names = {"easylist-downloads.adblockplus.org"};
+ https_server_.SetSSLConfig(cert_config);
+ EXPECT_TRUE(https_server_.Start());
+ SetFilterListServerPortForTesting(https_server_.port());
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("easylist-downloads.adblockplus.org", "127.0.0.1");
+ }
+
+ void CheckRequestParams(const net::test_server::HttpRequest& request,
+ std::string expected_disabled_value) {
+ std::string os;
+ base::ReplaceChars(version_info::GetOSType(), base::kWhitespaceASCII, "",
+ &os);
+ EXPECT_TRUE(request.relative_url.find("addonName=eyeo-chromium-sdk") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("addonVersion=2.0.0") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("platformVersion=1.0") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("platform=" + os) !=
+ std::string::npos);
+ if (RunsOnEyeoCI()) {
+ // Those two checks below require "eyeo_application_name" and
+ // "eyeo_application_version" to be set as gn gen args.
+ EXPECT_TRUE(
+ request.relative_url.find("application=app_name_from_ci_config") !=
+ std::string::npos)
+ << "Did you set \"eyeo_application_name\" gn gen arg?";
+ EXPECT_TRUE(request.relative_url.find(
+ "applicationVersion=app_version_from_ci_config") !=
+ std::string::npos)
+ << "Did you set \"eyeo_application_version\" gn gen arg?";
+ }
+ EXPECT_TRUE(
+ request.relative_url.find("disabled=" + expected_disabled_value) !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("safe=true") != std::string::npos);
+ }
+
+ virtual std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (request.method == net::test_server::HttpMethod::METHOD_GET &&
+ (base::StartsWith(request.relative_url, "/abp-filters-anti-cv.txt") ||
+ base::StartsWith(request.relative_url, "/easylist.txt") ||
+ base::StartsWith(request.relative_url, "/exceptionrules.txt"))) {
+ CheckRequestParams(request, "false");
+ default_lists_.insert(request.relative_url.substr(
+ 1, request.relative_url.find_first_of("?") - 1));
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ // This is fine for the purpose of this test.
+ return nullptr;
+ }
+
+ bool RunsOnEyeoCI() {
+ auto env = base::Environment::Create();
+ std::string value;
+ env->GetVar("CI_PROJECT_NAME", &value);
+ return value == "chromium-sdk";
+ }
+
+ protected:
+ net::EmbeddedTestServer https_server_;
+ std::set<std::string> default_lists_;
+ bool finish_condition_met_ = false;
+ base::RepeatingClosure quit_closure_;
+};
+
+class AdblockEnabledFilterListDownloadTest
+ : public AdblockFilterListDownloadTestBase {
+ public:
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ auto result = AdblockFilterListDownloadTestBase::RequestHandler(request);
+ // If we get all expected requests we simply finish the test by closing
+ // the browser, otherwise test will fail with a timeout.
+ if (CheckExpectedDownloads()) {
+ NotifyTestFinished();
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ return result;
+ }
+
+ bool CheckExpectedDownloads() {
+ return (default_lists_.find("abp-filters-anti-cv.txt") !=
+ default_lists_.end()) &&
+ (default_lists_.find("easylist.txt") != default_lists_.end()) &&
+ (default_lists_.find("exceptionrules.txt") != default_lists_.end());
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockEnabledFilterListDownloadTest,
+ TestInitialDownloads) {
+ RunUntilTestFinished();
+}
+
+class AdblockEnabledAcceptableAdsDisabledFilterListDownloadTest
+ : public AdblockFilterListDownloadTestBase {
+ public:
+ AdblockEnabledAcceptableAdsDisabledFilterListDownloadTest() {
+ const auto testing_interval = base::Seconds(1);
+ SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting(
+ testing_interval);
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ // If we get expected HEAD request we simply finish the test by closing
+ // the browser, otherwise test will fail with a timeout.
+ if (request.method == net::test_server::HttpMethod::METHOD_HEAD &&
+ base::StartsWith(request.relative_url, "/exceptionrules.txt")) {
+ CheckRequestParams(request, "true");
+ NotifyTestFinished();
+ }
+
+ return nullptr;
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(adblock::switches::kDisableAcceptableAds);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockEnabledAcceptableAdsDisabledFilterListDownloadTest,
+ TestInitialDownloads) {
+ RunUntilTestFinished();
+}
+
+enum class DisableSwitch { Adblock, Eyeo };
+
+class AdblockDisabledFilterListDownloadTest
+ : public AdblockFilterListDownloadTestBase,
+ public testing::WithParamInterface<DisableSwitch> {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(GetParam() == DisableSwitch::Adblock
+ ? adblock::switches::kDisableAdblock
+ : adblock::switches::kDisableEyeoFiltering);
+ }
+
+ void VerifyNoDownloads() {
+ ASSERT_EQ(0u, default_lists_.size());
+ NotifyTestFinished();
+ }
+};
+
+IN_PROC_BROWSER_TEST_P(AdblockDisabledFilterListDownloadTest,
+ TestInitialDownloads) {
+ // This test assumes that inital downloads (for adblock enabled) will happen
+ // within 10 seconds. When tested locally it always happens within 3
+ // seconds.
+ base::OneShotTimer timer;
+ timer.Start(
+ FROM_HERE, base::Seconds(10),
+ base::BindOnce(&AdblockDisabledFilterListDownloadTest::VerifyNoDownloads,
+ base::Unretained(this)));
+ RunUntilTestFinished();
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockDisabledFilterListDownloadTest,
+ testing::Values(DisableSwitch::Adblock,
+ DisableSwitch::Eyeo));
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+enum class Country {
+ Arabic,
+ Bulgaria,
+ China,
+ Czech,
+ France,
+ Germany,
+ Hungary,
+ India,
+ Indonesia,
+ Israel,
+ Italy,
+ Japan,
+ Korea,
+ Latvia,
+ Lithuania,
+ Netherlands,
+ Norway,
+ Poland,
+ Portugal,
+ Romania,
+ Russia,
+ Spain,
+ Thailand,
+ Turkey,
+ Vietnam
+};
+
+class AdblockLocaleFilterListDownloadTest
+ : public AdblockFilterListDownloadTestBase,
+ public testing::WithParamInterface<Country> {
+ public:
+ using LocaleToEasylistPath = std::pair<std::string, std::vector<std::string>>;
+ AdblockLocaleFilterListDownloadTest() : locale_data_(GetLocale(GetParam())) {
+ setenv("LANGUAGE", locale_data_.first.c_str(), 1);
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ if (request.method == net::test_server::HttpMethod::METHOD_GET &&
+ (base::StartsWith(request.relative_url, "/abp-filters-anti-cv.txt") ||
+ base::StartsWith(request.relative_url, "/exceptionrules.txt") ||
+ base::StartsWith(request.relative_url, "/easylist.txt") ||
+ IsExpectedCountrySpecificPath(request.relative_url, locale_data_))) {
+ CheckRequestParams(request, "false");
+ default_lists_.insert(request.relative_url.substr(
+ 1, request.relative_url.find_first_of("?") - 1));
+ }
+
+ // If we get all expected requests we simply finish the test by closing
+ // the browser, otherwise test will fail with a timeout.
+ if (default_lists_.size() == (3 + locale_data_.second.size())) {
+ NotifyTestFinished();
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ return nullptr;
+ }
+
+ private:
+ LocaleToEasylistPath locale_data_;
+ LocaleToEasylistPath GetLocale(Country country) {
+ switch (country) {
+ case Country::Arabic:
+ return std::make_pair("ar_SA", std::vector<std::string>{
+ "/liste_ar.txt", "/liste_fr.txt"});
+ case Country::Bulgaria:
+ return std::make_pair("bg_BG",
+ std::vector<std::string>{"/bulgarian_list.txt"});
+ case Country::China:
+ return std::make_pair("zh_CN",
+ std::vector<std::string>{"/easylistchina.txt"});
+ case Country::Czech:
+ return std::make_pair(
+ "cs_CZ", std::vector<std::string>{"/easylistczechslovak.txt"});
+ case Country::France:
+ return std::make_pair("fr_FR",
+ std::vector<std::string>{"/liste_fr.txt"});
+ case Country::Germany:
+ return std::make_pair("de_DE",
+ std::vector<std::string>{"/easylistgermany.txt"});
+ case Country::Hungary:
+ return std::make_pair("hu_HU",
+ std::vector<std::string>{"/hufilter.txt"});
+ case Country::India:
+ return std::make_pair("ml_IN",
+ std::vector<std::string>{"/indianlist.txt"});
+ case Country::Indonesia:
+ return std::make_pair("id_ID",
+ std::vector<std::string>{"/abpindo.txt"});
+ case Country::Israel:
+ return std::make_pair("he_IL",
+ std::vector<std::string>{"/israellist.txt"});
+ case Country::Italy:
+ return std::make_pair("it_IT",
+ std::vector<std::string>{"/easylistitaly.txt"});
+ case Country::Japan:
+ return std::make_pair(
+ "ja_JP", std::vector<std::string>{"/japanese-filters.txt"});
+ case Country::Korea:
+ return std::make_pair("ko_KR",
+ std::vector<std::string>{"/koreanlist.txt"});
+ case Country::Latvia:
+ return std::make_pair("lv_LV",
+ std::vector<std::string>{"/latvianlist.txt"});
+ case Country::Lithuania:
+ return std::make_pair(
+ "lt_LT", std::vector<std::string>{"/easylistlithuania.txt"});
+ case Country::Netherlands:
+ return std::make_pair("nl_NL",
+ std::vector<std::string>{"/easylistdutch.txt"});
+ case Country::Norway:
+ return std::make_pair(
+ "no_NO",
+ std::vector<std::string>{"/dandelion_sprouts_nordic_filters.txt"});
+ case Country::Poland:
+ return std::make_pair("pl_PL",
+ std::vector<std::string>{"/easylistpolish.txt"});
+ case Country::Portugal:
+ return std::make_pair(
+ "pt_PT", std::vector<std::string>{"/easylistportuguese.txt"});
+ case Country::Romania:
+ return std::make_pair("ro_RO", std::vector<std::string>{"/rolist.txt"});
+ case Country::Russia:
+ return std::make_pair("ru_RU",
+ std::vector<std::string>{"/ruadlist.txt"});
+ case Country::Spain:
+ return std::make_pair("es_ES",
+ std::vector<std::string>{"/easylistspanish.txt"});
+ case Country::Thailand:
+ return std::make_pair("th_TH",
+ std::vector<std::string>{"/global-filters.txt"});
+ case Country::Turkey:
+ return std::make_pair("tr_TR",
+ std::vector<std::string>{"/turkish-filters.txt"});
+ case Country::Vietnam:
+ return std::make_pair("vi_VN", std::vector<std::string>{"/abpvn.txt"});
+ default:
+ return std::make_pair("en_US",
+ std::vector<std::string>{"/easylist.txt"});
+ }
+ }
+
+ bool IsExpectedCountrySpecificPath(const std::string& expected_path,
+ const LocaleToEasylistPath& locale_data) {
+ auto language = locale_data.first.substr(0, 2);
+ return base::ranges::any_of(
+ config::GetKnownSubscriptions(), [&](const auto& config_entry) {
+ return base::ranges::count(config_entry.languages, language) > 0 &&
+ base::StartsWith(expected_path, config_entry.url.path());
+ });
+ }
+};
+
+IN_PROC_BROWSER_TEST_P(AdblockLocaleFilterListDownloadTest,
+ TestInitialDownloads) {
+ RunUntilTestFinished();
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockLocaleFilterListDownloadTest,
+ testing::Values(Country::Arabic,
+ Country::Bulgaria,
+ Country::China,
+ Country::Czech,
+ Country::France,
+ Country::Germany,
+ Country::Hungary,
+ Country::India,
+ Country::Indonesia,
+ Country::Israel,
+ Country::Italy,
+ Country::Japan,
+ Country::Korea,
+ Country::Latvia,
+ Country::Lithuania,
+ Country::Netherlands,
+ Country::Norway,
+ Country::Poland,
+ Country::Portugal,
+ Country::Romania,
+ Country::Russia,
+ Country::Spain,
+ Country::Thailand,
+ Country::Turkey,
+ Country::Vietnam));
+
+#endif
+
+class AdblockCombinedFilterListMigratedTest
+ : public AdblockFilterListDownloadTestBase {
+ public:
+ AdblockCombinedFilterListMigratedTest() {
+ SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting(
+ base::Seconds(1));
+ }
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ EXPECT_FALSE(
+ base::StartsWith(request.relative_url, "/easylistpolish+easylist.txt"));
+ if (request.method == net::test_server::HttpMethod::METHOD_GET &&
+ (base::StartsWith(request.relative_url, "/easylist.txt") ||
+ base::StartsWith(request.relative_url, "/easylistpolish.txt"))) {
+ CheckRequestParams(request, "false");
+ default_lists_.insert(request.relative_url.substr(
+ 1, request.relative_url.find_first_of("?") - 1));
+ }
+
+ // If we get all expected requests we simply finish the test by closing
+ // the browser, otherwise test will fail with a timeout.
+ if (default_lists_.size() == 2) {
+ NotifyTestFinished();
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ return nullptr;
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(adblock::switches::kDisableAdblock);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockCombinedFilterListMigratedTest,
+ PRE_TestMigration) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ for (const auto& url : adblock_configuration->GetFilterLists()) {
+ adblock_configuration->RemoveFilterList(url);
+ }
+ adblock_configuration->AddFilterList(
+ GURL{AdblockBaseFilterListUrl().spec() + "easylistpolish+easylist.txt"});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockCombinedFilterListMigratedTest, TestMigration) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ adblock_configuration->SetEnabled(true);
+ RunUntilTestFinished();
+}
+
+class AdblockGeolocatedFilterListsTest
+ : public AdblockFilterListDownloadTestBase,
+ public adblock::FilteringConfiguration::Observer {
+ public:
+ AdblockGeolocatedFilterListsTest() {
+ const auto testing_interval = base::Seconds(1);
+ SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting(
+ testing_interval);
+ list_pl_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/easylistpolish.txt",
+ https_server_.port());
+ list_de_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/easylistgermany.txt",
+ https_server_.port());
+ list_hu_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/hufilter.txt",
+ https_server_.port());
+ list_pt_manual_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/easylistportuguese.txt",
+ https_server_.port());
+ list_easylist_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/easylist.txt",
+ https_server_.port());
+ list_exceptionrules_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/exceptionrules.txt",
+ https_server_.port());
+ list_anticv_ = base::StringPrintf(
+ "https://easylist-downloads.adblockplus.org:%d/abp-filters-anti-cv.txt",
+ https_server_.port());
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ if (request.method == net::test_server::HttpMethod::METHOD_GET &&
+ (base::StartsWith(request.relative_url, "/abp-filters-anti-cv.txt") ||
+ base::StartsWith(request.relative_url, "/easylist.txt") ||
+ base::StartsWith(request.relative_url, "/exceptionrules.txt") ||
+ base::StartsWith(request.relative_url, "/easylistportuguese.txt") ||
+ base::StartsWith(request.relative_url, "/easylistpolish.txt") ||
+ base::StartsWith(request.relative_url, "/easylistgermany.txt") ||
+ base::StartsWith(request.relative_url, "/hufilter.txt"))) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("[Adblock Plus 2.0]");
+ http_response->set_content_type("text/plain");
+ return std::move(http_response);
+ } else if (base::StartsWith(request.relative_url,
+ "/recommendations.json") &&
+ !recommendations_json_.empty()) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(recommendations_json_);
+ http_response->set_content_type("text/plain");
+ recommendations_json_ = "";
+ return std::move(http_response);
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ return nullptr;
+ }
+
+ void OnFilterListsChanged(adblock::FilteringConfiguration* config) override {
+ // For the sake of current implementation we don't need to check what
+ // has changed here as we use this check for one case and we do the
+ // verification in test body
+ if (filter_list_removed_closure_) {
+ filter_list_removed_closure_.Run();
+ }
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockFilterListDownloadTestBase::SetUpOnMainThread();
+ configuration_ =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ configuration_->AddObserver(this);
+
+ // Verify starting condition - no language based list installed
+ ASSERT_EQ(3u, configuration_->GetFilterLists().size());
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_pl_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_de_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ }
+
+ protected:
+ std::string list_easylist_;
+ std::string list_exceptionrules_;
+ std::string list_anticv_;
+ std::string list_pl_;
+ std::string list_de_;
+ std::string list_hu_;
+ std::string list_pt_manual_;
+ static constexpr char recommendations_json_pattern_[] = "[{\"url\": \"%s\"}]";
+ static constexpr char recommendations_json_pattern_2_[] =
+ "[{\"url\": \"%s\"}, {\"url\": \"%s\"}]";
+ std::string recommendations_json_;
+ raw_ptr<FilteringConfiguration, DanglingUntriaged> configuration_;
+ base::RepeatingClosure filter_list_removed_closure_;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockGeolocatedFilterListsTest,
+ CheckRecommendationsChange) {
+ std::vector<GURL> subscriptions;
+
+ // Add non auto installed filter list
+ {
+ subscriptions = {GURL{list_pt_manual_}};
+ auto waiter = GetSubscriptionInstalledWaiter();
+ configuration_->AddFilterList(GURL{list_pt_manual_});
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ ASSERT_EQ(4u, configuration_->GetFilterLists().size());
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_easylist_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_exceptionrules_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_anticv_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pt_manual_}));
+
+ // Let's push two lists in recommendations.json
+ {
+ subscriptions = {GURL{list_de_}, GURL{list_pl_}};
+ auto waiter = GetSubscriptionInstalledWaiter();
+ recommendations_json_ = base::StringPrintf(recommendations_json_pattern_2_,
+ subscriptions[0].spec().c_str(),
+ subscriptions[1].spec().c_str());
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() { return TimeNowIgnoringOverride() + base::Days(1); }, nullptr,
+ nullptr);
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ // Now we should also have two lists from recommendations.json
+ ASSERT_EQ(6u, configuration_->GetFilterLists().size());
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pl_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_de_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pt_manual_}));
+
+ // Let's push new single list in recommendations.json and advance
+ // clocks to make previous two auto installed lists expired
+ {
+ subscriptions = {GURL{list_hu_}};
+ auto waiter = GetSubscriptionInstalledWaiter();
+ recommendations_json_ =
+ base::StringPrintf(recommendations_json_pattern_, list_hu_.c_str());
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() {
+ return TimeNowIgnoringOverride() + base::Days(1) + base::Days(14);
+ },
+ nullptr, nullptr);
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ // Now we should have one new list added and two previous auto installed
+ // lists removed
+ ASSERT_EQ(5u, configuration_->GetFilterLists().size());
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_pl_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_de_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pt_manual_}));
+
+ // Let's advance the clocks to make remaining auto installed list expired
+ // and removed
+ {
+ recommendations_json_ = "[]";
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() {
+ return TimeNowIgnoringOverride() + base::Days(1) + base::Days(14) +
+ base::Days(14);
+ },
+ nullptr, nullptr);
+ base::RunLoop run_loop;
+ filter_list_removed_closure_ = run_loop.QuitClosure();
+ std::move(run_loop).Run();
+ ASSERT_EQ(4u, configuration_->GetFilterLists().size());
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pt_manual_}));
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockGeolocatedFilterListsTest,
+ VerifyRecommendedNotRemovedAheadOfTime) {
+ std::vector<GURL> subscriptions;
+
+ // Let's push a list_hu_ only in recommendations.json
+ {
+ subscriptions = {GURL{list_hu_}};
+ auto waiter = GetSubscriptionInstalledWaiter();
+ recommendations_json_ =
+ base::StringPrintf(recommendations_json_pattern_, list_hu_.c_str());
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() { return TimeNowIgnoringOverride() + base::Days(1); }, nullptr,
+ nullptr);
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ // Now we should also have a lists from recommendations.json
+ ASSERT_EQ(4u, configuration_->GetFilterLists().size());
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_easylist_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_exceptionrules_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_anticv_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+
+ // Change recommendation to list_pl_ only
+ {
+ subscriptions = {GURL{list_pl_}};
+ auto waiter = GetSubscriptionInstalledWaiter();
+ recommendations_json_ =
+ base::StringPrintf(recommendations_json_pattern_, list_pl_.c_str());
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() {
+ return TimeNowIgnoringOverride() + base::Days(1) + base::Days(12);
+ },
+ nullptr, nullptr);
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ // Recommendation changed but the old list does not get removed before 14
+ // daysRecommendation changed but the old list does not get removed before 14
+ // days
+ ASSERT_EQ(5u, configuration_->GetFilterLists().size());
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pl_}));
+
+ // The originally recommended list is removed after 14 days
+ base::subtle::ScopedTimeClockOverrides time_override(
+ []() {
+ return TimeNowIgnoringOverride() + base::Days(1) + base::Days(14);
+ },
+ nullptr, nullptr);
+
+ recommendations_json_ = "[]";
+ base::RunLoop run_loop;
+ filter_list_removed_closure_ = run_loop.QuitClosure();
+ std::move(run_loop).Run();
+
+ ASSERT_EQ(4u, configuration_->GetFilterLists().size());
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_hu_}));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(GURL{list_pl_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_de_}));
+ EXPECT_FALSE(configuration_->IsFilterListPresent(GURL{list_pt_manual_}));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_filtering_configurations_browsertest.cc b/components/adblock/content/browser/test/adblock_filtering_configurations_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_filtering_configurations_browsertest.cc
@@ -0,0 +1,666 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <list>
+
+#include "base/memory/raw_ptr.h"
+#include "base/ranges/algorithm.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "gmock/gmock.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockFilteringConfigurationBrowserTest : public AdblockBrowserTestBase {
+ public:
+ AdblockFilteringConfigurationBrowserTest() {
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ "components/test/data/adblock");
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &AdblockFilteringConfigurationBrowserTest::RequestHandler,
+ base::Unretained(this)));
+ EXPECT_TRUE(embedded_test_server()->Start());
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (request.GetURL() == AcceptableAdsUrl()) {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(
+ "[Adblock Plus 2.0]\n"
+ "! Checksum: X5A8vtJDBW2a9EgS9glqbg\n"
+ "! Version: 202202061935\n"
+ "! Last modified: 06 Feb 2022 19:35 UTC\n"
+ "! Expires: 1 days (update frequency)\n\n");
+ http_response->set_content_type("text/plain");
+ return std::move(http_response);
+ }
+ return nullptr;
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ }
+
+ GURL BlockingFilterListUrl() {
+ return embedded_test_server()->GetURL(
+ "/filterlist_that_blocks_resource.txt");
+ }
+
+ GURL ElementHidingFilterListUrl() {
+ return embedded_test_server()->GetURL(
+ "/filterlist_that_hides_resource.txt");
+ }
+
+ GURL AllowingFilterListUrl() {
+ return embedded_test_server()->GetURL(
+ "/filterlist_that_allows_resource.txt");
+ }
+
+ GURL GetPageUrl() {
+ return embedded_test_server()->GetURL("test.org", "/innermost_frame.html");
+ }
+
+ void NavigateToPage() {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ }
+
+ std::unique_ptr<PersistentFilteringConfiguration> MakeConfiguration(
+ std::string name) {
+ return std::make_unique<PersistentFilteringConfiguration>(GetPrefs(),
+ std::move(name));
+ }
+
+ void InstallFilteringConfiguration(
+ std::unique_ptr<FilteringConfiguration> configuration) {
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->InstallFilteringConfiguration(std::move(configuration));
+ }
+
+ void UninstallFilteringConfiguration(const std::string& configuration_name) {
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->UninstallFilteringConfiguration(configuration_name);
+ }
+
+ void WaitUntilSubscriptionsInstalled(std::vector<GURL> subscriptions) {
+ auto waiter = GetSubscriptionInstalledWaiter();
+ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions));
+ }
+
+ std::string GetResourcesComputedStyle() {
+ const std::string javascript =
+ "window.getComputedStyle(document.getElementById('subresource'))."
+ "display";
+ return content::EvalJs(web_contents(), javascript).ExtractString();
+ }
+
+ void ExpectResourceHidden() {
+ EXPECT_EQ("none", GetResourcesComputedStyle());
+ }
+
+ void ExpectResourceNotHidden() {
+ EXPECT_EQ("inline", GetResourcesComputedStyle());
+ }
+
+ bool IsResourceLoaded() {
+ const std::string javascript =
+ "document.getElementById('subresource').naturalHeight !== 0;";
+ return content::EvalJs(web_contents(), javascript).ExtractBool();
+ }
+
+ void ExpectResourceBlocked() { EXPECT_FALSE(IsResourceLoaded()); }
+
+ void ExpectResourceNotBlocked() { EXPECT_TRUE(IsResourceLoaded()); }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ NoBlockingByDefault) {
+ auto configuration = MakeConfiguration("config");
+ InstallFilteringConfiguration(std::move(configuration));
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ResourceBlockedByFilteringConfigurationsList) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddFilterList(BlockingFilterListUrl());
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ WaitUntilSubscriptionsInstalled({BlockingFilterListUrl()});
+
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ResourceHiddenByFilteringConfigurationsList) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddFilterList(ElementHidingFilterListUrl());
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ WaitUntilSubscriptionsInstalled({ElementHidingFilterListUrl()});
+
+ NavigateToPage();
+ ExpectResourceHidden();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ResourceAllowedByFilteringConfigurationsList) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddFilterList(BlockingFilterListUrl());
+ configuration->AddFilterList(ElementHidingFilterListUrl());
+ configuration->AddFilterList(AllowingFilterListUrl());
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ WaitUntilSubscriptionsInstalled({BlockingFilterListUrl(),
+ AllowingFilterListUrl(),
+ ElementHidingFilterListUrl()});
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+ ExpectResourceNotHidden();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ BlockingTakesPrecedenceBetweenConfigurations) {
+ auto blocking_configuration = MakeConfiguration("blocking");
+ blocking_configuration->AddFilterList(BlockingFilterListUrl());
+
+ auto allowing_configuration = MakeConfiguration("allowing");
+ allowing_configuration->AddFilterList(AllowingFilterListUrl());
+
+ InstallFilteringConfiguration(std::move(blocking_configuration));
+ InstallFilteringConfiguration(std::move(allowing_configuration));
+
+ WaitUntilSubscriptionsInstalled(
+ {BlockingFilterListUrl(), AllowingFilterListUrl()});
+
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ElementBlockedByCustomFilter) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddCustomFilter("*resource.png");
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ElementAllowedByCustomFilter) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddCustomFilter("*resource.png");
+ configuration->AddCustomFilter("@@*resource.png");
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ElementAllowedByAllowedDomain) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddCustomFilter("*resource.png");
+ configuration->AddAllowedDomain(GetPageUrl().host());
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_CustomFiltersPersist) {
+ auto configuration = MakeConfiguration("persistent");
+ // This custom filter will survive browser restart.
+ configuration->AddCustomFilter("*resource.png");
+ InstallFilteringConfiguration(std::move(configuration));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ CustomFiltersPersist) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ EXPECT_THAT((*configuration)->GetCustomFilters(),
+ testing::UnorderedElementsAre("*resource.png"));
+
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ DisabledConfigurationDoesNotBlock) {
+ auto configuration = MakeConfiguration("config");
+ configuration->AddCustomFilter("*resource.png");
+ configuration->SetEnabled(false);
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ConfigurationCanBeUsedAfterInstalling) {
+ auto configuration = MakeConfiguration("config");
+ auto* configuration_ptr = configuration.get();
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ configuration_ptr->AddCustomFilter("*resource.png");
+
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ ConfigurationCanBeDisabledAfterInstalling) {
+ auto configuration = MakeConfiguration("config");
+ auto* configuration_ptr = configuration.get();
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ configuration_ptr->AddCustomFilter("*resource.png");
+ configuration_ptr->SetEnabled(false);
+
+ NavigateToPage();
+ ExpectResourceNotBlocked();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ SubscriptionsDownloadedAfterConfigurationEnabled) {
+ auto configuration = MakeConfiguration("config");
+ configuration->SetEnabled(false);
+ configuration->AddFilterList(BlockingFilterListUrl());
+ configuration->AddFilterList(ElementHidingFilterListUrl());
+ configuration->AddFilterList(AllowingFilterListUrl());
+ auto* configuration_ptr = configuration.get();
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ configuration_ptr->SetEnabled(true);
+
+ WaitUntilSubscriptionsInstalled({BlockingFilterListUrl(),
+ AllowingFilterListUrl(),
+ ElementHidingFilterListUrl()});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_DownloadedSubscriptionsPersistOnDisk) {
+ auto configuration = MakeConfiguration("config");
+ // This filter list setting will survive browser restart.
+ configuration->AddFilterList(BlockingFilterListUrl());
+
+ InstallFilteringConfiguration(std::move(configuration));
+
+ // This downloaded subscription won't need to be re-downloaded after restart.
+ WaitUntilSubscriptionsInstalled({BlockingFilterListUrl()});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ DownloadedSubscriptionsPersistOnDisk) {
+ NavigateToPage();
+ ExpectResourceBlocked();
+}
+
+// 1st run: create.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_PRE_CreateThenRemoveCustomConfiguration) {
+ auto configuration = MakeConfiguration("persistent");
+ InstallFilteringConfiguration(std::move(configuration));
+}
+
+// 2nd run: remove.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_CreateThenRemoveCustomConfiguration) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ UninstallFilteringConfiguration("persistent");
+ configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration == configurations.end());
+}
+
+// 3rd run: verify not present.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ CreateThenRemoveCustomConfiguration) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration == configurations.end());
+}
+
+// 1st run: confirm "adblock" configuration is created and contains expected
+// default settings, then change some settings and verify in the next run.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_PRE_RemoveAdblockConfiguration) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ ASSERT_TRUE((*configuration)->IsEnabled());
+ auto domains = (*configuration)->GetAllowedDomains();
+ ASSERT_TRUE(domains.empty());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_EQ(3u, subscriptions.size());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ DefaultSubscriptionUrl()) != subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AntiCVUrl()) != subscriptions.end());
+ // Change some settings and check in 2nd run.
+ (*configuration)->AddAllowedDomain("example.com");
+ (*configuration)->SetEnabled(false);
+}
+
+// 2nd run: make sure that previously changed settings are persisted, then
+// remove "adblock" configuration.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_RemoveAdblockConfiguration) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+
+ // Check previously changed settings.
+ ASSERT_FALSE((*configuration)->IsEnabled());
+ auto domains = (*configuration)->GetAllowedDomains();
+ ASSERT_EQ(1u, domains.size());
+ ASSERT_EQ("example.com", (*configuration)->GetAllowedDomains().front());
+
+ UninstallFilteringConfiguration(kAdblockFilteringConfigurationName);
+ configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration == configurations.end());
+}
+
+// 3rd run: verify "adblock" configuration is not present.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ RemoveAdblockConfiguration) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration == configurations.end());
+}
+
+// 1st run: set legacy prefs and verify migration in the next run.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_MigrateSettings) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto* prefs = GetPrefs();
+ ASSERT_FALSE(
+ prefs->GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ ASSERT_TRUE(
+ prefs->GetList(common::prefs::kAdblockCustomSubscriptionsLegacy).empty());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ GURL{"https://custom.bar"}) == subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ GURL{"https://default.bar"}) == subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+ ASSERT_TRUE((*configuration)->GetAllowedDomains().empty());
+ ASSERT_TRUE((*configuration)->GetCustomFilters().empty());
+ ASSERT_TRUE((*configuration)->IsEnabled());
+
+ // Now set legacy prefs.
+ {
+ ScopedListPrefUpdate update(
+ prefs, common::prefs::kAdblockCustomSubscriptionsLegacy);
+ update->Append("https://custom.bar");
+ }
+ {
+ ScopedListPrefUpdate update(prefs,
+ common::prefs::kAdblockSubscriptionsLegacy);
+ update->Append("https://default.bar");
+ }
+ {
+ ScopedListPrefUpdate update(prefs,
+ common::prefs::kAdblockAllowedDomainsLegacy);
+ update->Append("example.com");
+ }
+ {
+ ScopedListPrefUpdate update(prefs,
+ common::prefs::kAdblockCustomFiltersLegacy);
+ update->Append("test.com$script");
+ }
+ prefs->SetBoolean(common::prefs::kEnableAdblockLegacy, false);
+ prefs->SetBoolean(common::prefs::kEnableAcceptableAdsLegacy, false);
+
+ // Remove "adblock" configuration to trigger migration in the next run.
+ UninstallFilteringConfiguration(kAdblockFilteringConfigurationName);
+}
+
+// 2nd run: check migrated settings.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ MigrateSettings) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto* prefs = GetPrefs();
+ ASSERT_FALSE(
+ prefs->GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ ASSERT_FALSE((*configuration)->IsEnabled());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ GURL{"https://custom.bar"}) != subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ GURL{"https://default.bar"}) != subscriptions.end());
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) == subscriptions.end());
+ auto domains = (*configuration)->GetAllowedDomains();
+ ASSERT_TRUE(std::find(domains.begin(), domains.end(), "example.com") !=
+ domains.end());
+ auto filters = (*configuration)->GetCustomFilters();
+ ASSERT_TRUE(std::find(filters.begin(), filters.end(), "test.com$script") !=
+ filters.end());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_PersistDisabledAAState) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+ (*configuration)->RemoveFilterList(AcceptableAdsUrl());
+ subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_FALSE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PersistDisabledAAState) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_FALSE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PRE_PersistEnabledAAState) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+ (*configuration)->RemoveFilterList(AcceptableAdsUrl());
+ subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_FALSE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+ (*configuration)->AddFilterList(AcceptableAdsUrl());
+ subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationBrowserTest,
+ PersistEnabledAAState) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration =
+ base::ranges::find(configurations, kAdblockFilteringConfigurationName,
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ auto subscriptions = (*configuration)->GetFilterLists();
+ ASSERT_TRUE(std::find(subscriptions.begin(), subscriptions.end(),
+ AcceptableAdsUrl()) != subscriptions.end());
+}
+
+class AdblockFilteringConfigurationDisableSwitchBrowserTest
+ : public AdblockFilteringConfigurationBrowserTest {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ if (base::StartsWith(
+ ::testing::UnitTest::GetInstance()->current_test_info()->name(),
+ "PRE_CreateConfigAndConfirmEnableStateAfterReset")) {
+ command_line->AppendSwitch(adblock::switches::kDisableEyeoFiltering);
+ }
+ }
+};
+
+// 1st run: create configuration and make sure it is enabled by default.
+IN_PROC_BROWSER_TEST_F(
+ AdblockFilteringConfigurationDisableSwitchBrowserTest,
+ PRE_PRE_PRE_CreateConfigAndConfirmEnableStateAfterReset) {
+ auto configuration = MakeConfiguration("persistent");
+ ASSERT_TRUE(configuration->IsEnabled());
+ InstallFilteringConfiguration(std::move(configuration));
+}
+
+// 2nd run: make sure configuration is enabled after restart.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationDisableSwitchBrowserTest,
+ PRE_PRE_CreateConfigAndConfirmEnableStateAfterReset) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ ASSERT_TRUE((*configuration)->IsEnabled());
+}
+
+// 3rd run: after adding "--disable-eyeo-filtering" make sure configuration is
+// disabled.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationDisableSwitchBrowserTest,
+ PRE_CreateConfigAndConfirmEnableStateAfterReset) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ ASSERT_FALSE((*configuration)->IsEnabled());
+}
+
+// 4th run: without "--disable-eyeo-filtering" make sure configuration is still
+// disabled.
+IN_PROC_BROWSER_TEST_F(AdblockFilteringConfigurationDisableSwitchBrowserTest,
+ CreateConfigAndConfirmEnableStateAfterReset) {
+ auto configurations =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetInstalledFilteringConfigurations();
+ auto configuration = base::ranges::find(configurations, "persistent",
+ &FilteringConfiguration::GetName);
+ ASSERT_TRUE(configuration != configurations.end());
+ ASSERT_FALSE((*configuration)->IsEnabled());
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_non_ascii_browsertest.cc b/components/adblock/content/browser/test/adblock_non_ascii_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_non_ascii_browsertest.cc
@@ -0,0 +1,67 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/strings/stringprintf.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockNonASCIIBrowserTest : public AdblockBrowserTestBase {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("xn----dtbfdbwspgnceulm.xn--p1ai", "127.0.0.1");
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ "components/test/data/adblock");
+ content::SetupCrossSiteRedirector(embedded_test_server());
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ std::string ExecuteScriptAndExtractString(const std::string& js_code) {
+ return content::EvalJs(web_contents(), js_code).ExtractString();
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockNonASCIIBrowserTest, BlockNonASCII) {
+ static constexpr char visibility_check[] =
+ "getComputedStyle(document.getElementsByClassName(\"форум\")[0]).display"
+ " === '%s'";
+
+ ASSERT_TRUE(content::NavigateToURL(
+ shell(),
+ embedded_test_server()->GetURL("форум-трейдеров.рф", "/non-ascii.html")));
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(visibility_check, "block").c_str()));
+
+ SetFilters({"xn----dtbfdbwspgnceulm.xn--p1ai##.форум"});
+ ASSERT_TRUE(content::NavigateToURL(
+ shell(),
+ embedded_test_server()->GetURL("форум-трейдеров.рф", "/non-ascii.html")));
+ EXPECT_TRUE(WaitAndVerifyCondition(
+ base::StringPrintf(visibility_check, "none").c_str()));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_page_view_stats_browsertest.cc b/components/adblock/content/browser/test/adblock_page_view_stats_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_page_view_stats_browsertest.cc
@@ -0,0 +1,840 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+
+#include "base/json/json_reader.h"
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+#include "gtest/gtest.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+namespace {
+
+struct ExpectedPageViewCount {
+ int aa_count;
+ int aa_bt_count;
+ int allowing_count;
+ int blocking_count;
+ int total_count;
+};
+
+enum ServerRespondsWith {
+ Ok,
+ Error,
+};
+
+} // namespace
+
+class AdblockPageViewStatsBrowserTest : public AdblockBrowserTestBase {
+ public:
+ AdblockPageViewStatsBrowserTest()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+ net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+ cert_config.dns_names = {"easylist-downloads.adblockplus.org",
+ GetTelemetryDomain(), "ad-delivery.net",
+ "btloader.com", "example.com"};
+ https_server_.SetSSLConfig(cert_config);
+
+ https_server_.RegisterRequestHandler(
+ base::BindRepeating(&AdblockPageViewStatsBrowserTest::RequestHandler,
+ base::Unretained(this)));
+ EXPECT_TRUE(https_server_.Start());
+
+ SetFilterListServerPortForTesting(https_server_.port());
+
+ ActivepingTelemetryTopicProvider::SetHttpsPortForTesting(
+ https_server_.port());
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ // Remove all pre-installed filter lists to avoid a race. We will re-add
+ // them in the test, ensuring that we set up the mock responses first.
+ RemoveAllDefaultFilterLists();
+ }
+
+ void TearDownOnMainThread() override {
+ AdblockBrowserTestBase::TearDownOnMainThread();
+ }
+
+ void RemoveAllDefaultFilterLists() {
+ for (const auto& subscription :
+ GetAdblockFilteringConfiguration()->GetFilterLists()) {
+ GetAdblockFilteringConfiguration()->RemoveFilterList(subscription);
+ }
+ }
+
+ void AddEasylistFilters(std::string_view filters) {
+ // Prepare the response (returned by RequestHandler) to contain the filters
+ // needed for the test.
+ mock_easylist_filters_ = "[Adblock Plus 2.0]\n" + std::string(filters);
+ // Add the filter list to the configuration, this will trigger a download.
+ GetAdblockFilteringConfiguration()->AddFilterList(DefaultSubscriptionUrl());
+ auto waiter = GetSubscriptionInstalledWaiter();
+ waiter.WaitUntilSubscriptionsInstalled({DefaultSubscriptionUrl()});
+ }
+
+ void AddExceptionrulesFilters(std::string_view filters) {
+ mock_exceptionrules_filters_ =
+ "[Adblock Plus 2.0]\n" + std::string(filters);
+ GetAdblockFilteringConfiguration()->AddFilterList(AcceptableAdsUrl());
+ auto waiter = GetSubscriptionInstalledWaiter();
+ waiter.WaitUntilSubscriptionsInstalled({AcceptableAdsUrl()});
+ }
+
+ void AddCustomFilters(std::vector<std::string> filters) {
+ for (const auto& filter : filters) {
+ GetAdblockFilteringConfiguration()->AddCustomFilter(filter);
+ }
+ }
+
+ void RegisterHtmlContent(std::string_view path, std::string_view content) {
+ mock_websites_.push_back({path, content});
+ }
+
+ std::unique_ptr<net::test_server::BasicHttpResponse> RespondWithContent(
+ std::string_view content,
+ std::string_view content_type) {
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(content);
+ http_response->set_content_type(content_type);
+ return http_response;
+ }
+
+ std::unique_ptr<net::test_server::BasicHttpResponse> RedirectToPage(
+ const std::string& redirect_to_target) {
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", redirect_to_target);
+ return http_response;
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> CreateTelemetryResponse() {
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ if (expected_response_ == ServerRespondsWith::Error) {
+ http_response->set_code(net::HTTP_NOT_FOUND);
+ } else {
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("{\"token\": \"dummy\"}");
+ http_response->set_content_type("text/plain");
+ }
+ return std::move(http_response);
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (base::StartsWith(request.relative_url, AcceptableAdsUrl().path())) {
+ return RespondWithContent(mock_exceptionrules_filters_, "text/plain");
+ } else if (base::StartsWith(request.relative_url,
+ DefaultSubscriptionUrl().path())) {
+ return RespondWithContent(mock_easylist_filters_, "text/plain");
+ }
+ if (base::StartsWith(request.relative_url, "/recovery?w=")) {
+ return RespondWithContent("", "text/plain");
+ }
+ if (base::StartsWith(request.relative_url, "/px.gif?ch=")) {
+ return RespondWithContent("", "image/gif");
+ }
+ if (base::StartsWith(request.relative_url, "/redirect_to")) {
+ GURL redirect_target("https://example.com/" + request.GetURL().query());
+ const GURL new_url = GetUrlmatchingServerPort(redirect_target);
+ return RedirectToPage(new_url.spec());
+ }
+
+ if (base::StartsWith(request.relative_url,
+ "/topic/eyeochromium_activeping/version/2")) {
+ EXPECT_TRUE(request.has_content);
+ telemetry_response_ = base::JSONReader::Read(request.content);
+ NotifyTestFinished();
+ return CreateTelemetryResponse();
+ }
+ const auto website = base::ranges::find_if(
+ mock_websites_, [&request](const MockWebsiteContent& website) {
+ return base::StartsWith(request.relative_url, website.url_path);
+ });
+ if (website != mock_websites_.end()) {
+ return RespondWithContent(website->html_content, "text/html");
+ }
+ // Unhandled requests result in the Embedded test server sending a 404. This
+ // is fine for the purpose of this test.
+ return nullptr;
+ }
+
+ void VerifyPageViewCount(ExpectedPageViewCount expected) {
+ auto* dict_payload = GetPayload();
+ auto* value = dict_payload->Find("aa_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.aa_count, value->GetInt());
+
+ value = dict_payload->Find("aa_bt_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.aa_bt_count, value->GetInt());
+
+ value = dict_payload->Find("allowed_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.allowing_count, value->GetInt());
+
+ value = dict_payload->Find("blocked_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.blocking_count, value->GetInt());
+
+ value = dict_payload->Find("pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.total_count, value->GetInt());
+ }
+
+ void NavigateToPage(GURL start_url,
+ GURL redirection_url = GURL(/*empty invalid url*/)) {
+ const GURL new_start_url = GetUrlmatchingServerPort(start_url);
+ // Navigate. Whatever the domain was, it will be redirected to localhost
+ // due to the host resolver rule. Filter matching will still see the
+ // original URL.
+ if (redirection_url.is_valid()) {
+ const GURL new_final_url = GetUrlmatchingServerPort(redirection_url);
+ ASSERT_TRUE(
+ content::NavigateToURL(shell(), new_start_url, new_final_url));
+ } else {
+ ASSERT_TRUE(content::NavigateToURL(shell(), new_start_url));
+ }
+ }
+
+ // Calling TriggerTelemetry(ServerRespondsWith::Error) prevents page view
+ // stats from being reset after triggering telemetry request.
+ void TriggerTelemetry(
+ ServerRespondsWith expected_response = ServerRespondsWith::Ok) {
+ expected_response_ = expected_response;
+ telemetry_response_ = absl::nullopt;
+ finish_condition_met_ = false;
+ AdblockTelemetryServiceFactory::GetForBrowserContext(browser_context())
+ ->TriggerConversationsWithoutDueTimeCheckForTesting();
+ RunUntilTestFinished();
+ }
+
+ protected:
+ FilteringConfiguration* GetAdblockFilteringConfiguration() {
+ return SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ }
+
+ GURL GetUrlmatchingServerPort(const GURL& initial_url) {
+ // Replace the port to match the EmbeddedTestServer.
+ GURL::Replacements replacements;
+ const std::string port_str = base::NumberToString(https_server_.port());
+ replacements.SetPortStr(port_str);
+ return initial_url.ReplaceComponents(replacements);
+ }
+
+ base::Value::Dict* GetPayload() {
+ EXPECT_TRUE(telemetry_response_ && telemetry_response_->is_dict());
+ base::Value::Dict* parsed_dict = telemetry_response_->GetIfDict();
+ EXPECT_TRUE(parsed_dict);
+ base::Value::Dict* payload = parsed_dict->FindDict("payload");
+ EXPECT_TRUE(payload);
+ return payload;
+ }
+
+ struct MockWebsiteContent {
+ std::string_view url_path; // All URLs are relative to localhost, only the
+ // path matters. Eg. "/test_page.html"
+ std::string_view html_content;
+ };
+ net::EmbeddedTestServer https_server_;
+ std::string mock_easylist_filters_;
+ std::string mock_exceptionrules_filters_;
+ std::vector<MockWebsiteContent> mock_websites_;
+ absl::optional<base::Value> telemetry_response_ = absl::nullopt;
+ ServerRespondsWith expected_response_ = ServerRespondsWith::Ok;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ SiteWithNoBlockedRequestDoesNotCount) {
+ // There are some filters defined...
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@resource.png");
+ // But none of them hit on this page:
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="image.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ SiteWithNoAllowlistingCountAsBlocked) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("");
+
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ResourceAllowedByExceptionrulesCountsInTwoMetrics) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ResourceAllowedByEasylistCountsInAllowlistingMetric) {
+ AddEasylistFilters(R"(
+ blocked_resource.png
+ @@.png
+ )");
+ AddExceptionrulesFilters("");
+
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ResourceAllowedByBlocktrhoughExceptionrulesCounted) {
+ // Expected script signaling that we need to increase aa_bt_count is requested
+ // when Easylist is detected (filter "||ad-delivery.net^") and
+ // then AA is detected (filter "@@||ad-delivery.net*/px.gif?ch=1").
+ std::string easylist_detect_filter = "||ad-delivery.net^";
+ auto easylist_detect_request = base::StringPrintf(
+ R"(https://ad-delivery.net:%d/px.gif?ch=2)", https_server_.port());
+ std::string aa_detect_filter = "@@||ad-delivery.net*/px.gif?ch=1";
+ auto aa_detect_request = base::StringPrintf(
+ R"(https://ad-delivery.net:%d/px.gif?ch=1&e=0.09078094279406423)",
+ https_server_.port());
+
+ AddEasylistFilters(easylist_detect_filter);
+ AddExceptionrulesFilters(aa_detect_filter);
+
+ auto expected_aa_bt_count_request = base::StringPrintf(
+ R"(https://btloader.com:%d/recovery?w=5742015956385792&upapi=true)",
+ https_server_.port());
+
+ // Here we just make all requests expected by the flow at once not in order,
+ // but in real case easylist_detect_request is triggered and when blocked,
+ // then aa_detect_request is triggered and when allowed, then
+ // expected_aa_bt_count_request is triggered. Also in real scenario both
+ // detection requests are triggered from a script not from a page directly.
+ auto page = base::StringPrintf(R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="%s">
+ <img src="%s">
+ <script src=%s></script>
+ </body>
+ </html>
+ )",
+ easylist_detect_request.c_str(),
+ aa_detect_request.c_str(),
+ expected_aa_bt_count_request.c_str());
+ RegisterHtmlContent("/test_page.html", page);
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ // aa_bt_count is subcategory of aa_count (which is subcategory of
+ // allowing_count) hence we increase once aa_bt_count, aa_count,
+ // allowing_count. But also during detection blocking filter is hit and
+ // blocking_count is increased.
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 1,
+ .allowing_count = 1,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ MultipleAllowedResourcesCountAsSingleHit) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+ // The image request is blocked by easylist but allowlisted by AA.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png?param=1">
+ <img src="blocked_resource.png?param=2">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ NavigatingToTheSiteAgainCountsAsNewHit) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+ // The image request is blocked by easylist but allowlisted by AA.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry(ServerRespondsWith::Error);
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ResourceAllowedWithinIframeCountsTowardsParentFrameHit) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+ // The main frame loads an iframe.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <iframe src="iframe.html"></iframe>
+ </body>
+ </html>
+ )");
+ // The iframe loads an image that is blocked by easylist but allowlisted by
+ // AA.
+ RegisterHtmlContent("/iframe.html", R"(
+ <html>
+ <head>
+ <title>Test iframe</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockPageViewStatsBrowserTest,
+ MultipleResourcesAllowedAcrossMultipleFramesCountTowardSingleHit) {
+ AddEasylistFilters(R"(
+ blocked_resource.png
+ blocked_ad.png
+ )");
+ AddExceptionrulesFilters("@@blocked*.png");
+ // The main frame loads 2 iframes and some blocked ad.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <iframe src="iframe.html?param=1"></iframe>
+ <iframe src="iframe.html?param=2"></iframe>
+ <img src="blocked_ad.png">
+ </body>
+ </html>
+ )");
+ // The iframe loads an image that is blocked by easylist but allowlisted by
+ // AA.
+ RegisterHtmlContent("/iframe.html", R"(
+ <html>
+ <head>
+ <title>Test iframe</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ WebsiteAllowlistedWithDocumentFilterCountsInTwoMetrics) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@example.com$document");
+ // The main frame is allowlisted by a document filter (full page allowlist).
+ // It loads an iframe with blocked content.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <iframe src="iframe.html"></iframe>
+ </body>
+ </html>
+ )");
+ RegisterHtmlContent("/iframe.html", R"(
+ <html>
+ <head>
+ <title>Test iframe</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockPageViewStatsBrowserTest,
+ IframeAllowlistedWithSubdocumentFilterCountsInTwoMetrics) {
+ AddEasylistFilters("iframe.html");
+ AddExceptionrulesFilters("@@iframe.html$subdocument");
+ // The main frame loads an iframe that is allowlisted by a subdocument filter.
+ // The iframe contains blocked content.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <iframe src="iframe.html"></iframe>
+ </body>
+ </html>
+ )");
+ RegisterHtmlContent("/iframe.html", R"(
+ <html>
+ <head>
+ <title>Test iframe</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ IframeBlockedWithSubdocumentFilterCounted) {
+ AddEasylistFilters("iframe.html");
+ // The main frame loads an iframe that is blocked by a subdocument filter.
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <iframe src="iframe.html"></iframe>
+ </body>
+ </html>
+ )");
+ RegisterHtmlContent("/iframe.html", R"(
+ <html>
+ <head>
+ <title>Test iframe</title>
+ </head>
+ <body>
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ PRE_CountResetAfterSuccessfulEyeometryRequest) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ CountResetAfterSuccessfulEyeometryRequest) {
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 0});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ CountNotResetAfterFailedEyeometryRequest) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("@@blocked_resource.png");
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry(ServerRespondsWith::Error);
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+
+ TriggerTelemetry(ServerRespondsWith::Error);
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+
+ NavigateToPage(GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry(ServerRespondsWith::Error);
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ServerRedirectionNotIncrementsPageCount) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("");
+ RegisterHtmlContent("/test_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/redirect_to?test_page.html"),
+ GURL("https://example.com/test_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ ClientRedirectionIncrementsPageCount) {
+ AddEasylistFilters("blocked_resource.png");
+ AddExceptionrulesFilters("");
+ RegisterHtmlContent("/before_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ <script type="text/javascript">
+ window.location = "/after_page.html"
+ </script>
+ </head>
+ </html>
+ )");
+ RegisterHtmlContent("/after_page.html", R"(
+ <html>
+ <head>
+ <title>Test page</title>
+ </head>
+ <body>
+ <img src="blocked_resource.png">
+ </body>
+ </html>
+ )");
+ NavigateToPage(GURL("https://example.com/before_page.html"),
+ GURL("https://example.com/after_page.html"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 2});
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockPageViewStatsBrowserTest,
+ AboutBlankPageIsNotCounted) {
+ NavigateToPage(GURL("about:blank"));
+
+ TriggerTelemetry();
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 0});
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_request_throttle_browsertest.cc b/components/adblock/content/browser/test/adblock_request_throttle_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_request_throttle_browsertest.cc
@@ -0,0 +1,127 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/adblock_request_throttle.h"
+
+#include "base/check.h"
+#include "base/functional/callback_forward.h"
+#include "base/time/time.h"
+#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h"
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "gtest/gtest.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockRequestThrottleBrowsertest : public AdblockBrowserTestBase {
+ public:
+ AdblockRequestThrottleBrowsertest()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+ auto request_handler =
+ base::BindRepeating(&AdblockRequestThrottleBrowsertest::RequestHandler,
+ base::Unretained(this));
+ https_server_.RegisterRequestHandler(request_handler);
+ // Filter list requests and recommendations.json are made over HTTPS.
+ // All of them target the same host, so we can use the same certificate.
+ net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+ cert_config.dns_names = {"easylist-downloads.adblockplus.org",
+ GetTelemetryDomain()};
+ https_server_.SetSSLConfig(cert_config);
+ EXPECT_TRUE(https_server_.Start());
+ SetFilterListServerPortForTesting(https_server_.port());
+
+ ActivepingTelemetryTopicProvider::SetHttpsPortForTesting(
+ https_server_.port());
+ // Make sure telemetry pings are sent often enough for our test to register
+ // one.
+ const auto testing_interval = base::Seconds(2);
+ ActivepingTelemetryTopicProvider::SetIntervalsForTesting(testing_interval);
+ AdblockTelemetryServiceFactory::GetInstance()->SetCheckIntervalForTesting(
+ testing_interval);
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ // Delay all network requests by 5 seconds (instead of the default 30, to
+ // make the test finish within the timeout).
+ AdblockRequestThrottleFactory::GetForBrowserContext(browser_context())
+ ->AllowRequestsAfter(base::Seconds(5));
+ host_resolver()->AddRule("*", "127.0.0.1");
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ // We expect 4 filter list downloads.
+ if (request.method == net::test_server::HttpMethod::METHOD_GET &&
+ (base::StartsWith(request.relative_url, "/abp-filters-anti-cv.txt") ||
+ base::StartsWith(request.relative_url, "/easylist.txt") ||
+ base::StartsWith(request.relative_url, "/exceptionrules.txt") ||
+ base::StartsWith(request.relative_url, "/recommendations.json"))) {
+ default_lists_.insert(request.relative_url.substr(
+ 1, request.relative_url.find_first_of("?") - 1));
+ }
+ // We also expect an activeping request.
+ if (request.method == net::test_server::HttpMethod::METHOD_POST &&
+ base::StartsWith(request.relative_url,
+ "/topic/eyeochromium_activeping")) {
+ activeping_request_received_ = true;
+ }
+ // If we get all expected requests we simply finish the test by closing
+ // the browser, otherwise test will fail with a timeout.
+ if (CheckExpectedDownloads()) {
+ NotifyTestFinished();
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ // This is fine for the purpose of this test.
+ return nullptr;
+ }
+
+ bool CheckExpectedDownloads() {
+ return default_lists_.size() == 4 && activeping_request_received_;
+ }
+
+ protected:
+ net::EmbeddedTestServer https_server_;
+ std::set<std::string> default_lists_;
+ bool activeping_request_received_ = false;
+ bool finish_condition_met_ = false;
+ base::RepeatingClosure quit_closure_;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockRequestThrottleBrowsertest,
+ AllInitialDownloadsAllowedEventually) {
+ // Runs untill all expected initial network requests are made:
+ // - default filter lists
+ // - activeping telemetry
+ // - recommendations.json
+ // This will "hang" for 5 seconds intentionally, esuring the request throttler
+ // does its job.
+ RunUntilTestFinished();
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_service_workers_browsertest.cc b/components/adblock/content/browser/test/adblock_service_workers_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_service_workers_browsertest.cc
@@ -0,0 +1,208 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <list>
+
+#include "base/memory/raw_ptr.h"
+#include "base/ranges/algorithm.h"
+#include "base/run_loop.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "gmock/gmock.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockServiceWorkersBrowserTest : public AdblockBrowserTestBase {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ // Note, fetch_from_service_worker.js and fetch_from_service_worker.html are
+ // also available in content/test/data. This could be a content_browsertest,
+ // probably.
+ embedded_test_server()->ServeFilesFromSourceDirectory("chrome/test/data");
+ embedded_test_server()->RegisterRequestHandler(
+ base::BindRepeating(&AdblockServiceWorkersBrowserTest::RequestHandler,
+ base::Unretained(this)));
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (request.relative_url == "/requested_path") {
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("fetch completed");
+ http_response->set_content_type("text/plain");
+ http_response->AddCustomHeader("test_header_key", "test_header_value");
+ return std::move(http_response);
+ }
+ return nullptr;
+ }
+
+ void AddAllowedDomain(std::string domain) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ adblock_configuration->AddAllowedDomain(domain);
+ }
+
+ GURL GetPageUrl() {
+ // Reusing an existing test page to avoid creating a new one.
+ // This page exposes a setup() function to register a service worker
+ // and a fetch_from_service_worker() function to perform a network fetch
+ // by sending an internal message to the service worker.
+ return embedded_test_server()->GetURL(
+ "/service_worker/fetch_from_service_worker.html");
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest, NoBlockingByDefault) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+
+ // "fetch completed" is returned by our RequestHandler.
+ EXPECT_EQ("fetch completed",
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ ResourceBlockedByCustomFilter) {
+ SetFilters({"*requested_path"});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+
+ EXPECT_EQ("TypeError: Failed to fetch",
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ ResourceBlockedByHeaderFilter) {
+ // A header filter must have a pattern that matches the request URL.
+ // The pattern is the host name of the test server.
+ const auto host_port_pair = embedded_test_server()->host_port_pair();
+ const std::string header_filter =
+ host_port_pair.host() + "$header=test_header_key=test_header_value";
+ SetFilters({header_filter});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+
+ EXPECT_EQ("TypeError: Failed to fetch",
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ ResourceRedirectedByRewriteFilter) {
+ const auto host_port_pair = embedded_test_server()->host_port_pair();
+ const auto host = host_port_pair.host();
+ const auto filter =
+ "||" + host +
+ "*/requested_path$rewrite=abp-resource:blank-html,domain=" + host;
+ SetFilters({filter});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+
+ // The service worker fetches /requested_path, but the response is a blank
+ // HTML document - as redirected by the filter.
+ // Seems that JS escapes '<' characters with '\u003C' in the response,
+ // possibly as a side effect or safety measure related to eval().
+ const std::string_view blank_html =
+ "\u003C!DOCTYPE "
+ "html>\u003Chtml>\u003Chead>\u003C/head>\u003Cbody>\u003C/body>\u003C/"
+ "html>";
+ EXPECT_EQ(blank_html,
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ ResourceAllowedByResourceSpecificAllowingRule) {
+ SetFilters({"*resource.png"});
+ SetFilters({"@@*resource.png"});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+ EXPECT_EQ("fetch completed",
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ ResourceAllowedByOrigin) {
+ SetFilters({"*resource.png"});
+ AddAllowedDomain(GetPageUrl().host());
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+ EXPECT_EQ("fetch completed",
+ content::EvalJs(web_contents(),
+ "fetch_from_service_worker('/requested_path');"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest, EntireScriptBlocked) {
+ SetFilters({"*fetch_from_service_worker.js"});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ // The content of the service worker script is not allowed to download, the
+ // page cannot register the service worker.
+ const auto setup_result = content::EvalJs(web_contents(), "setup();");
+ // The error message will indicate blocking succeeded.
+ EXPECT_NE(
+ setup_result.error.find("TypeError: Failed to register a ServiceWorker"),
+ std::string::npos);
+ EXPECT_NE(setup_result.error.find("error occurred when fetching the script"),
+ std::string::npos);
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ EntireScriptAllowedByOrigin) {
+ SetFilters({"*fetch_from_service_worker.js"});
+ AddAllowedDomain(GetPageUrl().host());
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockServiceWorkersBrowserTest,
+ EntireScriptAllowedBySpecificAllowingRule) {
+ SetFilters({"*fetch_from_service_worker.js"});
+ SetFilters({"@@*fetch_from_service_worker.js"});
+
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ EXPECT_EQ("ready", content::EvalJs(web_contents(), "setup();"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_sitekey_browsertest.cc b/components/adblock/content/browser/test/adblock_sitekey_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_sitekey_browsertest.cc
@@ -0,0 +1,177 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/base64.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/signature_creator.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockSitekeyTest : public AdblockBrowserTestBase {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule(kTestDomain, "127.0.0.1");
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &AdblockSitekeyTest::RequestHandler, base::Unretained(this)));
+ ASSERT_TRUE(embedded_test_server()->Start());
+ InitResourceClassificationObserver();
+ }
+
+ virtual std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ if (base::StartsWith(request.relative_url, "/test_page.html")) {
+ static constexpr char kMainFrame[] =
+ R"(
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <iframe src="sitekey_iframe.html"></iframe>
+ </body>
+ </html>)";
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ if (add_doc_sitekey_) {
+ http_response->AddCustomHeader(
+ kSiteKeyHeaderKey, sitekey_publickey_ + "_" + sitekey_signature_);
+ }
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(kMainFrame);
+ http_response->set_content_type("text/html");
+ return std::move(http_response);
+ } else if (base::StartsWith(request.relative_url, "/sitekey_iframe.html")) {
+ static constexpr char kIframe[] =
+ R"(
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <img src="/iframe_image.png" />
+ </body>
+ </html>)";
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ if (add_iframe_sitekey_) {
+ http_response->AddCustomHeader(
+ kSiteKeyHeaderKey, sitekey_publickey_ + "_" + sitekey_signature_);
+ }
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(kIframe);
+ http_response->set_content_type("text/html");
+ return std::move(http_response);
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404. This
+ // is fine for the purpose of this test.
+ return nullptr;
+ }
+
+ GURL GetPageUrl(const std::string& path = "/test_page.html") {
+ return embedded_test_server()->GetURL(kTestDomain, path);
+ }
+
+ void NavigateToPage() {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ }
+
+ protected:
+ void CreateSitekey(const std::string& sitekey_uri) {
+ std::string sitekey_ua =
+ content::ShellContentBrowserClient::Get()->GetUserAgent();
+ std::string sitekey_encryption_input =
+ sitekey_uri + '\0' + kTestDomain + '\0' + sitekey_ua;
+ std::unique_ptr<crypto::RSAPrivateKey> key_original(
+ crypto::RSAPrivateKey::Create(1024));
+ std::vector<uint8_t> priv_key;
+ EXPECT_TRUE(key_original->ExportPrivateKey(&priv_key));
+ std::vector<uint8_t> pub_key;
+ EXPECT_TRUE(key_original->ExportPublicKey(&pub_key));
+ std::unique_ptr<crypto::RSAPrivateKey> key(
+ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(priv_key));
+ std::unique_ptr<crypto::SignatureCreator> signer(
+ crypto::SignatureCreator::Create(key.get(),
+ crypto::SignatureCreator::SHA1));
+ EXPECT_TRUE(signer.get());
+ EXPECT_TRUE(signer->Update(
+ reinterpret_cast<const uint8_t*>(sitekey_encryption_input.c_str()),
+ sitekey_encryption_input.size()));
+ std::vector<uint8_t> signature;
+ EXPECT_TRUE(signer->Final(&signature));
+ sitekey_signature_ = base::Base64Encode(signature);
+ sitekey_publickey_ = base::Base64Encode(pub_key);
+ }
+
+ std::string sitekey_signature_;
+ std::string sitekey_publickey_;
+ bool add_doc_sitekey_ = false;
+ bool add_iframe_sitekey_ = false;
+ static constexpr char kTestDomain[] = "test.org";
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockSitekeyTest, VerifyIframeSitekey) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration);
+ CreateSitekey("/sitekey_iframe.html");
+ add_iframe_sitekey_ = true;
+ SetFilters(
+ {"iframe_image.png", "@@iframe_image.png$sitekey=" + sitekey_publickey_});
+ NavigateToPage();
+ ASSERT_EQ(observer_.allowed_ads_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.allowed_ads_notifications.front() ==
+ GetPageUrl("/iframe_image.png"))
+ << "Request not allowed!";
+ EXPECT_TRUE(observer_.allowed_pages_notifications.empty());
+ EXPECT_TRUE(observer_.blocked_ads_notifications.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockSitekeyTest, VerifyDocumentSitekey) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ DCHECK(adblock_configuration);
+ CreateSitekey("/test_page.html");
+ add_doc_sitekey_ = true;
+ SetFilters({"iframe_image.png",
+ "@@iframe_image.png$sitekey=" + sitekey_publickey_,
+ "@@test.org$document,sitekey=" + sitekey_publickey_});
+ NavigateToPage();
+ ASSERT_EQ(observer_.allowed_ads_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.allowed_ads_notifications.front() ==
+ GetPageUrl("/iframe_image.png"))
+ << "Request not allowed!";
+ ASSERT_EQ(observer_.allowed_pages_notifications.size(), 1u);
+ EXPECT_TRUE(observer_.allowed_pages_notifications.front() ==
+ GetPageUrl("/test_page.html"))
+ << "Request not allowed!";
+ EXPECT_TRUE(observer_.blocked_ads_notifications.empty());
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_snippets_browsertest.cc b/components/adblock/content/browser/test/adblock_snippets_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_snippets_browsertest.cc
@@ -0,0 +1,76 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <vector>
+
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockSnippetsBrowserTest : public AdblockBrowserTestBase {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ "components/test/data/adblock");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ void SetFilters(std::vector<std::string> filters) {
+ auto* adblock_configuration =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context())
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName);
+ for (auto& filter : filters) {
+ adblock_configuration->AddCustomFilter(filter);
+ }
+ }
+
+ GURL GetUrl(const std::string& path) {
+ return embedded_test_server()->GetURL("example.org", path);
+ }
+
+ void VerifyTargetVisibility(bool is_hidden, const std::string& id) {
+ std::string condition_js =
+ "getComputedStyle(document.getElementById('{{node id}}')).display "
+ "{{condition}} 'none'";
+ base::ReplaceSubstringsAfterOffset(&condition_js, 0, "{{node id}}", id);
+ base::ReplaceSubstringsAfterOffset(&condition_js, 0, "{{condition}}",
+ is_hidden ? "==" : "!=");
+ EXPECT_TRUE(WaitAndVerifyCondition(condition_js.c_str()));
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockSnippetsBrowserTest, VerifyXpath3) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl("/xpath3.html")));
+ VerifyTargetVisibility(false, "xpath3-target");
+ SetFilters(
+ {"example.org#$#hide-if-matches-xpath3 //*[@id=\"xpath3-target\"]"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl("/xpath3.html")));
+ VerifyTargetVisibility(true, "xpath3-target");
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_subscription_service_browsertest.cc b/components/adblock/content/browser/test/adblock_subscription_service_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_subscription_service_browsertest.cc
@@ -0,0 +1,202 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/ranges/algorithm.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "base/test/bind.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/version_info/version_info.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/http/http_request_headers.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockSubscriptionServiceBrowserTest
+ : public AdblockBrowserTestBase,
+ public SubscriptionService::SubscriptionObserver {
+ public:
+ AdblockSubscriptionServiceBrowserTest() {
+ SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting(
+ base::Seconds(1));
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ https_server_ = std::make_unique<net::EmbeddedTestServer>(
+ net::EmbeddedTestServer::TYPE_HTTPS);
+ https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+ }
+
+ bool RequestHeadersContainAcceptLanguage(
+ const net::test_server::HttpRequest& request) {
+ const auto accept_language_it =
+ request.headers.find(net::HttpRequestHeaders::kAcceptLanguage);
+ return accept_language_it != request.headers.end() &&
+ !accept_language_it->second.empty();
+ }
+
+ bool RequestHeadersContainAcceptEncodingBrotli(
+ const net::test_server::HttpRequest& request) {
+ const auto accept_encoding_it =
+ request.headers.find(net::HttpRequestHeaders::kAcceptEncoding);
+ if (accept_encoding_it == request.headers.end()) {
+ return false;
+ }
+ const auto split_encodings =
+ base::SplitString(accept_encoding_it->second, ",",
+ base::WhitespaceHandling::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+ return base::ranges::find(split_encodings, "br") != split_encodings.end();
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse>
+ HandleSubscriptionUpdateRequestWithUrlCheck(
+ std::string expected_url_part,
+ const net::test_server::HttpRequest& request) {
+ static const char kSubscriptionHeader[] =
+ "[Adblock Plus 2.0]\n"
+ "! Checksum: X5A8vtJDBW2a9EgS9glqbg\n"
+ "! Version: 202202061935\n"
+ "! Last modified: 06 Feb 2022 19:35 UTC\n"
+ "! Expires: 1 days (update frequency)\n\n";
+ if (base::StartsWith(request.relative_url, kSubscription,
+ base::CompareCase::SENSITIVE) &&
+ !request_already_handled_) {
+ request_already_handled_ = true;
+ EXPECT_TRUE(RequestHeadersContainAcceptLanguage(request));
+ EXPECT_TRUE(RequestHeadersContainAcceptEncodingBrotli(request));
+ std::string os;
+ base::ReplaceChars(version_info::GetOSType(), base::kWhitespaceASCII, "",
+ &os);
+ EXPECT_TRUE(request.relative_url.find(expected_url_part) !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("addonName=eyeo-chromium-sdk") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("addonVersion=2.0.0") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("platformVersion=1.0") !=
+ std::string::npos);
+ EXPECT_TRUE(request.relative_url.find("platform=" + os) !=
+ std::string::npos);
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content(kSubscriptionHeader);
+ http_response->set_content_type("text/plain");
+ return std::move(http_response);
+ }
+
+ // Unhandled requests result in the Embedded test server sending a 404.
+ return nullptr;
+ }
+
+ void ExpectFilterListRequestMadeWithLastVersion(std::string last_version) {
+ https_server_->RegisterRequestHandler(
+ base::BindRepeating(&AdblockSubscriptionServiceBrowserTest::
+ HandleSubscriptionUpdateRequestWithUrlCheck,
+ base::Unretained(this), last_version));
+ ASSERT_TRUE(https_server_->Start(kPort));
+ }
+
+ // adblock::SubscriptionService::SubscriptionObserver
+ void OnSubscriptionInstalled(const GURL& url) override {
+ if (base::StartsWith(url.spec(),
+ https_server_->GetURL(kSubscription).spec())) {
+ // In order to ensure next run requests an update, mark the subscription
+ // as almost expired.
+ SubscriptionPersistentMetadataFactory::GetForBrowserContext(
+ browser_context())
+ ->SetExpirationInterval(url, base::Milliseconds(1));
+ NotifyTestFinished();
+ }
+ }
+
+ std::unique_ptr<net::EmbeddedTestServer> https_server_;
+ bool request_already_handled_ = false;
+ static const char kSubscription[];
+ // Port is hardcoded so the server url is the same across tests
+ static const int kPort = 65432;
+};
+
+const char AdblockSubscriptionServiceBrowserTest::kSubscription[] =
+ "/subscription.txt";
+
+IN_PROC_BROWSER_TEST_F(AdblockSubscriptionServiceBrowserTest, PRE_LastVersion) {
+ ExpectFilterListRequestMadeWithLastVersion("&lastVersion=0&");
+ // Downloading a filter list and setting its expiry time to almost zero, so
+ // the next run will have to update it ASAP.
+
+ auto* subscription_service =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context());
+ subscription_service->AddObserver(this);
+ // Using a custom subscription URL here because before test sets
+ // up the server then SubscriptionService already started fetching default
+ // subscriptions.
+ subscription_service
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName)
+ ->AddFilterList(https_server_->GetURL(kSubscription));
+ // Wait until subscription is downloaded and stored.
+ RunUntilTestFinished();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockSubscriptionServiceBrowserTest, LastVersion) {
+ ExpectFilterListRequestMadeWithLastVersion("&lastVersion=202202061935&");
+ auto* subscription_service =
+ SubscriptionServiceFactory::GetForBrowserContext(browser_context());
+ subscription_service->AddObserver(this);
+ // Wait for subscription update to trigger a network request.
+ RunUntilTestFinished();
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockSubscriptionServiceBrowserTest,
+ FilterFileDeletedAfterConversion) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ ConversionExecutors* conversion_executors =
+ SubscriptionServiceFactory::GetInstance();
+ DCHECK(conversion_executors);
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ const auto filter_list_path = temp_dir.GetPath().AppendASCII("easylist.txt");
+ std::vector<std::string_view> filter_list_contents = {
+ "[\"Adblock Plus 2.0\"]\n", "invalid file"};
+ for (const auto& file_content : filter_list_contents) {
+ base::WriteFile(filter_list_path, file_content);
+ ASSERT_TRUE(base::PathExists(filter_list_path));
+ base::RunLoop run_loop;
+ conversion_executors->ConvertFilterListFile(
+ DefaultSubscriptionUrl(), filter_list_path,
+ base::BindLambdaForTesting(
+ [&run_loop](ConversionResult result) { run_loop.Quit(); }));
+ run_loop.Run();
+ ASSERT_FALSE(base::PathExists(filter_list_path));
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_telemetry_service_browsertest.cc b/components/adblock/content/browser/test/adblock_telemetry_service_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_telemetry_service_browsertest.cc
@@ -0,0 +1,280 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/json/json_reader.h"
+#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockTelemetryServiceBrowserTestBase : public AdblockBrowserTestBase {
+ public:
+ AdblockTelemetryServiceBrowserTestBase()
+ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+ net::EmbeddedTestServer::ServerCertificateConfig cert_config;
+ cert_config.dns_names = {GetTelemetryDomain()};
+ https_server_.SetSSLConfig(cert_config);
+ https_server_.RegisterRequestHandler(base::BindRepeating(
+ &AdblockTelemetryServiceBrowserTestBase::RequestHandler,
+ base::Unretained(this)));
+ EXPECT_TRUE(https_server_.Start());
+ ActivepingTelemetryTopicProvider::SetHttpsPortForTesting(
+ https_server_.port());
+ auto testing_interval = base::Seconds(2);
+
+ ActivepingTelemetryTopicProvider::SetIntervalsForTesting(testing_interval);
+ AdblockTelemetryServiceFactory::GetInstance()->SetCheckIntervalForTesting(
+ testing_interval);
+ }
+
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule(
+ ActivepingTelemetryTopicProvider::DefaultBaseUrl().host(), "127.0.0.1");
+ }
+
+ base::Value* GetFirstPing(absl::optional<base::Value>& parsed) {
+ return GetPayload(parsed)->Find("first_ping");
+ }
+
+ base::Value* GetLastPing(absl::optional<base::Value>& parsed) {
+ return GetPayload(parsed)->Find("last_ping");
+ }
+
+ base::Value* GetLastPingTag(absl::optional<base::Value>& parsed) {
+ return GetPayload(parsed)->Find("last_ping_tag");
+ }
+
+ base::Value* GetPreviousLastPing(absl::optional<base::Value>& parsed) {
+ return GetPayload(parsed)->Find("previous_last_ping");
+ }
+
+ void CloseBrowserAsynchronously(content::Shell* shell) { shell->Close(); }
+
+ void CloseBrowserFromAnyThread() {
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
+ ->PostTask(FROM_HERE,
+ base::BindOnce(&AdblockTelemetryServiceBrowserTestBase::
+ CloseBrowserAsynchronously,
+ base::Unretained(this), shell()));
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> CreateResponse(
+ const std::string& token) {
+ auto http_response =
+ std::make_unique<net::test_server::BasicHttpResponse>();
+ http_response->set_code(net::HTTP_OK);
+ http_response->set_content("{\"token\": \"" + token + "\"}");
+ http_response->set_content_type("text/plain");
+ return std::move(http_response);
+ }
+
+ virtual std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) = 0;
+
+ private:
+ base::Value::Dict* GetPayload(absl::optional<base::Value>& parsed) {
+ EXPECT_TRUE(parsed && parsed->is_dict());
+ base::Value::Dict* parsed_dict = parsed->GetIfDict();
+ EXPECT_TRUE(parsed_dict);
+ base::Value::Dict* payload = parsed_dict->FindDict("payload");
+ EXPECT_TRUE(payload);
+ return payload;
+ }
+
+ net::EmbeddedTestServer https_server_;
+};
+
+// Test three initial pings each after startup and each fails for the 1st time
+class AdblockTelemetryServiceFirstPingAfterRestartWithRetryBrowserTest
+ : public AdblockTelemetryServiceBrowserTestBase {
+ public:
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ EXPECT_TRUE(base::StartsWith(request.relative_url,
+ "/topic/eyeochromium_activeping/version/2"));
+ EXPECT_TRUE(request.has_content);
+ absl::optional<base::Value> parsed =
+ base::JSONReader::Read(request.content);
+ base::Value* first_ping = GetFirstPing(parsed);
+ base::Value* last_ping = GetLastPing(parsed);
+ base::Value* previous_last_ping = GetPreviousLastPing(parsed);
+
+ if (expected_first_ping_.empty()) {
+ EXPECT_FALSE(first_ping);
+ } else {
+ EXPECT_EQ(expected_first_ping_, *first_ping);
+ }
+
+ if (expected_last_ping_.empty()) {
+ EXPECT_FALSE(last_ping);
+ } else {
+ EXPECT_EQ(expected_last_ping_, *last_ping);
+ }
+
+ if (expected_previous_last_ping_.empty()) {
+ EXPECT_FALSE(previous_last_ping);
+ } else {
+ EXPECT_EQ(expected_previous_last_ping_, *previous_last_ping);
+ }
+
+ if (!attempt_++) {
+ // Force retry by 404 response but 1st save last_ping_tag if any
+ base::Value* last_ping_tag = GetLastPingTag(parsed);
+ if (last_ping_tag) {
+ previous_last_ping_tag_ = last_ping_tag->GetString();
+ }
+ return nullptr;
+ }
+
+ // Verifies that retried ping has the same last_ping_tag
+ if (!previous_last_ping_tag_.empty()) {
+ base::Value* last_ping_tag = GetLastPingTag(parsed);
+ EXPECT_EQ(previous_last_ping_tag_, *last_ping_tag);
+ }
+
+ NotifyTestFinished();
+
+ return CreateResponse(server_response_);
+ }
+
+ void SetUpInProcessBrowserTestFixture() override {
+ // Here we set returned ping values (server_response_) for current test and
+ // expectations (expected_*_) about ping values for the next test run.
+ // We need to set expectations for telemetry before actual test runs so
+ // we do it in SetUpInProcessBrowserTestFixture.
+ if (base::StartsWith(
+ ::testing::UnitTest::GetInstance()->current_test_info()->name(),
+ "PRE_PRE_TestPing")) {
+ server_response_ = "11111";
+ } else if (base::StartsWith(::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->name(),
+ "PRE_TestPing")) {
+ server_response_ = "22222";
+ expected_first_ping_ = expected_last_ping_ = "11111";
+ } else if (base::StartsWith(::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->name(),
+ "TestPing")) {
+ expected_first_ping_ = "11111";
+ expected_last_ping_ = "22222";
+ expected_previous_last_ping_ = "11111";
+ }
+ AdblockTelemetryServiceBrowserTestBase::SetUpInProcessBrowserTestFixture();
+ }
+
+ void TearDownInProcessBrowserTestFixture() override {
+ // Make sure we called RequestHandler exactly twice: 1st ping failed, 2nd
+ // was successful
+ EXPECT_EQ(2, attempt_);
+ }
+
+ protected:
+ std::string server_response_;
+ std::string expected_first_ping_;
+ std::string expected_last_ping_;
+ std::string expected_previous_last_ping_;
+
+ private:
+ int attempt_ = 0;
+ std::string previous_last_ping_tag_ = "";
+};
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockTelemetryServiceFirstPingAfterRestartWithRetryBrowserTest,
+ PRE_PRE_TestPing) {
+ RunUntilTestFinished();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockTelemetryServiceFirstPingAfterRestartWithRetryBrowserTest,
+ PRE_TestPing) {
+ RunUntilTestFinished();
+}
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockTelemetryServiceFirstPingAfterRestartWithRetryBrowserTest,
+ TestPing) {
+ RunUntilTestFinished();
+}
+
+// Test three inital pings
+class AdblockTelemetryServiceSubsequentPingsBrowserTest
+ : public AdblockTelemetryServiceBrowserTestBase {
+ public:
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request) override {
+ EXPECT_TRUE(base::StartsWith(request.relative_url,
+ "/topic/eyeochromium_activeping/version/2"));
+ EXPECT_TRUE(request.has_content);
+ absl::optional<base::Value> parsed =
+ base::JSONReader::Read(request.content);
+ base::Value* first_ping = GetFirstPing(parsed);
+ base::Value* last_ping = GetLastPing(parsed);
+ base::Value* previous_last_ping = GetPreviousLastPing(parsed);
+
+ if (count_ == 1) {
+ // No ping payload in the very 1st ping
+ EXPECT_FALSE(first_ping);
+ EXPECT_FALSE(last_ping);
+ } else if (count_ == 2) {
+ // For 2nd ping `first_ping` == `last_ping`
+ EXPECT_EQ(first_ping_, *first_ping);
+ EXPECT_EQ(first_ping_, *last_ping);
+ } else if (count_ == 3) {
+ // From 3rd ping onward `first_ping` != `last_ping` and we also get
+ // `previous_last_ping`
+ EXPECT_EQ(first_ping_, *first_ping);
+ EXPECT_EQ(second_ping_, *last_ping);
+ EXPECT_EQ(first_ping_, *previous_last_ping);
+ }
+
+ // If we get three expected telemetry pings we simply finish the test by
+ // closing the browser, otherwise test will fail with a timeout.
+ if (count_ == 3) {
+ NotifyTestFinished();
+ return nullptr;
+ }
+ return CreateResponse(count_++ == 1 ? first_ping_ : second_ping_);
+ }
+
+ void TearDownInProcessBrowserTestFixture() override {
+ // Make sure we called RequestHandler exactly three times
+ EXPECT_EQ(3, count_);
+ }
+
+ private:
+ const std::string first_ping_ = "11111";
+ const std::string second_ping_ = "22222";
+ int count_ = 1;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockTelemetryServiceSubsequentPingsBrowserTest,
+ TestPing) {
+ RunUntilTestFinished();
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_trusted_events_browsertest.cc b/components/adblock/content/browser/test/adblock_trusted_events_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_trusted_events_browsertest.cc
@@ -0,0 +1,135 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdint>
+
+#include "content/public/common/isolated_world_ids.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace adblock {
+
+class AdblockTrustedEventsTest
+ : public content::ContentBrowserTest,
+ public testing::WithParamInterface<content::IsolatedWorldIDs> {
+ public:
+ void SetUpOnMainThread() override {
+ content::ContentBrowserTest::SetUpOnMainThread();
+ host_resolver()->AddRule(kTestDomain, "127.0.0.1");
+ embedded_test_server()->ServeFilesFromSourceDirectory(
+ "components/test/data/adblock");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ GURL GetPageUrl() {
+ return embedded_test_server()->GetURL(kTestDomain, "/trusted_events.html");
+ }
+
+ void NavigateToPage() {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetPageUrl()));
+ static std::string script =
+ R"(
+ function triggerJsClick() {
+ document.getElementById("test_button").click();
+ return false;
+ }
+ function dispatchJsClickEvent() {
+ let clickEvent = new Event('click');
+ document.getElementById("test_button").dispatchEvent(clickEvent);
+ return false;
+ }
+ function dispatchJsMouseoverEvent() {
+ let mouseoverEvent = new Event('mouseover');
+ document.getElementById("test_button").dispatchEvent(mouseoverEvent);
+ return false;
+ }
+ function reset(value) {
+ document.getElementById("result").value = (value ? value : "");
+ return false;
+ }
+ document.getElementById("test_button").addEventListener("click", (event) => reset(String(event.isTrusted)));
+ document.getElementById("test_button").addEventListener("mouseover", (event) => reset(String(event.isTrusted)));
+ document.getElementById("reset").onclick = reset;
+ document.getElementById("triggerJsClick").onclick = triggerJsClick;
+ document.getElementById("dispatchJsClickEvent").onclick = dispatchJsClickEvent;
+ )";
+ (void)(content::EvalJs(
+ shell(), script, content::EvalJsOptions::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+ GetParam()));
+ }
+
+ bool GetIsTrustedValue() {
+ auto value =
+ content::EvalJs(shell(), "document.getElementById('result').value")
+ .ExtractString();
+ CHECK(value == "true" || value == "false");
+ return value == "true";
+ }
+
+ void TriggerEvent(const std::string& js_code) {
+ EXPECT_EQ(false, content::EvalJs(
+ shell(), js_code,
+ content::EvalJsOptions::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+ GetParam()));
+ }
+
+ bool IsAdblockWorld() {
+ return GetParam() == content::IsolatedWorldIDs::ISOLATED_WORLD_ID_ADBLOCK;
+ }
+
+ private:
+ static constexpr char kTestDomain[] = "test.org";
+};
+
+IN_PROC_BROWSER_TEST_P(AdblockTrustedEventsTest,
+ VerifyClickEventTrustedInAdblockIsolatedWorld) {
+ NavigateToPage();
+ TriggerEvent("triggerJsClick();");
+ // Should be trusted only in adblock isolated world
+ EXPECT_EQ(IsAdblockWorld(), GetIsTrustedValue());
+}
+
+IN_PROC_BROWSER_TEST_P(AdblockTrustedEventsTest,
+ VerifyDispatchClickEventTrustedInAdblockIsolatedWorld) {
+ NavigateToPage();
+ TriggerEvent("dispatchJsClickEvent();");
+ // Should be trusted only in adblock isolated world
+ EXPECT_EQ(IsAdblockWorld(), GetIsTrustedValue());
+}
+
+IN_PROC_BROWSER_TEST_P(
+ AdblockTrustedEventsTest,
+ VerifyDispatchMouseoverEventTrustedInAdblockIsolatedWorld) {
+ NavigateToPage();
+ TriggerEvent("dispatchJsMouseoverEvent();");
+ // Should be trusted only in adblock isolated world
+ EXPECT_EQ(IsAdblockWorld(), GetIsTrustedValue());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ AdblockTrustedEventsTest,
+ testing::Values(content::IsolatedWorldIDs::ISOLATED_WORLD_ID_GLOBAL,
+ content::IsolatedWorldIDs::ISOLATED_WORLD_ID_ADBLOCK,
+ content::IsolatedWorldIDs::ISOLATED_WORLD_ID_CONTENT_END));
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_url_loader_factory_test.cc b/components/adblock/content/browser/test/adblock_url_loader_factory_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_url_loader_factory_test.cc
@@ -0,0 +1,512 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_url_loader_factory.h"
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/frame_opener_info.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h"
+#include "components/adblock/content/browser/test/mock_element_hider.h"
+#include "components/adblock/content/browser/test/mock_resource_classification_runner.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "components/adblock/core/test/mock_sitekey_storage.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_renderer_host.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char kTestUserAgent[] = "test-user-agent";
+
+enum class HostState { Alive, Dead };
+
+enum class MakeRedirection {
+ Yes,
+ No,
+};
+
+struct RequestFlow {
+ GURL url{"https://test.com"};
+ adblock::FilterMatchResult request_match =
+ adblock::FilterMatchResult::kAllowRule;
+ adblock::FilterMatchResult response_match =
+ adblock::FilterMatchResult::kAllowRule;
+ bool element_hidable = true;
+ network::mojom::RequestDestination destination =
+ network::mojom::RequestDestination::kFrame;
+ // If set this is the final url to which we redirect
+ std::optional<GURL> redirect_url = std::nullopt;
+ scoped_refptr<net::HttpResponseHeaders> headers =
+ base::MakeRefCounted<net::HttpResponseHeaders>("");
+};
+
+class AdblockURLLoaderFactoryTest
+ : public content::RenderViewHostTestHarness,
+ public testing::WithParamInterface<MakeRedirection> {
+ public:
+ AdblockURLLoaderFactoryTest() : test_factory_receiver_(&test_factory_) {}
+
+ AdblockURLLoaderFactoryTest(const AdblockURLLoaderFactoryTest&) = delete;
+ AdblockURLLoaderFactoryTest& operator=(const AdblockURLLoaderFactoryTest&) =
+ delete;
+
+ void MaybeMakeRedirection() {
+ if (GetParam() == MakeRedirection::Yes) {
+ flow.redirect_url = GURL{"https://example.com"};
+ }
+ }
+
+ // Get urls for the flow. Note that in case we block initial request then
+ // even if there is a redirect_url set it will not be hit.
+ std::vector<GURL> GetUrlsForCurrentFlow() {
+ std::vector<GURL> urls{flow.url};
+ if (flow.redirect_url &&
+ (flow.request_match != adblock::FilterMatchResult::kBlockRule)) {
+ urls.push_back(flow.redirect_url.value());
+ }
+ return urls;
+ }
+
+ // If we don't block initial request and it redirects then we return
+ // redirected url, otherwise initial url
+ GURL GetUrlForResponseProcessing() {
+ return (flow.redirect_url &&
+ (flow.request_match != adblock::FilterMatchResult::kBlockRule))
+ ? flow.redirect_url.value()
+ : flow.url;
+ }
+
+ void StartRequest() {
+ auto request = std::make_unique<network::ResourceRequest>();
+ request->url = flow.url;
+ request->destination = flow.destination;
+ ConfigureSubscriptionService();
+
+ loader_ = network::SimpleURLLoader::Create(std::move(request),
+ TRAFFIC_ANNOTATION_FOR_TESTS);
+ mojo::Remote<network::mojom::URLLoaderFactory> factory_remote;
+ auto factory_request = factory_remote.BindNewPipeAndPassReceiver();
+ loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ factory_remote.get(),
+ base::BindOnce(&AdblockURLLoaderFactoryTest::OnDownload,
+ base::Unretained(this)));
+
+ adblock_factory_ = std::make_unique<adblock::AdblockURLLoaderFactory>(
+ adblock::AdblockURLLoaderFactoryConfig{
+ &subscription_service_, &resource_classifier_, &element_hider_,
+ &sitekey_storage_, &csp_injector_},
+ adblock::RequestInitiator(web_contents()->GetPrimaryMainFrame()),
+ std::move(factory_request),
+ test_factory_receiver_.BindNewPipeAndPassRemote(), kTestUserAgent,
+ base::BindOnce(&AdblockURLLoaderFactoryTest::OnDisconnect,
+ base::Unretained(this)));
+ std::string body("Hello.");
+ network::URLLoaderCompletionStatus status;
+ status.decoded_body_length = body.size();
+ auto head = network::mojom::URLResponseHead::New();
+ head->headers = flow.headers;
+ if (!flow.redirect_url) {
+ test_factory_.AddResponse(flow.url, std::move(head), body, status);
+ } else {
+ network::TestURLLoaderFactory::Redirects redirects;
+ net::RedirectInfo redirect_info;
+ redirect_info.new_url = flow.redirect_url.value();
+ redirects.push_back(
+ {redirect_info, network::mojom::URLResponseHead::New()});
+ test_factory_.AddResponse(flow.url, std::move(head), body, status,
+ std::move(redirects));
+ }
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void OnDownload(std::optional<std::string> response_body) {}
+
+ void ConfigureSubscriptionService() {
+ EXPECT_CALL(subscription_service_, GetCurrentSnapshot())
+ .WillRepeatedly([]() {
+ adblock::SubscriptionService::Snapshot snapshot;
+ auto collection =
+ std::make_unique<adblock::MockSubscriptionCollection>();
+ // TODO(mpawlowski) will the collection be queried for classification?
+ // If yes, add EXPECT_CALL(collection, ...) here.
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+ }
+
+ void OnDisconnect(adblock::AdblockURLLoaderFactory* factory) {
+ EXPECT_EQ(factory, adblock_factory_.get());
+ adblock_factory_.reset();
+ }
+
+ void ExpectCheckRewrite(HostState state = HostState::Alive) {
+ EXPECT_CALL(
+ resource_classifier_,
+ CheckRewriteFilterMatch(testing::_, flow.url, testing::_, testing::_))
+ .WillOnce(
+ [&, state](
+ auto, const GURL&, const adblock::RequestInitiator&,
+ base::OnceCallback<void(const absl::optional<GURL>&)> cb) {
+ if (state == HostState::Dead) {
+ // Simulate frame death that happens during the async execution.
+ DeleteContents();
+ }
+ std::move(cb).Run({});
+ });
+ }
+
+ void ExpectNoCheckRewrite() {
+ EXPECT_CALL(
+ resource_classifier_,
+ CheckRewriteFilterMatch(testing::_, testing::_, testing::_, testing::_))
+ .Times(0);
+ }
+
+ void InitializeFlow() {
+ ExpectCheckRewrite();
+ for (const auto& url : GetUrlsForCurrentFlow()) {
+ EXPECT_CALL(resource_classifier_,
+ CheckDocumentAllowlisted(testing::_, url, testing::_))
+ .Times(0);
+ EXPECT_CALL(resource_classifier_,
+ CheckRequestFilterMatch(testing::_, url, testing::_,
+ testing::_, testing::_))
+ .WillOnce([&](auto, const GURL&, adblock::ContentType,
+ const adblock::RequestInitiator&,
+ adblock::CheckFilterMatchCallback cb) {
+ std::move(cb).Run(flow.request_match);
+ });
+ }
+ }
+
+ void InitializePopupFlow() {
+ flow.destination = network::mojom::RequestDestination::kDocument;
+ adblock::FrameOpenerInfo::CreateForWebContents(web_contents());
+ auto* info = adblock::FrameOpenerInfo::FromWebContents(web_contents());
+ info->SetOpener(main_rfh()->GetGlobalId());
+ ExpectNoCheckRewrite(); // We never rewrite popups
+ for (const auto& url : GetUrlsForCurrentFlow()) {
+ EXPECT_CALL(resource_classifier_,
+ CheckDocumentAllowlisted(testing::_, url, testing::_))
+ .Times(0);
+ EXPECT_CALL(
+ resource_classifier_,
+ CheckPopupFilterMatch(testing::_, url, testing::_, testing::_))
+ .WillOnce([&](auto, const GURL&, content::RenderFrameHost&,
+ adblock::CheckFilterMatchCallback cb) {
+ std::move(cb).Run(flow.request_match);
+ });
+ }
+ }
+
+ void ExpectRequestAllowed(HostState state = HostState::Alive) {
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(resource_classifier_,
+ CheckResponseFilterMatch(testing::_, url, testing::_,
+ testing::_, testing::_, testing::_))
+ .WillOnce([&, state](auto, const GURL&, adblock::ContentType,
+ const adblock::RequestInitiator&, const auto&,
+ adblock::CheckFilterMatchCallback cb) {
+ if (state == HostState::Dead) {
+ // Simulate frame death that happens during the async execution.
+ DeleteContents();
+ }
+ std::move(cb).Run(flow.response_match);
+ });
+ }
+
+ void ExpectPopupAllowed(HostState state = HostState::Alive) {
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(resource_classifier_,
+ CheckResponseFilterMatch(testing::_, url, testing::_,
+ testing::_, testing::_, testing::_))
+ .WillOnce([&, state](auto, const GURL&, adblock::ContentType,
+ const adblock::RequestInitiator&, const auto&,
+ adblock::CheckFilterMatchCallback cb) {
+ if (state == HostState::Dead) {
+ // Simulate frame death that happens during the async execution.
+ DeleteContents();
+ }
+ std::move(cb).Run(flow.response_match);
+ });
+ }
+
+ void ExpectRequestBlockedOrNotHappened() {
+ // if request was not processed or blocked, response processing should not
+ // take place.
+ EXPECT_CALL(resource_classifier_,
+ CheckResponseFilterMatch(testing::_, flow.url, testing::_,
+ testing::_, testing::_, testing::_))
+ .Times(0);
+ }
+
+ void ExpectElemhideDone() {
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(element_hider_, IsElementTypeHideable(testing::_))
+ .WillOnce(testing::Return(flow.element_hidable));
+ EXPECT_CALL(element_hider_, HideBlockedElement(url, testing::_))
+ .Times(flow.element_hidable ? 1 : 0);
+ }
+
+ void ExpectElemhideSkipped() {
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(element_hider_, IsElementTypeHideable(testing::_)).Times(0);
+ EXPECT_CALL(element_hider_, HideBlockedElement(url, testing::_)).Times(0);
+ }
+
+ void ExpectResponseAllowed(HostState state = HostState::Alive) {
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(sitekey_storage_,
+ ProcessResponseHeaders(url, testing::_, kTestUserAgent))
+ .Times(1);
+ EXPECT_CALL(csp_injector_, InsertContentSecurityPolicyHeadersIfApplicable(
+ url, testing::_, testing::_, testing::_))
+ .WillOnce(
+ [&, state](const GURL&, auto, auto,
+ adblock::InsertContentSecurityPolicyHeadersCallback cb) {
+ if (state == HostState::Dead) {
+ // Simulate frame death that happens during the async execution.
+ DeleteContents();
+ }
+ std::move(cb).Run(nullptr);
+ });
+ }
+
+ void ExpectResponseBlockedOrNotHappened() {
+ // if response was not processed or blocked, headers processing should not
+ // take place.
+ GURL url = GetUrlForResponseProcessing();
+ EXPECT_CALL(sitekey_storage_,
+ ProcessResponseHeaders(url, testing::_, kTestUserAgent))
+ .Times(0);
+ EXPECT_CALL(csp_injector_, InsertContentSecurityPolicyHeadersIfApplicable(
+ url, testing::_, testing::_, testing::_))
+ .Times(0);
+ }
+
+ std::unique_ptr<network::SimpleURLLoader> loader_;
+ std::unique_ptr<adblock::AdblockURLLoaderFactory> adblock_factory_;
+ network::TestURLLoaderFactory test_factory_;
+ mojo::Receiver<network::mojom::URLLoaderFactory> test_factory_receiver_;
+ adblock::MockSubscriptionService subscription_service_;
+ adblock::MockResourceClassificationRunner resource_classifier_;
+ adblock::MockElementHider element_hider_;
+ adblock::MockSitekeyStorage sitekey_storage_;
+ adblock::MockAdblockContentSecurityPolicyInjector csp_injector_;
+ std::vector<base::OnceClosure> deferred_tasks_;
+ RequestFlow flow;
+};
+
+TEST_P(AdblockURLLoaderFactoryTest, HappyPath) {
+ MaybeMakeRedirection();
+ InitializeFlow();
+ ExpectRequestAllowed();
+ ExpectResponseAllowed();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, MissingResponseHeaders) {
+ MaybeMakeRedirection();
+ flow.headers = nullptr;
+ InitializeFlow();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, BlockedWithRequestFilter) {
+ MaybeMakeRedirection();
+ flow.request_match = adblock::FilterMatchResult::kBlockRule;
+ InitializeFlow();
+ ExpectRequestBlockedOrNotHappened();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideDone();
+ StartRequest();
+ EXPECT_EQ(net::ERR_BLOCKED_BY_ADMINISTRATOR, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, BlockedWithResponseFilter) {
+ MaybeMakeRedirection();
+ flow.response_match = adblock::FilterMatchResult::kBlockRule;
+ InitializeFlow();
+ ExpectRequestAllowed();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideDone();
+ StartRequest();
+ EXPECT_EQ(net::ERR_BLOCKED_BY_ADMINISTRATOR, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, BlockedWithRequestFilterNonHideable) {
+ MaybeMakeRedirection();
+ flow.request_match = adblock::FilterMatchResult::kBlockRule;
+ flow.element_hidable = false;
+ InitializeFlow();
+ ExpectRequestBlockedOrNotHappened();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideDone();
+ StartRequest();
+ EXPECT_EQ(net::ERR_BLOCKED_BY_ADMINISTRATOR, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, BlockedWithResponseFilterNonHideable) {
+ MaybeMakeRedirection();
+ flow.response_match = adblock::FilterMatchResult::kBlockRule;
+ flow.element_hidable = false;
+ InitializeFlow();
+ ExpectRequestAllowed();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideDone();
+ StartRequest();
+ EXPECT_EQ(net::ERR_BLOCKED_BY_ADMINISTRATOR, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, DocumentNavigation) {
+ MaybeMakeRedirection();
+ flow.destination = network::mojom::RequestDestination::kDocument;
+ ExpectNoCheckRewrite(); // We never rewrite document navigation.
+ for (const auto& url : GetUrlsForCurrentFlow()) {
+ EXPECT_CALL(resource_classifier_,
+ CheckRequestFilterMatch(testing::_, url, testing::_, testing::_,
+ testing::_))
+ .Times(0);
+ }
+ // We call CheckDocumentAllowlisted() when we receive a response so we do that
+ // only for final url after all redirections
+ EXPECT_CALL(resource_classifier_,
+ CheckDocumentAllowlisted(
+ testing::_, GetUrlForResponseProcessing(), testing::_));
+
+ ExpectRequestAllowed();
+ ExpectResponseAllowed();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_P(AdblockURLLoaderFactoryTest, PopupNavigation) {
+ MaybeMakeRedirection();
+ InitializePopupFlow();
+ ExpectRequestAllowed();
+ ExpectResponseAllowed();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, FrameDiesWhilePopupNavigation) {
+ InitializePopupFlow();
+ ExpectRequestAllowed(HostState::Dead);
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, FrameDiesWhileRewriteCheck) {
+ ExpectCheckRewrite(HostState::Dead);
+ EXPECT_CALL(resource_classifier_,
+ CheckDocumentAllowlisted(testing::_, flow.url, testing::_))
+ .Times(0);
+ EXPECT_CALL(resource_classifier_,
+ CheckRequestFilterMatch(testing::_, flow.url, testing::_,
+ testing::_, testing::_))
+ .Times(0);
+
+ ExpectRequestBlockedOrNotHappened();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, FrameDiesWhileRequestMatch) {
+ InitializeFlow();
+ ExpectRequestAllowed(HostState::Dead);
+ ExpectResponseBlockedOrNotHappened();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, FrameDiesBeforeResponseMatch) {
+ InitializeFlow();
+ ExpectRequestAllowed();
+ ExpectResponseAllowed(HostState::Dead);
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, SkipCspForNonDocument) {
+ flow.destination = network::mojom::RequestDestination::kImage;
+ InitializeFlow();
+ ExpectRequestAllowed();
+ EXPECT_CALL(sitekey_storage_,
+ ProcessResponseHeaders(flow.url, testing::_, kTestUserAgent))
+ .Times(0);
+ EXPECT_CALL(csp_injector_, InsertContentSecurityPolicyHeadersIfApplicable(
+ flow.url, testing::_, testing::_, testing::_))
+ .Times(0);
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, Localhost) {
+ flow.url = GURL("http://localhost:8080/test");
+ InitializeFlow();
+ ExpectRequestAllowed();
+ ExpectResponseAllowed();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+TEST_F(AdblockURLLoaderFactoryTest, NonHttp) {
+ flow.url = GURL(
+ "data:image/"
+ "png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4/"
+ "/8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+ flow.destination = network::mojom::RequestDestination::kImage;
+ EXPECT_CALL(resource_classifier_,
+ CheckRequestFilterMatch(testing::_, flow.url, testing::_,
+ testing::_, testing::_))
+ .Times(0);
+ ExpectRequestBlockedOrNotHappened();
+ ExpectResponseBlockedOrNotHappened();
+ ExpectNoCheckRewrite();
+ ExpectElemhideSkipped();
+ StartRequest();
+ EXPECT_EQ(net::OK, loader_->NetError());
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockURLLoaderFactoryTest,
+ testing::Values(MakeRedirection::Yes,
+ MakeRedirection::No));
diff --git a/components/adblock/content/browser/test/adblock_web_bundle_browsertest.cc b/components/adblock/content/browser/test/adblock_web_bundle_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_web_bundle_browsertest.cc
@@ -0,0 +1,425 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdint>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/functional/bind.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h"
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "components/adblock/content/browser/test/adblock_browsertest_base.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/web_package/web_bundle_builder.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockWebBundleBrowserTest
+ : public AdblockBrowserTestBase,
+ public ResourceClassificationRunner::Observer {
+ public:
+ void SetUpOnMainThread() override {
+ AdblockBrowserTestBase::SetUpOnMainThread();
+ host_resolver()->AddRule("*", "127.0.0.1");
+ embedded_test_server()->RegisterDefaultHandler(
+ base::BindRepeating(&AdblockWebBundleBrowserTest::HandleFileRequest,
+ base::Unretained(this)));
+ ASSERT_TRUE(embedded_test_server()->Start());
+ PrepareTempDirWithContent();
+ ResourceClassificationRunnerFactory::GetForBrowserContext(browser_context())
+ ->AddObserver(this);
+ }
+
+ void TearDownOnMainThread() override {
+ ResourceClassificationRunnerFactory::GetForBrowserContext(browser_context())
+ ->RemoveObserver(this);
+ AdblockBrowserTestBase::TearDownOnMainThread();
+ }
+
+ void PrepareTempDirWithContent() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ CreateIndexFile();
+ CreateByResourceWebBundle();
+ CreateByBundleFileWebBundle();
+ CreateByScopeFileWebBundle();
+ }
+
+ std::string GetFileContentFromTestDir(
+ base::FilePath::StringType relative_path) {
+ base::FilePath root;
+ CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root));
+ root = root.AppendASCII("components/test/data/adblock/wbn");
+ std::string content;
+ CHECK(base::ReadFileToString(root.Append(relative_path), &content));
+ return content;
+ }
+
+ // In order for an html file to use absolute URLs in 'src' attributes to
+ // resources hosted by the embedded_test_server(), we need to replace
+ // {{{baseUrl}}} with the server's URL. It changes for every run.
+ std::string GetHtmlContentWithReplacements(
+ base::FilePath::StringType relative_path) {
+ std::string content = GetFileContentFromTestDir(relative_path);
+ base::ReplaceSubstringsAfterOffset(
+ &content, 0, "{{{baseUrl}}}",
+ embedded_test_server()->GetURL("example.org", "/").spec());
+ return content;
+ }
+
+ void CreateByResourceWebBundle() {
+ web_package::WebBundleBuilder builder;
+ builder.AddExchange("by_resource/blue_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/blue_subresource_loading.css")));
+ builder.AddExchange("by_resource/blue_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/blue_subresource_loading.png")));
+ builder.AddExchange("by_resource/red_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/red_subresource_loading.css")));
+ builder.AddExchange("by_resource/red_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/red_subresource_loading.png")));
+ builder.AddExchange(
+ "by_resource/xhr_result_1_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/xhr_result_1_subresource_loading.json")));
+ builder.AddExchange(
+ "by_resource/fetch_result_1_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_resource/fetch_result_1_subresource_loading.json")));
+ builder.AddPrimaryURL(embedded_test_server()->GetURL("example.org", "/"));
+ const auto binary_data = builder.CreateBundle();
+ ASSERT_TRUE(base::WriteFile(
+ temp_dir_.GetPath().AppendASCII("by_resource.wbn"), binary_data));
+ }
+
+ void CreateByBundleFileWebBundle() {
+ web_package::WebBundleBuilder builder;
+ builder.AddExchange("by_bundle_file/green_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/green_subresource_loading.css")));
+ builder.AddExchange("by_bundle_file/green_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/green_subresource_loading.png")));
+ builder.AddExchange("by_bundle_file/purple_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/purple_subresource_loading.css")));
+ builder.AddExchange("by_bundle_file/purple_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/purple_subresource_loading.png")));
+ builder.AddExchange(
+ "by_bundle_file/xhr_result_2_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/xhr_result_2_subresource_loading.json")));
+ builder.AddExchange(
+ "by_bundle_file/fetch_result_2_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_bundle_file/fetch_result_2_subresource_loading.json")));
+ builder.AddPrimaryURL(embedded_test_server()->GetURL("example.org", "/"));
+ const auto binary_data = builder.CreateBundle();
+ ASSERT_TRUE(base::WriteFile(
+ temp_dir_.GetPath().AppendASCII("by_bundle_file.wbn"), binary_data));
+ }
+
+ void CreateByScopeFileWebBundle() {
+ web_package::WebBundleBuilder builder;
+ builder.AddExchange("by_scope/orange_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/orange_subresource_loading.css")));
+ builder.AddExchange("by_scope/orange_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/orange_subresource_loading.png")));
+ builder.AddExchange("by_scope/pink_subresource_loading.css",
+ {{":status", "200"}, {"content-type", "text/css"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/pink_subresource_loading.css")));
+ builder.AddExchange("by_scope/pink_subresource_loading.png",
+ {{":status", "200"}, {"content-type", "image/png"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/pink_subresource_loading.png")));
+ builder.AddExchange(
+ "by_scope/xhr_result_3_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/xhr_result_3_subresource_loading.json")));
+ builder.AddExchange(
+ "by_scope/fetch_result_3_subresource_loading.json",
+ {{":status", "200"}, {"content-type", "application/json"}},
+ GetFileContentFromTestDir(FILE_PATH_LITERAL(
+ "by_scope/fetch_result_3_subresource_loading.json")));
+ builder.AddPrimaryURL(embedded_test_server()->GetURL("example.org", "/"));
+ const auto binary_data = builder.CreateBundle();
+ ASSERT_TRUE(base::WriteFile(temp_dir_.GetPath().AppendASCII("by_scope.wbn"),
+ binary_data));
+ }
+
+ void CreateIndexFile() {
+ const auto html = GetHtmlContentWithReplacements(
+ FILE_PATH_LITERAL("index.html.mustache"));
+ ASSERT_TRUE(
+ base::WriteFile(temp_dir_.GetPath().AppendASCII("index.html"), html));
+ }
+
+ std::unique_ptr<net::test_server::HttpResponse> HandleFileRequest(
+ const net::test_server::HttpRequest& request) {
+ auto response =
+ net::test_server::HandleFileRequest(temp_dir_.GetPath(), request);
+ if (response) {
+ auto* basic =
+ static_cast<net::test_server::BasicHttpResponse*>(response.get());
+ if (temp_dir_.GetPath()
+ .AppendASCII(request.GetURL().path().substr(1))
+ .MatchesExtension(FILE_PATH_LITERAL(".wbn"))) {
+ basic->set_content_type("application/webbundle");
+ }
+ basic->AddCustomHeader("X-Content-Type-Options", "nosniff");
+ basic->AddCustomHeader("Access-Control-Allow-Origin", "*");
+ }
+ return response;
+ }
+
+ GURL GetUrl() {
+ return embedded_test_server()->GetURL("example.org", "/index.html");
+ }
+
+ bool EvaluateJs(std::string value) {
+ return content::EvalJs(web_contents(), value).ExtractBool();
+ }
+
+ bool IsImageLoaded(std::string selector) {
+ return EvaluateJs(
+ "(function(){ node = document.querySelector('" + selector +
+ "'); return node.complete && node.naturalHeight !== 0; })()");
+ }
+
+ bool IsCssBlocked(std::string selector) {
+ // On the test page, a box with this |id| is grey if element-specific CSS
+ // was blocked. Grey is the default color for divs, and gets overridden
+ // to element-specific colors by blockable CSS files.
+ return EvaluateJs(
+ "(function(){ return "
+ "window.getComputedStyle(document.querySelector('" +
+ selector + "')).backgroundColor == 'rgb(128, 128, 128)'; })()");
+ }
+
+ // Waits until the textContent of the selected item stops being "Pending" and
+ // returns whatever it's final value is.
+ // This asynchronous wait is because scripts that change the textContent may
+ // execute with a delay.
+ std::string GetSelectorTextContent(std::string selector) {
+ return content::EvalJs(web_contents(),
+ R"(
+ (async function (selector) {
+ return new Promise(resolve => {
+ if (!document.querySelector(selector).textContent.includes('Pending')) {
+ return resolve(document.querySelector(selector).textContent);
+ }
+
+ const observer = new MutationObserver(mutations => {
+ if (!document.querySelector(selector).textContent.includes('Pending')) {
+ resolve(document.querySelector(selector).textContent);
+ observer.disconnect();
+ }
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+ });
+})(' )" + selector + "');")
+ .ExtractString();
+ }
+
+ bool IsXhrOrFetchRequestBlocked(std::string selector) {
+ return GetSelectorTextContent(selector).find("Error") != std::string::npos;
+ }
+
+ bool IsXhrOrFetchRequestAllowed(std::string selector) {
+ return GetSelectorTextContent(selector).find("succeeded") !=
+ std::string::npos;
+ }
+
+ // ResourceClassificationRunner::Observer:
+ void OnRequestMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override {
+ if (match_result == FilterMatchResult::kBlockRule) {
+ if (!blocked_ads_expectations_.empty()) {
+ blocked_ads_expectations_.erase(
+ base::ranges::remove(blocked_ads_expectations_, url),
+ blocked_ads_expectations_.end());
+ if (blocked_ads_expectations_.empty()) {
+ NotifyTestFinished();
+ }
+ } else {
+ blocked_ads_notifications_.push_back(url);
+ }
+ }
+ }
+
+ void OnPageAllowed(const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override {}
+
+ void OnPopupMatched(const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name) override {}
+
+ base::ScopedTempDir temp_dir_;
+ // Note: Use either blocked_ads_notifications_ or blocked_ads_expectations_
+ std::vector<GURL> blocked_ads_notifications_;
+ std::vector<GURL> blocked_ads_expectations_;
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockImageByResource) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ const auto* blocked_image_selector =
+ R"(img[src="by_resource/blue_subresource_loading.png"])";
+ const auto* other_image_selector =
+ R"(img[src="by_resource/red_subresource_loading.png"])";
+ EXPECT_TRUE(IsImageLoaded(blocked_image_selector));
+ EXPECT_TRUE(IsImageLoaded(other_image_selector));
+ SetFilters({"/blue_subresource_loading.png"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_FALSE(IsImageLoaded(blocked_image_selector));
+ EXPECT_TRUE(IsImageLoaded(other_image_selector));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockCssByResource) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_FALSE(IsCssBlocked("div.blue"));
+ SetFilters({"blue_subresource_loading.css"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_TRUE(IsCssBlocked("div.blue"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockXhrByResource) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ const auto* blocked_selector = "code#xhr_result_by_resource";
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed(blocked_selector));
+ SetFilters({"/xhr_result_1_subresource_loading.json"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_TRUE(IsXhrOrFetchRequestBlocked(blocked_selector));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockFetchByResource) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ const auto* blocked_selector = "code#fetch_result_by_resource";
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed(blocked_selector));
+ SetFilters({"/fetch_result_1_subresource_loading.json"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_TRUE(IsXhrOrFetchRequestBlocked(blocked_selector));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockByBundle) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ const auto* blocked_image_selector =
+ R"(img[src="by_bundle_file/green_subresource_loading.png"])";
+ EXPECT_TRUE(IsImageLoaded(blocked_image_selector));
+ EXPECT_FALSE(IsCssBlocked("div.green"));
+ EXPECT_FALSE(IsCssBlocked("div.purple"));
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed("code#xhr_result_by_bundle_file"));
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed("code#fetch_result_by_bundle_file"));
+
+ SetFilters({"by_bundle_file.wbn$webbundle"});
+ // DPD-2689: Sometimes 2nd navigation stuck and test timeouts due to lack
+ // of propagation that image(s) within a bundle were blocked (by blocking
+ // whole bundle). To workaround this timing related upstream issue (as it
+ // seems) instead of calling blocking NavigateToURL() we just commit
+ // navigation directly and wait for expected request blocked event and
+ // document ready state before we proceed with subresources verification.
+ blocked_ads_expectations_.push_back(
+ embedded_test_server()->GetURL("example.org", "/by_bundle_file.wbn"));
+ web_contents()->GetController().LoadURL(
+ GetUrl(), content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
+ RunUntilTestFinished();
+ WaitAndVerifyCondition("document.readyState === 'complete'");
+ EXPECT_FALSE(IsImageLoaded(blocked_image_selector));
+ EXPECT_TRUE(IsCssBlocked("div.green"));
+ EXPECT_TRUE(IsCssBlocked("div.purple"));
+ // FIXME the following two expectations fail because the state of the requests
+ // is "pending" rather than allowed or blocked: DPD-1887
+ // EXPECT_TRUE(IsXhrOrFetchRequestBlocked("code#xhr_result_by_bundle_file"));
+ // EXPECT_TRUE(IsXhrOrFetchRequestBlocked("code#fetch_result_by_bundle_file"));
+}
+
+IN_PROC_BROWSER_TEST_F(AdblockWebBundleBrowserTest, BlockByScope) {
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ const auto* blocked_image_selector =
+ R"(img[src="by_scope/orange_subresource_loading.png"])";
+ EXPECT_TRUE(IsImageLoaded(blocked_image_selector));
+ EXPECT_FALSE(IsCssBlocked("div.orange"));
+ EXPECT_FALSE(IsCssBlocked("div.pink"));
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed("code#xhr_result_by_scope"));
+ EXPECT_TRUE(IsXhrOrFetchRequestAllowed("code#fetch_result_by_scope"));
+
+ SetFilters({"by_scope/"});
+ ASSERT_TRUE(content::NavigateToURL(shell(), GetUrl()));
+ EXPECT_FALSE(IsImageLoaded(blocked_image_selector));
+ EXPECT_TRUE(IsCssBlocked("div.orange"));
+ EXPECT_TRUE(IsCssBlocked("div.pink"));
+ EXPECT_TRUE(IsXhrOrFetchRequestBlocked("code#xhr_result_by_scope"));
+ EXPECT_TRUE(IsXhrOrFetchRequestBlocked("code#fetch_result_by_scope"));
+}
+
+// TODO:
+// - Mixed origins
+// - Signed bundles. including case of navigation between signed and unsigned
+// content.
+// - If you block the whole bundle, all resources requests to components
+// declared in <script type="webbundle"> should be aborted. See sample test
+// here:
+// https://gitlab.com/eyeo/adblockplus/abc/webext-sdk/-/merge_requests/623/diffs#c24c6bdc211dc8e196dc5e2e32ddbefeb5fd4390_179_179
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_web_ui_browsertest.cc b/components/adblock/content/browser/test/adblock_web_ui_browsertest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_web_ui_browsertest.cc
@@ -0,0 +1,58 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/strings/strcat.h"
+#include "components/adblock/core/common/web_ui_constants.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/shell/app/shell_main_delegate.h"
+#include "content/shell/browser/shell.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockWebUIBrowserTest : public content::ContentBrowserTest {
+ public:
+ // Without this override there is no AdblockShellContentBrowserClient
+ // (created by ShellMainDelegate) but default ShellContentBrowserClient.
+ content::ContentMainDelegate* GetOptionalContentMainDelegateOverride()
+ override {
+ return new content::ShellMainDelegate(true);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(AdblockWebUIBrowserTest,
+ AdblockInternalsPageLoadsSuccessfully) {
+ ASSERT_TRUE(content::NavigateToURL(
+ shell(),
+ GURL(base::StrCat({"chrome://", kChromeUIAdblockInternalsHost}))));
+ EXPECT_TRUE(shell()->web_contents()->GetWebUI());
+ EXPECT_EQ(u"Ad-Filtering Internals", shell()->web_contents()->GetTitle());
+}
+
+IN_PROC_BROWSER_TEST_F(
+ AdblockWebUIBrowserTest,
+ VisitingAdblockInternalsWithInvalidSchemeResultsInErrorPage) {
+ EXPECT_FALSE(content::NavigateToURL(
+ shell(), GURL(base::StrCat({"http://", kChromeUIAdblockInternalsHost}))));
+ EXPECT_FALSE(shell()->web_contents()->GetWebUI());
+ EXPECT_EQ(u"Error", shell()->web_contents()->GetTitle());
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/adblock_webcontents_observer_test.cc b/components/adblock/content/browser/test/adblock_webcontents_observer_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/adblock_webcontents_observer_test.cc
@@ -0,0 +1,189 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/adblock_webcontents_observer.h"
+
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/test/mock_element_hider.h"
+#include "components/adblock/content/browser/test/mock_frame_hierarchy_builder.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "components/adblock/core/test/mock_sitekey_storage.h"
+#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/test_renderer_host.h"
+
+namespace adblock {
+
+using testing::_;
+
+class AdblockWebContentObserverTest
+ : public content::RenderViewHostTestHarness {
+ public:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+
+ auto* web_contents = this->web_contents();
+ AdblockWebContentObserver::CreateForWebContents(
+ web_contents, &service_, &hider_, &storage_,
+ std::make_unique<MockFrameHierarchyBuilder>(),
+ navigation_counter_mock_.Get());
+ observer_ = AdblockWebContentObserver::FromWebContents(web_contents);
+ }
+
+ void TearDown() override {
+ observer_ = nullptr;
+ content::RenderViewHostTestHarness::TearDown();
+ }
+
+ MockSubscriptionService service_;
+ MockElementHider hider_;
+ MockSitekeyStorage storage_;
+ raw_ptr<AdblockWebContentObserver> observer_;
+ base::MockRepeatingCallback<void(content::RenderFrameHost*)>
+ navigation_counter_mock_;
+};
+
+TEST_F(AdblockWebContentObserverTest, DidFinishNavigationNotCommited) {
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(false);
+
+ service_.WillRequireFiltering(true);
+ EXPECT_CALL(hider_, HideBlockedElement(_, _)).Times(0);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(_, _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(navigation_counter_mock_, Run(_)).Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, DidFinishNavigationWithNiceUrl) {
+ content::RenderFrameHost* frame_host = main_rfh();
+ content::MockNavigationHandle mock_navigation_handle;
+ GURL url("https://test.com");
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_url(url);
+ mock_navigation_handle.set_render_frame_host(frame_host);
+
+ service_.WillRequireFiltering(true);
+ EXPECT_CALL(storage_, FindSiteKeyForAnyUrl(std::vector<GURL>{}))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(hider_, HideBlockedElement(_, _)).Times(0);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(
+ GURL{}, std::vector<GURL>{}, frame_host, _, _))
+ .Times(1);
+ // Navigation counter is triggered, for the purpose of collecting AA stats.
+ EXPECT_CALL(navigation_counter_mock_, Run(frame_host)).Times(1);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, DidFinishNavigationWithErrorPage) {
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_is_error_page(true);
+
+ service_.WillRequireFiltering(true);
+ EXPECT_CALL(hider_, HideBlockedElement(_, _)).Times(0);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(_, _, _, _, _))
+ .Times(0);
+ // Error pages are not counted as navigations.
+ EXPECT_CALL(navigation_counter_mock_, Run(_)).Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, DidFinishNavigationWithAboutBlank) {
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_url(GURL("about:blank"));
+
+ service_.WillRequireFiltering(true);
+ EXPECT_CALL(hider_, HideBlockedElement(_, _)).Times(0);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(_, _, _, _, _))
+ .Times(0);
+ // Blank pages are not counted as navigations.
+ EXPECT_CALL(navigation_counter_mock_, Run(_)).Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, DidFinishNavigationWithBlockedFrame) {
+ GURL url("https://test.com/frame");
+ GURL parent("https://test.com");
+ content::RenderFrameHost* frame_host = main_rfh();
+ service_.WillRequireFiltering(true);
+ NavigateAndCommit(parent);
+
+ content::RenderFrameHost* child_frame =
+ content::RenderFrameHostTester::For(frame_host)->AppendChild("");
+ ASSERT_TRUE(child_frame != nullptr);
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_is_error_page(true);
+ mock_navigation_handle.set_net_error_code(net::ERR_BLOCKED_BY_ADMINISTRATOR);
+ mock_navigation_handle.set_render_frame_host(child_frame);
+ mock_navigation_handle.set_url(url);
+
+ EXPECT_CALL(hider_, HideBlockedElement(url, frame_host)).Times(1);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(_, _, _, _, _))
+ .Times(0);
+ // Expecting no calls to navigation counter because this navigation is for an
+ // iframe that's blocked.
+ EXPECT_CALL(navigation_counter_mock_, Run(_)).Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, NoElementHidingWhenFilteringDisabled) {
+ service_.WillRequireFiltering(false);
+
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_url(GURL("https://test.com"));
+ mock_navigation_handle.set_render_frame_host(main_rfh());
+
+ // Despite a correct navigation handle, there will be no attempt to apply
+ // element hiding on the page because there are no enabled
+ // FilteringConfigurations in SubscriptionService.
+ EXPECT_CALL(hider_, HideBlockedElement(_, _)).Times(0);
+ EXPECT_CALL(hider_, ApplyElementHidingEmulationOnPage(_, _, _, _, _))
+ .Times(0);
+ // Navigation counter is not triggered if ad filtering is disabled.
+ EXPECT_CALL(navigation_counter_mock_, Run(_)).Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+TEST_F(AdblockWebContentObserverTest, NoElementHidingForUnsupportedScheme) {
+ service_.WillRequireFiltering(true);
+
+ content::MockNavigationHandle mock_navigation_handle;
+ mock_navigation_handle.set_has_committed(true);
+ mock_navigation_handle.set_url(GURL("file:///path/to/page.html"));
+ mock_navigation_handle.set_render_frame_host(main_rfh());
+
+ // Despite a correct navigation handle, there will be no attempt to apply
+ // element hiding for unsupported scheme.
+ EXPECT_CALL(hider_,
+ ApplyElementHidingEmulationOnPage(
+ testing::_, testing::_, testing::_, testing::_, testing::_))
+ .Times(0);
+
+ observer_->DidFinishNavigation(&mock_navigation_handle);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/content_security_policy_injector_impl_test.cc b/components/adblock/content/browser/test/content_security_policy_injector_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/content_security_policy_injector_impl_test.cc
@@ -0,0 +1,218 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/content_security_policy_injector_impl.h"
+
+#include <memory>
+#include <set>
+#include <string_view>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "components/adblock/content/browser/test/mock_frame_hierarchy_builder.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/test/test_renderer_host.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "net/http/http_response_headers.h"
+#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
+#include "services/network/public/mojom/parsed_headers.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+using testing::_;
+using testing::Ref;
+using testing::Return;
+using MockResponseCallback =
+ base::MockCallback<InsertContentSecurityPolicyHeadersCallback>;
+
+class FakeSubscriptionCollection : public MockSubscriptionCollection {
+ public:
+ explicit FakeSubscriptionCollection(
+ std::set<std::string> injections,
+ GURL expected_request_url,
+ std::vector<GURL> expected_frame_hierarchy)
+ : injections_(std::move(injections)),
+ expected_request_url_(std::move(expected_request_url)),
+ expected_frame_hierarchy_(std::move(expected_frame_hierarchy)) {}
+
+ std::set<std::string_view> GetCspInjections(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const override {
+ EXPECT_EQ(request_url, expected_request_url_);
+ EXPECT_EQ(frame_hierarchy, expected_frame_hierarchy_);
+ // The data |injection_views| points to will be alive for the lifetime of
+ // this object but will then go out of scope. This simulates the underlying
+ // Subscriptions being updated or removed during the test, as Subscriptions
+ // are only guaranteed to be kept alive during the lifetime of current
+ // Snapshot.
+ std::set<std::string_view> injection_views;
+ for (const auto& injection : injections_) {
+ injection_views.insert(injection);
+ }
+ return injection_views;
+ }
+
+ std::set<std::string> injections_;
+ GURL expected_request_url_;
+ std::vector<GURL> expected_frame_hierarchy_;
+};
+
+class AdblockContentSecurityPolicyInjectorImplTest
+ : public content::RenderViewHostTestHarness {
+ public:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+
+ auto frame_hierarchy_builder =
+ std::make_unique<MockFrameHierarchyBuilder>();
+ frame_hierarchy_builder_ = frame_hierarchy_builder.get();
+ csp_injector_ = std::make_unique<ContentSecurityPolicyInjectorImpl>(
+ &subscription_service_, std::move(frame_hierarchy_builder));
+ }
+
+ void TearDown() override {
+ // Avoid dangling pointers during destruction.
+ frame_hierarchy_builder_ = nullptr;
+ content::RenderViewHostTestHarness::TearDown();
+ }
+
+ void FrameHierarchyWillBeBuilt() {
+ EXPECT_CALL(*frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+ }
+
+ void SnapshotWillContainInjections(std::set<std::string> injections) {
+ FrameHierarchyWillBeBuilt();
+ // SubscriptionService will be asked for the current snapshot...
+ EXPECT_CALL(subscription_service_, GetCurrentSnapshot())
+ .WillOnce([this, injections]() {
+ // |injections| are captured by copy, they will be alive for the
+ // duration of the lifetime of |subscription_collection| but not
+ // longer.
+ auto subscription_collection =
+ std::make_unique<FakeSubscriptionCollection>(
+ std::move(injections), kRequestUrl, kFrameHierarchy);
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(subscription_collection));
+ return snapshot;
+ });
+ }
+
+ void AssertParsedHeadersContainScriptSrcNone(
+ const network::mojom::ParsedHeadersPtr& parsed_headers) {
+ ASSERT_EQ(parsed_headers->content_security_policy.size(), 1u);
+ EXPECT_EQ(parsed_headers->content_security_policy[0]
+ ->raw_directives[network::mojom::CSPDirectiveName::ScriptSrc],
+ "'none'");
+ }
+
+ const GURL kRequestUrl{"https://request.com/resource.txt"};
+ const std::vector<GURL> kFrameHierarchy{GURL{"https://test.com/"}};
+ MockSubscriptionService subscription_service_;
+ raw_ptr<MockFrameHierarchyBuilder> frame_hierarchy_builder_;
+ std::unique_ptr<ContentSecurityPolicyInjectorImpl> csp_injector_;
+};
+
+TEST_F(AdblockContentSecurityPolicyInjectorImplTest,
+ NoHeaderAddedWhenNoCspInjectionFound) {
+ // An empty injection set means no CSP filter found.
+ SnapshotWillContainInjections({});
+
+ MockResponseCallback callback;
+
+ // Call testee:
+ auto headers = net::HttpResponseHeaders::TryToCreate("");
+ CHECK(headers);
+ csp_injector_->InsertContentSecurityPolicyHeadersIfApplicable(
+ kRequestUrl, RequestInitiator(main_rfh()), headers, callback.Get());
+
+ // Callback is ran via posted task, with no parsed headers because headers
+ // were not modified.
+ EXPECT_CALL(callback, Run(testing::IsFalse())).Times(1);
+ task_environment()->RunUntilIdle();
+
+ // No header was injected.
+ EXPECT_FALSE(headers->HasHeader("Content-Security-Policy"));
+}
+
+TEST_F(AdblockContentSecurityPolicyInjectorImplTest,
+ HeaderAddedWhenCspInjectionFound) {
+ SnapshotWillContainInjections({"script-src 'none'"});
+
+ MockResponseCallback callback;
+
+ // Call testee:
+ auto headers = net::HttpResponseHeaders::TryToCreate("");
+ CHECK(headers);
+ csp_injector_->InsertContentSecurityPolicyHeadersIfApplicable(
+ kRequestUrl, RequestInitiator(main_rfh()), headers, callback.Get());
+
+ // Callback is ran via posted task with correctly parsed headers.
+ EXPECT_CALL(callback, Run(_))
+ .WillOnce([&](network::mojom::ParsedHeadersPtr parsed_headers) {
+ AssertParsedHeadersContainScriptSrcNone(parsed_headers);
+ });
+ task_environment()->RunUntilIdle();
+
+ // The header was injected.
+ EXPECT_TRUE(
+ headers->HasHeaderValue("Content-Security-Policy", "script-src 'none'"));
+}
+
+TEST_F(AdblockContentSecurityPolicyInjectorImplTest,
+ HeadersAddedWhenMultipleCspInjectionFound) {
+ SnapshotWillContainInjections({"script-src 'first'", "script-src 'second'"});
+
+ MockResponseCallback callback;
+
+ // Call testee:
+ auto headers = net::HttpResponseHeaders::TryToCreate("");
+ CHECK(headers);
+ csp_injector_->InsertContentSecurityPolicyHeadersIfApplicable(
+ kRequestUrl, RequestInitiator(main_rfh()), headers, callback.Get());
+
+ // Callback is ran via posted task with correctly parsed headers.
+ EXPECT_CALL(callback, Run(_))
+ .WillOnce([&](network::mojom::ParsedHeadersPtr parsed_headers) {
+ ASSERT_EQ(parsed_headers->content_security_policy.size(), 2u);
+ EXPECT_EQ(
+ parsed_headers->content_security_policy[0]
+ ->raw_directives[network::mojom::CSPDirectiveName::ScriptSrc],
+ "'first'");
+ EXPECT_EQ(
+ parsed_headers->content_security_policy[1]
+ ->raw_directives[network::mojom::CSPDirectiveName::ScriptSrc],
+ "'second'");
+ });
+ task_environment()->RunUntilIdle();
+
+ // The header was injected.
+ EXPECT_TRUE(
+ headers->HasHeaderValue("Content-Security-Policy", "script-src 'first'"));
+ EXPECT_TRUE(headers->HasHeaderValue("Content-Security-Policy",
+ "script-src 'second'"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/element_hider_impl_test.cc b/components/adblock/content/browser/test/element_hider_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/element_hider_impl_test.cc
@@ -0,0 +1,433 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/element_hider_impl.h"
+
+#include <string_view>
+
+#include "base/functional/callback_forward.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/resource/mock_resource_bundle_delegate.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+namespace {
+using ContentFiltersData = InstalledSubscription::ContentFiltersData;
+using Selectors = InstalledSubscription::ContentFiltersData::Selectors;
+using NiceMockResourceBundleDelegate = NiceMock<ui::MockResourceBundleDelegate>;
+} // namespace
+
+class AdblockElementHiderImplTest : public content::RenderViewHostTestHarness {
+ protected:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+ orig_instance_ = ui::ResourceBundle::SwapSharedInstanceForTesting(nullptr);
+ ui::ResourceBundle::InitSharedInstanceWithLocale(
+ "en-US", &mock_delegate_,
+ ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
+ EXPECT_CALL(mock_delegate_, LoadDataResourceString(IDR_ADBLOCK_SNIPPETS_JS))
+ .WillRepeatedly(testing::Return("snippets_lib"));
+ EXPECT_CALL(mock_delegate_,
+ LoadDataResourceString(IDR_ADBLOCK_SNIPPETS_XPATH3_DEP_JS))
+ .WillRepeatedly(testing::Return("xpath3_dep"));
+ EXPECT_CALL(mock_delegate_,
+ LoadDataResourceString(IDR_ADBLOCK_ELEMHIDE_EMU_JS))
+ .WillRepeatedly(testing::Return("{{elemHidingEmulatedPatternsDef}}"));
+ }
+
+ void TearDown() override {
+ content::RenderViewHostTestHarness::TearDown();
+ ui::ResourceBundle::CleanupSharedInstance();
+ ui::ResourceBundle::SwapSharedInstanceForTesting(orig_instance_);
+ }
+
+ MockSubscriptionService sub_service_;
+ NiceMockResourceBundleDelegate mock_delegate_;
+ raw_ptr<ui::ResourceBundle> orig_instance_;
+
+ const GURL kUrl{"https://domain.com"};
+ const std::vector<GURL> kFrameHierarchy{kUrl};
+ const SiteKey kSitekey{""};
+};
+
+TEST_F(AdblockElementHiderImplTest, BatchesSelectors) {
+ ContentFiltersData selectors;
+ selectors.elemhide_selectors = Selectors{1u << 11u, "selector"};
+ ContentFiltersData emu_selectors;
+
+ ElementHiderImpl element_hide(&sub_service_);
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot())
+ .WillOnce([this, selectors, emu_selectors]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors));
+ EXPECT_CALL(*collection, GetElementHideEmulationData(kUrl))
+ .WillOnce(testing::Return(emu_selectors));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ const auto lines =
+ base::SplitString(data.stylesheet, "\n", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ EXPECT_EQ(lines.size(), 2u);
+ for (auto& line : lines) {
+ EXPECT_EQ(base::SplitString(line, ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY)
+ .size(),
+ (1u << 10u)); // must not be bigger than 2 ^ 10 = 1024
+
+ EXPECT_TRUE(base::EndsWith(line, "{display: none !important;}"));
+ }
+ }));
+}
+
+TEST_F(AdblockElementHiderImplTest, AppliesElementHidingOnSiteWithWeirdUrl) {
+ ContentFiltersData selectors;
+ selectors.elemhide_selectors = {"a", "b"};
+ ContentFiltersData emu_selectors;
+
+ // When loading web bundles, URLs of iframes may not look like ordinary
+ // addresses:
+ const GURL kNonStandardFrameUrl{
+ "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ad"};
+
+ ElementHiderImpl element_hide(&sub_service_);
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot())
+ .WillOnce([this, selectors, emu_selectors, &kNonStandardFrameUrl]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document,
+ kNonStandardFrameUrl, kFrameHierarchy,
+ kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Elemhide,
+ kNonStandardFrameUrl, kFrameHierarchy,
+ kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection, GetElementHideData(kNonStandardFrameUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors));
+ EXPECT_CALL(*collection,
+ GetElementHideEmulationData(kNonStandardFrameUrl))
+ .WillOnce(testing::Return(emu_selectors));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kNonStandardFrameUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet, "a, b {display: none !important;}\n");
+ }));
+}
+
+TEST_F(AdblockElementHiderImplTest, GeneratesSnippetsWhenEhAllowListed) {
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot()).WillOnce([this]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(GURL("about:blank")));
+ base::Value::List snippets;
+ snippets.Append(base::Value::List());
+ EXPECT_CALL(*collection, GenerateSnippets(kUrl, kFrameHierarchy))
+ .WillOnce(testing::Return(testing::ByMove(std::move(snippets))));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ ElementHiderImpl element_hide(&sub_service_);
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet, "");
+ EXPECT_EQ(data.elemhide_js, "");
+ EXPECT_TRUE(!data.snippet_js.empty());
+ }));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockElementHiderImplTest, GeneratesNothingWhenDocumentAllowListed) {
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot()).WillOnce([this]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(GURL("about:blank")));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ ElementHiderImpl element_hide(&sub_service_);
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet, "");
+ EXPECT_EQ(data.elemhide_js, "");
+ EXPECT_EQ(data.snippet_js, "");
+ }));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockElementHiderImplTest, UsesTwoConfigs) {
+ ContentFiltersData selectors_config_1;
+ selectors_config_1.elemhide_selectors = {"a1", "b1"};
+ ContentFiltersData emu_selectors_config_1;
+ emu_selectors_config_1.elemhide_selectors = {"c1", "d1"};
+ base::Value::List snippets_config_1;
+ snippets_config_1.Append("snippets_config_1_code_1");
+ snippets_config_1.Append("snippets_config_1_code_2");
+ ContentFiltersData selectors_config_2;
+ selectors_config_2.elemhide_selectors = {"a2", "b2"};
+ ContentFiltersData emu_selectors_config_2;
+ emu_selectors_config_2.elemhide_selectors = {"c2", "d2"};
+ base::Value::List snippets_config_2;
+ snippets_config_2.Append("snippets_config_2_code_1");
+ snippets_config_2.Append("snippets_config_2_code_2");
+
+ ElementHiderImpl element_hide(&sub_service_);
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot()).WillOnce([&]() {
+ auto collection1 = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection1,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection1,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection1,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors_config_1));
+ EXPECT_CALL(*collection1, GetElementHideEmulationData(kUrl))
+ .WillOnce(testing::Return(emu_selectors_config_1));
+ EXPECT_CALL(*collection1, GenerateSnippets(kUrl, kFrameHierarchy))
+ .WillOnce(
+ testing::Return(testing::ByMove(std::move(snippets_config_1))));
+ auto collection2 = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection2,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection2,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection2,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors_config_2));
+ EXPECT_CALL(*collection2, GetElementHideEmulationData(kUrl))
+ .WillOnce(testing::Return(emu_selectors_config_2));
+ EXPECT_CALL(*collection2, GenerateSnippets(kUrl, kFrameHierarchy))
+ .WillOnce(
+ testing::Return(testing::ByMove(std::move(snippets_config_2))));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection1));
+ snapshot.push_back(std::move(collection2));
+ return snapshot;
+ });
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet,
+ "a1, b1, a2, b2 {display: none !important;}\n");
+ EXPECT_EQ(data.elemhide_js,
+ "{selector:\"c1\"}, \n{selector:\"d1\"}, "
+ "\n{selector:\"c2\"}, \n{selector:\"d2\"}, \n");
+ EXPECT_EQ(
+ data.snippet_js,
+ "(snippets_lib)({}, "
+ "...[\"snippets_config_1_code_1\",\"snippets_config_1_code_2\","
+ "\"snippets_config_2_code_1\",\"snippets_config_2_code_2\"]);");
+ }));
+}
+
+TEST_F(AdblockElementHiderImplTest, UsesSecondConfigWhenFirstAllowlisted) {
+ ContentFiltersData selectors_config_2;
+ selectors_config_2.elemhide_selectors = {"a2", "b2"};
+ ContentFiltersData emu_selectors_config_2;
+ emu_selectors_config_2.elemhide_selectors = {"c2", "d2"};
+ base::Value::List snippets_config_2;
+ snippets_config_2.Append("snippets_config_2_code_1");
+ snippets_config_2.Append("snippets_config_2_code_2");
+
+ ElementHiderImpl element_hide(&sub_service_);
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot()).WillOnce([&]() {
+ auto collection1 = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection1,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(GURL("about:blank")));
+ EXPECT_CALL(*collection1,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .Times(0);
+ EXPECT_CALL(*collection1, GetElementHideEmulationData(kUrl)).Times(0);
+ EXPECT_CALL(*collection1, GenerateSnippets(kUrl, kFrameHierarchy)).Times(0);
+ auto collection2 = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection2,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection2,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection2,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors_config_2));
+ EXPECT_CALL(*collection2, GetElementHideEmulationData(kUrl))
+ .WillOnce(testing::Return(emu_selectors_config_2));
+ EXPECT_CALL(*collection2, GenerateSnippets(kUrl, kFrameHierarchy))
+ .WillOnce(
+ testing::Return(testing::ByMove(std::move(snippets_config_2))));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection1));
+ snapshot.push_back(std::move(collection2));
+ return snapshot;
+ });
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting([&](const ElementHider::ElemhideInjectionData&
+ data) {
+ EXPECT_EQ(data.stylesheet, "a2, b2 {display: none !important;}\n");
+ EXPECT_EQ(data.elemhide_js,
+ "{selector:\"c2\"}, \n{selector:\"d2\"}, \n");
+ EXPECT_EQ(
+ data.snippet_js,
+ "(snippets_lib)({}, "
+ "...[\"snippets_config_2_code_1\",\"snippets_config_2_code_2\"]);");
+ }));
+}
+
+TEST_F(AdblockElementHiderImplTest, SecondInjectionSkipped) {
+ ContentFiltersData selectors;
+ selectors.elemhide_selectors = {"a", "b"};
+ ContentFiltersData emu_selectors;
+
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot())
+ .WillRepeatedly([this, selectors, emu_selectors]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ GetElementHideData(kUrl, kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(selectors));
+ EXPECT_CALL(*collection, GetElementHideEmulationData(kUrl))
+ .WillOnce(testing::Return(emu_selectors));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ ElementHiderImpl element_hide(&sub_service_);
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet, "a, b {display: none !important;}\n");
+ }));
+ task_environment()->RunUntilIdle();
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.stylesheet, "");
+ }));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockElementHiderImplTest, InjectsXpath3DepWhenXpath3FilterFound) {
+ base::Value::List snippets_config;
+ snippets_config.Append("hide-if-matches-xpath3");
+
+ ElementHiderImpl element_hide(&sub_service_);
+ EXPECT_CALL(sub_service_, GetCurrentSnapshot()).WillOnce([&]() {
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(absl::nullopt));
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Elemhide, kUrl,
+ kFrameHierarchy, kSitekey))
+ .WillOnce(testing::Return(GURL("about:blank")));
+ EXPECT_CALL(*collection, GenerateSnippets(kUrl, kFrameHierarchy))
+ .WillOnce(testing::Return(testing::ByMove(std::move(snippets_config))));
+ SubscriptionService::Snapshot snapshot;
+ snapshot.push_back(std::move(collection));
+ return snapshot;
+ });
+
+ element_hide.ApplyElementHidingEmulationOnPage(
+ kUrl, kFrameHierarchy, main_rfh(), kSitekey,
+ base::BindLambdaForTesting(
+ [&](const ElementHider::ElemhideInjectionData& data) {
+ EXPECT_EQ(data.snippet_js,
+ "(xpath3_dep)();(snippets_lib)({}, "
+ "...[\"hide-if-matches-xpath3\"]);");
+ }));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/frame_hierarchy_builder_test.cc b/components/adblock/content/browser/test/frame_hierarchy_builder_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/frame_hierarchy_builder_test.cc
@@ -0,0 +1,131 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+
+#include <cstdint>
+
+#include "components/adblock/content/browser/request_initiator.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_renderer_host.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class AdblockFrameHierarchyTest : public content::RenderViewHostTestHarness {
+ public:
+ AdblockFrameHierarchyTest() = default;
+ ~AdblockFrameHierarchyTest() override = default;
+
+ AdblockFrameHierarchyTest(const AdblockFrameHierarchyTest&) = delete;
+ AdblockFrameHierarchyTest& operator=(const AdblockFrameHierarchyTest&) =
+ delete;
+
+ content::RenderFrameHost* AddChildFrame(content::RenderFrameHost* parent,
+ const GURL& url) {
+ content::RenderFrameHost* child =
+ content::RenderFrameHostTester::For(parent)->AppendChild("");
+ content::RenderFrameHostTester::For(child)->InitializeRenderFrameIfNeeded();
+ auto simulator =
+ content::NavigationSimulator::CreateRendererInitiated(url, child);
+ simulator->Commit();
+ return simulator->GetFinalRenderFrameHost();
+ }
+
+ std::vector<GURL> BuildFrameHierarchy(const GURL& root,
+ std::initializer_list<GURL> urls) {
+ NavigateAndCommit(root);
+ auto* frame = main_rfh();
+
+ for (const GURL& it : urls) {
+ frame = AddChildFrame(frame, it);
+ }
+
+ adblock::FrameHierarchyBuilder builder;
+ return builder.BuildFrameHierarchy(adblock::RequestInitiator(frame));
+ }
+};
+
+} // namespace
+
+namespace adblock {
+
+const char kRoot[] = "https://foo.com/root";
+const char kRootFile[] = "file:///some/path/to.file";
+const char kFrame1[] = "https://foo.com/frame1";
+const char kFrame2[] = "https://foo.com/frame2";
+const char kBlank[] = "about:blank";
+
+TEST_F(AdblockFrameHierarchyTest, Build) {
+ GURL root(kRoot);
+ GURL url1(kFrame1);
+ GURL url2(kFrame2);
+ auto result = BuildFrameHierarchy(root, {url1, url2});
+
+ ASSERT_EQ(3u, result.size());
+ EXPECT_EQ(url2, result.at(0));
+ EXPECT_EQ(url1, result.at(1));
+ EXPECT_EQ(root, result.at(2));
+}
+
+TEST_F(AdblockFrameHierarchyTest, BuildForFile) {
+ GURL root(kRootFile);
+ GURL url1(kFrame1);
+ GURL url2(kFrame2);
+ auto result = BuildFrameHierarchy(root, {url1, url2});
+
+ ASSERT_EQ(3u, result.size());
+ EXPECT_EQ(url2, result.at(0));
+ EXPECT_EQ(url1, result.at(1));
+ EXPECT_EQ(kRootFile, result.at(2));
+}
+
+TEST_F(AdblockFrameHierarchyTest, BuildBlank) {
+ GURL root(kRoot);
+ GURL url1(kBlank);
+ GURL url2(kFrame2);
+ auto result = BuildFrameHierarchy(root, {url1, url2});
+
+ ASSERT_EQ(2u, result.size());
+ EXPECT_EQ(url2, result.at(0));
+ EXPECT_EQ(root, result.at(1));
+}
+
+TEST_F(AdblockFrameHierarchyTest, BuildEmpty) {
+ GURL root(kRoot);
+ GURL url1(kFrame1);
+ GURL url2;
+ auto result = BuildFrameHierarchy(root, {url1, url2});
+
+ ASSERT_EQ(2u, result.size());
+ EXPECT_EQ(url1, result.at(0));
+ EXPECT_EQ(root, result.at(1));
+}
+
+TEST_F(AdblockFrameHierarchyTest, BuildForDetachedRequest) {
+ const GURL url(kFrame1);
+
+ FrameHierarchyBuilder builder;
+ const auto result = builder.BuildFrameHierarchy(RequestInitiator(url));
+ EXPECT_EQ(result, std::vector<GURL>({url}));
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.cc b/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.cc
@@ -0,0 +1,28 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h"
+
+namespace adblock {
+
+MockAdblockContentSecurityPolicyInjector::
+ MockAdblockContentSecurityPolicyInjector() = default;
+
+MockAdblockContentSecurityPolicyInjector::
+ ~MockAdblockContentSecurityPolicyInjector() = default;
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h b/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_adblock_content_security_policy_injector.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ADBLOCK_CONTENT_SECURITY_POLICY_INJECTOR_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ADBLOCK_CONTENT_SECURITY_POLICY_INJECTOR_H_
+
+#include "components/adblock/content/browser/content_security_policy_injector.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class MockAdblockContentSecurityPolicyInjector
+ : public ContentSecurityPolicyInjector {
+ public:
+ MockAdblockContentSecurityPolicyInjector();
+ ~MockAdblockContentSecurityPolicyInjector() override;
+
+ MOCK_METHOD(void,
+ InsertContentSecurityPolicyHeadersIfApplicable,
+ (const GURL&,
+ const RequestInitiator&,
+ const scoped_refptr<net::HttpResponseHeaders>&,
+ InsertContentSecurityPolicyHeadersCallback),
+ (override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ADBLOCK_CONTENT_SECURITY_POLICY_INJECTOR_H_
diff --git a/components/adblock/content/browser/test/mock_element_hider.cc b/components/adblock/content/browser/test/mock_element_hider.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_element_hider.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/test/mock_element_hider.h"
+
+namespace adblock {
+
+MockElementHider::MockElementHider() = default;
+
+MockElementHider::~MockElementHider() = default;
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/mock_element_hider.h b/components/adblock/content/browser/test/mock_element_hider.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_element_hider.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ELEMENT_HIDER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ELEMENT_HIDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "components/adblock/content/browser/element_hider.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class MockElementHider : public ElementHider {
+ public:
+ MockElementHider();
+ ~MockElementHider() override;
+ MOCK_METHOD(
+ void,
+ ApplyElementHidingEmulationOnPage,
+ (GURL url,
+ std::vector<GURL> frame_hierarchy,
+ content::RenderFrameHost* render_frame_host,
+ SiteKey sitekey,
+ base::OnceCallback<void(const ElementHider::ElemhideInjectionData&)>),
+ (override));
+
+ MOCK_METHOD(bool, IsElementTypeHideable, (ContentType), (override, const));
+
+ MOCK_METHOD(void,
+ HideBlockedElement,
+ (const GURL& url, content::RenderFrameHost* render_frame_host),
+ (override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_ELEMENT_HIDER_H_
diff --git a/components/adblock/content/browser/test/mock_frame_hierarchy_builder.cc b/components/adblock/content/browser/test/mock_frame_hierarchy_builder.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_frame_hierarchy_builder.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/test/mock_frame_hierarchy_builder.h"
+
+namespace adblock {
+
+MockFrameHierarchyBuilder::MockFrameHierarchyBuilder() = default;
+
+MockFrameHierarchyBuilder::~MockFrameHierarchyBuilder() = default;
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/mock_frame_hierarchy_builder.h b/components/adblock/content/browser/test/mock_frame_hierarchy_builder.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_frame_hierarchy_builder.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_FRAME_HIERARCHY_BUILDER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_FRAME_HIERARCHY_BUILDER_H_
+
+#include "components/adblock/content/browser/frame_hierarchy_builder.h"
+#include "components/adblock/content/browser/request_initiator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class MockFrameHierarchyBuilder
+ : public testing::NiceMock<FrameHierarchyBuilder> {
+ public:
+ MockFrameHierarchyBuilder();
+ ~MockFrameHierarchyBuilder() override;
+
+ MOCK_METHOD(std::vector<GURL>,
+ BuildFrameHierarchy,
+ (const RequestInitiator&),
+ (const, override));
+ MOCK_METHOD(GURL,
+ FindUrlForFrame,
+ (content::RenderFrameHost*, content::WebContents*),
+ (const, override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_FRAME_HIERARCHY_BUILDER_H_
diff --git a/components/adblock/content/browser/test/mock_resource_classification_runner.cc b/components/adblock/content/browser/test/mock_resource_classification_runner.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_resource_classification_runner.cc
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/test/mock_resource_classification_runner.h"
+#include "gtest/gtest.h"
+
+namespace adblock {
+
+MockResourceClassificationRunner::MockResourceClassificationRunner() = default;
+MockResourceClassificationRunner::~MockResourceClassificationRunner() = default;
+
+void MockResourceClassificationRunner::AddObserver(
+ ResourceClassificationRunner::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void MockResourceClassificationRunner::RemoveObserver(
+ ResourceClassificationRunner::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void MockResourceClassificationRunner::NotifyResourceMatched(
+ const GURL& url,
+ FilterMatchResult result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription) {
+ for (auto& observer : observers_) {
+ observer.OnRequestMatched(url, result, parent_frame_urls, content_type,
+ render_frame_host, subscription, "");
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/mock_resource_classification_runner.h b/components/adblock/content/browser/test/mock_resource_classification_runner.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/mock_resource_classification_runner.h
@@ -0,0 +1,96 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_RESOURCE_CLASSIFICATION_RUNNER_H_
+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_RESOURCE_CLASSIFICATION_RUNNER_H_
+
+#include "components/adblock/content/browser/resource_classification_runner.h"
+#include "content/public/browser/render_frame_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class MockResourceClassificationRunner : public ResourceClassificationRunner {
+ public:
+ MockResourceClassificationRunner();
+ ~MockResourceClassificationRunner() override;
+
+ void AddObserver(ResourceClassificationRunner::Observer*) override;
+ void RemoveObserver(ResourceClassificationRunner::Observer*) override;
+ void NotifyResourceMatched(const GURL& url,
+ FilterMatchResult result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription);
+
+ MOCK_METHOD(FilterMatchResult,
+ ShouldBlockPopup,
+ (const SubscriptionService::Snapshot&,
+ const GURL&,
+ content::RenderFrameHost*),
+ (override));
+
+ MOCK_METHOD(void,
+ CheckPopupFilterMatch,
+ (SubscriptionService::Snapshot,
+ const GURL&,
+ content::RenderFrameHost&,
+ CheckFilterMatchCallback),
+ (override));
+
+ MOCK_METHOD(void,
+ CheckRequestFilterMatch,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ ContentType,
+ const RequestInitiator&,
+ CheckFilterMatchCallback),
+ (override));
+
+ MOCK_METHOD(void,
+ CheckDocumentAllowlisted,
+ (SubscriptionService::Snapshot,
+ const GURL& request_url,
+ const RequestInitiator& request_initiator),
+ (override));
+
+ MOCK_METHOD(void,
+ CheckResponseFilterMatch,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ ContentType,
+ const RequestInitiator&,
+ const scoped_refptr<net::HttpResponseHeaders>&,
+ CheckFilterMatchCallback),
+ (override));
+
+ MOCK_METHOD(void,
+ CheckRewriteFilterMatch,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ const RequestInitiator&,
+ base::OnceCallback<void(const absl::optional<GURL>&)>),
+ (override));
+
+ base::ObserverList<ResourceClassificationRunner::Observer> observers_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_TEST_MOCK_RESOURCE_CLASSIFICATION_RUNNER_H_
diff --git a/components/adblock/content/browser/test/page_view_stats_test.cc b/components/adblock/content/browser/test/page_view_stats_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/page_view_stats_test.cc
@@ -0,0 +1,334 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/page_view_stats.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/adblock_filter_match.h"
+#include "components/adblock/content/browser/test/mock_resource_classification_runner.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+struct ExpectedPageViewCount {
+ int aa_count;
+ int aa_bt_count;
+ int allowing_count;
+ int blocking_count;
+ int total_count;
+};
+
+class AdblockPageViewStatsTest : public content::RenderViewHostTestHarness {
+ public:
+ void SetUp() override {
+ RenderViewHostTestHarness::SetUp();
+ // The testee needs a pref for persistently storing the stats. It's
+ // registered by the main pref-registering function.
+ adblock::common::prefs::RegisterProfilePrefs(prefs_.registry());
+ page_view_stats_ =
+ std::make_unique<PageViewStats>(&classification_runner_, &prefs_);
+ // Ensure that the observer was added by the constructor.
+ EXPECT_TRUE(
+ classification_runner_.observers_.HasObserver(page_view_stats_.get()));
+ }
+
+ void TearDown() override {
+ page_view_stats_.reset();
+ // Ensure that the observer was removed by the destructor.
+ EXPECT_TRUE(classification_runner_.observers_.empty());
+ RenderViewHostTestHarness::TearDown();
+ }
+
+ void RegisterPopupFilterHitFromFilterList(GURL subscription_url,
+ FilterMatchResult match_result) {
+ page_view_stats_->OnPopupMatched(GURL("https://example.com/popup.htmml"),
+ match_result, GURL("https://example.com/"),
+ main_rfh(), subscription_url,
+ kAdblockFilteringConfigurationName);
+ }
+
+ void RegisterSubresourceFilterHitFromFilterList(
+ GURL subscription_url,
+ FilterMatchResult match_result,
+ content::RenderFrameHost* rfh) {
+ page_view_stats_->OnRequestMatched(
+ GURL("https://example.com/ad.jpg"), match_result,
+ std::vector<GURL>{GURL("https://example.com")}, ContentType::Image, rfh,
+ subscription_url, kAdblockFilteringConfigurationName);
+ }
+
+ void RegisterSubresourceFilterHitFromFilterList(
+ GURL subscription_url,
+ FilterMatchResult match_result = FilterMatchResult::kAllowRule) {
+ RegisterSubresourceFilterHitFromFilterList(subscription_url, match_result,
+ main_rfh());
+ }
+
+ void VerifyPageViewCount(ExpectedPageViewCount expected) {
+ auto dict_payload = page_view_stats_->GetPayload();
+ auto* value = dict_payload.Find("aa_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.aa_count, value->GetInt());
+
+ value = dict_payload.Find("aa_bt_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.aa_bt_count, value->GetInt());
+
+ value = dict_payload.Find("allowed_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.allowing_count, value->GetInt());
+
+ value = dict_payload.Find("blocked_pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.blocking_count, value->GetInt());
+
+ value = dict_payload.Find("pageviews");
+ ASSERT_TRUE(value);
+ EXPECT_EQ(expected.total_count, value->GetInt());
+ }
+
+ using PayloadCallback = base::MockOnceCallback<void(std::string)>;
+ MockResourceClassificationRunner classification_runner_;
+ TestingPrefServiceSimple prefs_;
+ std::unique_ptr<PageViewStats> page_view_stats_;
+};
+
+TEST_F(AdblockPageViewStatsTest, NoPageViewsReportedInitially) {
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 0});
+}
+
+TEST_F(AdblockPageViewStatsTest, EasylistAllowingHitCounted) {
+ const GURL subscription_url = DefaultSubscriptionUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kAllowRule);
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, EasylistBlockingHitCounted) {
+ const GURL subscription_url = DefaultSubscriptionUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kBlockRule);
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, EasylistPopupAllowingHitCounted) {
+ const GURL subscription_url = DefaultSubscriptionUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterPopupFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kAllowRule);
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, EasylistPopupBlockingHitCounted) {
+ const GURL subscription_url = DefaultSubscriptionUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterPopupFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kBlockRule);
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, AAAllowingHitCountedInTwoMetrics) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kAllowRule);
+
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, AABlockingHitCountedInBlockingMetric) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url,
+ FilterMatchResult::kBlockRule);
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 1,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, AABlockthroughAllowingFakeFilterHitCounted) {
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ page_view_stats_->RegisterAcceptableAdsBlockthroughtHit(main_rfh());
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 1,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, MultipleFilterHitsReportedAsSinglePageView) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ page_view_stats_->OnRequestMatched(
+ GURL("https://example.com/ad1.jpg"), FilterMatchResult::kAllowRule,
+ std::vector<GURL>{GURL("https://example.com")}, ContentType::Image,
+ main_rfh(), subscription_url, kAdblockFilteringConfigurationName);
+ page_view_stats_->OnRequestMatched(
+ GURL("https://example.com/ad2.jpg"), FilterMatchResult::kAllowRule,
+ std::vector<GURL>{GURL("https://example.com")}, ContentType::Image,
+ main_rfh(), subscription_url, kAdblockFilteringConfigurationName);
+ page_view_stats_->OnPageAllowed(GURL("https://example.com"), main_rfh(),
+ subscription_url,
+ kAdblockFilteringConfigurationName);
+
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, ChildFrameCountsTowardsParentsPageView) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ // There is one filter hit in the parent frame:
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+
+ // And another in an iframe, a child of main_rfh():
+ content::RenderFrameHostTester* rfh_tester =
+ content::RenderFrameHostTester::For(main_rfh());
+ auto* child_rfh = rfh_tester->AppendChild("subframe");
+ page_view_stats_->RegisterMainFrameNavigation(child_rfh);
+ RegisterSubresourceFilterHitFromFilterList(
+ subscription_url, FilterMatchResult::kAllowRule, child_rfh);
+
+ // The page view should be counted only once.
+ VerifyPageViewCount({.aa_count = 1,
+ .aa_bt_count = 0,
+ .allowing_count = 1,
+ .blocking_count = 0,
+ .total_count = 1});
+}
+
+TEST_F(AdblockPageViewStatsTest, NavigatingToNewPageCreatesNewPageViewStat) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ // Filter hit in the original page:
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+
+ // Navigate to a new page
+ NavigateAndCommit(GURL("https://example.com/page2.html"));
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+
+ // Filter hit in the new page
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+
+ // Now 2 page views are reported
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+}
+
+TEST_F(AdblockPageViewStatsTest, PageViewStoredPersistently) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+ NavigateAndCommit(GURL("https://example.com/page2.html"));
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+
+ // PageViewStats dies and is recreated on subsequent browser restart.
+ // The data should be persisted (in prefs).
+ page_view_stats_.reset();
+ page_view_stats_ =
+ std::make_unique<PageViewStats>(&classification_runner_, &prefs_);
+
+ // The page views should be restored.
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+}
+
+TEST_F(AdblockPageViewStatsTest, PageViewCountResetAfterSuccessfulReport) {
+ const GURL subscription_url = AcceptableAdsUrl();
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+ NavigateAndCommit(GURL("https://example.com/page2.html"));
+ page_view_stats_->RegisterMainFrameNavigation(main_rfh());
+ RegisterSubresourceFilterHitFromFilterList(subscription_url);
+ VerifyPageViewCount({.aa_count = 2,
+ .aa_bt_count = 0,
+ .allowing_count = 2,
+ .blocking_count = 0,
+ .total_count = 2});
+
+ page_view_stats_->ResetStats();
+
+ VerifyPageViewCount({.aa_count = 0,
+ .aa_bt_count = 0,
+ .allowing_count = 0,
+ .blocking_count = 0,
+ .total_count = 0});
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/resource_classification_runner_impl_test.cc b/components/adblock/content/browser/test/resource_classification_runner_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/resource_classification_runner_impl_test.cc
@@ -0,0 +1,422 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/resource_classification_runner_impl.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/frame_opener_info.h"
+#include "components/adblock/content/browser/test/mock_frame_hierarchy_builder.h"
+#include "components/adblock/core/classifier/test/mock_resource_classifier.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "components/adblock/core/test/mock_sitekey_storage.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_renderer_host.h"
+#include "gmock/gmock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
+
+namespace adblock {
+
+using testing::_;
+using testing::Ref;
+using testing::Return;
+using Decision = ResourceClassifier::ClassificationResult::Decision;
+
+namespace {
+class MockObserver : public ResourceClassificationRunner::Observer {
+ public:
+ MOCK_METHOD(void,
+ OnRequestMatched,
+ (const GURL& url,
+ FilterMatchResult match_result,
+ const std::vector<GURL>& parent_frame_urls,
+ ContentType content_type,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name),
+ (override));
+ MOCK_METHOD(void,
+ OnPageAllowed,
+ (const GURL& url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name),
+ (override));
+ MOCK_METHOD(void,
+ OnPopupMatched,
+ (const GURL& url,
+ FilterMatchResult match_result,
+ const GURL& opener_url,
+ content::RenderFrameHost* render_frame_host,
+ const GURL& subscription,
+ const std::string& configuration_name),
+ (override));
+};
+
+} // namespace
+
+class AdblockResourceClassificationRunnerImplTest
+ : public content::RenderViewHostTestHarness {
+ public:
+ void SetUp() override {
+ RenderViewHostTestHarness::SetUp();
+ std::unique_ptr<MockFrameHierarchyBuilder> mock_frame_hierarchy_builder =
+ std::make_unique<MockFrameHierarchyBuilder>();
+ mock_frame_hierarchy_builder_ = mock_frame_hierarchy_builder.get();
+ mock_snapshot_ = std::make_unique<SubscriptionService::Snapshot>();
+ mock_snapshot_->push_back(std::make_unique<MockSubscriptionCollection>());
+ mock_resource_classifier_ = base::MakeRefCounted<MockResourceClassifier>();
+ classification_runner_ = std::make_unique<ResourceClassificationRunnerImpl>(
+ mock_resource_classifier_, std::move(mock_frame_hierarchy_builder),
+ &mock_sitekey_storage_);
+ classification_runner_->AddObserver(&mock_observer_);
+ }
+
+ void TearDown() override {
+ // Avoid dangling pointers during destruction.
+ mock_frame_hierarchy_builder_ = nullptr;
+ classification_runner_->RemoveObserver(&mock_observer_);
+ classification_runner_.reset();
+ RenderViewHostTestHarness::TearDown();
+ }
+
+ MockSubscriptionCollection& mock_subscription_collection() {
+ return *static_cast<MockSubscriptionCollection*>(
+ mock_snapshot_->front().get());
+ }
+
+ void SiteKeyWillBePresent(const std::vector<GURL>& frame_hierarchy,
+ SiteKey sitekey) {
+ EXPECT_CALL(mock_sitekey_storage_, FindSiteKeyForAnyUrl(frame_hierarchy))
+ .WillRepeatedly(
+ Return(std::make_pair(frame_hierarchy.front(), sitekey)));
+ }
+
+ void FrameHierarchyWillBeBuilt() {
+ EXPECT_CALL(*mock_frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+ }
+
+ void ClassifierReturnsRequestClassification(GURL url,
+ ContentType content_type,
+ Decision decision) {
+ EXPECT_CALL(
+ *mock_resource_classifier_,
+ ClassifyRequest(_, url, kFrameHierarchy, content_type, kSitekey))
+ .WillOnce(testing::Return(ResourceClassifier::ClassificationResult{
+ decision, kSubscriptionUrl, kConfigurationName}));
+ }
+
+ void ClassifierReturnsPopupClassification(GURL popup_url, Decision decision) {
+ EXPECT_CALL(*mock_resource_classifier_,
+ ClassifyPopup(_, popup_url, _, kSitekey))
+ .WillOnce(testing::Return(ResourceClassifier::ClassificationResult{
+ decision, kSubscriptionUrl, kConfigurationName}));
+ }
+
+ void ClassifierReturnsResponseClassification(
+ GURL url,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ Decision decision) {
+ EXPECT_CALL(*mock_resource_classifier_,
+ ClassifyResponse(_, url, kFrameHierarchy, _, headers))
+ .WillOnce(testing::Return(ResourceClassifier::ClassificationResult{
+ decision, kSubscriptionUrl, kConfigurationName}));
+ }
+
+ void AdMatchedWillBeNotified(GURL url,
+ ContentType content_type,
+ FilterMatchResult result) {
+ EXPECT_CALL(
+ mock_observer_,
+ OnRequestMatched(url, result, kFrameHierarchy, content_type, main_rfh(),
+ kSubscriptionUrl, kConfigurationName));
+ }
+
+ void PopupMatchedWillBeNotified(GURL popup_url, FilterMatchResult result) {
+ EXPECT_CALL(
+ mock_observer_,
+ OnPopupMatched(popup_url, result, kFrameHierarchy.front(), main_rfh(),
+ kSubscriptionUrl, kConfigurationName));
+ }
+
+ MockObserver mock_observer_;
+ MockSitekeyStorage mock_sitekey_storage_;
+ raw_ptr<MockFrameHierarchyBuilder> mock_frame_hierarchy_builder_;
+ scoped_refptr<MockResourceClassifier> mock_resource_classifier_;
+ std::unique_ptr<ResourceClassificationRunnerImpl> classification_runner_;
+ std::unique_ptr<SubscriptionService::Snapshot> mock_snapshot_;
+
+ const GURL kUrl{"https://test.com/url.x"};
+ const GURL kWebsocketUrl{"wss://test.com/url.x"};
+ const std::vector<GURL> kFrameHierarchy{GURL{"https://test.com/"}};
+ const SiteKey kSitekey{"key"};
+ const GURL kSubscriptionUrl{"https://easylist.com/list.txt"};
+ const std::string kConfigurationName{"test_configuration"};
+};
+
+TEST_F(AdblockResourceClassificationRunnerImplTest, CheckGurlSpecNormalizes) {
+ // added as a check that gurl keeps normalizing url through spec.
+ EXPECT_EQ("http://hostname.com/", GURL("http:HOSTNAME.com").spec());
+ EXPECT_EQ("http://hostname.com/", GURL("http:HOSTNAME.com:80").spec());
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckRequestFilterMatch_ValidParameters) {
+ FrameHierarchyWillBeBuilt();
+ SiteKeyWillBePresent(kFrameHierarchy, kSitekey);
+
+ ClassifierReturnsRequestClassification(kUrl, ContentType::Image,
+ Decision::Blocked);
+
+ // The final callback will be called with kBlockRule result.
+ base::MockCallback<CheckFilterMatchCallback> callback;
+ EXPECT_CALL(callback, Run(FilterMatchResult::kBlockRule));
+
+ AdMatchedWillBeNotified(kUrl, ContentType::Image,
+ FilterMatchResult::kBlockRule);
+
+ classification_runner_->CheckRequestFilterMatch(
+ std::move(*mock_snapshot_), kUrl, ContentType::Image,
+ RequestInitiator(main_rfh()), callback.Get());
+
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckRequestFilterMatchForWebSocket_ValidParameters) {
+ // For WebSocket interception, we already have a RenderFrameHost found, so
+ // FrameHierarchyBuilder only has to assemble the frame hierarchy.
+ EXPECT_CALL(*mock_frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+
+ SiteKeyWillBePresent(kFrameHierarchy, kSitekey);
+
+ ClassifierReturnsRequestClassification(kWebsocketUrl, ContentType::Websocket,
+ Decision::Blocked);
+
+ // The final callback will be called with kBlockRule result.
+ base::MockCallback<CheckFilterMatchCallback> callback;
+ EXPECT_CALL(callback, Run(FilterMatchResult::kBlockRule));
+
+ AdMatchedWillBeNotified(kWebsocketUrl, ContentType::Websocket,
+ FilterMatchResult::kBlockRule);
+
+ classification_runner_->CheckRequestFilterMatch(
+ std::move(*mock_snapshot_), kWebsocketUrl, ContentType::Websocket,
+ RequestInitiator(main_rfh()), callback.Get());
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckRequestFilterMatch_AllowlistedMainframeInOneConfig) {
+ EXPECT_CALL(mock_observer_, OnPageAllowed(kUrl, main_rfh(), kSubscriptionUrl,
+ kConfigurationName))
+ .Times(0);
+
+ // In default config page is not allowlisted...
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ std::vector<GURL>(), SiteKey()))
+ .WillOnce(testing::Return(absl::nullopt));
+
+ // ...so the other config is not even asked
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ std::vector<GURL>(), SiteKey()))
+ .Times(0);
+ mock_snapshot_->push_back(std::move(collection));
+
+ classification_runner_->CheckDocumentAllowlisted(
+ std::move(*mock_snapshot_), kUrl, RequestInitiator(main_rfh()));
+
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckRequestFilterMatch_AllowlistedMainframeInTwoConfigs) {
+ std::string other_configuration = "other";
+ EXPECT_CALL(mock_observer_, OnPageAllowed(kUrl, main_rfh(), kSubscriptionUrl,
+ other_configuration));
+
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ std::vector<GURL>(), SiteKey()))
+ .WillOnce(testing::Return(absl::optional<GURL>{kSubscriptionUrl}));
+
+ auto collection = std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*collection,
+ FindBySpecialFilter(SpecialFilterType::Document, kUrl,
+ std::vector<GURL>(), SiteKey()))
+ .WillOnce(testing::Return(absl::optional<GURL>{kSubscriptionUrl}));
+ EXPECT_CALL(*collection, GetFilteringConfigurationName())
+ .WillRepeatedly(testing::ReturnRef(other_configuration));
+ EXPECT_CALL(mock_subscription_collection(), GetFilteringConfigurationName())
+ .WillRepeatedly(testing::ReturnRef(kConfigurationName));
+ mock_snapshot_->push_back(std::move(collection));
+
+ classification_runner_->CheckDocumentAllowlisted(
+ std::move(*mock_snapshot_), kUrl, RequestInitiator(main_rfh()));
+
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckRequestFilterMatch_RenderFrameHostDiesBeforeElementHiding) {
+ FrameHierarchyWillBeBuilt();
+ SiteKeyWillBePresent(kFrameHierarchy, kSitekey);
+
+ ClassifierReturnsRequestClassification(kUrl, ContentType::Image,
+ Decision::Blocked);
+
+ // The final callback will be called with kBlockRule result.
+ base::MockCallback<CheckFilterMatchCallback> callback;
+ EXPECT_CALL(callback, Run(FilterMatchResult::kBlockRule));
+
+ classification_runner_->CheckRequestFilterMatch(
+ std::move(*mock_snapshot_), kUrl, ContentType::Image,
+
+ RequestInitiator(main_rfh()), callback.Get());
+
+ // Before running the thread loop, destroy the RFH.
+ DeleteContents();
+ // Observer will be notified with null RFH.
+ EXPECT_CALL(mock_observer_,
+ OnRequestMatched(kUrl, FilterMatchResult::kBlockRule,
+ kFrameHierarchy, ContentType::Image, nullptr,
+ kSubscriptionUrl, kConfigurationName))
+ .Times(0);
+
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckResponseFilterMatch_ResponseFilterMatchResultBlocked) {
+ auto headers = net::HttpResponseHeaders::TryToCreate("whatever");
+ ASSERT_TRUE(headers);
+ base::MockCallback<CheckFilterMatchCallback> callback;
+
+ EXPECT_CALL(*mock_frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+ ClassifierReturnsResponseClassification(kUrl, headers, Decision::Blocked);
+
+ classification_runner_->CheckResponseFilterMatch(
+ std::move(*mock_snapshot_), kUrl, ContentType::Image,
+ RequestInitiator(main_rfh()), headers, callback.Get());
+
+ EXPECT_CALL(callback, Run(FilterMatchResult::kBlockRule));
+ EXPECT_CALL(
+ mock_observer_,
+ OnRequestMatched(kUrl, FilterMatchResult::kBlockRule, kFrameHierarchy, _,
+ _, kSubscriptionUrl, kConfigurationName));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckResponseFilterMatch_ResponseFilterMatchResultAllowed) {
+ auto headers = net::HttpResponseHeaders::TryToCreate("whatever");
+ ASSERT_TRUE(headers);
+ base::MockCallback<CheckFilterMatchCallback> callback;
+
+ EXPECT_CALL(*mock_frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+ ClassifierReturnsResponseClassification(kUrl, headers, Decision::Allowed);
+
+ classification_runner_->CheckResponseFilterMatch(
+ std::move(*mock_snapshot_), kUrl, ContentType::Image,
+ RequestInitiator(main_rfh()), headers, callback.Get());
+
+ EXPECT_CALL(callback, Run(FilterMatchResult::kAllowRule));
+ EXPECT_CALL(
+ mock_observer_,
+ OnRequestMatched(kUrl, FilterMatchResult::kAllowRule, kFrameHierarchy, _,
+ _, kSubscriptionUrl, kConfigurationName));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckResponseFilterMatch_ResponseFilterMatchResultIgnored) {
+ auto headers = net::HttpResponseHeaders::TryToCreate("whatever");
+ ASSERT_TRUE(headers);
+ base::MockCallback<CheckFilterMatchCallback> callback;
+
+ EXPECT_CALL(*mock_frame_hierarchy_builder_,
+ BuildFrameHierarchy(RequestInitiator(main_rfh())))
+ .WillOnce(Return(kFrameHierarchy));
+ ClassifierReturnsResponseClassification(kUrl, headers, Decision::Ignored);
+
+ classification_runner_->CheckResponseFilterMatch(
+ std::move(*mock_snapshot_), kUrl, ContentType::Image,
+ RequestInitiator(main_rfh()), headers, callback.Get());
+
+ EXPECT_CALL(callback, Run(FilterMatchResult::kNoRule));
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckPopupFilterMatch_ValidParameters) {
+ // Set opener pointing to valid RFH
+ adblock::FrameOpenerInfo::CreateForWebContents(web_contents());
+ auto* info = adblock::FrameOpenerInfo::FromWebContents(web_contents());
+ info->SetOpener(main_rfh()->GetGlobalId());
+
+ FrameHierarchyWillBeBuilt();
+ SiteKeyWillBePresent(kFrameHierarchy, kSitekey);
+
+ ClassifierReturnsPopupClassification(kUrl, Decision::Blocked);
+
+ // The final callback will be called with kBlockRule result.
+ base::MockCallback<CheckFilterMatchCallback> callback;
+ EXPECT_CALL(callback, Run(FilterMatchResult::kBlockRule));
+
+ PopupMatchedWillBeNotified(kUrl, FilterMatchResult::kBlockRule);
+
+ classification_runner_->CheckPopupFilterMatch(
+ std::move(*mock_snapshot_), kUrl, *main_rfh(), callback.Get());
+
+ task_environment()->RunUntilIdle();
+}
+
+TEST_F(AdblockResourceClassificationRunnerImplTest,
+ CheckPopupFilterMatch_RenderFrameHostNotFoundForOpener) {
+ // Set opener pointing to invalid RFH
+ adblock::FrameOpenerInfo::CreateForWebContents(web_contents());
+
+ base::MockCallback<CheckFilterMatchCallback> callback;
+ EXPECT_CALL(callback, Run(FilterMatchResult::kNoRule));
+
+ classification_runner_->CheckPopupFilterMatch(
+ std::move(*mock_snapshot_), kUrl, *main_rfh(), callback.Get());
+
+ task_environment()->RunUntilIdle();
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/session_stats_impl_test.cc b/components/adblock/content/browser/test/session_stats_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/session_stats_impl_test.cc
@@ -0,0 +1,74 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/session_stats_impl.h"
+
+#include "base/test/mock_callback.h"
+#include "components/adblock/content/browser/test/mock_resource_classification_runner.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+namespace {
+
+constexpr char kAllowedTestSub[] = "http://allowed.sub.com/";
+constexpr char kBlockedTestSub[] = "http://blocked.sub.com/";
+
+} // namespace
+
+class AdblockSessionStatsTest : public testing::Test {
+ public:
+ AdblockSessionStatsTest() = default;
+
+ void SetUp() override {
+ session_stats_ = std::make_unique<SessionStatsImpl>(&classfier_);
+ }
+
+ MockResourceClassificationRunner classfier_;
+ std::unique_ptr<SessionStatsImpl> session_stats_;
+};
+
+TEST_F(AdblockSessionStatsTest, StatsDataCollected) {
+ classfier_.NotifyResourceMatched(
+ GURL(), FilterMatchResult::kAllowRule, std::vector<GURL>(),
+ ContentType::Subdocument, nullptr, GURL{kAllowedTestSub});
+
+ classfier_.NotifyResourceMatched(
+ GURL(), FilterMatchResult::kBlockRule, std::vector<GURL>(),
+ ContentType::Subdocument, nullptr, GURL{kBlockedTestSub});
+
+ auto allowed_result = session_stats_->GetSessionAllowedResourcesCount();
+ auto blocked_result = session_stats_->GetSessionBlockedResourcesCount();
+
+ EXPECT_EQ((std::map<GURL, long>{{GURL(kAllowedTestSub), 1}}), allowed_result);
+ EXPECT_EQ((std::map<GURL, long>{{GURL(kBlockedTestSub), 1}}), blocked_result);
+
+ classfier_.NotifyResourceMatched(
+ GURL(), FilterMatchResult::kBlockRule, std::vector<GURL>(),
+ ContentType::Subdocument, nullptr, GURL{kBlockedTestSub});
+
+ allowed_result = session_stats_->GetSessionAllowedResourcesCount();
+ blocked_result = session_stats_->GetSessionBlockedResourcesCount();
+
+ EXPECT_EQ((std::map<GURL, long>{{GURL(kAllowedTestSub), 1}}), allowed_result);
+ EXPECT_EQ((std::map<GURL, long>{{GURL(kBlockedTestSub), 2}}), blocked_result);
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/browser/test/subscription_service_factory_test.cc b/components/adblock/content/browser/test/subscription_service_factory_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/browser/test/subscription_service_factory_test.cc
@@ -0,0 +1,488 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/content/browser/factories/subscription_service_factory.h"
+
+#include "base/command_line.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+namespace {
+bool IsFilterListPresent(const std::vector<GURL>& subscriptions,
+ const GURL& url) {
+ return std::find(subscriptions.begin(), subscriptions.end(), url) !=
+ subscriptions.end();
+}
+} // namespace
+
+class SubscriptionServiceFactoryTestImpl : public SubscriptionServiceFactory {
+ public:
+ SubscriptionServiceFactoryTestImpl() = default;
+ ~SubscriptionServiceFactoryTestImpl() override = default;
+
+ SubscriptionService* GetSubscriptionService(
+ content::BrowserContext* context) {
+ auto uptr = BuildServiceInstanceForBrowserContext(context);
+ return static_cast<SubscriptionService*>(uptr.release());
+ }
+
+ void SetPrefs(TestingPrefServiceSimple* prefs) {
+ prefs_ = prefs;
+ adblock::common::prefs::RegisterProfilePrefs(prefs_->registry());
+ }
+
+ void SetLocale(std::string locale) { locale_ = locale; }
+
+ protected:
+ SubscriptionPersistentMetadata* GetSubscriptionPersistentMetadata(
+ content::BrowserContext* context) const override {
+ static MockSubscriptionPersistentMetadata
+ mock_subscription_persistent_metadata;
+ return &mock_subscription_persistent_metadata;
+ }
+
+ PrefService* GetPrefs(content::BrowserContext* context) const override {
+ return prefs_;
+ }
+
+ std::string GetLocale() const override { return locale_; }
+
+ private:
+ std::string locale_ = "en-US";
+ raw_ptr<TestingPrefServiceSimple> prefs_;
+};
+
+class SubscriptionServiceFactoryTest : public testing::Test {
+ public:
+ SubscriptionServiceFactoryTest() = default;
+
+ void SetUp() override {
+ subscription_service_factory_test_impl_.SetPrefs(&prefs_);
+ }
+
+ content::BrowserTaskEnvironment task_environment_;
+ content::TestBrowserContext browser_context_;
+ TestingPrefServiceSimple prefs_;
+ SubscriptionServiceFactoryTestImpl subscription_service_factory_test_impl_;
+};
+
+TEST_F(SubscriptionServiceFactoryTest, TestFirstRun) {
+ ASSERT_TRUE(
+ prefs_.GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ // Confirm 1st run flag is now off and default settings are applied.
+ ASSERT_FALSE(
+ prefs_.GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ ASSERT_TRUE(configuration->IsEnabled());
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_EQ(3u, subscriptions.size());
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, DefaultSubscriptionUrl()));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AntiCVUrl()));
+
+ // Change settings.
+ configuration->SetEnabled(false);
+ configuration->RemoveFilterList(AcceptableAdsUrl());
+ configuration->RemoveFilterList(DefaultSubscriptionUrl());
+ configuration->RemoveFilterList(AntiCVUrl());
+ }
+
+ {
+ // Recreate service and make sure settings are not overwritten.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(
+ subscription_service
+ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName)
+ ->IsEnabled());
+ ASSERT_TRUE(configuration->GetFilterLists().empty());
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestFirstRunNonEnglishLocale) {
+ auto pl_and_en_subscription = GURL{
+ "https://easylist-downloads.adblockplus.org/"
+ "easylistpolish+easylist.txt"};
+ auto pl_subscription = GURL{
+ "https://easylist-downloads.adblockplus.org/"
+ "easylistpolish.txt"};
+ subscription_service_factory_test_impl_.SetLocale("pl-PL");
+ ASSERT_TRUE(
+ prefs_.GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ // Confirm 1st run flag is now off and default settings are applied.
+ ASSERT_FALSE(
+ prefs_.GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ ASSERT_TRUE(configuration->IsEnabled());
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_EQ(4u, subscriptions.size());
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AntiCVUrl()));
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, pl_and_en_subscription));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, DefaultSubscriptionUrl()));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, pl_subscription));
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestDisableAcceptableAdsSwitch) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kDisableAcceptableAds);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+
+ // Re-enable AA.
+ configuration->AddFilterList(AcceptableAdsUrl());
+ }
+
+ {
+ // Verify switch is applied again.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestDisableAdblockSwitch) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kDisableAdblock);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsEnabled());
+
+ // Re-enable.
+ configuration->SetEnabled(true);
+ }
+
+ {
+ // Verify switch is applied again.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsEnabled());
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestDisableEyeoFilteringSwitch) {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kDisableEyeoFiltering);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsEnabled());
+
+ // Re-enable.
+ configuration->SetEnabled(true);
+ }
+
+ {
+ // Verify switch is applied again.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsEnabled());
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestDisableEyeoByDefault) {
+ auto flag_set = OverrideEyeoFilteringDisabledByDefault(true);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsEnabled());
+
+ // Re-enable.
+ configuration->SetEnabled(true);
+ }
+
+ {
+ // Verify 1st run switch is NOT applied again.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_TRUE(configuration->IsEnabled());
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestDisableAAByDefault) {
+ auto flag_set = OverrideAcceptableAdsDisabledByDefault(true);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_FALSE(configuration->IsFilterListPresent(AcceptableAdsUrl()));
+
+ // Re-enable.
+ configuration->AddFilterList(AcceptableAdsUrl());
+ }
+
+ {
+ // Verify 1st run switch is NOT applied again.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ ASSERT_TRUE(configuration->IsFilterListPresent(AcceptableAdsUrl()));
+ }
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestSettingsMigration) {
+ prefs_.SetBoolean(common::prefs::kInstallFirstStartSubscriptions, false);
+ {
+ // Now set legacy prefs.
+ {
+ ScopedListPrefUpdate update(&prefs_,
+ common::prefs::kAdblockSubscriptionsLegacy);
+ update->Append("https://default.bar");
+ }
+
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ // Verify settings were migrated.
+ ASSERT_TRUE(configuration->IsEnabled());
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_EQ(2u, subscriptions.size());
+ ASSERT_TRUE(
+ IsFilterListPresent(subscriptions, GURL{"https://default.bar"}));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ // Disable AA.
+ configuration->RemoveFilterList(AcceptableAdsUrl());
+ }
+ {
+ // Verify that AA is not migrated 2nd time.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_EQ(1u, subscriptions.size());
+ ASSERT_TRUE(
+ IsFilterListPresent(subscriptions, GURL{"https://default.bar"}));
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ }
+}
+
+void VerifyCombinedFilterListsMigratedToStandalone(
+ const std::unique_ptr<SubscriptionService>& subscription_service) {
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_TRUE(IsFilterListPresent(subscriptions,
+ GURL{"https://default.bar/file3+file4.txt"}));
+ ASSERT_TRUE(IsFilterListPresent(
+ subscriptions, GURL{AdblockBaseFilterListUrl().spec() + "file.txt"}));
+ ASSERT_TRUE(IsFilterListPresent(
+ subscriptions,
+ GURL{AdblockBaseFilterListUrl().spec() + "file1+file2.txt"}));
+ ASSERT_FALSE(IsFilterListPresent(
+ subscriptions, GURL{AdblockBaseFilterListUrl().spec() + "file1.txt"}));
+ ASSERT_FALSE(IsFilterListPresent(
+ subscriptions, GURL{AdblockBaseFilterListUrl().spec() + "file2.txt"}));
+ ASSERT_FALSE(IsFilterListPresent(
+ subscriptions,
+ GURL{AdblockBaseFilterListUrl().spec() + "abpindo+easylist.txt"}));
+ ASSERT_TRUE(IsFilterListPresent(
+ subscriptions, GURL{AdblockBaseFilterListUrl().spec() + "abpindo.txt"}));
+ ASSERT_TRUE(IsFilterListPresent(
+ subscriptions, GURL{AdblockBaseFilterListUrl().spec() + "easylist.txt"}));
+}
+
+TEST_F(SubscriptionServiceFactoryTest,
+ TestSettingsMigrationToGeolocationFromOldPrefs) {
+ prefs_.SetBoolean(common::prefs::kInstallFirstStartSubscriptions, false);
+ // Now set legacy prefs.
+ {
+ ScopedListPrefUpdate subscriptions(
+ &prefs_, common::prefs::kAdblockSubscriptionsLegacy);
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() + "file.txt");
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() +
+ "abpindo+easylist.txt");
+ // Should not be migrated
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() +
+ "file1+file2.txt");
+ subscriptions->Append("https://default.bar/file3+file4.txt");
+ }
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ VerifyCombinedFilterListsMigratedToStandalone(subscription_service);
+}
+
+TEST_F(SubscriptionServiceFactoryTest,
+ TestSettingsMigrationToGeolocationFromNewPrefs) {
+ prefs_.SetBoolean(common::prefs::kInstallFirstStartSubscriptions, false);
+ // Now set "adblock" configuration prefs.
+ {
+ base::Value::Dict adblock_data;
+ adblock_data.EnsureList("subscriptions");
+ auto* subscriptions = adblock_data.FindList("subscriptions");
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() + "file.txt");
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() +
+ "abpindo+easylist.txt");
+ // Should not be migrated
+ subscriptions->Append(AdblockBaseFilterListUrl().spec() +
+ "file1+file2.txt");
+ subscriptions->Append("https://default.bar/file3+file4.txt");
+ ScopedDictPrefUpdate update(&prefs_,
+ common::prefs::kConfigurationsPrefsPath);
+ update.Get().Set(kAdblockFilteringConfigurationName, adblock_data.Clone());
+ }
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ VerifyCombinedFilterListsMigratedToStandalone(subscription_service);
+}
+
+TEST_F(SubscriptionServiceFactoryTest, TestSettingsMigrationWithFirstRun) {
+ ASSERT_TRUE(
+ prefs_.GetBoolean(common::prefs::kInstallFirstStartSubscriptions));
+ // Now set legacy prefs.
+ {
+ ScopedListPrefUpdate update(
+ &prefs_, common::prefs::kAdblockCustomSubscriptionsLegacy);
+ update->Append("https://custom.bar");
+ }
+ {
+ ScopedListPrefUpdate update(&prefs_,
+ common::prefs::kAdblockSubscriptionsLegacy);
+ update->Append("https://default.bar");
+ }
+ {
+ ScopedListPrefUpdate update(&prefs_,
+ common::prefs::kAdblockAllowedDomainsLegacy);
+ update->Append("example.com");
+ }
+ {
+ ScopedListPrefUpdate update(&prefs_,
+ common::prefs::kAdblockCustomFiltersLegacy);
+ update->Append("test.com$script");
+ }
+ prefs_.SetBoolean(common::prefs::kEnableAdblockLegacy, false);
+ prefs_.SetBoolean(common::prefs::kEnableAcceptableAdsLegacy, false);
+
+ {
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ // Verify settings were migrated.
+ ASSERT_FALSE(configuration->IsEnabled());
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, GURL{"https://custom.bar"}));
+ ASSERT_TRUE(
+ IsFilterListPresent(subscriptions, GURL{"https://default.bar"}));
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ // Along with migration default subscriptions were applied (1st run logic).
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, DefaultSubscriptionUrl()));
+ ASSERT_TRUE(IsFilterListPresent(subscriptions, AntiCVUrl()));
+ auto domains = configuration->GetAllowedDomains();
+ ASSERT_TRUE(std::find(domains.begin(), domains.end(), "example.com") !=
+ domains.end());
+ auto filters = configuration->GetCustomFilters();
+ ASSERT_TRUE(std::find(filters.begin(), filters.end(), "test.com$script") !=
+ filters.end());
+
+ // Change some settings.
+ configuration->SetEnabled(false);
+ configuration->RemoveFilterList(GURL{"https://custom.bar"});
+ configuration->RemoveAllowedDomain("example.com");
+ }
+
+ {
+ // Recreate service and make sure settings are not overwritten.
+ std::unique_ptr<SubscriptionService> subscription_service(
+ subscription_service_factory_test_impl_.GetSubscriptionService(
+ &browser_context_));
+ auto* configuration = subscription_service->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ auto subscriptions = configuration->GetFilterLists();
+ ASSERT_FALSE(
+ IsFilterListPresent(subscriptions, GURL{"https://custom.bar"}));
+ ASSERT_TRUE(
+ IsFilterListPresent(subscriptions, GURL{"https://default.bar"}));
+ ASSERT_FALSE(IsFilterListPresent(subscriptions, AcceptableAdsUrl()));
+ auto domains = configuration->GetAllowedDomains();
+ ASSERT_TRUE(domains.empty());
+ auto filters = configuration->GetCustomFilters();
+ ASSERT_TRUE(std::find(filters.begin(), filters.end(), "test.com$script") !=
+ filters.end());
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/content/resources/adblock_internals/BUILD.gn b/components/adblock/content/resources/adblock_internals/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/resources/adblock_internals/BUILD.gn
@@ -0,0 +1,32 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//ui/webui/resources/tools/build_webui.gni")
+
+build_webui("build") {
+ grd_prefix = "adblock_internals"
+ static_files = [ "adblock_internals.html" ]
+ ts_files = [ "adblock_internals.ts" ]
+ mojo_files_deps = [ "//components/adblock/content/browser/mojom:adblock_internals_ts__generator" ]
+ mojo_files = [ "$root_gen_dir/components/adblock/content/browser/mojom/adblock_internals.mojom-webui.ts" ]
+ ts_definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+ ts_deps = [
+ "//ui/webui/resources/js:build_ts",
+ "//ui/webui/resources/mojo:build_ts",
+ ]
+ webui_context_type = "trusted"
+ grit_output_dir = "$root_gen_dir/components"
+}
diff --git a/components/adblock/content/resources/adblock_internals/adblock_internals.html b/components/adblock/content/resources/adblock_internals/adblock_internals.html
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/resources/adblock_internals/adblock_internals.html
@@ -0,0 +1,45 @@
+<!--
+ This file is part of eyeo Chromium SDK,
+ Copyright (C) 2006-present eyeo GmbH
+
+ eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation.
+
+ eyeo Chromium SDK is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE HTML>
+<html lang="en">
+
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Ad-Filtering Internals</title>
+ <script type="module" src="adblock_internals.js"></script>
+</head>
+
+<body>
+ <button id="download-button">Download</button>
+ <button id="copy-button">Copy to Clipboard</button>
+ <button id="refresh">Refresh</button>
+
+ <hr>
+
+ <div>
+ Testpages Filterlist:&nbsp;
+ <button id="toggle-tp-fl-button">Subscribe</button>
+ </div>
+
+ <hr>
+
+ <pre id="content" style="white-space: pre-wrap;"></pre>
+</body>
+
+</html>
diff --git a/components/adblock/content/resources/adblock_internals/adblock_internals.ts b/components/adblock/content/resources/adblock_internals/adblock_internals.ts
new file mode 100644
--- /dev/null
+++ b/components/adblock/content/resources/adblock_internals/adblock_internals.ts
@@ -0,0 +1,65 @@
+// This file is part of eyeo Chromium SDK,
+// Copyright (C) 2006-present eyeo GmbH
+//
+// eyeo Chromium SDK is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 3 as
+// published by the Free Software Foundation.
+//
+// eyeo Chromium SDK is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import {getRequiredElement} from 'chrome://resources/js/util.js';
+import {AdblockInternalsPageHandler} from './adblock_internals.mojom-webui.js';
+
+async function debugInfo(): Promise<string> {
+ const info = await AdblockInternalsPageHandler.getRemote().getDebugInfo();
+ return info.debugInfo;
+}
+
+async function refresh() {
+ getRequiredElement('content').innerText = await debugInfo();
+}
+
+async function toggleTestpagesFLSubscription() {
+ const info = await AdblockInternalsPageHandler.getRemote().toggleTestpagesFLSubscription();
+ if (info.isSubscribed) {
+ getRequiredElement('toggle-tp-fl-button').innerText = "Unsubscribe";
+ } else {
+ getRequiredElement('toggle-tp-fl-button').innerText = "Subscribe";
+ }
+ await refresh();
+}
+
+async function initializeTestpagesFLSubscriptionButtonText() {
+ const info = await AdblockInternalsPageHandler.getRemote().isSubscribedToTestpagesFL();
+ if (info.isSubscribed) {
+ getRequiredElement('toggle-tp-fl-button').innerText = "Unsubscribe";
+ } else {
+ getRequiredElement('toggle-tp-fl-button').innerText = "Subscribe";
+ }
+}
+
+getRequiredElement('copy-button').addEventListener('click', async () => {
+ navigator.clipboard.writeText(await debugInfo());
+});
+
+getRequiredElement('download-button').addEventListener('click', async () => {
+ const url = URL.createObjectURL(new Blob([await debugInfo()], {type: 'text/plain'}));
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'adblock-internals.txt';
+ a.click();
+ URL.revokeObjectURL(url);
+});
+
+getRequiredElement('refresh').addEventListener('click', refresh);
+
+getRequiredElement('toggle-tp-fl-button').addEventListener('click', toggleTestpagesFLSubscription);
+
+document.addEventListener('DOMContentLoaded', refresh);
+document.addEventListener('DOMContentLoaded', initializeTestpagesFLSubscriptionButtonText);
diff --git a/components/adblock/core/BUILD.gn b/components/adblock/core/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/BUILD.gn
@@ -0,0 +1,156 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//components/adblock/features.gni")
+import("//third_party/flatbuffers/flatbuffer.gni")
+
+flatbuffer("schema") {
+ sources = [ "schema/filter_list_schema.fbs" ]
+}
+
+# Generates .cc file which contains the hash of the generated schema header.
+# This is used to detect changes in the schema at runtime and trigger a re-download of
+# the filter lists.
+action("generate_schema_hash") {
+ # The script lives in /chrome but has no dependencies to the //chrome target.
+ # According to the script's author, it could be moved to /build but only if
+ # there's code *upstream* that needs it outside of /chrome.
+ script = "//chrome/tools/build/sha256_file.py"
+ outputs = [ "${target_gen_dir}/schema/schema_hash.cc" ]
+ inputs = [ "${target_gen_dir}/schema/filter_list_schema_generated.h" ]
+
+ # The script expects an extensionless prefix of the output, rather than a path
+ # to the output file itself. It appends the ".cc" itself.
+ result_prefix = "${target_gen_dir}/schema/schema_hash"
+
+ args = [ rebase_path(result_prefix, root_build_dir) ] +
+ rebase_path(inputs, root_build_dir)
+
+ deps = [ ":schema" ]
+}
+
+source_set("schema_hash") {
+ sources = get_target_outputs(":generate_schema_hash")
+ sources += [ "//components/adblock/core/schema/schema_hash.h" ]
+
+ deps = [ ":generate_schema_hash" ]
+}
+
+config("eyeo_telemetry_config") {
+ defines = []
+
+ if (eyeo_telemetry_server_url != "") {
+ # Explicitly setting Telemetry server URL, used for testing with a test
+ # server.
+ defines += [ "EYEO_TELEMETRY_SERVER_URL=\"$eyeo_telemetry_server_url\"" ]
+ } else {
+ # Implicitly setting production Telemetry server URL based on
+ # eyeo_telemetry_client_id (or a default client id as a fallback).
+ if (eyeo_telemetry_client_id != "") {
+ defines += [ "EYEO_TELEMETRY_CLIENT_ID=\"$eyeo_telemetry_client_id\"" ]
+ } else {
+ print("WARNING! gn arg eyeo_telemetry_client_id is not set. " +
+ "Users will not be counted correctly by eyeo.")
+ eyeo_telemetry_client_id = "eyeochromium"
+ }
+ eyeo_telemetry_server_url =
+ "https://${eyeo_telemetry_client_id}.telemetry.eyeo.com/"
+ defines += [ "EYEO_TELEMETRY_SERVER_URL=\"$eyeo_telemetry_server_url\"" ]
+ }
+
+ if (eyeo_telemetry_activeping_auth_token != "") {
+ defines += [ "EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN=\"$eyeo_telemetry_activeping_auth_token\"" ]
+ } else {
+ print("WARNING! gn arg eyeo_telemetry_activeping_auth_token is not set. " +
+ "Users will not be counted correctly by eyeo.")
+ }
+}
+
+source_set("core") {
+ output_name = "adblock_core"
+ sources = [
+ "activeping_telemetry_topic_provider.cc",
+ "activeping_telemetry_topic_provider.h",
+ "adblock_telemetry_service.cc",
+ "adblock_telemetry_service.h",
+ "features.cc",
+ "features.h",
+ "sitekey_storage.h",
+ "sitekey_storage_impl.cc",
+ "sitekey_storage_impl.h",
+ ]
+
+ deps = [
+ "//components/language/core/common",
+ "//third_party/flatbuffers",
+ ]
+
+ public_deps = [
+ "//components/adblock/core/classifier",
+ "//components/adblock/core/common",
+ "//components/adblock/core/common:utils",
+ "//components/adblock/core/configuration",
+ "//components/adblock/core/subscription",
+ "//components/keyed_service/core",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/version_info",
+ ]
+
+ configs += [ ":eyeo_telemetry_config" ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/mock_sitekey_storage.cc",
+ "test/mock_sitekey_storage.h",
+ ]
+
+ public_deps = [
+ ":core",
+ "//components/adblock/core/subscription:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/activeping_telemetry_topic_provider_test.cc",
+ "test/adblock_telemetry_service_unittest.cc",
+ "test/bundled_subscription_test.cc",
+ "test/sitekey_storage_impl_test.cc",
+ ]
+
+ deps = [
+ ":core",
+ ":test_support",
+ "//base/test:test_support",
+ "//components/adblock/core/configuration:test_support",
+ "//components/adblock/core/net:test_support",
+ "//components/adblock/core/resources:adblock_resources",
+ "//components/adblock/core/subscription:test_support",
+ "//components/prefs:test_support",
+ "//components/sync_preferences:test_support",
+ "//net:test_support",
+ "//services/network:test_support",
+ "//testing/gtest",
+ ]
+
+ configs += [ ":eyeo_telemetry_config" ]
+}
diff --git a/components/adblock/core/activeping_telemetry_topic_provider.cc b/components/adblock/core/activeping_telemetry_topic_provider.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/activeping_telemetry_topic_provider.cc
@@ -0,0 +1,299 @@
+/* This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+
+#include <string_view>
+
+#include "base/i18n/time_formatting.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/time/time.h"
+#include "base/uuid.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/app_info.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+
+namespace adblock {
+namespace {
+int g_https_port_for_testing = 0;
+std::optional<base::TimeDelta> g_time_delta_for_testing;
+
+GURL GetUrl() {
+ GURL url(EYEO_TELEMETRY_SERVER_URL);
+ if (!g_https_port_for_testing) {
+ return url;
+ }
+ GURL::Replacements replacements;
+ const std::string port_str = base::NumberToString(g_https_port_for_testing);
+ replacements.SetPortStr(port_str);
+ return url.ReplaceComponents(replacements);
+}
+
+base::TimeDelta GetNormalPingInterval() {
+ static base::TimeDelta kNormalPingInterval =
+ g_time_delta_for_testing ? g_time_delta_for_testing.value()
+ : base::Hours(12);
+ return kNormalPingInterval;
+}
+
+base::TimeDelta GetRetryPingInterval() {
+ static base::TimeDelta kRetryPingInterval =
+ g_time_delta_for_testing ? g_time_delta_for_testing.value()
+ : base::Hours(1);
+ return kRetryPingInterval;
+}
+
+void AppendStringIfPresent(PrefService* pref_service,
+ const std::string& pref_name,
+ std::string_view payload_key,
+ base::Value::Dict& payload) {
+ auto str = pref_service->GetString(pref_name);
+ if (!str.empty()) {
+ payload.Set(payload_key, std::move(str));
+ }
+}
+
+std::string FormatNextRequestTime(base::Time time) {
+ if (time.is_null()) {
+ return "[Unset]";
+ }
+ return base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(time));
+}
+} // namespace
+
+ActivepingTelemetryTopicProvider::ActivepingTelemetryTopicProvider(
+ PrefService* pref_service,
+ SubscriptionService* subscription_service,
+ const GURL& base_url,
+ const std::string& auth_token,
+ std::unique_ptr<StatsPayloadProvider> stats_payload_provider)
+ : pref_service_(pref_service),
+ subscription_service_(subscription_service),
+ base_url_(base_url),
+ auth_token_(auth_token),
+ stats_payload_provider_(std::move(stats_payload_provider)) {}
+
+ActivepingTelemetryTopicProvider::~ActivepingTelemetryTopicProvider() = default;
+
+// static
+GURL ActivepingTelemetryTopicProvider::DefaultBaseUrl() {
+#if !defined(EYEO_TELEMETRY_CLIENT_ID)
+ LOG(WARNING)
+ << "[eyeo] Using default Telemetry server since a Telemetry client ID "
+ "was "
+ "not provided. Users will not be counted correctly by eyeo. Please "
+ "set an ID via \"eyeo_telemetry_client_id\" gn argument.";
+#endif
+ return GetUrl();
+}
+
+// static
+std::string ActivepingTelemetryTopicProvider::DefaultAuthToken() {
+#if defined(EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN)
+ DVLOG(1) << "[eyeo] Using " << EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN
+ << " as Telemetry authentication token";
+ return EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN;
+#else
+ LOG(WARNING)
+ << "[eyeo] No Telemetry authentication token defined. Users will "
+ "not be counted correctly by eyeo. Please set a token via "
+ "\"eyeo_telemetry_activeping_auth_token\" gn argument.";
+ return "";
+#endif
+}
+
+GURL ActivepingTelemetryTopicProvider::GetEndpointURL() const {
+ return base_url_.Resolve("/topic/eyeochromium_activeping/version/2");
+}
+
+std::string ActivepingTelemetryTopicProvider::GetAuthToken() const {
+ return auth_token_;
+}
+
+void ActivepingTelemetryTopicProvider::GetPayload(
+ PayloadCallback callback) const {
+ std::string serialized;
+ // The only way JSONWriter::Write() can return fail is then the Value
+ // contains lists or dicts that are too deep (200 levels). We just built the
+ // payload and root objects here, they should be really shallow.
+ CHECK(base::JSONWriter::Write(GetPayloadInternal(), &serialized));
+ std::move(callback).Run(std::move(serialized));
+}
+
+base::Time ActivepingTelemetryTopicProvider::GetTimeOfNextRequest() const {
+ const auto next_ping_time =
+ pref_service_->GetTime(common::prefs::kTelemetryNextPingTime);
+ // Next ping time may be unset if this is a first run. Next request should
+ // happen ASAP.
+ if (next_ping_time.is_null()) {
+ return base::Time::Now();
+ }
+
+ return next_ping_time;
+}
+
+void ActivepingTelemetryTopicProvider::ParseResponse(
+ std::unique_ptr<std::string> response_content) {
+ if (!response_content) {
+ VLOG(1) << "[eyeo] Telemetry ping failed, no response from server";
+ ScheduleNextPing(GetRetryPingInterval());
+ return;
+ }
+
+ VLOG(1) << "[eyeo] Response from Telemetry server: " << *response_content;
+ auto parsed = base::JSONReader::ReadDict(*response_content);
+ if (!parsed) {
+ VLOG(1)
+ << "[eyeo] Telemetry ping failed, response could not be parsed as JSON";
+ ScheduleNextPing(GetRetryPingInterval());
+ return;
+ }
+
+ auto* error_message = parsed->FindString("error");
+ if (error_message) {
+ VLOG(1) << "[eyeo] Telemetry ping failed, error message: "
+ << *error_message;
+ ScheduleNextPing(GetRetryPingInterval());
+ return;
+ }
+
+ // For legacy reasons, "ping_response_time" is sent to us as "token". This
+ // should be the server time of when the ping was handled, possibly truncated
+ // for anonymity. We don't parse it or interpret it, just send it back with
+ // next ping.
+ auto* ping_response_time = parsed->FindString("token");
+ if (!ping_response_time) {
+ VLOG(1) << "[eyeo] Telemetry ping failed, response did not contain a last "
+ "ping / token value";
+ ScheduleNextPing(GetRetryPingInterval());
+ return;
+ }
+
+ VLOG(1) << "[eyeo] Telemetry ping succeeded";
+ ScheduleNextPing(GetNormalPingInterval());
+ UpdatePrefs(*ping_response_time);
+}
+
+void ActivepingTelemetryTopicProvider::FetchDebugInfo(
+ DebugInfoCallback callback) const {
+ base::Value::Dict debug_info;
+ debug_info.Set("endpoint_url", GetEndpointURL().spec());
+ debug_info.Set("payload", GetPayloadInternal());
+ debug_info.Set("first_ping",
+ pref_service_->GetString(
+ adblock::common::prefs::kTelemetryFirstPingTime));
+ debug_info.Set("time_of_next_request",
+ FormatNextRequestTime(GetTimeOfNextRequest()));
+ debug_info.Set(
+ "last_ping",
+ pref_service_->GetString(adblock::common::prefs::kTelemetryLastPingTime));
+ debug_info.Set("previous_last_ping",
+ pref_service_->GetString(
+ adblock::common::prefs::kTelemetryPreviousLastPingTime));
+
+ std::string serialized;
+ // The only way JSONWriter::Write() can return fail is then the Value
+ // contains lists or dicts that are too deep (200 levels). We just built the
+ // payload and root objects here, they should be really shallow.
+ CHECK(base::JSONWriter::WriteWithOptions(
+ debug_info, base::JsonOptions::OPTIONS_PRETTY_PRINT, &serialized));
+ std::move(callback).Run(std::move(serialized));
+}
+
+void ActivepingTelemetryTopicProvider::ScheduleNextPing(base::TimeDelta delay) {
+ pref_service_->SetTime(common::prefs::kTelemetryNextPingTime,
+ base::Time::Now() + delay);
+ if (stats_payload_provider_ && delay == GetNormalPingInterval()) {
+ stats_payload_provider_->ResetStats();
+ }
+}
+
+void ActivepingTelemetryTopicProvider::UpdatePrefs(
+ const std::string& ping_response_time) {
+ // First ping is only set once per client.
+ if (pref_service_->GetString(common::prefs::kTelemetryFirstPingTime)
+ .empty()) {
+ pref_service_->SetString(common::prefs::kTelemetryFirstPingTime,
+ ping_response_time);
+ }
+ // Previous-to-last becomes last, last becomes current.
+ pref_service_->SetString(
+ common::prefs::kTelemetryPreviousLastPingTime,
+ pref_service_->GetString(common::prefs::kTelemetryLastPingTime));
+ pref_service_->SetString(common::prefs::kTelemetryLastPingTime,
+ ping_response_time);
+ // Generate a new random tag that wil be sent along with ping times in the
+ // next request.
+ const auto tag = base::Uuid::GenerateRandomV4();
+ pref_service_->SetString(common::prefs::kTelemetryLastPingTag,
+ tag.AsLowercaseString());
+}
+
+base::Value ActivepingTelemetryTopicProvider::GetPayloadInternal() const {
+ base::Value::Dict payload;
+ bool aa_enabled = false;
+ auto* adblock_configuration =
+ subscription_service_->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ if (adblock_configuration) {
+ aa_enabled = base::ranges::any_of(
+ adblock_configuration->GetFilterLists(),
+ [&](const auto& url) { return url == AcceptableAdsUrl(); });
+ }
+ payload.Set("addon_name", "eyeo-chromium-sdk");
+ payload.Set("addon_version", "2.0.0");
+ payload.Set("application", AppInfo::Get().name);
+ payload.Set("application_version", AppInfo::Get().version);
+ payload.Set("aa_active", aa_enabled);
+ payload.Set("platform", base::SysInfo::OperatingSystemName());
+ payload.Set("platform_version", base::SysInfo::OperatingSystemVersion());
+ if (stats_payload_provider_) {
+ payload.Merge(stats_payload_provider_->GetPayload());
+ }
+ // Server requires the following parameters to either have a correct,
+ // non-empty value, or not be present at all. We shall not send empty strings.
+ AppendStringIfPresent(pref_service_, common::prefs::kTelemetryLastPingTag,
+ "last_ping_tag", payload);
+ AppendStringIfPresent(pref_service_, common::prefs::kTelemetryFirstPingTime,
+ "first_ping", payload);
+ AppendStringIfPresent(pref_service_, common::prefs::kTelemetryLastPingTime,
+ "last_ping", payload);
+ AppendStringIfPresent(pref_service_,
+ common::prefs::kTelemetryPreviousLastPingTime,
+ "previous_last_ping", payload);
+
+ base::Value::Dict root;
+ root.Set("payload", std::move(payload));
+ return base::Value(std::move(root));
+}
+
+// static
+void ActivepingTelemetryTopicProvider::SetHttpsPortForTesting(
+ int https_port_for_testing) {
+ g_https_port_for_testing = https_port_for_testing;
+}
+
+// static
+void ActivepingTelemetryTopicProvider::SetIntervalsForTesting(
+ base::TimeDelta time_delta) {
+ g_time_delta_for_testing = time_delta;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/activeping_telemetry_topic_provider.h b/components/adblock/core/activeping_telemetry_topic_provider.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/activeping_telemetry_topic_provider.h
@@ -0,0 +1,94 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_ACTIVEPING_TELEMETRY_TOPIC_PROVIDER_H_
+#define COMPONENTS_ADBLOCK_CORE_ACTIVEPING_TELEMETRY_TOPIC_PROVIDER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
+#include "components/adblock/core/adblock_telemetry_service.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/pref_service.h"
+
+namespace adblock {
+
+// Telemetry topic provider that uploads user-counting data for periodic pings.
+// Provides the following data in Payload:
+// - Last ping time, previous-to-last ping time, first ping time
+// - Unique, non-persistent tag for disambiguating pings made by clients in
+// the same day
+// - Whether Acceptable Ads is enabled
+// - Application name & version, platform name & version
+// Note: Provides no user-identifiable information, no persistent tracking
+// data (ie. no traceable UUID) and no information about user actions.
+class ActivepingTelemetryTopicProvider final
+ : public AdblockTelemetryService::TopicProvider {
+ public:
+ // Provides additional statistics payload for ActivepingTelemetryTopicProvider
+ class StatsPayloadProvider {
+ public:
+ virtual ~StatsPayloadProvider() = default;
+ virtual base::Value::Dict GetPayload() const = 0;
+ virtual void ResetStats() = 0;
+ };
+ ActivepingTelemetryTopicProvider(
+ PrefService* pref_service,
+ SubscriptionService* subscription_service,
+ const GURL& base_url,
+ const std::string& auth_token,
+ std::unique_ptr<StatsPayloadProvider> stats_payload_provider);
+ ~ActivepingTelemetryTopicProvider() final;
+
+ static GURL DefaultBaseUrl();
+ static std::string DefaultAuthToken();
+
+ GURL GetEndpointURL() const final;
+ std::string GetAuthToken() const final;
+ void GetPayload(PayloadCallback callback) const final;
+
+ // Normally 12 hours since last ping, 1 hour in case of retries.
+ base::Time GetTimeOfNextRequest() const final;
+
+ // Attempts to parse "token" (an opaque server description of last ping time)
+ // from |response_content|.
+ void ParseResponse(std::unique_ptr<std::string> response_content) final;
+
+ void FetchDebugInfo(DebugInfoCallback callback) const final;
+
+ // Sets the port used by the embedded http server required for browser tests.
+ // Must be called before the first call to DefaultBaseUrl().
+ static void SetHttpsPortForTesting(int https_port_for_testing);
+
+ // Sets the internal timing for sending pings required for browser tests.
+ // Must be called before AdblockTelemetryService::Start().
+ static void SetIntervalsForTesting(base::TimeDelta time_delta);
+
+ private:
+ void ScheduleNextPing(base::TimeDelta delay);
+ void UpdatePrefs(const std::string& ping_response_time);
+ base::Value GetPayloadInternal() const;
+
+ raw_ptr<PrefService> pref_service_;
+ raw_ptr<SubscriptionService> subscription_service_;
+ const GURL base_url_;
+ const std::string auth_token_;
+ std::unique_ptr<StatsPayloadProvider> stats_payload_provider_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_ACTIVEPING_TELEMETRY_TOPIC_PROVIDER_H_
diff --git a/components/adblock/core/adblock_telemetry_service.cc b/components/adblock/core/adblock_telemetry_service.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/adblock_telemetry_service.cc
@@ -0,0 +1,308 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/adblock_telemetry_service.h"
+
+#include <string>
+
+#include "base/barrier_callback.h"
+#include "base/functional/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/net/adblock_request_throttle.h"
+#include "components/prefs/pref_service.h"
+#include "net/base/load_flags.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace adblock {
+
+namespace {
+
+const char kDataType[] = "application/json";
+net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("adblock_telemetry_request", R"(
+ semantics {
+ sender: "AdblockTelemetryService"
+ description:
+ "Messages sent to telemetry.eyeo.com to report usage statistics."
+ "Contain no user-identifiable data."
+ trigger:
+ "Periodic, several times a day."
+ data:
+ "Subject to change: "
+ "Dates of first ping, last ping and previous-to-last ping. "
+ "A non-persistent, unique ID that disambiguates pings made in the "
+ "same day. "
+ "Application name and version (ex. Chromium 86.0.4240.183). "
+ "Platform name and version (ex. Windows 10). "
+ "Whether Acceptable Ads are in use (yes/no)."
+ destination: WEBSITE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "Enabled or disabled via 'Ad blocking' setting."
+ policy_exception_justification:
+ "Parent setting may be controlled by policy"
+ }
+ })");
+
+} // namespace
+
+// Represents an ongoing chain of requests relevant to a Topic.
+// A Topic is and endpoint on the Telemetry server that expects messages
+// about a domain of activity, ex. usage of Acceptable Ads or frequency of
+// filter "hits" per filter list. The browser may report on multiple topics.
+// Messages are sent periodically. The interval of communication and the
+// content of the messages is provided by a TopicProvider.
+class AdblockTelemetryService::Conversation {
+ public:
+ Conversation(
+ std::unique_ptr<TopicProvider> topic_provider,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ AdblockRequestThrottle* request_throttle)
+ : topic_provider_(std::move(topic_provider)),
+ url_loader_factory_(url_loader_factory),
+ request_throttle_(request_throttle) {}
+
+ bool IsRequestDue() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ const auto due_time = topic_provider_->GetTimeOfNextRequest();
+ if (due_time > base::Time::Now()) {
+ VLOG(1) << "[eyeo] Telemetry request for "
+ << topic_provider_->GetEndpointURL()
+ << " not due yet, should run at " << due_time;
+ return false;
+ }
+ if (IsRequestInFlight()) {
+ VLOG(1) << "[eyeo] Telemetry request for "
+ << topic_provider_->GetEndpointURL() << " already in-flight";
+ return false;
+ }
+ VLOG(1) << "[eyeo] Telemetry request for "
+ << topic_provider_->GetEndpointURL() << " is due";
+ return true;
+ }
+
+ void StartRequest() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ VLOG(1) << "[eyeo] Telemetry request for "
+ << topic_provider_->GetEndpointURL() << " starting now";
+ is_in_flight_ = true;
+ request_throttle_->RunWhenRequestsAllowed(base::BindOnce(
+ &Conversation::OnRequestsAllowed, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ url_loader_.reset();
+ }
+
+ const std::unique_ptr<TopicProvider>& GetTopicProvider() const {
+ return topic_provider_;
+ }
+
+ private:
+ bool IsRequestInFlight() { return is_in_flight_; }
+
+ void OnRequestsAllowed() {
+ topic_provider_->GetPayload(base::BindOnce(&Conversation::MakeRequest,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ void MakeRequest(std::string payload) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto request = std::make_unique<network::ResourceRequest>();
+ request->url = topic_provider_->GetEndpointURL();
+ VLOG(1) << "[eyeo] Sending request to: " << request->url;
+ request->method = net::HttpRequestHeaders::kPostMethod;
+ // The server expects authorization via a bearer token. The token may be
+ // empty in testing builds.
+ const auto auth_token = topic_provider_->GetAuthToken();
+ if (!auth_token.empty()) {
+ request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
+ "Bearer " + auth_token);
+ }
+ // Notify the server we're expecting a JSON response.
+ request->headers.SetHeader(net::HttpRequestHeaders::kAccept, kDataType);
+ // Disallow using cache - identical requests should be physically sent to
+ // the server.
+ request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+ // Omitting credentials prevents cookies from being sent. The server does
+ // not expect or parse cookies, but we want to be on the safe side,
+ // privacy-wise.
+ request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ // If any url_loader_ existed previously, it will be overwritten and its
+ // request will be cancelled.
+ url_loader_ = network::SimpleURLLoader::Create(std::move(request),
+ kTrafficAnnotation);
+
+ VLOG(2) << "[eyeo] Payload: " << payload;
+ url_loader_->AttachStringForUpload(payload, kDataType);
+ // The Telemetry server responds with a JSON that contains a description of
+ // any potential error. We want to parse this JSON if possible, we're not
+ // content with just an HTTP error code. Process the response content even
+ // if the code is not 200.
+ url_loader_->SetAllowHttpErrorResults(true);
+
+ url_loader_->DownloadToString(
+ url_loader_factory_.get(),
+ base::BindOnce(&Conversation::OnResponseArrived,
+ base::Unretained(this)),
+ network::SimpleURLLoader::kMaxBoundedStringDownloadSize - 1);
+ }
+
+ void OnResponseArrived(std::unique_ptr<std::string> server_response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ topic_provider_->ParseResponse(std::move(server_response));
+ url_loader_.reset();
+ is_in_flight_ = false;
+ }
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ bool is_in_flight_ = false;
+ std::unique_ptr<TopicProvider> topic_provider_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ raw_ptr<AdblockRequestThrottle> request_throttle_;
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+ base::WeakPtrFactory<Conversation> weak_ptr_factory_{this};
+};
+
+AdblockTelemetryService::AdblockTelemetryService(
+ SubscriptionService* subscription_service,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ AdblockRequestThrottle* request_throttle,
+ base::TimeDelta check_interval)
+ : subscription_service_(subscription_service),
+ url_loader_factory_(url_loader_factory),
+ request_throttle_(request_throttle),
+ check_interval_(check_interval) {
+ DCHECK(subscription_service_);
+ subscription_service_->AddObserver(this);
+}
+
+AdblockTelemetryService::~AdblockTelemetryService() {
+ DCHECK(subscription_service_);
+ subscription_service_->RemoveObserver(this);
+}
+
+void AdblockTelemetryService::AddTopicProvider(
+ std::unique_ptr<TopicProvider> topic_provider) {
+ ongoing_conversations_.push_back(std::make_unique<Conversation>(
+ std::move(topic_provider), url_loader_factory_, request_throttle_));
+}
+
+void AdblockTelemetryService::Start() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* adblock_configuration =
+ subscription_service_->GetFilteringConfiguration(
+ kAdblockFilteringConfigurationName);
+ if (adblock_configuration) {
+ OnEnabledStateChanged(adblock_configuration);
+ adblock_configuration->AddObserver(this);
+ }
+}
+
+void AdblockTelemetryService::GetTopicProvidersDebugInfo(
+ TopicProvidersDebugInfoCallback service_callback) const {
+ const auto barrier_callback = base::BarrierCallback<std::string>(
+ ongoing_conversations_.size(), std::move(service_callback));
+ for (const auto& conversation : ongoing_conversations_) {
+ conversation->GetTopicProvider()->FetchDebugInfo(barrier_callback);
+ }
+}
+
+void AdblockTelemetryService::OnEnabledStateChanged(
+ FilteringConfiguration* config) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (config->IsEnabled() && !timer_.IsRunning()) {
+ VLOG(1) << "[eyeo] Starting periodic Telemetry requests for enabled "
+ "configuration";
+ RunPeriodicCheck();
+ } else if (!config->IsEnabled() && timer_.IsRunning()) {
+ VLOG(1) << "[eyeo] Stopping periodic Telemetry requests for disabled "
+ "configuration";
+ Shutdown();
+ }
+}
+
+void AdblockTelemetryService::OnFilteringConfigurationInstalled(
+ FilteringConfiguration* config) {
+ if (config->GetName() != kAdblockFilteringConfigurationName) {
+ return;
+ }
+ if (config->IsEnabled() && !timer_.IsRunning()) {
+ VLOG(1) << "[eyeo] Starting periodic Telemetry requests for configuration";
+ RunPeriodicCheck();
+ }
+ // We cannot add twice the same observer, but calling RemoveObserver() for not
+ // added previously observer is fine. The situation of adding twice the same
+ // observer should not happen in production code without programmer error
+ // though it happens in our tests, but for extra safety instead of changing
+ // test code let's call RemoveObserver().
+ config->RemoveObserver(this);
+ config->AddObserver(this);
+}
+
+void AdblockTelemetryService::OnFilteringConfigurationUninstalled(
+ std::string_view config_name) {
+ if (config_name != kAdblockFilteringConfigurationName) {
+ return;
+ }
+ if (timer_.IsRunning()) {
+ VLOG(1) << "[eyeo] Stopping periodic Telemetry requests for removed "
+ "configuration";
+ Shutdown();
+ }
+}
+
+void AdblockTelemetryService::RunPeriodicCheck() {
+ for (auto& conversation : ongoing_conversations_) {
+ if (conversation->IsRequestDue()) {
+ conversation->StartRequest();
+ }
+ }
+ timer_.Start(FROM_HERE, check_interval_,
+ base::BindRepeating(&AdblockTelemetryService::RunPeriodicCheck,
+ base::Unretained(this)));
+}
+
+void AdblockTelemetryService::Shutdown() {
+ timer_.Stop();
+ for (auto& conversation : ongoing_conversations_) {
+ conversation->Stop();
+ }
+}
+
+void AdblockTelemetryService::
+ TriggerConversationsWithoutDueTimeCheckForTesting() {
+ for (auto& conversation : ongoing_conversations_) {
+ conversation->StartRequest();
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/adblock_telemetry_service.h b/components/adblock/core/adblock_telemetry_service.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/adblock_telemetry_service.h
@@ -0,0 +1,132 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_
+#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/net/adblock_request_throttle.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "partition_alloc/pointers/raw_ptr.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+namespace network {
+class SimpleURLLoader;
+} // namespace network
+
+namespace adblock {
+/**
+ * @brief Sends periodic pings to eyeo in order to count active users. Executed
+ * from Browser process UI main thread.
+ */
+class AdblockTelemetryService
+ : public KeyedService,
+ public FilteringConfiguration::Observer,
+ public SubscriptionService::SubscriptionObserver {
+ public:
+ // Provides data and behavior relevant for a Telemetry "topic". A topic could
+ // be "counting users" or "reporting filter list hits" for example.
+ class TopicProvider {
+ public:
+ using PayloadCallback = base::OnceCallback<void(std::string payload)>;
+ using DebugInfoCallback = base::OnceCallback<void(std::string payload)>;
+ virtual ~TopicProvider() = default;
+ // Endpoint URL on the Telemetry server onto which requests should be sent.
+ virtual GURL GetEndpointURL() const = 0;
+ // Authorization bearer token for the endpoint defined by GetEndpointURL().
+ virtual std::string GetAuthToken() const = 0;
+ // Data uploaded with the request, should be valid for the schema
+ // present on the server. Async to allow querying asynchronous data sources.
+ virtual void GetPayload(PayloadCallback callback) const = 0;
+ // Returns the desired time when AdblockTelemetryService should make the
+ // next network request.
+ virtual base::Time GetTimeOfNextRequest() const = 0;
+ // Parses the response returned by the Telemetry server. |response_content|
+ // may be null. Implementation is free to implement a "retry" in case of
+ // response errors via GetTimeToNextRequest().
+ virtual void ParseResponse(
+ std::unique_ptr<std::string> response_content) = 0;
+ // Gets debugging info to be logged on chrome://adblock-internals. Do not
+ // put any secrets here (tokens, api keys). Asynchronous to allow reusing
+ // the async logic of GetPayload, if needed.
+ virtual void FetchDebugInfo(DebugInfoCallback callback) const = 0;
+ };
+ AdblockTelemetryService(
+ SubscriptionService* subscription_service_,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ AdblockRequestThrottle* request_throttle,
+ base::TimeDelta check_interval);
+ ~AdblockTelemetryService() override;
+ using TopicProvidersDebugInfoCallback =
+ base::OnceCallback<void(std::vector<std::string>)>;
+
+ // Add all required topic providers before calling Start().
+ void AddTopicProvider(std::unique_ptr<TopicProvider> topic_provider);
+
+ // Starts periodic Telemetry requests, provided ad-blocking is enabled.
+ // If ad blocking is disabled, the schedule will instead start when
+ // ad blocking becomes enabled.
+ void Start();
+
+ // KeyedService:
+ void Shutdown() override;
+
+ // FilteringConfiguration::Observer
+ void OnEnabledStateChanged(FilteringConfiguration* config) override;
+
+ // Collects debug information from all topic providers. Runs |callback| once
+ // all topic providers have provided their info.
+ void GetTopicProvidersDebugInfo(
+ TopicProvidersDebugInfoCallback callback) const;
+ // SubscriptionService::SubscriptionObserver
+ void OnFilteringConfigurationInstalled(
+ FilteringConfiguration* config) override;
+ void OnFilteringConfigurationUninstalled(
+ std::string_view config_name) override;
+
+ // For testing purposes only: triggers immediately requests for all added
+ // providers
+ void TriggerConversationsWithoutDueTimeCheckForTesting();
+
+ private:
+ void RunPeriodicCheck();
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ raw_ptr<SubscriptionService> subscription_service_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ raw_ptr<AdblockRequestThrottle> request_throttle_;
+ base::TimeDelta check_interval_;
+
+ class Conversation;
+ std::vector<std::unique_ptr<Conversation>> ongoing_conversations_;
+ base::OneShotTimer timer_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_
diff --git a/components/adblock/core/classifier/BUILD.gn b/components/adblock/core/classifier/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/BUILD.gn
@@ -0,0 +1,77 @@
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("classifier") {
+ sources = [
+ "resource_classifier.cc",
+ "resource_classifier.h",
+ "resource_classifier_impl.cc",
+ "resource_classifier_impl.h",
+ ]
+
+ deps = [ "//components/adblock/core/common:utils" ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core/common",
+ "//components/adblock/core/subscription:subscription",
+ "//net:net",
+ "//url:url",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/mock_resource_classifier.cc",
+ "test/mock_resource_classifier.h",
+ ]
+
+ deps = [
+ ":classifier",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "test/resource_classifier_impl_test.cc" ]
+
+ deps = [
+ ":test_support",
+ "//components/adblock/core",
+ "//components/adblock/core/subscription:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+}
+
+source_set("perf_tests") {
+ testonly = true
+ sources = [ "test/resource_classifier_perftest.cc" ]
+
+ deps = [
+ ":classifier",
+ "//components/adblock/core/converter",
+ "//testing/gtest",
+ "//testing/perf:perf",
+ "//third_party/zlib/google:compression_utils",
+ ]
+
+ data = [
+ "//components/test/data/adblock/easylist.txt.gz",
+ "//components/test/data/adblock/exceptionrules.txt.gz",
+ "//components/test/data/adblock/anticv.txt.gz",
+ "//components/test/data/adblock/longurl.txt.gz",
+ ]
+}
diff --git a/components/adblock/core/classifier/resource_classifier.cc b/components/adblock/core/classifier/resource_classifier.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/resource_classifier.cc
@@ -0,0 +1,24 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/classifier/resource_classifier.h"
+
+namespace adblock {
+
+ResourceClassifier::~ResourceClassifier() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/classifier/resource_classifier.h b/components/adblock/core/classifier/resource_classifier.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/resource_classifier.h
@@ -0,0 +1,90 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_
+#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "net/http/http_response_headers.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Classifies resources encountered on websites.
+// May be called from multiple threads, thread-safe and immutable.
+class ResourceClassifier
+ : public base::RefCountedThreadSafe<ResourceClassifier> {
+ public:
+ struct ClassificationResult {
+ enum class Decision {
+ // The resource should be blocked as there's a blocking filter in at least
+ // one of the Subscriptions.
+ Blocked,
+ // There is a blocking filter but at least one of the Subscriptions also
+ // has an overriding allowing filter.
+ Allowed,
+ // There are no filters that apply to this resource.
+ Ignored,
+ } decision;
+ // If decision is Blocked or Allowed, |decisive_subscription| has the URL of
+ // the subscription that had the decisive filter.
+ GURL decisive_subscription;
+ // If decision is Blocked or Allowed, |decisive_configuration_name| has the
+ // name of the FilteringConfiguration that contain matched filter.
+ std::string decisive_configuration_name;
+ };
+
+ virtual ClassificationResult ClassifyRequest(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const = 0;
+
+ virtual ClassificationResult ClassifyPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ const std::vector<GURL>& opener_frame_hierarchy,
+ const SiteKey& sitekey) const = 0;
+
+ virtual ClassificationResult ClassifyResponse(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const scoped_refptr<net::HttpResponseHeaders>& response_headers)
+ const = 0;
+
+ virtual absl::optional<GURL> CheckRewrite(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ResourceClassifier>;
+ virtual ~ResourceClassifier();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_
diff --git a/components/adblock/core/classifier/resource_classifier_impl.cc b/components/adblock/core/classifier/resource_classifier_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/resource_classifier_impl.cc
@@ -0,0 +1,406 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/classifier/resource_classifier_impl.h"
+
+#include <string_view>
+
+#include "base/strings/string_split.h"
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+
+namespace adblock {
+namespace {
+
+using ClassificationResult = ResourceClassifier::ClassificationResult;
+
+absl::optional<HeaderFilterData> IsHeaderFilterOverruled(
+ std::string_view blocking_header_filter,
+ std::set<HeaderFilterData>& allowing_filters) {
+ for (auto filter : allowing_filters) {
+ if (filter.header_filter.empty()) {
+ // Allowing header filters may not contain payload, allow all headers in
+ // that case.
+ return filter;
+ }
+ if (utils::RegexMatches(filter.header_filter, blocking_header_filter,
+ true)) {
+ return filter;
+ }
+ }
+ return absl::nullopt;
+}
+
+bool ContainsHeaderValue(const scoped_refptr<net::HttpResponseHeaders>& headers,
+ std::string_view header_name,
+ const std::string& header_value) {
+ size_t iter = 0;
+ std::string value;
+ while (headers->EnumerateHeader(&iter, header_name, &value)) {
+ if (value.find(header_value) != std::string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ClassificationResult ClassifyRequestWithSingleCollection(
+ const SubscriptionCollection& subscription_collection,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) {
+ // Search all subscriptions for any blocking filters (generic or
+ // domain-specific).
+ const auto subscription_with_blocking_filter_it =
+ subscription_collection.FindBySubresourceFilter(
+ request_url, frame_hierarchy, content_type, sitekey,
+ FilterCategory::Blocking);
+ if (!subscription_with_blocking_filter_it) {
+ // Found no blocking filters in any of the subscriptions.
+ return ClassificationResult{
+ ClassificationResult::Decision::Ignored, {}, {}};
+ }
+ // Found a blocking filter but perhaps one of the subscriptions has an
+ // allowing filter to override it?
+ const auto subscription_with_allowing_filter_it =
+ subscription_collection.FindByAllowFilter(request_url, frame_hierarchy,
+ content_type, sitekey);
+ if (subscription_with_allowing_filter_it) {
+ // Found an overriding allowing filter:
+ return ClassificationResult{
+ ClassificationResult::Decision::Allowed,
+ *subscription_with_allowing_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+ // Last chance to avoid blocking: maybe there is a GENERICBLOCK filter and we
+ // should re-search for domain-specific filters only?
+ if (subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Genericblock, request_url, frame_hierarchy,
+ sitekey)) {
+ // This is a relatively rare case - we should have searched for
+ // domain-specific filters only.
+ const auto subscription_with_domain_specific_blocking_filter_it =
+ subscription_collection.FindBySubresourceFilter(
+ request_url, frame_hierarchy, content_type, sitekey,
+ FilterCategory::DomainSpecificBlocking);
+ if (subscription_with_domain_specific_blocking_filter_it) {
+ // There was a domain-specific blocking filter, the resource is blocked by
+ // it.
+ return ClassificationResult{
+ ClassificationResult::Decision::Blocked,
+ *subscription_with_domain_specific_blocking_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+ } else {
+ // There were no domain-specific blocking filters, our first match must
+ // have been a generic filter.
+ return ClassificationResult{
+ ClassificationResult::Decision::Ignored, {}, {}};
+ }
+ }
+ // There was no GENERICBLOCK filter available, so the original blocking result
+ // is valid.
+ return ClassificationResult{
+ ClassificationResult::Decision::Blocked,
+ *subscription_with_blocking_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+}
+
+ClassificationResult ClassifyPopupWithSingleCollection(
+ const SubscriptionCollection& subscription_collection,
+ const GURL& popup_url,
+ const std::vector<GURL>& opener_frame_hierarchy,
+ const SiteKey& sitekey) {
+ // Search all subscriptions for popup blocking filters (generic or
+ // domain-specific).
+ const auto subscription_with_blocking_filter_it =
+ subscription_collection.FindByPopupFilter(
+ popup_url, opener_frame_hierarchy, sitekey, FilterCategory::Blocking);
+ if (!subscription_with_blocking_filter_it) {
+ // Found no blocking filters in any of the subscriptions.
+ return ClassificationResult{
+ ClassificationResult::Decision::Ignored, {}, {}};
+ }
+ // Found a blocking filter but perhaps one of the subscriptions has an
+ // allowing filter to override it?
+ const auto subscription_with_allowing_filter_it =
+ subscription_collection.FindByPopupFilter(
+ popup_url, opener_frame_hierarchy, sitekey, FilterCategory::Allowing);
+ if (subscription_with_allowing_filter_it) {
+ // Found an overriding allowing filter:
+ return ClassificationResult{
+ ClassificationResult::Decision::Allowed,
+ *subscription_with_allowing_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+ const auto subscription_with_document_allowing_filter_it =
+ subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Document, popup_url, opener_frame_hierarchy,
+ sitekey);
+ if (subscription_with_document_allowing_filter_it) {
+ // Found an overriding document allowing filter for the frame hierarchy:
+ return ClassificationResult{
+ ClassificationResult::Decision::Allowed,
+ *subscription_with_document_allowing_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+ // There is no overriding allowing filter, the popup should be blocked.
+ return ClassificationResult{
+ ClassificationResult::Decision::Blocked,
+ *subscription_with_blocking_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+}
+
+ClassificationResult CheckHeaderFiltersMatchResponseHeaders(
+ const SubscriptionCollection& subscription_collection,
+ const GURL request_url,
+ const std::vector<GURL> frame_hierarchy,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ std::set<HeaderFilterData> blocking_filters,
+ std::set<HeaderFilterData> allowing_filters) {
+ ClassificationResult result{ClassificationResult::Decision::Ignored, {}, {}};
+
+ for (const auto& filter : blocking_filters) {
+ auto key_value =
+ base::SplitString(filter.header_filter, "=", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ // If no '=' occurs, filter blocks response contains header, regardless
+ // header value
+ if (key_value.size() == 1u) {
+ if (headers->HasHeader(filter.header_filter)) {
+ if (auto allow_rule = IsHeaderFilterOverruled(filter.header_filter,
+ allowing_filters)) {
+ result = {ClassificationResult::Decision::Allowed,
+ allow_rule->subscription_url,
+ subscription_collection.GetFilteringConfigurationName()};
+ } else {
+ return ClassificationResult{
+ ClassificationResult::Decision::Blocked, filter.subscription_url,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+ }
+ } else {
+ DCHECK_EQ(2u, key_value.size());
+ if (ContainsHeaderValue(headers, key_value[0], key_value[1])) {
+ if (auto allow_rule = IsHeaderFilterOverruled(filter.header_filter,
+ allowing_filters)) {
+ result = {ClassificationResult::Decision::Allowed,
+ allow_rule->subscription_url,
+ subscription_collection.GetFilteringConfigurationName()};
+ } else {
+ return ClassificationResult{
+ ClassificationResult::Decision::Blocked, filter.subscription_url,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+ }
+ }
+ }
+ return result;
+}
+
+ClassificationResult ClassifyResponseWithSingleCollection(
+ const SubscriptionCollection& subscription_collection,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const scoped_refptr<net::HttpResponseHeaders>& response_headers) {
+ auto blocking_filters = subscription_collection.GetHeaderFilters(
+ request_url, frame_hierarchy, content_type, FilterCategory::Blocking);
+ if (blocking_filters.empty()) {
+ return ClassificationResult{
+ ClassificationResult::Decision::Ignored, {}, {}};
+ }
+
+ // If blocking filters found, check first if filters are not overruled
+ const auto subscription_with_allowing_document_filter_it =
+ subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Document, request_url, frame_hierarchy, SiteKey());
+ if (subscription_with_allowing_document_filter_it) {
+ // Found no blocking filters in any of the subscriptions.
+ return ClassificationResult{
+ ClassificationResult::Decision::Allowed,
+ *subscription_with_allowing_document_filter_it,
+ subscription_collection.GetFilteringConfigurationName()};
+ }
+
+ if (subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Genericblock, request_url, frame_hierarchy,
+ SiteKey())) {
+ // If genericblock filter found, searched for blocking domain-specific
+ // filters.
+ blocking_filters = subscription_collection.GetHeaderFilters(
+ request_url, frame_hierarchy, content_type,
+ FilterCategory::DomainSpecificBlocking);
+
+ return CheckHeaderFiltersMatchResponseHeaders(
+ subscription_collection, request_url, frame_hierarchy, response_headers,
+ std::move(blocking_filters), {});
+ }
+ // If no special filters found, get allowing filters and check which filters
+ // applies.
+ auto allowing_filters = subscription_collection.GetHeaderFilters(
+ request_url, frame_hierarchy, content_type, FilterCategory::Allowing);
+ return CheckHeaderFiltersMatchResponseHeaders(
+ subscription_collection, request_url, frame_hierarchy, response_headers,
+ std::move(blocking_filters), std::move(allowing_filters));
+}
+
+absl::optional<GURL> CheckRewriteWithSingleCollection(
+ const SubscriptionCollection& subscription_collection,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) {
+ auto blocking_rewrites = subscription_collection.GetRewriteFilters(
+ request_url, frame_hierarchy, FilterCategory::Blocking);
+ if (blocking_rewrites.empty()) {
+ return absl::nullopt;
+ }
+
+ // If blocking filters are found, check first if blocking filters are not
+ // overruled completely.
+ const auto subscription_with_allowing_document_filter_it =
+ subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Document, request_url, frame_hierarchy, SiteKey());
+ if (subscription_with_allowing_document_filter_it) {
+ return absl::nullopt;
+ }
+
+ if (subscription_collection.FindBySpecialFilter(
+ SpecialFilterType::Genericblock, request_url, frame_hierarchy,
+ SiteKey())) {
+ // If genericblock filter is found, searched for blocking domain-specific
+ // filters.
+ blocking_rewrites = subscription_collection.GetRewriteFilters(
+ request_url, frame_hierarchy, FilterCategory::DomainSpecificBlocking);
+
+ if (blocking_rewrites.empty()) {
+ return absl::nullopt;
+ }
+ }
+
+ // Check if blocking filters are not overruled by allowing ones.
+ auto allowing_rewrites = subscription_collection.GetRewriteFilters(
+ request_url, frame_hierarchy, FilterCategory::Allowing);
+ if (allowing_rewrites.empty()) {
+ // Any filter will do, take the 1st one.
+ return absl::optional<GURL>(GURL{*blocking_rewrites.begin()});
+ }
+
+ // Change set to vector to call algorithm
+ std::vector<std::string> blocking_rewrites_v(blocking_rewrites.begin(),
+ blocking_rewrites.end());
+ // Remove blocking filters overruled by allowing filters.
+ blocking_rewrites_v.erase(
+ std::remove_if(blocking_rewrites_v.begin(), blocking_rewrites_v.end(),
+ [&allowing_rewrites](const auto blocking_rewrite) {
+ return std::ranges::find_if(
+ allowing_rewrites,
+ [&](const auto& allowing_rewrite) {
+ return blocking_rewrite == allowing_rewrite;
+ }) != allowing_rewrites.end();
+ }),
+ blocking_rewrites_v.end());
+
+ if (blocking_rewrites_v.empty()) {
+ return absl::nullopt;
+ }
+
+ // Any filter will do, take the 1st one.
+ return absl::optional<GURL>(GURL{*blocking_rewrites_v.begin()});
+}
+
+} // namespace
+
+ResourceClassifierImpl::~ResourceClassifierImpl() = default;
+
+ClassificationResult ResourceClassifierImpl::ClassifyRequest(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const {
+ auto classification =
+ ClassificationResult{ClassificationResult::Decision::Ignored, {}, {}};
+ for (const auto& collection : subscription_collections) {
+ auto result = ClassifyRequestWithSingleCollection(
+ *collection, request_url, frame_hierarchy, content_type, sitekey);
+ if (result.decision == ClassificationResult::Decision::Blocked) {
+ return result;
+ }
+ if (result.decision == ClassificationResult::Decision::Allowed) {
+ classification = result;
+ }
+ }
+ return classification;
+}
+
+ClassificationResult ResourceClassifierImpl::ClassifyPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ const std::vector<GURL>& opener_frame_hierarchy,
+ const SiteKey& sitekey) const {
+ auto classification =
+ ClassificationResult{ClassificationResult::Decision::Ignored, {}, {}};
+ for (const auto& collection : subscription_collections) {
+ auto result = ClassifyPopupWithSingleCollection(
+ *collection, popup_url, opener_frame_hierarchy, sitekey);
+ if (result.decision == ClassificationResult::Decision::Blocked) {
+ return result;
+ }
+ if (result.decision == ClassificationResult::Decision::Allowed) {
+ classification = result;
+ }
+ }
+ return classification;
+}
+
+ClassificationResult ResourceClassifierImpl::ClassifyResponse(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const scoped_refptr<net::HttpResponseHeaders>& response_headers) const {
+ auto classification =
+ ClassificationResult{ClassificationResult::Decision::Ignored, {}, {}};
+ for (const auto& collection : subscription_collections) {
+ auto result = ClassifyResponseWithSingleCollection(
+ *collection, request_url, frame_hierarchy, content_type,
+ response_headers);
+ if (result.decision == ClassificationResult::Decision::Blocked) {
+ return result;
+ }
+ if (result.decision == ClassificationResult::Decision::Allowed) {
+ classification = result;
+ }
+ }
+ return classification;
+}
+
+absl::optional<GURL> ResourceClassifierImpl::CheckRewrite(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const {
+ for (const auto& collection : subscription_collections) {
+ auto result = CheckRewriteWithSingleCollection(*collection, request_url,
+ frame_hierarchy);
+ if (result) {
+ return result;
+ }
+ }
+ return absl::nullopt;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/classifier/resource_classifier_impl.h b/components/adblock/core/classifier/resource_classifier_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/resource_classifier_impl.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_
+
+#include <set>
+
+#include "components/adblock/core/classifier/resource_classifier.h"
+
+namespace adblock {
+
+class ResourceClassifierImpl final : public ResourceClassifier {
+ public:
+ ResourceClassifierImpl() = default;
+
+ ClassificationResult ClassifyRequest(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const final;
+
+ ClassificationResult ClassifyPopup(
+ const SubscriptionService::Snapshot& subscription_collections,
+ const GURL& popup_url,
+ const std::vector<GURL>& opener_frame_hierarchy,
+ const SiteKey& sitekey) const final;
+
+ ClassificationResult ClassifyResponse(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const scoped_refptr<net::HttpResponseHeaders>& response_headers)
+ const final;
+
+ absl::optional<GURL> CheckRewrite(
+ const SubscriptionService::Snapshot subscription_collections,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const final;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ResourceClassifierImpl>;
+ ~ResourceClassifierImpl() override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_
diff --git a/components/adblock/core/classifier/test/mock_resource_classifier.cc b/components/adblock/core/classifier/test/mock_resource_classifier.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/test/mock_resource_classifier.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/classifier/test/mock_resource_classifier.h"
+
+namespace adblock {
+
+MockResourceClassifier::MockResourceClassifier() = default;
+
+MockResourceClassifier::~MockResourceClassifier() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/classifier/test/mock_resource_classifier.h b/components/adblock/core/classifier/test/mock_resource_classifier.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/test/mock_resource_classifier.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_TEST_MOCK_RESOURCE_CLASSIFIER_H_
+#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_TEST_MOCK_RESOURCE_CLASSIFIER_H_
+
+#include "components/adblock/core/classifier/resource_classifier.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace adblock {
+
+class MockResourceClassifier : public ResourceClassifier {
+ public:
+ MockResourceClassifier();
+ MOCK_METHOD(ClassificationResult,
+ ClassifyRequest,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ const std::vector<GURL>&,
+ ContentType,
+ const SiteKey&),
+ (override, const));
+ MOCK_METHOD(ClassificationResult,
+ ClassifyPopup,
+ (const SubscriptionService::Snapshot&,
+ const GURL&,
+ const std::vector<GURL>&,
+ const SiteKey&),
+ (override, const));
+ MOCK_METHOD(ClassificationResult,
+ ClassifyResponse,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ const std::vector<GURL>&,
+ ContentType,
+ const scoped_refptr<net::HttpResponseHeaders>&),
+ (override, const));
+ MOCK_METHOD(absl::optional<GURL>,
+ CheckRewrite,
+ (const SubscriptionService::Snapshot,
+ const GURL&,
+ const std::vector<GURL>&),
+ (override, const));
+
+ protected:
+ ~MockResourceClassifier() override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CLASSIFIER_TEST_MOCK_RESOURCE_CLASSIFIER_H_
diff --git a/components/adblock/core/classifier/test/resource_classifier_impl_test.cc b/components/adblock/core/classifier/test/resource_classifier_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/test/resource_classifier_impl_test.cc
@@ -0,0 +1,626 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/classifier/resource_classifier_impl.h"
+
+#include <string_view>
+
+#include "absl/types/optional.h"
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "gmock/gmock-actions.h"
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using testing::_;
+using testing::Return;
+
+using Decision = ResourceClassifier::ClassificationResult::Decision;
+
+class AdblockResourceClassifierImplTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ classifier_ = base::MakeRefCounted<ResourceClassifierImpl>();
+ mock_snapshot_ = std::make_unique<SubscriptionService::Snapshot>();
+ mock_snapshot_->push_back(std::make_unique<MockSubscriptionCollection>());
+ EXPECT_CALL(mock_subscription_collection(), GetFilteringConfigurationName())
+ .WillRepeatedly(testing::ReturnRef(kConfigurationName));
+ }
+
+ ResourceClassifier::ClassificationResult ClassifyRequest() {
+ return classifier_->ClassifyRequest(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress},
+ kContentType, kSitekey);
+ }
+
+ MockSubscriptionCollection& mock_subscription_collection() {
+ return *static_cast<MockSubscriptionCollection*>(
+ mock_snapshot_->front().get());
+ }
+
+ void FindBySubresourceFilterReturns(const absl::optional<GURL>& return_value,
+ FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySubresourceFilter(_, _, _, _, category))
+ .WillOnce(Return(return_value));
+ }
+
+ void FindBySpecialFilterReturns(const absl::optional<GURL>& return_value,
+ SpecialFilterType type) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySpecialFilter(type, _, _, _))
+ .WillOnce(Return(return_value));
+ }
+
+ void FindByAllowFilterReturns(const absl::optional<GURL>& return_value) {
+ EXPECT_CALL(mock_subscription_collection(), FindByAllowFilter(_, _, _, _))
+ .WillOnce(Return(return_value));
+ }
+
+ void FindByPopupFilterReturns(const absl::optional<GURL>& return_value,
+ FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindByPopupFilter(_, _, _, category))
+ .WillOnce(Return(return_value));
+ }
+
+ void GetHeaderFiltersReturns(const std::set<HeaderFilterData>& return_value,
+ FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ GetHeaderFilters(_, _, _, category))
+ .WillOnce(Return(return_value));
+ }
+
+ void AssertFindBySubresourceFilterNotCalled(FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySubresourceFilter(_, _, _, _, category))
+ .Times(0);
+ }
+
+ void GetRewriteFiltersReturns(const std::set<std::string_view>& return_value,
+ FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ GetRewriteFilters(_, _, category))
+ .WillOnce(Return(return_value));
+ }
+
+ void AssertFindBySpecialFilterNotCalled(SpecialFilterType type) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindBySpecialFilter(type, _, _, _))
+ .Times(0);
+ }
+
+ void AssertFindByAllowFilterNotCalled() {
+ EXPECT_CALL(mock_subscription_collection(), FindByAllowFilter(_, _, _, _))
+ .Times(0);
+ }
+
+ void AssertFindByPopupFilterNotCalled(FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ FindByPopupFilter(_, _, _, category))
+ .Times(0);
+ }
+
+ void AssertGetHeaderFiltersNotCalled(FilterCategory category) {
+ EXPECT_CALL(mock_subscription_collection(),
+ GetHeaderFilters(_, _, _, category))
+ .Times(0);
+ }
+
+ scoped_refptr<net::HttpResponseHeaders> CreateResponseHeaders(
+ std::string_view header) {
+ std::string full_headers = "HTTP/1.1 200 OK\n";
+ full_headers.append(std::string(header)).append(":");
+ auto headers = net::HttpResponseHeaders::TryToCreate(full_headers);
+ CHECK(headers);
+ return headers;
+ }
+ const GURL kResourceAddress{"https://address.com/image.png"};
+ const GURL kParentAddress{"https://parent.com/"};
+ const ContentType kContentType = ContentType::Image;
+ const SiteKey kSitekey{"abc"};
+ const GURL kSourceUrl{"https://subscription.com/easylist.txt"};
+ const std::set<HeaderFilterData> kAllowingHeaderFilters = {
+ {"allowing_filter", kSourceUrl}};
+ const std::set<HeaderFilterData> kBlockingHeaderFilters = {
+ {"blocking_filter", kSourceUrl}};
+ const std::set<HeaderFilterData> kDomainSpecificHeaderFilters = {
+ {"domain_specific_filter", kSourceUrl}};
+ const std::string kConfigurationName = "test_configuration";
+ std::unique_ptr<SubscriptionService::Snapshot> mock_snapshot_;
+ scoped_refptr<ResourceClassifier> classifier_;
+};
+
+TEST_F(AdblockResourceClassifierImplTest, BlockingFilterNotFound) {
+ // Subscriptions get queried for url filters, no blocking filter is found.
+ FindBySubresourceFilterReturns(absl::nullopt, FilterCategory::Blocking);
+
+ // Since there's no blocking filter, all search stops now.
+ AssertFindByAllowFilterNotCalled();
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Genericblock);
+ AssertFindBySubresourceFilterNotCalled(
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest, BlockingFilterFound) {
+ // Subscriptions get queried for url filters, one reports a blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+
+ // Since there's a blocking filter, subscriptions are queried for allowing
+ // filters
+ FindByAllowFilterReturns(absl::nullopt);
+
+ // Subscriptions have no allowing filter, so the last chance of not
+ // blocking the request is a generic block filter (separate test).
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+ AssertFindBySubresourceFilterNotCalled(
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, BlockingAndAllowingFilterFound) {
+ // Subscriptions get queried for url filters, one reports a blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+
+ // Since there's a blocking filter, subscriptions are queried for allowing
+ // filters
+ FindByAllowFilterReturns(absl::optional<GURL>(kSourceUrl));
+
+ // Allowing filter has been found, make other searach stops now.
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Genericblock);
+ AssertFindBySubresourceFilterNotCalled(
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Allowed);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, GenericBlockSearch) {
+ // Subscriptions get queried for url filters, one reports a blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+
+ // Since there's a blocking filter, subscriptions are queried for allowing
+ // filters
+ FindByAllowFilterReturns(absl::nullopt);
+
+ // Allowing filter has not been found and genericblock is also not found.
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+ AssertFindBySubresourceFilterNotCalled(
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, DomainSpecificNotFound) {
+ // Subscriptions get queried for url filters, one reports a blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+
+ // Since there's a blocking filter, subscriptions are queried for allowing
+ // filters
+ FindByAllowFilterReturns(absl::nullopt);
+
+ // Allowing filter has not been found but gebricblock exists.
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+
+ // However there is no domain specific filter.
+ FindBySubresourceFilterReturns(absl::nullopt,
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest, DomainSpecificFound) {
+ // Subscriptions get queried for url filters, one reports a blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+
+ // Since there's a blocking filter, subscriptions are queried for allowing
+ // filters
+ FindByAllowFilterReturns(absl::nullopt);
+
+ // Allowing filter has not been found but gebricblock exists.
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+
+ // And there is a domain specific filter.
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::DomainSpecificBlocking);
+
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, PopupNoHasFilters) {
+ FindByPopupFilterReturns(absl::nullopt, FilterCategory::Blocking);
+ AssertFindByPopupFilterNotCalled(FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyPopup(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress}, kSitekey);
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest, PopupHasBlockingFilter) {
+ FindByPopupFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+ FindByPopupFilterReturns(absl::nullopt, FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyPopup(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress}, kSitekey);
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, PopupHasAllowingFilter) {
+ FindByPopupFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+ FindByPopupFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyPopup(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress}, kSitekey);
+ EXPECT_EQ(result.decision, Decision::Allowed);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, NoBlockingHeaderFilters) {
+ GetHeaderFiltersReturns({}, FilterCategory::Blocking);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, {});
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersWithDocumentAllowingFilter) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Document);
+
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Genericblock);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::DomainSpecificBlocking);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, {});
+ EXPECT_EQ(result.decision, Decision::Allowed);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersWithGenericblockButNoDomainSpecific) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+ GetHeaderFiltersReturns({}, FilterCategory::DomainSpecificBlocking);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::Allowing);
+
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, {});
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersWithGenericblockAndDomainSpecificNoMatch) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+ GetHeaderFiltersReturns(kDomainSpecificHeaderFilters,
+ FilterCategory::DomainSpecificBlocking);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::Allowing);
+
+ auto headers = CreateResponseHeaders("not_matching");
+ CHECK(headers);
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, headers);
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersWithGenericblockAndDomainSpecificMatch) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+ GetHeaderFiltersReturns(kDomainSpecificHeaderFilters,
+ FilterCategory::DomainSpecificBlocking);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::Allowing);
+
+ auto headers = CreateResponseHeaders(
+ kDomainSpecificHeaderFilters.begin()->header_filter);
+ CHECK(headers);
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, headers);
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, BlockingHeaderFiltersNoMatch) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::DomainSpecificBlocking);
+ GetHeaderFiltersReturns(kAllowingHeaderFilters, FilterCategory::Allowing);
+
+ auto headers = CreateResponseHeaders("no_match");
+ CHECK(headers);
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, headers);
+ EXPECT_EQ(result.decision, Decision::Ignored);
+ EXPECT_EQ(result.decisive_configuration_name, "");
+ EXPECT_EQ(result.decisive_subscription, GURL());
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersMatchNoAllowingFilterMatch) {
+ GetHeaderFiltersReturns(kBlockingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::DomainSpecificBlocking);
+ GetHeaderFiltersReturns(kAllowingHeaderFilters, FilterCategory::Allowing);
+
+ auto headers =
+ CreateResponseHeaders(kBlockingHeaderFilters.begin()->header_filter);
+ CHECK(headers);
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, headers);
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ BlockingHeaderFiltersMatchWithAllowingFilterMatch) {
+ GetHeaderFiltersReturns(kAllowingHeaderFilters, FilterCategory::Blocking);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+ AssertGetHeaderFiltersNotCalled(FilterCategory::DomainSpecificBlocking);
+ GetHeaderFiltersReturns(kAllowingHeaderFilters, FilterCategory::Allowing);
+
+ auto headers =
+ CreateResponseHeaders(kAllowingHeaderFilters.begin()->header_filter);
+ CHECK(headers);
+ auto result = classifier_->ClassifyResponse(
+ std::move(*mock_snapshot_), kResourceAddress, {kParentAddress},
+ kContentType, headers);
+ EXPECT_EQ(result.decision, Decision::Allowed);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ TwoConfigs_DefaultAllowsOtherBlocks_BlockingFilterFound) {
+ // Default configuration has allowing filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+ FindByAllowFilterReturns(absl::optional<GURL>(kSourceUrl));
+
+ // Other configuration has blocking filter
+ std::string other_coniguration = "other";
+ GURL other_source{"https://subscription.com/other.txt"};
+ auto mock_subscription_collection =
+ std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*mock_subscription_collection,
+ FindBySubresourceFilter(_, _, _, _, FilterCategory::Blocking))
+ .WillOnce(Return(absl::optional<GURL>(other_source)));
+ EXPECT_CALL(*mock_subscription_collection, FindByAllowFilter(_, _, _, _))
+ .WillOnce(Return(absl::nullopt));
+ EXPECT_CALL(*mock_subscription_collection,
+ FindBySpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .WillOnce(Return(absl::nullopt));
+ EXPECT_CALL(*mock_subscription_collection, GetFilteringConfigurationName())
+ .WillRepeatedly(testing::ReturnRef(other_coniguration));
+
+ mock_snapshot_->push_back(std::move(mock_subscription_collection));
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, other_coniguration);
+ EXPECT_EQ(result.decisive_subscription, other_source);
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ TwoConfigs_DefaultBlocksOtherIsNotChecked_BlockingFilterFound) {
+ // Default configuration has blocking filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+ FindByAllowFilterReturns(absl::nullopt);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+
+ // Other configuration should not be called
+ auto mock_subscription_collection =
+ std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*mock_subscription_collection,
+ FindBySubresourceFilter(_, _, _, _, FilterCategory::Blocking))
+ .Times(0);
+
+ mock_snapshot_->push_back(std::move(mock_subscription_collection));
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Blocked);
+ EXPECT_EQ(result.decisive_configuration_name, kConfigurationName);
+ EXPECT_EQ(result.decisive_subscription, kSourceUrl);
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ TwoConfigs_DefaultAndOtherAllows_AllowingFilterFound) {
+ // Default configuration has allowing filter
+ FindBySubresourceFilterReturns(absl::optional<GURL>(kSourceUrl),
+ FilterCategory::Blocking);
+ FindByAllowFilterReturns(absl::optional<GURL>(kSourceUrl));
+
+ // Other configuration has also allowing filter
+ std::string other_coniguration = "other";
+ GURL other_source{"https://subscription.com/other.txt"};
+ MockSubscriptionCollection* mock_subscription_collection =
+ new MockSubscriptionCollection();
+ EXPECT_CALL(*mock_subscription_collection,
+ FindBySubresourceFilter(_, _, _, _, FilterCategory::Blocking))
+ .WillOnce(Return(other_source));
+ EXPECT_CALL(*mock_subscription_collection, FindByAllowFilter(_, _, _, _))
+ .WillOnce(Return(absl::optional<GURL>(other_source)));
+ EXPECT_CALL(*mock_subscription_collection, GetFilteringConfigurationName())
+ .WillRepeatedly(testing::ReturnRef(other_coniguration));
+
+ mock_snapshot_->emplace_back(mock_subscription_collection);
+ auto result = ClassifyRequest();
+ EXPECT_EQ(result.decision, Decision::Allowed);
+ EXPECT_EQ(result.decisive_configuration_name, other_coniguration);
+ EXPECT_EQ(result.decisive_subscription, other_source);
+}
+
+TEST_F(AdblockResourceClassifierImplTest, RewriteFilterNotFound) {
+ // There are no blocking filters found.
+ GetRewriteFiltersReturns({}, FilterCategory::Blocking);
+
+ // Since there are no blocking filters, no need to check allow filters.
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Document);
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Genericblock);
+
+ // Empty result means no redirect necessary.
+ EXPECT_FALSE(classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+TEST_F(AdblockResourceClassifierImplTest, RewriteFilterFound) {
+ std::string_view redirect = "about::blank";
+
+ GetRewriteFiltersReturns({redirect}, FilterCategory::Blocking);
+ GetRewriteFiltersReturns({}, FilterCategory::Allowing);
+
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+
+ EXPECT_EQ(GURL{redirect},
+ classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+TEST_F(AdblockResourceClassifierImplTest, RewriteFilterDomainSpecificFound) {
+ GetRewriteFiltersReturns({"about::blank/generic"}, FilterCategory::Blocking);
+ GetRewriteFiltersReturns({}, FilterCategory::Allowing);
+
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Genericblock);
+ GetRewriteFiltersReturns({"about::blank/domain_specific"},
+ FilterCategory::DomainSpecificBlocking);
+
+ EXPECT_EQ(absl::optional<GURL>{GURL("about::blank/domain_specific")},
+ classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ RewriteBlockingFilterOverruledViaDocumentAllowingRule) {
+ GetRewriteFiltersReturns({"about::blank"}, FilterCategory::Blocking);
+
+ FindBySpecialFilterReturns(absl::optional<GURL>(kSourceUrl),
+ SpecialFilterType::Document);
+ AssertFindBySpecialFilterNotCalled(SpecialFilterType::Genericblock);
+
+ // Empty result means no redirect necessary.
+ EXPECT_FALSE(classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ RewriteBlockingFilterOverruledViaOtherRewriteFilter) {
+ GetRewriteFiltersReturns({"about::blank"}, FilterCategory::Blocking);
+
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+
+ GetRewriteFiltersReturns({"about::blank_not_matching", "about::blank"},
+ FilterCategory::Allowing);
+
+ // Empty result means no redirect necessary.
+ EXPECT_FALSE(classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+TEST_F(AdblockResourceClassifierImplTest,
+ TwoConfigs_RewriteFilterFound_OtherConfigNotChecked) {
+ // Default configuration has blocking rewrite filter.
+ std::string_view redirect = "about::blank";
+
+ GetRewriteFiltersReturns({redirect}, FilterCategory::Blocking);
+ GetRewriteFiltersReturns({}, FilterCategory::Allowing);
+
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Document);
+ FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
+
+ // Other configuration should not be called
+ auto mock_subscription_collection =
+ std::make_unique<MockSubscriptionCollection>();
+ EXPECT_CALL(*mock_subscription_collection, GetRewriteFilters(_, _, _))
+ .Times(0);
+
+ mock_snapshot_->push_back(std::move(mock_subscription_collection));
+ EXPECT_EQ(GURL{redirect},
+ classifier_->CheckRewrite(std::move(*mock_snapshot_),
+ kResourceAddress, {kParentAddress}));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/classifier/test/resource_classifier_perftest.cc b/components/adblock/core/classifier/test/resource_classifier_perftest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/classifier/test/resource_classifier_perftest.cc
@@ -0,0 +1,394 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/adblock/core/classifier/resource_classifier_impl.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "components/adblock/core/subscription/subscription_collection_impl.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace adblock {
+
+namespace {
+constexpr char kMetricRuntime[] = ".runtime";
+
+std::string GetTestName() {
+ auto* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ return std::string(test_info->test_suite_name()) + "." + test_info->name();
+}
+} // namespace
+
+class AdblockResourceClassifierPerfTestBase : public testing::Test {
+ public:
+ void SetUp() override {
+ classifier_ = base::MakeRefCounted<ResourceClassifierImpl>();
+ converter_ = base::MakeRefCounted<FlatbufferConverter>();
+ }
+
+ virtual std::string GetTimerResolutionUnits() const = 0;
+ virtual void AddResult(perf_test::PerfResultReporter& reporter,
+ const base::ElapsedTimer& timer) const = 0;
+
+ static std::string ReadFromTestData(const std::string& file_name) {
+ base::FilePath source_file;
+ EXPECT_TRUE(
+ base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_file));
+ source_file = source_file.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("adblock")
+ .AppendASCII(file_name);
+ std::string content;
+ CHECK(base::ReadFileToString(source_file, &content));
+ CHECK(compression::GzipUncompress(content, &content));
+ return content;
+ }
+
+ static int BenchmarkRepetitions() {
+ // Android devices are much slower than Desktop computers, reduce the
+ // number of reps so they don't time out. On Desktop, a higher number of
+ // reps allows more reliable measurement via perf tools.
+#if BUILDFLAG(IS_ANDROID)
+ return 50;
+#else
+ return 500;
+#endif
+ }
+
+ void MeasureUrlMatchingTime(
+ GURL url,
+ ContentType content_type,
+ std::vector<scoped_refptr<InstalledSubscription>> state,
+ int cycles = BenchmarkRepetitions()) {
+ ResourceClassifier::ClassificationResult classification_result;
+ perf_test::PerfResultReporter reporter(GetTestName(), "url_matching");
+ reporter.RegisterImportantMetric(kMetricRuntime, GetTimerResolutionUnits());
+ base::ElapsedTimer timer;
+ // Call matching many times to make sure perf woke up for measurement.
+ for (int i = 0; i < cycles; ++i) {
+ SubscriptionService::Snapshot snapshot;
+ snapshot.emplace_back(
+ std::make_unique<SubscriptionCollectionImpl>(state, ""));
+ classification_result = classifier_->ClassifyRequest(
+ std::move(snapshot), url, DefaultFrameHierarchy(), content_type,
+ DefaultSitekey());
+ }
+ AddResult(reporter, timer);
+ VLOG(1) << "Classification result: "
+ << ClassificationResultToString(classification_result);
+ }
+
+ void MeasureCSPMatchingTime(
+ GURL url,
+ ContentType content_type,
+ std::vector<scoped_refptr<InstalledSubscription>> state,
+ int cycles = BenchmarkRepetitions()) {
+ std::set<std::string_view> csp_injections;
+ auto sub_collection =
+ std::make_unique<SubscriptionCollectionImpl>(state, "");
+ perf_test::PerfResultReporter reporter(GetTestName(), "csp_matching");
+ reporter.RegisterImportantMetric(kMetricRuntime, GetTimerResolutionUnits());
+ base::ElapsedTimer timer;
+ // Call matching many times to make sure perf woke up for measurement.
+ for (int i = 0; i < cycles; ++i) {
+ csp_injections =
+ sub_collection->GetCspInjections(url, DefaultFrameHierarchy());
+ }
+ AddResult(reporter, timer);
+ for (const auto& csp_i : csp_injections) {
+ VLOG(1) << "CSP injection found: " << csp_i;
+ }
+ }
+
+ void MeasureElemhideGenerationTime(
+ GURL url,
+ std::vector<scoped_refptr<InstalledSubscription>> state) {
+ auto sub_collection =
+ std::make_unique<SubscriptionCollectionImpl>(state, "");
+ perf_test::PerfResultReporter reporter(GetTestName(),
+ "elemhide_generation");
+ reporter.RegisterImportantMetric(kMetricRuntime, GetTimerResolutionUnits());
+ base::ElapsedTimer timer;
+ // Call generation many times to make sure perf woke up for measurement.
+ for (int i = 0; i < BenchmarkRepetitions(); ++i) {
+ sub_collection->GetElementHideData(url, DefaultFrameHierarchy(),
+ DefaultSitekey());
+ }
+ AddResult(reporter, timer);
+ }
+
+ const GURL& UnknownAddress() const {
+ static const GURL kUnknownAddress{
+ "https://eyeo.com/themes/custom/eyeo_theme/logo.svg"};
+ return kUnknownAddress;
+ }
+
+ const GURL& BlockedAddress() const {
+ static const GURL kBlockedAddress{"https://0265331.com/whatever/image.png"};
+ return kBlockedAddress;
+ }
+
+ const std::vector<GURL>& DefaultFrameHierarchy() const {
+ static const std::vector<GURL> kFrameHierarchy{
+ GURL("https://frame.com/frame1.html"),
+ GURL("https://frame.com/frame2.html"),
+ GURL("https://frame.com/"),
+ };
+ return kFrameHierarchy;
+ }
+
+ const SiteKey& DefaultSitekey() const {
+ static const SiteKey kSiteKey{"abc"};
+ return kSiteKey;
+ }
+
+ std::string_view ClassificationResultToString(
+ const ResourceClassifier::ClassificationResult& result) {
+ switch (result.decision) {
+ case ResourceClassifier::ClassificationResult::Decision::Allowed:
+ return "Allowed";
+ case ResourceClassifier::ClassificationResult::Decision::Blocked:
+ return "Blocked";
+ case ResourceClassifier::ClassificationResult::Decision::Ignored:
+ return "Ignored";
+ }
+ return "";
+ }
+
+ scoped_refptr<ResourceClassifier> classifier_;
+ scoped_refptr<FlatbufferConverter> converter_;
+};
+
+// Uses real filter lists
+class AdblockResourceClassifierPerfTestFull
+ : public AdblockResourceClassifierPerfTestBase {
+ public:
+ std::vector<scoped_refptr<InstalledSubscription>> CreateState(
+ std::initializer_list<std::string> filenames) {
+ std::vector<scoped_refptr<InstalledSubscription>> state;
+ for (const auto& cur : filenames) {
+ const std::string content = ReadFromTestData(cur);
+ std::stringstream input(std::move(content));
+ auto converter_result =
+ converter_->Convert(input, CustomFiltersUrl(), false);
+ DCHECK(absl::holds_alternative<std::unique_ptr<FlatbufferData>>(
+ converter_result));
+ state.push_back(base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(
+ absl::get<std::unique_ptr<FlatbufferData>>(converter_result)),
+ Subscription::InstallationState::Installed, base::Time()));
+ }
+ return state;
+ }
+
+ std::string GetTimerResolutionUnits() const override { return "ms"; }
+ void AddResult(perf_test::PerfResultReporter& reporter,
+ const base::ElapsedTimer& timer) const override {
+ reporter.AddResult(kMetricRuntime,
+ static_cast<size_t>(timer.Elapsed().InMilliseconds()));
+ }
+};
+
+TEST_F(AdblockResourceClassifierPerfTestFull, UrlNoMatch) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+ MeasureUrlMatchingTime(UnknownAddress(), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestFull, UrlBlocked) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+ MeasureUrlMatchingTime(BlockedAddress(), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestFull, ElemhideNoMatch) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+ MeasureElemhideGenerationTime(UnknownAddress(), std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestFull, ElemhideMatch) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+ MeasureElemhideGenerationTime(
+ GURL{"https://www.heise.de/news/"
+ "Privacy-Shield-2-0-Viele-offene-Fragen-zum-Datenverkehr-mit-den-"
+ "USA-6658370.html"},
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestFull, LongUrlMatch) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+ // "longurl.txt.gz" contains a 300K-long URL that encodes a ton of debug data.
+ // This URL was recorded from a real site. It can be orders of magnitude
+ // slower to match than typical URLs so we use a custom repetition count.
+#if BUILDFLAG(IS_ANDROID)
+ const int kRepCount = 5;
+#else
+ const int kRepCount = 50;
+#endif
+ const GURL long_url(ReadFromTestData("longurl.txt.gz"));
+ MeasureUrlMatchingTime(long_url, ContentType::Subdocument, std::move(state),
+ kRepCount);
+}
+
+TEST_F(AdblockResourceClassifierPerfTestFull, LongUrlFindCsp) {
+ auto state = CreateState({"easylist.txt.gz", "exceptionrules.txt.gz"});
+#if BUILDFLAG(IS_ANDROID)
+ const int kRepCount = 5;
+#else
+ const int kRepCount = 50;
+#endif
+ const GURL long_url(ReadFromTestData("longurl.txt.gz"));
+ MeasureCSPMatchingTime(long_url, ContentType::Subdocument, std::move(state),
+ kRepCount);
+}
+
+// Uses one or just a couple of filters
+class AdblockResourceClassifierPerfTestSimple
+ : public AdblockResourceClassifierPerfTestBase {
+ public:
+ std::vector<scoped_refptr<InstalledSubscription>> CreateStateFromFilters(
+ std::vector<std::string> filters) {
+ auto converter_result =
+ converter_->Convert(std::move(filters), CustomFiltersUrl(), false);
+ DCHECK(converter_result);
+ std::vector<scoped_refptr<InstalledSubscription>> state;
+ state.push_back(base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(converter_result), Subscription::InstallationState::Installed,
+ base::Time()));
+ return state;
+ }
+
+ std::string GetTimerResolutionUnits() const override { return "us"; }
+ void AddResult(perf_test::PerfResultReporter& reporter,
+ const base::ElapsedTimer& timer) const override {
+ reporter.AddResult(kMetricRuntime, timer.Elapsed());
+ }
+};
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, NoWildcardDomainElemhideMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"example.com###selector"});
+ MeasureElemhideGenerationTime(GURL("https://example.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple,
+ NoWildcardMultipleDomainsElemhideMatch) {
+ auto state = CreateStateFromFilters(std::vector<std::string>{
+ "example.de,example.pl,example.net,example.co.uk,example.com,example.fr, "
+ "example.hu###selector"});
+ MeasureElemhideGenerationTime(GURL("https://example.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, WildcardDomainElemhideMatch) {
+ auto state =
+ CreateStateFromFilters(std::vector<std::string>{"example.*###selector"});
+ MeasureElemhideGenerationTime(GURL("https://example.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple,
+ NoWildcardDomainElemhideNoMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"example.com###selector"});
+ MeasureElemhideGenerationTime(GURL("https://wrong.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple,
+ NoWildcardMultipleDomainsElemhideNoMatch) {
+ auto state = CreateStateFromFilters(std::vector<std::string>{
+ "example.de,example.pl,example.net,example.co.uk,example.com,example.fr, "
+ "example.hu###selector"});
+ MeasureElemhideGenerationTime(GURL("https://wrong.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, WildcardDomainElemhideNoMatch) {
+ auto state =
+ CreateStateFromFilters(std::vector<std::string>{"example.*###selector"});
+ MeasureElemhideGenerationTime(GURL("https://wrong.com/frame.html"),
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, NoWildcardDomainUrlMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"ad.png$domain=frame.com"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple,
+ NoWildcardMultipleDomainsUrlMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"ad.png$domain=frame.de|frame.pl|frame.net|"
+ "frame.co.uk|frame.com|frame.fr|frame.hu"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, WildcardDomainUrlMatch) {
+ auto state =
+ CreateStateFromFilters(std::vector<std::string>{"ad.png$domain=frame.*"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, NoWildcardDomainUrlNoMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"ad.png$domain=wrong.com"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple,
+ NoWildcardMultipleDomainsUrlNoMatch) {
+ auto state = CreateStateFromFilters(
+ std::vector<std::string>{"ad.png$domain=wrong.de|wrong.pl|wrong.net|"
+ "wrong.co.uk|wrong.com|wrong.fr|wrong.hu"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+TEST_F(AdblockResourceClassifierPerfTestSimple, WildcardDomainUrlNoMatch) {
+ auto state =
+ CreateStateFromFilters(std::vector<std::string>{"ad.png$domain=wrong.*"});
+
+ MeasureUrlMatchingTime(GURL("https://example.com/ad.png"), ContentType::Image,
+ std::move(state));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/BUILD.gn b/components/adblock/core/common/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/BUILD.gn
@@ -0,0 +1,139 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//components/adblock/features.gni")
+
+config("eyeo_filtering_config") {
+ defines = []
+
+ if (eyeo_disable_filtering_by_default) {
+ defines += [ "EYEO_DISABLE_FILTERING_BY_DEFAULT=true" ]
+ } else {
+ defines += [ "EYEO_DISABLE_FILTERING_BY_DEFAULT=false" ]
+ }
+
+ if (eyeo_disable_aa_by_default) {
+ defines += [ "EYEO_DISABLE_AA_BY_DEFAULT=true" ]
+ } else {
+ defines += [ "EYEO_DISABLE_AA_BY_DEFAULT=false" ]
+ }
+}
+
+config("eyeo_application_config") {
+ defines = []
+
+ if (eyeo_application_name != "") {
+ defines += [ "EYEO_APPLICATION_NAME=\"$eyeo_application_name\"" ]
+ }
+
+ if (eyeo_application_version != "") {
+ defines += [ "EYEO_APPLICATION_VERSION=\"$eyeo_application_version\"" ]
+ }
+}
+
+source_set("common") {
+ configs += [
+ ":eyeo_application_config",
+ ":eyeo_filtering_config",
+ ]
+
+ sources = [
+ "adblock_constants.cc",
+ "adblock_constants.h",
+ "adblock_prefs.cc",
+ "adblock_prefs.h",
+ "adblock_switches.cc",
+ "adblock_switches.h",
+ "app_info.cc",
+ "app_info.h",
+ "content_type.cc",
+ "content_type.h",
+ "flatbuffer_data.cc",
+ "flatbuffer_data.h",
+ "header_filter_data.h",
+ "keyword_extractor_utils.cc",
+ "keyword_extractor_utils.h",
+ "regex_filter_pattern.cc",
+ "regex_filter_pattern.h",
+ "sitekey.h",
+ "task_scheduler.h",
+ "task_scheduler_impl.cc",
+ "task_scheduler_impl.h",
+ "web_ui_constants.cc",
+ "web_ui_constants.h",
+ ]
+
+ deps = [
+ "//components/prefs",
+ "//components/version_info",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core:schema",
+ "//components/adblock/core:schema_hash",
+ "//third_party/abseil-cpp:absl",
+ "//url",
+ ]
+}
+
+source_set("utils") {
+ sources = [
+ "adblock_utils.cc",
+ "adblock_utils.h",
+ ]
+
+ deps = [
+ "//third_party/icu/",
+ "//third_party/re2",
+ "//ui/base",
+ "//url",
+ ]
+
+ public_deps = [
+ ":common",
+ "//base",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/mock_task_scheduler.cc",
+ "test/mock_task_scheduler.h",
+ ]
+
+ public_deps = [
+ ":common",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/flatbuffer_data_test.cc",
+ "test/task_scheduler_impl_test.cc",
+ ]
+
+ deps = [
+ ":common",
+ "//base/test:test_support",
+ "//components/adblock/core/subscription:subscription",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/adblock/core/common/adblock_constants.cc b/components/adblock/core/common/adblock_constants.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_constants.cc
@@ -0,0 +1,174 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/adblock_constants.h"
+
+#include "base/base64.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "components/adblock/core/schema/schema_hash.h"
+
+namespace adblock {
+
+const char kSiteKeyHeaderKey[] = "x-adblock-key";
+
+const char kAllowlistEverythingFilter[] = "@@*$document";
+
+const char kAdblockFilteringConfigurationName[] = "adblock";
+
+const char kBlankHtml[] =
+ "data:text/html,<!DOCTYPE html><html><head></head><body></body></html>";
+
+const char kBlankMp3[] =
+ "data:audio/"
+ "mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjIwLjEwMAAAAAAAAAAAAAAA//"
+ "tUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAGAAADAABgYGBg"
+ "YGBgYGBgYGBgYGBggICAgICAgICAgICAgICAgICgoKCgoKCgoKCgoKCgoKCgwMDAwMDAwMDAwM"
+ "DAwMDAwMDg4ODg4ODg4ODg4ODg4ODg4P////////////////////"
+ "8AAAAATGF2YzU4LjM1AAAAAAAAAAAAAAAAJAYAAAAAAAAAAwDVxttG//"
+ "sUZAAP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAETEFNRTMuMTAwVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//"
+ "sUZB4P8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//"
+ "sUZDwP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//"
+ "sUZFoP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//"
+ "sUZHgP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//"
+ "sUZJYP8AAAaQAAAAgAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV";
+
+const char kBlankMp4[] =
+ "data:video/"
+ "mp4;base64,"
+ "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAuJtZGF0AAACrwYF//"
+ "+r3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MCByMzAxMU0gY2RlOWE5MyAtIEguMjY0"
+ "L01QRUctNCBBVkMgY29kZWMgLSBDb3B5bGVmdCAyMDAzLTIwMjAgLSBodHRwOi8vd3d3LnZpZG"
+ "VvbGFuLm9yZy94MjY0Lmh0bWwgLSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTow"
+ "OjAgYW5hbHlzZT0weDM6MHgxMTMgbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC"
+ "4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MCB0cmVsbGlzPTEgOHg4ZGN0"
+ "PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LT"
+ "IgdGhyZWFkcz0yIGxvb2thaGVhZF90aHJlYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRl"
+ "Y2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT"
+ "0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2FkYXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2Vp"
+ "Z2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtleWludD0yNTAga2V5aW50X21pbj0yNSBzY2"
+ "VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9va2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9"
+ "MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBtYXg9NjkgcXBzdGVwPTQgaXBfcmF0aW"
+ "89MS40MCBhcT0xOjEuMDAAgAAAACNliIQAK//"
+ "+9dvzLK5umjbe9jc2CT9EPcfnoOYC2tjtP+"
+ "go4QAAAwRtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAKAABAAABAAAAAAAAAAAAAAAA"
+ "AQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAACAAACLnRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAKAAAAAAAAAAA"
+ "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAZAAAADgAAAAAAC"
+ "RlZHRzAAAAHGVsc3QAAAAAAAAAAQAAACgAAAAAAAEAAAAAAaZtZGlhAAAAIG1kaGQAAAAAAAAA"
+ "AAAAAAAAADIAAAACAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSG"
+ "FuZGxlcgAAAAFRbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAA"
+ "AAABAAAADHVybCAAAAABAAABEXN0YmwAAACtc3RzZAAAAAAAAAABAAAAnWF2YzEAAAAAAAAAAQ"
+ "AAAAAAAAAAAAAAAAAAAAAAZAA4AEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAY//8AAAA3YXZjQwFkAAr/"
+ "4QAaZ2QACvNlHJ42JwEQAAADABAAAAMDIPEiWWABAAZo6+PLIsD8+"
+ "PgAAAAAEHBhc3AAAAABAAAAAQAAABhzdHRzAAAAAAAAAAEAAAABAAACAAAAABxzdHNjAAAAAAA"
+ "AAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAALaAAAAAQAAABRzdGNvAAAAAAAAAAEAAAAwA"
+ "AAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAA"
+ "taWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1OC40NS4xMDA=";
+
+const char kBlankGif[] =
+ "data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///"
+ "yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+
+const char kBlankPng2x2[] =
+ "data:image/"
+ "png;base64,"
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAC0lEQ"
+ "VQI12NgQAcAABIAAe+JVKQAAAAASUVORK5CYII=";
+
+const char kBlankPng3x2[] =
+ "data:image/"
+ "png;base64,"
+ "iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAC0lEQVQI12NgwAUAABoAASRETu"
+ "UAAAAASUVORK5CYII=";
+
+const char kBlankPng32x32[] =
+ "data:image/"
+ "png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGklEQVRYw+"
+ "3BAQEAAACCIP+vbkhAAQAAAO8GECAAAZf3V9cAAAAASUVORK5CYII=";
+
+const std::string& CurrentSchemaVersion() {
+ static std::string kCurrentSchemaVersion =
+ base::Base64Encode(kSha256_filter_list_schema_generated_h);
+ return kCurrentSchemaVersion;
+}
+
+const GURL& TestPagesSubscriptionUrl() {
+ static GURL kTestPagesUrl(
+ "https://abptestpages.org/en/abp-testcase-subscription.txt");
+ return kTestPagesUrl;
+}
+
+const GURL& CustomFiltersUrl() {
+ static GURL kCustomFiltersUrl("adblock:custom");
+ return kCustomFiltersUrl;
+}
+
+std::string_view RewriteUrl(flat::AbpResource type) {
+ switch (type) {
+ case flat::AbpResource_BlankText:
+ return "data:text/plain,";
+ case flat::AbpResource_BlankCss:
+ return "data:text/css,";
+ case flat::AbpResource_BlankJs:
+ return "data:application/javascript,";
+ case flat::AbpResource_BlankHtml:
+ return kBlankHtml;
+ case flat::AbpResource_BlankMp3:
+ return kBlankMp3;
+ case flat::AbpResource_BlankMp4:
+ return kBlankMp4;
+ case flat::AbpResource_TransparentGif1x1:
+ return kBlankGif;
+ case flat::AbpResource_TransparentPng2x2:
+ return kBlankPng2x2;
+ case flat::AbpResource_TransparentPng3x2:
+ return kBlankPng3x2;
+ case flat::AbpResource_TransparentPng32x32:
+ return kBlankPng32x32;
+ default:
+ return {};
+ }
+}
+
+bool g_eyeo_disable_filtering_by_default = EYEO_DISABLE_FILTERING_BY_DEFAULT;
+
+bool IsEyeoFilteringDisabledByDefault() {
+ return g_eyeo_disable_filtering_by_default;
+}
+
+base::AutoReset<bool> OverrideEyeoFilteringDisabledByDefault(bool val) {
+ return base::AutoReset<bool>(&g_eyeo_disable_filtering_by_default, val);
+}
+
+bool g_eyeo_disable_aa_by_default = EYEO_DISABLE_AA_BY_DEFAULT;
+
+bool IsAcceptableAdsDisabledByDefault() {
+ return g_eyeo_disable_aa_by_default;
+}
+
+base::AutoReset<bool> OverrideAcceptableAdsDisabledByDefault(bool val) {
+ return base::AutoReset<bool>(&g_eyeo_disable_aa_by_default, val);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/adblock_constants.h b/components/adblock/core/common/adblock_constants.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_constants.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_
+
+#include <string_view>
+
+#include "base/auto_reset.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+namespace flat {
+enum AbpResource : int8_t;
+}
+
+extern const char kSiteKeyHeaderKey[];
+extern const char kAllowlistEverythingFilter[];
+extern const char kAdblockFilteringConfigurationName[];
+
+const std::string& CurrentSchemaVersion();
+const GURL& TestPagesSubscriptionUrl();
+const GURL& CustomFiltersUrl();
+std::string_view RewriteUrl(flat::AbpResource type);
+
+bool IsEyeoFilteringDisabledByDefault();
+bool IsAcceptableAdsDisabledByDefault();
+
+// Override result of IsEyeoFilteringDisabledByDefault() for the
+// duration of the returned AutoReset's lifetime. Used for testing.
+base::AutoReset<bool> OverrideEyeoFilteringDisabledByDefault(bool val);
+
+// Override result of IsAcceptableAdsDisabledByDefault() for the
+// duration of the returned AutoReset's lifetime. Used for testing.
+base::AutoReset<bool> OverrideAcceptableAdsDisabledByDefault(bool val);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_
diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adblock/core/common/adblock_prefs.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_prefs.cc
@@ -0,0 +1,167 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/adblock_prefs.h"
+
+#include "base/logging.h"
+#include "components/prefs/pref_registry_simple.h"
+
+namespace adblock::common::prefs {
+
+// Legacy: Whether to block ads
+const char kEnableAdblockLegacy[] = "adblock.enable";
+
+// Legacy: Whether to allow acceptable ads or block them all.
+// Used now just to map CLI switch. Otherwise use kAdblockSubscriptionsLegacy.
+const char kEnableAcceptableAdsLegacy[] = "adblock.aa_enable";
+
+// Legacy: List of domains ad blocking will be disabled for
+const char kAdblockAllowedDomainsLegacy[] = "adblock.allowed_domains";
+
+// Legacy: List of custom filters added explicitly by the user
+const char kAdblockCustomFiltersLegacy[] = "adblock.custom_filters";
+
+// Legacy: List of recommended subscriptions
+const char kAdblockSubscriptionsLegacy[] = "adblock.subscriptions";
+
+// Legacy: List of custom (user defined) subscriptions.
+// Use just kAdblockSubscriptionsLegacy.
+const char kAdblockCustomSubscriptionsLegacy[] = "adblock.custom_subscriptions";
+
+// Whether more options item is enabled in the UI
+const char kAdblockMoreOptionsEnabled[] = "adblock.more_options";
+
+// Whether a first-run installation of preloaded subscriptions and
+// language-based recommended subscriptions is necessary.
+const char kInstallFirstStartSubscriptions[] =
+ "adblock.install_first_run_subscriptions";
+
+// Dictionary mapping subscription filename to subscription content hash,
+// stored in Tracked Preferences to ensure untrusted subscription files aren't
+// added, or existing subscription files aren't modified.
+const char kSubscriptionSignatures[] = "adblock.subscription_signatures";
+
+// Stores the schema version used to create currently installed subscriptions.
+// Allows discovering a need to re-install subscriptions when the schema
+// version used by this browser build is newer.
+const char kLastUsedSchemaVersion[] = "adblock.last_used_schema_version";
+
+// Map of subscription URL into subscription metadata, containing ex. expiration
+// time, download count etc. Used for driving the subscription update process
+// and for setting query parameters in subscription download requests.
+const char kSubscriptionMetadata[] = "adblock.subscription_metadata";
+
+// Client-generated UUID4 that uniquely identifies the server response that
+// sent kTelemetryLastPingTime. Sent along with other ping times to
+// disambiguate between other clients who send ping requests the same day.
+// Regenerated on every successful response.
+const char kTelemetryLastPingTag[] =
+ "adblock.telemetry.activeping.last_ping_tag";
+
+// Server UTC time of last ping response, updated with every successful
+// response. Shall not be compared to client time (even UTC). Sent by the
+// telemetry server, stored as unparsed string (ex. "2022-02-08T09:30:00Z").
+const char kTelemetryLastPingTime[] =
+ "adblock.telemetry.activeping.last_ping_time";
+
+// Previous last ping time, gets replaced by kTelemetryLastPingTime when a new
+// successful ping response arrives. Sent in a ping request.
+const char kTelemetryPreviousLastPingTime[] =
+ "adblock.telemetry.activeping.previous_last_ping_time";
+
+// Time of first recorded response for a telemetry ping request, sent along
+// with future ping requests, to further disambiguate
+// user-counting without being able to uniquely track a user.
+const char kTelemetryFirstPingTime[] =
+ "adblock.telemetry.activeping.first_ping_time";
+
+// Client time, when to perform the next ping?
+// Not sent, used locally to ensure we don't ping too often.
+const char kTelemetryNextPingTime[] =
+ "adblock.telemetry.activeping.next_ping_time";
+
+// FilteringConfiguration data
+const char kConfigurationsPrefsPath[] = "filtering.configurations";
+
+// Last time recommended subscriptions were updated
+const char kEnableAutoInstalledSubscriptions[] =
+ "adblock.auto_installed_subscriptions.enable";
+
+// Last time recommended subscriptions were updated
+const char kAutoInstalledSubscriptionsNextUpdateTime[] =
+ "adblock.auto_installed_subscriptions.last_update_time";
+
+// Dict containing stats about acceptable ads page views
+const char kTelemetryPageViewStats[] = "adblock.telemetry.page_view_stats";
+
+void RegisterTelemetryPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterStringPref(kTelemetryLastPingTag, "");
+ registry->RegisterStringPref(kTelemetryLastPingTime, "");
+ registry->RegisterStringPref(kTelemetryPreviousLastPingTime, "");
+ registry->RegisterStringPref(kTelemetryFirstPingTime, "");
+ registry->RegisterTimePref(kTelemetryNextPingTime, base::Time());
+ registry->RegisterDictionaryPref(kTelemetryPageViewStats);
+}
+
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+ registry->RegisterBooleanPref(kEnableAdblockLegacy, true);
+ registry->RegisterBooleanPref(kEnableAcceptableAdsLegacy, true);
+ registry->RegisterBooleanPref(kAdblockMoreOptionsEnabled, false);
+ registry->RegisterListPref(kAdblockAllowedDomainsLegacy, {});
+ registry->RegisterListPref(kAdblockCustomFiltersLegacy, {});
+ registry->RegisterListPref(kAdblockSubscriptionsLegacy, {});
+ registry->RegisterListPref(kAdblockCustomSubscriptionsLegacy, {});
+ registry->RegisterBooleanPref(kInstallFirstStartSubscriptions, true);
+ registry->RegisterDictionaryPref(kSubscriptionSignatures);
+ registry->RegisterStringPref(kLastUsedSchemaVersion, "");
+ registry->RegisterDictionaryPref(kSubscriptionMetadata);
+ registry->RegisterDictionaryPref(kConfigurationsPrefsPath);
+ registry->RegisterBooleanPref(kEnableAutoInstalledSubscriptions, true);
+ // Set to |now| so the first update happens ASAP
+ registry->RegisterTimePref(kAutoInstalledSubscriptionsNextUpdateTime,
+ base::Time::Now());
+ RegisterTelemetryPrefs(registry);
+
+ VLOG(3) << "[eyeo] Registered prefs";
+}
+
+std::vector<std::string_view> GetPrefs() {
+ static std::vector<std::string_view> prefs = {
+ kEnableAdblockLegacy,
+ kEnableAcceptableAdsLegacy,
+ kAdblockMoreOptionsEnabled,
+ kAdblockAllowedDomainsLegacy,
+ kAdblockCustomFiltersLegacy,
+ kAdblockSubscriptionsLegacy,
+ kAdblockCustomSubscriptionsLegacy,
+ kInstallFirstStartSubscriptions,
+ kSubscriptionSignatures,
+ kLastUsedSchemaVersion,
+ kSubscriptionMetadata,
+ kTelemetryLastPingTag,
+ kTelemetryLastPingTime,
+ kTelemetryPreviousLastPingTime,
+ kTelemetryFirstPingTime,
+ kTelemetryNextPingTime,
+ kTelemetryPageViewStats,
+ kConfigurationsPrefsPath,
+ kEnableAutoInstalledSubscriptions,
+ kAutoInstalledSubscriptionsNextUpdateTime};
+ return prefs;
+}
+
+} // namespace adblock::common::prefs
diff --git a/components/adblock/core/common/adblock_prefs.h b/components/adblock/core/common/adblock_prefs.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_prefs.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_
+
+#include <string_view>
+#include <vector>
+
+class PrefRegistrySimple;
+
+namespace adblock::common::prefs {
+
+extern const char kEnableAdblockLegacy[];
+extern const char kEnableAcceptableAdsLegacy[];
+extern const char kAdblockAllowedDomainsLegacy[];
+extern const char kAdblockCustomFiltersLegacy[];
+extern const char kAdblockSubscriptionsLegacy[];
+extern const char kAdblockCustomSubscriptionsLegacy[];
+extern const char kAdblockMoreOptionsEnabled[];
+extern const char kInstallFirstStartSubscriptions[];
+extern const char kSubscriptionSignatures[];
+extern const char kLastUsedSchemaVersion[];
+extern const char kSubscriptionMetadata[];
+extern const char kTelemetryLastPingTag[];
+extern const char kTelemetryLastPingTime[];
+extern const char kTelemetryPreviousLastPingTime[];
+extern const char kTelemetryFirstPingTime[];
+extern const char kTelemetryNextPingTime[];
+extern const char kTelemetryPageViewStats[];
+extern const char kConfigurationsPrefsPath[];
+extern const char kEnableAutoInstalledSubscriptions[];
+extern const char kAutoInstalledSubscriptionsNextUpdateTime[];
+
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+std::vector<std::string_view> GetPrefs();
+
+} // namespace adblock::common::prefs
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_
diff --git a/components/adblock/core/common/adblock_switches.cc b/components/adblock/core/common/adblock_switches.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_switches.cc
@@ -0,0 +1,28 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/adblock_switches.h"
+
+namespace adblock::switches {
+
+const char kDisableAcceptableAds[] = "disable-aa";
+const char kDisableAdblock[] = "disable-adblock";
+const char kDisableEyeoFiltering[] = "disable-eyeo-filtering";
+const char kDisableEyeoRequestThrottling[] = "disable-eyeo-request-throttling";
+const char kStoreFilterText[] = "store-filter-text";
+
+} // namespace adblock::switches
diff --git a/components/adblock/core/common/adblock_switches.h b/components/adblock/core/common/adblock_switches.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_switches.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_SWITCHES_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_SWITCHES_H_
+
+namespace adblock::switches {
+
+extern const char kDisableAcceptableAds[];
+extern const char kDisableAdblock[];
+extern const char kDisableEyeoFiltering[];
+extern const char kDisableEyeoRequestThrottling[];
+extern const char kStoreFilterText[];
+
+} // namespace adblock::switches
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_SWITCHES_H_
diff --git a/components/adblock/core/common/adblock_utils.cc b/components/adblock/core/common/adblock_utils.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_utils.cc
@@ -0,0 +1,80 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/adblock_utils.h"
+
+#include "base/logging.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace adblock::utils {
+
+std::vector<std::string> ConvertURLs(const std::vector<GURL>& input) {
+ std::vector<std::string> output;
+ output.reserve(input.size());
+ std::transform(std::begin(input), std::end(input), std::back_inserter(output),
+ [](const GURL& gurl) { return gurl.spec(); });
+ return output;
+}
+
+std::unique_ptr<FlatbufferData> MakeFlatbufferDataFromResourceBundle(
+ int resource_id) {
+ return std::make_unique<InMemoryFlatbufferData>(
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ resource_id));
+}
+
+bool RegexMatches(std::string_view pattern,
+ std::string_view input,
+ bool case_sensitive) {
+ re2::RE2::Options options;
+ options.set_case_sensitive(case_sensitive);
+ options.set_never_capture(true);
+ options.set_log_errors(false);
+ options.set_encoding(re2::RE2::Options::EncodingLatin1);
+ const re2::RE2 re2_pattern(pattern.data(), options);
+ if (re2_pattern.ok()) {
+ return re2::RE2::PartialMatch(input.data(), re2_pattern);
+ }
+ VLOG(2) << "[eyeo] RE2 does not support filter pattern " << pattern
+ << " and return with error message: " << re2_pattern.error();
+
+ // Maximum length of the string to match to avoid causing an icu::RegexMatcher
+ // stack overflow. (crbug.com/1198219)
+ if (input.size() > url::kMaxURLChars) {
+ return false;
+ }
+ const icu::UnicodeString icu_pattern(pattern.data(), pattern.length());
+ const icu::UnicodeString icu_input(input.data(), input.length());
+ UErrorCode status = U_ZERO_ERROR;
+ const auto icu_case_sensetive = case_sensitive ? 0u : UREGEX_CASE_INSENSITIVE;
+ icu::RegexMatcher matcher(icu_pattern, icu_case_sensetive, status);
+
+ // is pattern supported by icu regex
+ if (U_FAILURE(status)) {
+ // should not happen as validation should take place before reaching
+ // this point
+ DLOG(ERROR) << "[eyeo] None of the regex engines can use pattern: "
+ << pattern;
+ return false;
+ }
+ matcher.reset(icu_input);
+ return matcher.find(0, status);
+}
+
+} // namespace adblock::utils
diff --git a/components/adblock/core/common/adblock_utils.h b/components/adblock/core/common/adblock_utils.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/adblock_utils.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "url/gurl.h"
+
+namespace adblock::utils {
+
+std::vector<std::string> ConvertURLs(const std::vector<GURL>& input);
+
+// Creates a FlatbufferData object that holds data from the ResourceBundle
+
+std::unique_ptr<FlatbufferData> MakeFlatbufferDataFromResourceBundle(
+ int resource_id);
+
+bool RegexMatches(std::string_view pattern,
+ std::string_view input,
+ bool case_sensitive);
+
+} // namespace adblock::utils
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_
diff --git a/components/adblock/core/common/app_info.cc b/components/adblock/core/common/app_info.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/app_info.cc
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/app_info.h"
+
+#include "base/strings/string_util.h"
+#include "components/version_info/version_info.h"
+
+namespace adblock {
+
+// static
+const AppInfo& AppInfo::Get() {
+ static AppInfo instance;
+ return instance;
+}
+
+AppInfo::AppInfo() {
+#if defined(EYEO_APPLICATION_NAME)
+ name = EYEO_APPLICATION_NAME;
+#else
+ name = version_info::GetProductName();
+#endif
+#if defined(EYEO_APPLICATION_VERSION)
+ version = EYEO_APPLICATION_VERSION;
+#else
+ version = version_info::GetVersionNumber();
+#endif
+ base::ReplaceChars(version_info::GetOSType(), base::kWhitespaceASCII, "",
+ &client_os);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/app_info.h b/components/adblock/core/common/app_info.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/app_info.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_APP_INFO_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_APP_INFO_H_
+
+#include <string>
+
+namespace adblock {
+
+class AppInfo {
+ public:
+ static const AppInfo& Get();
+
+ AppInfo(const AppInfo&) = delete;
+ AppInfo& operator=(const AppInfo&) = delete;
+ ~AppInfo() = default;
+
+ std::string name;
+ std::string client_os;
+ std::string version;
+
+ private:
+ AppInfo();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_APP_INFO_H_
diff --git a/components/adblock/core/common/content_type.cc b/components/adblock/core/common/content_type.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/content_type.cc
@@ -0,0 +1,91 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/content_type.h"
+
+namespace adblock {
+
+std::string ContentTypeToString(ContentType content_type) {
+ switch (content_type) {
+ case ContentType::Unknown:
+ return "UNKNOWN";
+ case ContentType::Other:
+ return "OTHER";
+ case ContentType::Script:
+ return "SCRIPT";
+ case ContentType::Image:
+ return "IMAGE";
+ case ContentType::Stylesheet:
+ return "STYLESHEET";
+ case ContentType::Object:
+ return "OBJECT";
+ case ContentType::Subdocument:
+ return "SUBDOCUMENT";
+ case ContentType::Websocket:
+ return "WEBSOCKET";
+ case ContentType::Webrtc:
+ return "WEBRTC";
+ case ContentType::Ping:
+ return "PING";
+ case ContentType::Xmlhttprequest:
+ return "XMLHTTPREQUEST";
+ case ContentType::Media:
+ return "MEDIA";
+ case ContentType::Font:
+ return "FONT";
+ case ContentType::WebBundle:
+ return "WEBBUNDLE";
+ case ContentType::Default:
+ return "DEFAULT";
+ }
+}
+
+// TODO(atokodi): Use StringPiece
+ContentType ContentTypeFromString(const std::string& content_type) {
+ if (content_type == "other" || content_type == "xbl" ||
+ content_type == "dtd") {
+ return ContentType::Other;
+ } else if (content_type == "script") {
+ return ContentType::Script;
+ } else if (content_type == "image" || content_type == "background") {
+ return ContentType::Image;
+ } else if (content_type == "stylesheet") {
+ return ContentType::Stylesheet;
+ } else if (content_type == "object") {
+ return ContentType::Object;
+ } else if (content_type == "subdocument") {
+ return ContentType::Subdocument;
+ } else if (content_type == "websocket") {
+ return ContentType::Websocket;
+ } else if (content_type == "webrtc") {
+ return ContentType::Webrtc;
+ } else if (content_type == "ping") {
+ return ContentType::Ping;
+ } else if (content_type == "xmlhttprequest") {
+ return ContentType::Xmlhttprequest;
+ } else if (content_type == "media") {
+ return ContentType::Media;
+ } else if (content_type == "font") {
+ return ContentType::Font;
+ } else if (content_type == "webbundle") {
+ return ContentType::WebBundle;
+ } else {
+ return ContentType::Unknown;
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/content_type.h b/components/adblock/core/common/content_type.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/content_type.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_
+
+#include <string>
+
+namespace adblock {
+
+enum ContentType {
+ Unknown = 0,
+ Other = 1,
+ Script = 2,
+ Image = 4,
+ Stylesheet = 8,
+ Object = 16,
+ Subdocument = 32,
+ Websocket = 128,
+ Webrtc = 256,
+ Ping = 1024,
+ Xmlhttprequest = 2048,
+ Media = 16384,
+ Font = 32768,
+ WebBundle = 65536,
+ Default = (1 << 24) - 1,
+};
+
+std::string ContentTypeToString(ContentType content_type);
+ContentType ContentTypeFromString(const std::string& content_type);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_
diff --git a/components/adblock/core/common/flatbuffer_data.cc b/components/adblock/core/common/flatbuffer_data.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/flatbuffer_data.cc
@@ -0,0 +1,105 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/flatbuffer_data.h"
+
+#include "absl/types/optional.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+
+namespace adblock {
+namespace {
+
+// Destroys memory mapped to a file on disk, and optionally removes the file
+// itself. Performs blocking operations, must run on a MayBlock() task runner.
+void DestroyMemoryMappedFile(std::unique_ptr<base::MemoryMappedFile> memory,
+ absl::optional<base::FilePath> path_to_remove) {
+ memory.reset();
+ // Deleting the file should happen *after* removing the memory mapping.
+ if (path_to_remove) {
+ base::DeleteFile(*path_to_remove);
+ }
+}
+
+} // namespace
+
+InMemoryFlatbufferData::InMemoryFlatbufferData(std::string data)
+ : data_(std::move(data)) {}
+
+InMemoryFlatbufferData::~InMemoryFlatbufferData() = default;
+
+const uint8_t* InMemoryFlatbufferData::data() const {
+ return reinterpret_cast<const uint8_t*>(data_.data());
+}
+
+size_t InMemoryFlatbufferData::size() const {
+ return data_.size();
+}
+
+const base::span<const uint8_t> InMemoryFlatbufferData::span() const {
+ return base::as_byte_span(data_);
+}
+
+MemoryMappedFlatbufferData::MemoryMappedFlatbufferData(base::FilePath path)
+ : permanently_remove_path_(false), path_(std::move(path)) {
+ file_ = std::make_unique<base::MemoryMappedFile>();
+ if (!file_->Initialize(path_)) {
+ file_.reset();
+ }
+}
+
+MemoryMappedFlatbufferData::~MemoryMappedFlatbufferData() {
+ const auto path_to_remove =
+ permanently_remove_path_.load()
+ ? absl::optional<base::FilePath>(std::move(path_))
+ : absl::nullopt;
+ base::ThreadPool::PostTask(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&DestroyMemoryMappedFile, std::move(file_),
+ std::move(path_to_remove)));
+}
+
+const uint8_t* MemoryMappedFlatbufferData::data() const {
+ if (!file_) {
+ return nullptr;
+ }
+ return file_->data();
+}
+
+size_t MemoryMappedFlatbufferData::size() const {
+ if (!file_) {
+ return 0u;
+ }
+ return file_->length();
+}
+
+const base::span<const uint8_t> MemoryMappedFlatbufferData::span() const {
+ if (!file_) {
+ return {};
+ }
+ return file_->bytes();
+}
+
+void MemoryMappedFlatbufferData::PermanentlyRemoveSourceOnDestruction() {
+ permanently_remove_path_.store(true);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/flatbuffer_data.h b/components/adblock/core/common/flatbuffer_data.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/flatbuffer_data.h
@@ -0,0 +1,90 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/memory_mapped_file.h"
+
+namespace adblock {
+
+// Holds raw flatbuffer data.
+// All methods must be thread-safe, the object can be accessed from multiple
+// task runners concurrently.
+class FlatbufferData {
+ public:
+ virtual ~FlatbufferData() = default;
+
+ virtual const uint8_t* data() const = 0;
+ virtual size_t size() const = 0;
+ virtual const base::span<const uint8_t> span() const = 0;
+
+ // Schedules permanent removal of the data source of this flatbuffer when
+ // |this| is destroyed. This can mean removing a file from disk or removing
+ // a record from a database etc.
+ virtual void PermanentlyRemoveSourceOnDestruction() {}
+};
+
+// Implementation that loads the flatbuffer into memory from a source string.
+// Requires around 5-10 MB of memory for a subscription like EasyList.
+class InMemoryFlatbufferData : public FlatbufferData {
+ public:
+ explicit InMemoryFlatbufferData(std::string data);
+ ~InMemoryFlatbufferData() override;
+
+ const uint8_t* data() const override;
+ size_t size() const override;
+ const base::span<const uint8_t> span() const override;
+
+ private:
+ std::string data_;
+};
+
+// Memory-mapped implementation that opens a file and memory-maps it. Should
+// use less memory than InMemoryFlatbufferData because the bulk of the data
+// resides on disk. Some memory is still consumed due to caching/paging and
+// the application's *shared* memory usage may increase.
+class MemoryMappedFlatbufferData : public FlatbufferData {
+ public:
+ // Ctor should be called on blocking task runner, performs file I/O/
+ explicit MemoryMappedFlatbufferData(base::FilePath path);
+ ~MemoryMappedFlatbufferData() override;
+
+ const uint8_t* data() const override;
+ size_t size() const override;
+ const base::span<const uint8_t> span() const override;
+
+ // Post cleanup of the mapped file to blocking task runner during destruction.
+ void PermanentlyRemoveSourceOnDestruction() final;
+
+ private:
+ // Since buffers may be accessed by many threads,
+ // PermanentlyRemoveSourceOnDestruction() must set the cleanup flag
+ // atomically.
+ std::atomic_bool permanently_remove_path_;
+ const base::FilePath path_;
+ std::unique_ptr<base::MemoryMappedFile> file_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_
diff --git a/components/adblock/core/common/header_filter_data.h b/components/adblock/core/common/header_filter_data.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/header_filter_data.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_
+
+#include <string_view>
+
+#include "url/gurl.h"
+
+namespace adblock {
+
+struct HeaderFilterData {
+ std::string_view header_filter;
+ GURL subscription_url;
+ // required by std::set
+ bool operator<(const HeaderFilterData& other) const {
+ return (header_filter < other.header_filter);
+ }
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_
diff --git a/components/adblock/core/common/keyword_extractor_utils.cc b/components/adblock/core/common/keyword_extractor_utils.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/keyword_extractor_utils.cc
@@ -0,0 +1,29 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/keyword_extractor_utils.h"
+
+namespace adblock {
+namespace utils {
+
+bool IsBadKeyword(std::string_view value) {
+ return value == "http" || value == "https" || value == "com" ||
+ value == "js" || value.size() < 2;
+}
+
+} // namespace utils
+} // namespace adblock
diff --git a/components/adblock/core/common/keyword_extractor_utils.h b/components/adblock/core/common/keyword_extractor_utils.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/keyword_extractor_utils.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_
+
+#include <string_view>
+
+namespace adblock {
+namespace utils {
+
+bool IsBadKeyword(std::string_view value);
+
+} // namespace utils
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_
diff --git a/components/adblock/core/common/regex_filter_pattern.cc b/components/adblock/core/common/regex_filter_pattern.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/regex_filter_pattern.cc
@@ -0,0 +1,31 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/regex_filter_pattern.h"
+
+namespace adblock {
+
+absl::optional<std::string_view> ExtractRegexFilterFromPattern(
+ std::string_view filter_pattern) {
+ if (!(filter_pattern.size() > 2 && filter_pattern.front() == '/' &&
+ filter_pattern.back() == '/')) {
+ return absl::nullopt;
+ }
+ return filter_pattern.substr(1, filter_pattern.size() - 2);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/regex_filter_pattern.h b/components/adblock/core/common/regex_filter_pattern.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/regex_filter_pattern.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_
+
+#include <string_view>
+
+#include "absl/types/optional.h"
+
+namespace adblock {
+
+// For a regex filter "/{expression}/" returns "{expression}".
+// For non-regex filters, returns nullopt.
+// Cheap, may be used to identify regex filter patterns.
+absl::optional<std::string_view> ExtractRegexFilterFromPattern(
+ std::string_view filter_pattern);
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_
diff --git a/components/adblock/core/common/sitekey.h b/components/adblock/core/common/sitekey.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/sitekey.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_SITEKEY_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_SITEKEY_H_
+
+#include "base/types/strong_alias.h"
+
+namespace adblock {
+
+using SiteKey = base::StrongAlias<class SiteKeyTag, std::string>;
+
+}
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_SITEKEY_H_
diff --git a/components/adblock/core/common/task_scheduler.h b/components/adblock/core/common/task_scheduler.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/task_scheduler.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_H_
+
+#include "base/functional/callback_forward.h"
+
+namespace adblock {
+
+// Periodically executes given task.
+class TaskScheduler {
+ public:
+ virtual ~TaskScheduler() = default;
+ virtual void StartSchedule(base::RepeatingClosure task) = 0;
+ virtual void StopSchedule() = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_H_
diff --git a/components/adblock/core/common/task_scheduler_impl.cc b/components/adblock/core/common/task_scheduler_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/task_scheduler_impl.cc
@@ -0,0 +1,59 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/task_scheduler_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace adblock {
+
+TaskSchedulerImpl::TaskSchedulerImpl(base::TimeDelta check_interval)
+ : check_interval_(check_interval) {}
+
+TaskSchedulerImpl::~TaskSchedulerImpl() = default;
+
+void TaskSchedulerImpl::StartSchedule(base::RepeatingClosure task) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!timer_.IsRunning());
+ task_ = std::move(task);
+ VLOG(1) << "[eyeo] Starting task schedule";
+ ExecuteTask();
+}
+
+void TaskSchedulerImpl::StopSchedule() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ VLOG(1) << "[eyeo] Stopping task schedule";
+ timer_.Stop();
+}
+
+void TaskSchedulerImpl::ExecuteTask() {
+ VLOG(1) << "[eyeo] Executing task";
+ task_.Run();
+ VLOG(1) << "[eyeo] Task executed, next run scheduled for "
+ << base::Time::Now() + check_interval_;
+ timer_.Start(FROM_HERE, check_interval_,
+ base::BindOnce(&TaskSchedulerImpl::ExecuteTask,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/task_scheduler_impl.h b/components/adblock/core/common/task_scheduler_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/task_scheduler_impl.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
+#include "components/adblock/core/common/task_scheduler.h"
+
+namespace adblock {
+
+class TaskSchedulerImpl final : public TaskScheduler {
+ public:
+ explicit TaskSchedulerImpl(base::TimeDelta check_interval);
+ ~TaskSchedulerImpl() final;
+ void StartSchedule(base::RepeatingClosure run_update_check) final;
+ void StopSchedule() final;
+
+ private:
+ void ExecuteTask();
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ base::RepeatingClosure task_;
+ const base::TimeDelta check_interval_;
+ base::OneShotTimer timer_;
+ base::WeakPtrFactory<TaskSchedulerImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_IMPL_H_
diff --git a/components/adblock/core/common/test/flatbuffer_data_test.cc b/components/adblock/core/common/test/flatbuffer_data_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/test/flatbuffer_data_test.cc
@@ -0,0 +1,80 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/flatbuffer_data.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/containers/span.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockMemoryMappedFlatbufferDataTest : public testing::Test {
+ public:
+ void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+ base::test::TaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(AdblockMemoryMappedFlatbufferDataTest, FileContentIsReadLikeMemory) {
+ base::FilePath test_file = temp_dir_.GetPath().AppendASCII("data.fb");
+ ASSERT_TRUE(base::WriteFile(test_file, "content"));
+
+ auto buffer = std::make_unique<MemoryMappedFlatbufferData>(test_file);
+ auto span = std::string_view(reinterpret_cast<const char*>(buffer->data()),
+ buffer->size());
+ EXPECT_EQ(span, "content");
+}
+
+TEST_F(AdblockMemoryMappedFlatbufferDataTest,
+ PermanentlyRemoveSourceOnDestruction) {
+ base::FilePath test_file = temp_dir_.GetPath().AppendASCII("data.fb");
+ ASSERT_TRUE(base::WriteFile(test_file, "content"));
+
+ auto buffer = std::make_unique<MemoryMappedFlatbufferData>(test_file);
+ buffer->PermanentlyRemoveSourceOnDestruction();
+
+ // File still present since buffer is alive.
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(test_file));
+
+ // Buffer dies, destroys file.
+ buffer.reset();
+ task_environment_.RunUntilIdle();
+ EXPECT_FALSE(base::PathExists(test_file));
+}
+
+TEST_F(AdblockMemoryMappedFlatbufferDataTest, SourceNotDestroyedWhenNotAsked) {
+ base::FilePath test_file = temp_dir_.GetPath().AppendASCII("data.fb");
+ ASSERT_TRUE(base::WriteFile(test_file, "content"));
+
+ auto buffer = std::make_unique<MemoryMappedFlatbufferData>(test_file);
+
+ // Buffer dies, source remains on disk as
+ // PermanentlyRemoveSourceOnDestruction() was not called.
+ buffer.reset();
+ task_environment_.RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(test_file));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/test/mock_task_scheduler.cc b/components/adblock/core/common/test/mock_task_scheduler.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/test/mock_task_scheduler.cc
@@ -0,0 +1,25 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/test/mock_task_scheduler.h"
+
+namespace adblock {
+
+MockTaskScheduler::MockTaskScheduler() = default;
+MockTaskScheduler::~MockTaskScheduler() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/common/test/mock_task_scheduler.h b/components/adblock/core/common/test/mock_task_scheduler.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/test/mock_task_scheduler.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_TEST_MOCK_TASK_SCHEDULER_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_TEST_MOCK_TASK_SCHEDULER_H_
+
+#include "base/functional/callback.h"
+#include "components/adblock/core/common/task_scheduler.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockTaskScheduler : public NiceMock<TaskScheduler> {
+ public:
+ MockTaskScheduler();
+ ~MockTaskScheduler() override;
+ MOCK_METHOD(void, StartSchedule, (base::RepeatingClosure), (override));
+ MOCK_METHOD(void, StopSchedule, (), (override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_TEST_MOCK_TASK_SCHEDULER_H_
diff --git a/components/adblock/core/common/test/task_scheduler_impl_test.cc b/components/adblock/core/common/test/task_scheduler_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/test/task_scheduler_impl_test.cc
@@ -0,0 +1,82 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/task_scheduler_impl.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+namespace {
+constexpr auto kCheckInterval = base::Hours(1);
+} // namespace
+
+class AdblockTaskSchedulerImplTest : public testing::Test {
+ public:
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ base::MockRepeatingClosure task_;
+ TaskSchedulerImpl task_scheduler_{kCheckInterval};
+};
+
+TEST_F(AdblockTaskSchedulerImplTest, TaskRanContinuously) {
+ // Schedule will run indefinitely after starting, with the first check
+ // happening immediately and subsequent checks scheduled with check interval.
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_scheduler_.StartSchedule(task_.Get());
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_environment_.FastForwardBy(kCheckInterval);
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_environment_.FastForwardBy(kCheckInterval);
+}
+
+TEST_F(AdblockTaskSchedulerImplTest, TaskNotRanAfterStopping) {
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_scheduler_.StartSchedule(task_.Get());
+
+ // Let the checks run for some time.
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_environment_.FastForwardBy(kCheckInterval);
+
+ // Stop now.
+ task_scheduler_.StopSchedule();
+ EXPECT_CALL(task_, Run()).Times(0);
+ task_environment_.FastForwardBy(kCheckInterval);
+ task_environment_.FastForwardBy(kCheckInterval);
+}
+
+TEST_F(AdblockTaskSchedulerImplTest, Restarting) {
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_scheduler_.StartSchedule(task_.Get());
+ task_scheduler_.StopSchedule();
+
+ // After restarting, the schedule starts immediately again.
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_scheduler_.StartSchedule(task_.Get());
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_environment_.FastForwardBy(kCheckInterval);
+ EXPECT_CALL(task_, Run()).Times(1);
+ task_environment_.FastForwardBy(kCheckInterval);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/common/web_ui_constants.cc b/components/adblock/core/common/web_ui_constants.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/web_ui_constants.cc
@@ -0,0 +1,24 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/common/web_ui_constants.h"
+
+namespace adblock {
+
+const char kChromeUIAdblockInternalsHost[] = "adblock-internals";
+
+}
diff --git a/components/adblock/core/common/web_ui_constants.h b/components/adblock/core/common/web_ui_constants.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/common/web_ui_constants.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_WEB_UI_CONSTANTS_H_
+#define COMPONENTS_ADBLOCK_CORE_COMMON_WEB_UI_CONSTANTS_H_
+
+namespace adblock {
+
+extern const char kChromeUIAdblockInternalsHost[];
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_WEB_UI_CONSTANTS_H_
diff --git a/components/adblock/core/configuration/BUILD.gn b/components/adblock/core/configuration/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/BUILD.gn
@@ -0,0 +1,60 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("configuration") {
+ sources = [
+ "filtering_configuration.h",
+ "persistent_filtering_configuration.cc",
+ "persistent_filtering_configuration.h",
+ ]
+
+ deps = [ "//components/adblock/core/common:common" ]
+
+ public_deps = [
+ "//base",
+ "//components/prefs",
+ "//url:url",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/fake_filtering_configuration.cc",
+ "test/fake_filtering_configuration.h",
+ "test/mock_filtering_configuration.cc",
+ "test/mock_filtering_configuration.h",
+ ]
+ public_deps = [
+ ":configuration",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "test/persistent_filtering_configuration_test.cc" ]
+
+ deps = [
+ ":configuration",
+ "//base/test:test_support",
+ "//components/adblock/core/common:common",
+ "//components/prefs:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/adblock/core/configuration/filtering_configuration.h b/components/adblock/core/configuration/filtering_configuration.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/filtering_configuration.h
@@ -0,0 +1,91 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_
+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_
+
+#include <string>
+
+#include "base/observer_list_types.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// A group of settings that control how to perform resource filtering.
+//
+// FilterConfigurations can be installed into a SubscriptionService.
+// SubscriptionService interprets how to express each configuration in terms of
+// installed subscriptions, and how to enact filtering.
+//
+// Each configuration is independent from others. If two FilteringConfigurations
+// have filters that result in a conflicting classification decision,
+// blocking a resource takes precedence over allowing a resource.
+//
+// Examples of multiple FilteringConfigurations could be:
+// - one configuration to filter ads
+// - another configuration to protect user privacy
+// - another configuration to enforce parental control
+// Each of these could be disabled/enabled or reconfigured individually, without
+// affecting others.
+class FilteringConfiguration {
+ public:
+ class Observer : public base::CheckedObserver {
+ public:
+ virtual void OnEnabledStateChanged(FilteringConfiguration* config) {}
+ virtual void OnFilterListsChanged(FilteringConfiguration* config) {}
+ virtual void OnAllowedDomainsChanged(FilteringConfiguration* config) {}
+ virtual void OnCustomFiltersChanged(FilteringConfiguration* config) {}
+ };
+
+ virtual ~FilteringConfiguration() = default;
+
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // The name must be unique across all created configurations.
+ virtual const std::string& GetName() const = 0;
+
+ // Enable or disable the entire configuration. A disabled configuration does
+ // not contribute filters to classification and behaves as if it was not
+ // installed.
+ virtual void SetEnabled(bool enabled) = 0;
+ virtual bool IsEnabled() const = 0;
+
+ // Adding an existing filter list, or removing a non-existing filter list, are
+ // NOPs and do not notify observers.
+ virtual void AddFilterList(const GURL& url) = 0;
+ virtual void RemoveFilterList(const GURL& url) = 0;
+ virtual std::vector<GURL> GetFilterLists() const = 0;
+ virtual bool IsFilterListPresent(const GURL& url) const = 0;
+
+ // Adding an existing allowed domain, or removing a non-existing allowed
+ // domain, are NOPs and do not notify observers.
+ virtual void AddAllowedDomain(const std::string& domain) = 0;
+ virtual void RemoveAllowedDomain(const std::string& domain) = 0;
+ virtual std::vector<std::string> GetAllowedDomains() const = 0;
+
+ // Adding an existing custom filter, or removing a non-existing custom filter,
+ // are NOPs and do not notify observers.
+ virtual void AddCustomFilter(const std::string& filter) = 0;
+ virtual void RemoveCustomFilter(const std::string& filter) = 0;
+ virtual std::vector<std::string> GetCustomFilters() const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_
diff --git a/components/adblock/core/configuration/persistent_filtering_configuration.cc b/components/adblock/core/configuration/persistent_filtering_configuration.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/persistent_filtering_configuration.cc
@@ -0,0 +1,270 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+
+#include <string>
+#include <string_view>
+
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+
+#include "base/logging.h"
+
+namespace adblock {
+namespace {
+
+constexpr auto kEnabledKey = std::string_view("enabled");
+constexpr auto kDomainsKey = std::string_view("domains");
+constexpr auto kCustomFiltersKey = std::string_view("filters");
+constexpr auto kFilterListsKey = std::string_view("subscriptions");
+
+base::Value::Dict ReadFromPrefs(PrefService* pref_service,
+ std::string_view configuration_name) {
+ const auto& all_configurations =
+ pref_service->GetValue(common::prefs::kConfigurationsPrefsPath).GetDict();
+ const auto* this_config = all_configurations.FindDict(configuration_name);
+ if (this_config) {
+ return base::Value::Dict(this_config->Clone());
+ }
+ return base::Value::Dict();
+}
+
+void StoreToPrefs(const base::Value::Dict& configuration,
+ PrefService* pref_service,
+ std::string_view configuration_name) {
+ // ScopedDictPrefUpdate requires an std::string for some reason:
+ static std::string kConfigurationsPrefsPathString(
+ common::prefs::kConfigurationsPrefsPath);
+ ScopedDictPrefUpdate update(pref_service, kConfigurationsPrefsPathString);
+ update.Get().Set(configuration_name, configuration.Clone());
+}
+
+void SetDefaultValuesIfNeeded(base::Value::Dict& configuration) {
+ if (!configuration.FindBool(kEnabledKey)) {
+ configuration.Set(kEnabledKey, true);
+ }
+ configuration.EnsureList(kDomainsKey);
+ configuration.EnsureList(kCustomFiltersKey);
+ configuration.EnsureList(kFilterListsKey);
+}
+
+bool AppendToList(base::Value::Dict& configuration,
+ std::string_view key,
+ const std::string& value) {
+ DCHECK(configuration.FindList(key)); // see SetDefaultValuesIfNeeded().
+ auto* list = configuration.FindList(key);
+ if (std::ranges::find(*list, base::Value(value)) != list->end()) {
+ // value already exists in the list.
+ return false;
+ }
+ list->Append(value);
+ return true;
+}
+
+bool RemoveFromList(base::Value::Dict& configuration,
+ std::string_view key,
+ const std::string& value) {
+ DCHECK(configuration.FindList(key)); // see SetDefaultValuesIfNeeded().
+ auto* list = configuration.FindList(key);
+ auto it = std::ranges::find(*list, base::Value(value));
+ if (it == list->end()) {
+ // value was not on the list.
+ return false;
+ }
+ list->erase(it);
+ return true;
+}
+
+template <typename T>
+std::vector<T> GetFromList(const base::Value::Dict& configuration,
+ std::string_view key) {
+ DCHECK(configuration.FindList(key)); // see SetDefaultValuesIfNeeded().
+ const auto* list = configuration.FindList(key);
+ std::vector<T> result;
+ for (const auto& value : *list) {
+ if (value.is_string()) {
+ result.emplace_back(value.GetString());
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+PersistentFilteringConfiguration::PersistentFilteringConfiguration(
+ PrefService* pref_service,
+ std::string name)
+ : pref_service_(pref_service),
+ name_(std::move(name)),
+ dictionary_(ReadFromPrefs(pref_service_, name_)) {
+ SetDefaultValuesIfNeeded(dictionary_);
+ PersistToPrefs();
+}
+
+PersistentFilteringConfiguration::~PersistentFilteringConfiguration() = default;
+
+void PersistentFilteringConfiguration::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+void PersistentFilteringConfiguration::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+const std::string& PersistentFilteringConfiguration::GetName() const {
+ return name_;
+}
+
+void PersistentFilteringConfiguration::SetEnabled(bool enabled) {
+ if (IsEnabled() == enabled) {
+ return;
+ }
+ dictionary_.Set(kEnabledKey, enabled);
+ PersistToPrefs();
+ NotifyEnabledStateChanged();
+}
+
+bool PersistentFilteringConfiguration::IsEnabled() const {
+ const auto pref_value = dictionary_.FindBool(kEnabledKey);
+ DCHECK(pref_value);
+ return *pref_value;
+}
+
+void PersistentFilteringConfiguration::AddFilterList(const GURL& url) {
+ if (AppendToList(dictionary_, kFilterListsKey, url.spec())) {
+ PersistToPrefs();
+ NotifyFilterListsChanged();
+ }
+}
+
+void PersistentFilteringConfiguration::RemoveFilterList(const GURL& url) {
+ if (RemoveFromList(dictionary_, kFilterListsKey, url.spec())) {
+ PersistToPrefs();
+ NotifyFilterListsChanged();
+ }
+}
+
+std::vector<GURL> PersistentFilteringConfiguration::GetFilterLists() const {
+ return GetFromList<GURL>(dictionary_, kFilterListsKey);
+}
+
+bool PersistentFilteringConfiguration::IsFilterListPresent(
+ const GURL& url) const {
+ return std::ranges::any_of(
+ GetFilterLists(),
+ [&](const GURL& filetr_list_url) { return filetr_list_url == url; });
+}
+
+void PersistentFilteringConfiguration::AddAllowedDomain(
+ const std::string& domain) {
+ if (AppendToList(dictionary_, kDomainsKey, domain)) {
+ PersistToPrefs();
+ NotifyAllowedDomainsChanged();
+ }
+}
+
+void PersistentFilteringConfiguration::RemoveAllowedDomain(
+ const std::string& domain) {
+ if (RemoveFromList(dictionary_, kDomainsKey, domain)) {
+ PersistToPrefs();
+ NotifyAllowedDomainsChanged();
+ }
+}
+
+std::vector<std::string> PersistentFilteringConfiguration::GetAllowedDomains()
+ const {
+ return GetFromList<std::string>(dictionary_, kDomainsKey);
+}
+
+void PersistentFilteringConfiguration::AddCustomFilter(
+ const std::string& filter) {
+ if (AppendToList(dictionary_, kCustomFiltersKey, filter)) {
+ PersistToPrefs();
+ NotifyCustomFiltersChanged();
+ }
+}
+
+void PersistentFilteringConfiguration::RemoveCustomFilter(
+ const std::string& filter) {
+ if (RemoveFromList(dictionary_, kCustomFiltersKey, filter)) {
+ PersistToPrefs();
+ NotifyCustomFiltersChanged();
+ }
+}
+
+std::vector<std::string> PersistentFilteringConfiguration::GetCustomFilters()
+ const {
+ return GetFromList<std::string>(dictionary_, kCustomFiltersKey);
+}
+
+void PersistentFilteringConfiguration::PersistToPrefs() {
+ StoreToPrefs(dictionary_, pref_service_, name_);
+}
+
+void PersistentFilteringConfiguration::NotifyEnabledStateChanged() {
+ for (auto& o : observers_) {
+ o.OnEnabledStateChanged(this);
+ }
+}
+
+void PersistentFilteringConfiguration::NotifyFilterListsChanged() {
+ for (auto& o : observers_) {
+ o.OnFilterListsChanged(this);
+ }
+}
+
+void PersistentFilteringConfiguration::NotifyAllowedDomainsChanged() {
+ for (auto& o : observers_) {
+ o.OnAllowedDomainsChanged(this);
+ }
+}
+
+void PersistentFilteringConfiguration::NotifyCustomFiltersChanged() {
+ for (auto& o : observers_) {
+ o.OnCustomFiltersChanged(this);
+ }
+}
+
+// static
+std::vector<std::unique_ptr<PersistentFilteringConfiguration>>
+PersistentFilteringConfiguration::GetPersistedConfigurations(
+ PrefService* pref_service) {
+ std::vector<std::unique_ptr<PersistentFilteringConfiguration>> configs;
+ const auto& all_configurations =
+ pref_service->GetValue(common::prefs::kConfigurationsPrefsPath).GetDict();
+ for (auto it = all_configurations.begin(); it != all_configurations.end();
+ ++it) {
+ configs.push_back(std::make_unique<PersistentFilteringConfiguration>(
+ pref_service, (it->first)));
+ }
+ return configs;
+}
+
+// static
+void PersistentFilteringConfiguration::RemovePersistedData(
+ PrefService* pref_service,
+ const std::string& name) {
+ static std::string kConfigurationsPrefsPathString(
+ common::prefs::kConfigurationsPrefsPath);
+ ScopedDictPrefUpdate update(pref_service, kConfigurationsPrefsPathString);
+ update.Get().Remove(name);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/configuration/persistent_filtering_configuration.h b/components/adblock/core/configuration/persistent_filtering_configuration.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/persistent_filtering_configuration.h
@@ -0,0 +1,84 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_PERSISTENT_FILTERING_CONFIGURATION_H_
+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_PERSISTENT_FILTERING_CONFIGURATION_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "base/values.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/prefs/pref_service.h"
+
+namespace adblock {
+
+// An implementation of FilteringConfiguration that persists itself to a dict
+// inside PrefService.
+//
+// All instances live in the same root node in prefs but in serialize themselves
+// to individual sub-keys based on their name.
+class PersistentFilteringConfiguration final : public FilteringConfiguration {
+ public:
+ // Each |name| must be unique, otherwise multiple
+ // PersistentFilteringConfigurations will try to serialize to the same path in
+ // prefs and conflict with one another.
+ PersistentFilteringConfiguration(PrefService* pref_service, std::string name);
+ ~PersistentFilteringConfiguration() final;
+
+ void AddObserver(Observer* observer) final;
+ void RemoveObserver(Observer* observer) final;
+
+ const std::string& GetName() const final;
+
+ void SetEnabled(bool enabled) final;
+ bool IsEnabled() const final;
+
+ void AddFilterList(const GURL& url) final;
+ void RemoveFilterList(const GURL& url) final;
+ std::vector<GURL> GetFilterLists() const final;
+ bool IsFilterListPresent(const GURL& url) const final;
+
+ void AddAllowedDomain(const std::string& domain) final;
+ void RemoveAllowedDomain(const std::string& domain) final;
+ std::vector<std::string> GetAllowedDomains() const final;
+
+ void AddCustomFilter(const std::string& filter) final;
+ void RemoveCustomFilter(const std::string& filter) final;
+ std::vector<std::string> GetCustomFilters() const final;
+
+ static std::vector<std::unique_ptr<PersistentFilteringConfiguration>>
+ GetPersistedConfigurations(PrefService* pref_service);
+ static void RemovePersistedData(PrefService* pref_service,
+ const std::string& name);
+
+ private:
+ void PersistToPrefs();
+ void NotifyEnabledStateChanged();
+ void NotifyFilterListsChanged();
+ void NotifyAllowedDomainsChanged();
+ void NotifyCustomFiltersChanged();
+
+ const raw_ptr<PrefService> pref_service_;
+ std::string name_;
+ base::ObserverList<Observer> observers_;
+ base::Value::Dict dictionary_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_PERSISTENT_FILTERING_CONFIGURATION_H_
diff --git a/components/adblock/core/configuration/test/fake_filtering_configuration.cc b/components/adblock/core/configuration/test/fake_filtering_configuration.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/test/fake_filtering_configuration.cc
@@ -0,0 +1,124 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/configuration/test/fake_filtering_configuration.h"
+
+#include "base/ranges/algorithm.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+FakeFilteringConfiguration::FakeFilteringConfiguration() : name("testing") {}
+
+FakeFilteringConfiguration::FakeFilteringConfiguration(std::string name)
+ : name(name) {}
+
+FakeFilteringConfiguration::~FakeFilteringConfiguration() {
+ EXPECT_FALSE(observer) << "Observer was not removed";
+}
+
+void FakeFilteringConfiguration::AddObserver(Observer* obs) {
+ EXPECT_FALSE(observer) << "Observer was already added";
+ observer = obs;
+}
+
+void FakeFilteringConfiguration::RemoveObserver(Observer* obs) {
+ EXPECT_EQ(observer, obs) << "This fake works with just a single observer";
+ observer = nullptr;
+}
+
+const std::string& FakeFilteringConfiguration::GetName() const {
+ return name;
+}
+
+void FakeFilteringConfiguration::SetEnabled(bool enabled) {
+ is_enabled = enabled;
+ if (observer) {
+ observer->OnEnabledStateChanged(this);
+ }
+}
+
+bool FakeFilteringConfiguration::IsEnabled() const {
+ return is_enabled;
+}
+
+void FakeFilteringConfiguration::AddFilterList(const GURL& url) {
+ filter_lists.push_back(url);
+ if (observer) {
+ observer->OnFilterListsChanged(this);
+ }
+}
+
+void FakeFilteringConfiguration::RemoveFilterList(const GURL& url) {
+ filter_lists.erase(std::ranges::remove(filter_lists, url),
+ filter_lists.end());
+ if (observer) {
+ observer->OnFilterListsChanged(this);
+ }
+}
+
+std::vector<GURL> FakeFilteringConfiguration::GetFilterLists() const {
+ return filter_lists;
+}
+
+bool FakeFilteringConfiguration::IsFilterListPresent(const GURL& url) const {
+ return std::ranges::any_of(
+ GetFilterLists(),
+ [&](const GURL& filetr_list_url) { return filetr_list_url == url; });
+}
+
+void FakeFilteringConfiguration::AddAllowedDomain(const std::string& domain) {
+ allowed_domains.push_back(domain);
+ if (observer) {
+ observer->OnAllowedDomainsChanged(this);
+ }
+}
+
+void FakeFilteringConfiguration::RemoveAllowedDomain(
+ const std::string& domain) {
+ allowed_domains.erase(std::ranges::remove(allowed_domains, domain),
+ allowed_domains.end());
+ if (observer) {
+ observer->OnAllowedDomainsChanged(this);
+ }
+}
+
+std::vector<std::string> FakeFilteringConfiguration::GetAllowedDomains() const {
+ return allowed_domains;
+}
+
+void FakeFilteringConfiguration::AddCustomFilter(const std::string& filter) {
+ custom_filters.push_back(filter);
+ if (observer) {
+ observer->OnCustomFiltersChanged(this);
+ }
+}
+
+void FakeFilteringConfiguration::RemoveCustomFilter(const std::string& filter) {
+ custom_filters.erase(std::ranges::remove(custom_filters, filter),
+ custom_filters.end());
+ if (observer) {
+ observer->OnCustomFiltersChanged(this);
+ }
+}
+
+std::vector<std::string> FakeFilteringConfiguration::GetCustomFilters() const {
+ return custom_filters;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/configuration/test/fake_filtering_configuration.h b/components/adblock/core/configuration/test/fake_filtering_configuration.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/test/fake_filtering_configuration.h
@@ -0,0 +1,68 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_FAKE_FILTERING_CONFIGURATION_H_
+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_FAKE_FILTERING_CONFIGURATION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/observer_list.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+
+namespace adblock {
+
+class FakeFilteringConfiguration : public FilteringConfiguration {
+ public:
+ FakeFilteringConfiguration();
+ explicit FakeFilteringConfiguration(std::string name);
+ ~FakeFilteringConfiguration() override;
+
+ void AddObserver(Observer* observer) override;
+ void RemoveObserver(Observer* observer) override;
+
+ const std::string& GetName() const override;
+
+ void SetEnabled(bool enabled) override;
+ bool IsEnabled() const override;
+
+ void AddFilterList(const GURL& url) override;
+ void RemoveFilterList(const GURL& url) override;
+ std::vector<GURL> GetFilterLists() const override;
+ bool IsFilterListPresent(const GURL& url) const override;
+
+ void AddAllowedDomain(const std::string& domain) override;
+ void RemoveAllowedDomain(const std::string& domain) override;
+ std::vector<std::string> GetAllowedDomains() const override;
+
+ void AddCustomFilter(const std::string& filter) override;
+ void RemoveCustomFilter(const std::string& filter) override;
+ std::vector<std::string> GetCustomFilters() const override;
+
+ raw_ptr<Observer> observer = nullptr;
+ std::string name;
+ bool is_enabled = true;
+ std::vector<GURL> filter_lists;
+ std::vector<std::string> allowed_domains;
+ std::vector<std::string> custom_filters;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_FAKE_FILTERING_CONFIGURATION_H_
diff --git a/components/adblock/core/configuration/test/mock_filtering_configuration.cc b/components/adblock/core/configuration/test/mock_filtering_configuration.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/test/mock_filtering_configuration.cc
@@ -0,0 +1,34 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/configuration/test/mock_filtering_configuration.h"
+
+namespace adblock {
+
+MockFilteringConfiguration::MockFilteringConfiguration() = default;
+MockFilteringConfiguration::~MockFilteringConfiguration() = default;
+
+void MockFilteringConfiguration::AddObserver(
+ FilteringConfiguration::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+void MockFilteringConfiguration::RemoveObserver(
+ FilteringConfiguration::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/configuration/test/mock_filtering_configuration.h b/components/adblock/core/configuration/test/mock_filtering_configuration.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/test/mock_filtering_configuration.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_MOCK_FILTERING_CONFIGURATION_H_
+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_MOCK_FILTERING_CONFIGURATION_H_
+
+#include "base/observer_list.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockFilteringConfiguration : public NiceMock<FilteringConfiguration> {
+ public:
+ MockFilteringConfiguration();
+ ~MockFilteringConfiguration() override;
+
+ void AddObserver(FilteringConfiguration::Observer* observer) override;
+ void RemoveObserver(FilteringConfiguration::Observer* observer) override;
+
+ // The name must be unique across all created configurations.
+ MOCK_METHOD(const std::string&, GetName, (), (const, override));
+
+ // Enable or disable the entire configuration. A disabled configuration does
+ // not contribute filters to classification and behaves as if it was not
+ // installed.
+ MOCK_METHOD(void, SetEnabled, (bool enabled), (override));
+ MOCK_METHOD(bool, IsEnabled, (), (const, override));
+
+ // Adding an existing filter list, or removing a non-existing filter list, are
+ // NOPs and do not notify observers.
+ MOCK_METHOD(void, AddFilterList, (const GURL& url), (override));
+ MOCK_METHOD(void, RemoveFilterList, (const GURL& url), (override));
+ MOCK_METHOD(std::vector<GURL>, GetFilterLists, (), (const, override));
+ MOCK_METHOD(bool, IsFilterListPresent, (const GURL& url), (const, override));
+
+ // Adding an existing allowed domain, or removing a non-existing allowed
+ // domain, are NOPs and do not notify observers.
+ MOCK_METHOD(void, AddAllowedDomain, (const std::string& domain), (override));
+ MOCK_METHOD(void,
+ RemoveAllowedDomain,
+ (const std::string& domain),
+ (override));
+ MOCK_METHOD(std::vector<std::string>,
+ GetAllowedDomains,
+ (),
+ (const, override));
+
+ // Adding an existing custom filter, or removing a non-existing custom filter,
+ // are NOPs and do not notify observers.
+ MOCK_METHOD(void, AddCustomFilter, (const std::string& filter), (override));
+ MOCK_METHOD(void,
+ RemoveCustomFilter,
+ (const std::string& filter),
+ (override));
+ MOCK_METHOD(std::vector<std::string>,
+ GetCustomFilters,
+ (),
+ (const, override));
+
+ base::ObserverList<FilteringConfiguration::Observer> observers_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_MOCK_FILTERING_CONFIGURATION_H_
diff --git a/components/adblock/core/configuration/test/persistent_filtering_configuration_test.cc b/components/adblock/core/configuration/test/persistent_filtering_configuration_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/configuration/test/persistent_filtering_configuration_test.cc
@@ -0,0 +1,255 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+
+#include <memory>
+
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/prefs/testing_pref_service.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace adblock {
+
+namespace {
+
+class MockObserver : public FilteringConfiguration::Observer {
+ public:
+ MOCK_METHOD(void,
+ OnEnabledStateChanged,
+ (FilteringConfiguration * config),
+ (override));
+ MOCK_METHOD(void,
+ OnFilterListsChanged,
+ (FilteringConfiguration * config),
+ (override));
+ MOCK_METHOD(void,
+ OnAllowedDomainsChanged,
+ (FilteringConfiguration * config),
+ (override));
+ MOCK_METHOD(void,
+ OnCustomFiltersChanged,
+ (FilteringConfiguration * config),
+ (override));
+};
+} // namespace
+
+// Wether the testee is destroyed and recreated by MaybeRecreateConfiguration().
+// Recreating validates persistence over browser restarts while not recreating
+// validates behavior within a single session.
+enum class RecreateConfiguration {
+ Yes,
+ No,
+};
+
+class AdblockPersistentFilteringConfigurationTest
+ : public testing::TestWithParam<RecreateConfiguration> {
+ public:
+ void SetUp() override {
+ adblock::common::prefs::RegisterProfilePrefs(pref_service_.registry());
+ RecreateConfiguration();
+ }
+
+ void TearDown() override { configuration_->RemoveObserver(&observer_); }
+
+ void RecreateConfiguration() {
+ if (configuration_) {
+ testing::Mock::VerifyAndClearExpectations(&observer_);
+ configuration_->RemoveObserver(&observer_);
+ }
+ configuration_ = std::make_unique<PersistentFilteringConfiguration>(
+ &pref_service_, kName);
+ configuration_->AddObserver(&observer_);
+ }
+
+ void MaybeRecreateConfiguration() {
+ if (GetParam() == RecreateConfiguration::Yes) {
+ RecreateConfiguration();
+ }
+ }
+
+ const std::string kName = "adblock";
+ const GURL kUrl1{"https://list.com/filters1.txt"};
+ const GURL kUrl2{"https://list.com/filters2.txt"};
+ const GURL kUrl3{"https://list.com/filters3.txt"};
+ const std::string kAllowedDomain1{"www.domain1.com"};
+ const std::string kAllowedDomain2{"www.domain2.com"};
+ const std::string kAllowedDomain3{"www.domain3.com"};
+ const std::string kCustomFilter1{"@@^domain1.com"};
+ const std::string kCustomFilter2{"@@^domain2.com"};
+ const std::string kCustomFilter3{"@@^domain3.com"};
+ MockObserver observer_;
+ TestingPrefServiceSimple pref_service_;
+ std::unique_ptr<PersistentFilteringConfiguration> configuration_;
+};
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, NameStored) {
+ MaybeRecreateConfiguration();
+ EXPECT_EQ(configuration_->GetName(), kName);
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, EnabledStateStored) {
+ // No notification for setting Enabled to true because it is the default
+ // state.
+ EXPECT_CALL(observer_, OnEnabledStateChanged(configuration_.get())).Times(0);
+ configuration_->SetEnabled(true);
+ MaybeRecreateConfiguration();
+ EXPECT_TRUE(configuration_->IsEnabled());
+
+ EXPECT_CALL(observer_, OnEnabledStateChanged(configuration_.get()));
+ configuration_->SetEnabled(false);
+ MaybeRecreateConfiguration();
+ EXPECT_FALSE(configuration_->IsEnabled());
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, FilterListAdded) {
+ // List initially empty.
+ EXPECT_TRUE(configuration_->GetFilterLists().empty());
+ // Observer will be notified about addition.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get()));
+ configuration_->AddFilterList(kUrl1);
+
+ // New URL is returned consistently.
+ MaybeRecreateConfiguration();
+ EXPECT_TRUE(configuration_->IsFilterListPresent(kUrl1));
+ EXPECT_THAT(configuration_->GetFilterLists(),
+ testing::UnorderedElementsAre(kUrl1));
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, FilterListRemoved) {
+ // Observer will be notified about addition.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get()));
+ configuration_->AddFilterList(kUrl1);
+ // Observer will be notified about removal.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get()));
+ configuration_->RemoveFilterList(kUrl1);
+
+ // Removed URL is no longer returned.
+ MaybeRecreateConfiguration();
+ EXPECT_FALSE(configuration_->IsFilterListPresent(kUrl1));
+ EXPECT_TRUE(configuration_->GetFilterLists().empty());
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, MultipleFilterLists) {
+ // Observer will be notified about all additions.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get())).Times(3);
+ configuration_->AddFilterList(kUrl1);
+ configuration_->AddFilterList(kUrl2);
+ configuration_->AddFilterList(kUrl3);
+ EXPECT_TRUE(configuration_->IsFilterListPresent(kUrl1));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(kUrl2));
+ EXPECT_TRUE(configuration_->IsFilterListPresent(kUrl3));
+ // Observer will be notified about one removal.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get()));
+ configuration_->RemoveFilterList(kUrl2);
+ EXPECT_FALSE(configuration_->IsFilterListPresent(kUrl2));
+
+ // Remaining lists are returned.
+ MaybeRecreateConfiguration();
+ EXPECT_THAT(configuration_->GetFilterLists(),
+ testing::UnorderedElementsAre(kUrl1, kUrl3));
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest,
+ DuplicateFilterListsIgnored) {
+ // Observer will be notified about only one addition.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get())).Times(1);
+ configuration_->AddFilterList(kUrl1);
+ configuration_->AddFilterList(kUrl1);
+ configuration_->AddFilterList(kUrl1);
+
+ // Duplicate URL was ignored, only one instance returned.
+ MaybeRecreateConfiguration();
+ EXPECT_THAT(configuration_->GetFilterLists(),
+ testing::UnorderedElementsAre(kUrl1));
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest,
+ SpuriousFilterListRemovalIgnored) {
+ // Observer will be notified about one addition.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get())).Times(1);
+ configuration_->AddFilterList(kUrl1);
+ // Observer will be notified about one removal.
+ EXPECT_CALL(observer_, OnFilterListsChanged(configuration_.get())).Times(1);
+ configuration_->RemoveFilterList(kUrl1);
+ configuration_->RemoveFilterList(kUrl1);
+ configuration_->RemoveFilterList(kUrl1);
+
+ MaybeRecreateConfiguration();
+ EXPECT_TRUE(configuration_->GetFilterLists().empty());
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, MultipleAllowedDomains) {
+ // List initially empty.
+ EXPECT_TRUE(configuration_->GetAllowedDomains().empty());
+ // Add some allowed domains.
+ EXPECT_CALL(observer_, OnAllowedDomainsChanged(configuration_.get()))
+ .Times(3);
+ configuration_->AddAllowedDomain(kAllowedDomain1);
+ configuration_->AddAllowedDomain(kAllowedDomain2);
+ configuration_->AddAllowedDomain(kAllowedDomain3);
+ // Spurious addition:
+ configuration_->AddAllowedDomain(kAllowedDomain3);
+ EXPECT_CALL(observer_, OnAllowedDomainsChanged(configuration_.get()))
+ .Times(1);
+ configuration_->RemoveAllowedDomain(kAllowedDomain2);
+ // Spurious removal:
+ configuration_->RemoveAllowedDomain(kAllowedDomain2);
+
+ MaybeRecreateConfiguration();
+ EXPECT_THAT(configuration_->GetAllowedDomains(),
+ testing::UnorderedElementsAre(kAllowedDomain1, kAllowedDomain3));
+}
+
+TEST_P(AdblockPersistentFilteringConfigurationTest, MultipleCustomFilters) {
+ // List initially empty.
+ EXPECT_TRUE(configuration_->GetCustomFilters().empty());
+ // Add some custom filters.
+ EXPECT_CALL(observer_, OnCustomFiltersChanged(configuration_.get())).Times(3);
+ configuration_->AddCustomFilter(kCustomFilter1);
+ configuration_->AddCustomFilter(kCustomFilter2);
+ configuration_->AddCustomFilter(kCustomFilter3);
+ // Spurious addition:
+ configuration_->AddCustomFilter(kCustomFilter3);
+
+ EXPECT_CALL(observer_, OnCustomFiltersChanged(configuration_.get())).Times(1);
+ configuration_->RemoveCustomFilter(kCustomFilter2);
+ // Spurious removal:
+ configuration_->RemoveCustomFilter(kCustomFilter2);
+
+ MaybeRecreateConfiguration();
+ EXPECT_THAT(configuration_->GetCustomFilters(),
+ testing::UnorderedElementsAre(kCustomFilter1, kCustomFilter3));
+}
+
+TEST_F(AdblockPersistentFilteringConfigurationTest,
+ PrefsClearedAfterRemovePersistedData) {
+ const auto& all_configurations =
+ pref_service_.GetValue(common::prefs::kConfigurationsPrefsPath).GetDict();
+ EXPECT_NE(nullptr, all_configurations.FindDict(kName));
+ PersistentFilteringConfiguration::RemovePersistedData(&pref_service_, kName);
+ EXPECT_EQ(nullptr, all_configurations.FindDict(kName));
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockPersistentFilteringConfigurationTest,
+ testing::Values(RecreateConfiguration::Yes,
+ RecreateConfiguration::No));
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/BUILD.gn b/components/adblock/core/converter/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/BUILD.gn
@@ -0,0 +1,67 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("converter") {
+ sources = [
+ "flatbuffer_converter.cc",
+ "flatbuffer_converter.h",
+ ]
+
+ deps = [
+ "//components/adblock/core/converter/parser",
+ "//components/adblock/core/converter/serializer",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core/common",
+ "//third_party/abseil-cpp:absl",
+ "//third_party/flatbuffers",
+ "//url",
+ ]
+
+ assert_no_deps = [
+ "//components/keyed_service/core",
+ "//net",
+ "//services/network/public/cpp",
+ ]
+}
+
+executable("adblock_flatbuffer_converter") {
+ sources = [ "converter_main.cc" ]
+
+ deps = [
+ ":converter",
+ "//third_party/zlib/google:compression_utils",
+ ]
+}
+
+source_set("perf_tests") {
+ testonly = true
+ sources = [ "test/flatbuffer_converter_perftest.cc" ]
+
+ deps = [
+ ":converter",
+ "//testing/gtest",
+ "//testing/perf:perf",
+ "//third_party/zlib/google:compression_utils",
+ ]
+
+ data = [
+ "//components/test/data/adblock/easylist.txt.gz",
+ "//components/test/data/adblock/exceptionrules.txt.gz",
+ ]
+}
diff --git a/components/adblock/core/converter/converter_main.cc b/components/adblock/core/converter/converter_main.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/converter_main.cc
@@ -0,0 +1,117 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sstream>
+#include <vector>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/strings/sys_string_conversions.h"
+#endif // BUILDFLAG(IS_WIN)
+
+namespace {
+
+bool Convert(base::FilePath input_path, GURL url, base::FilePath output_path) {
+ auto converter = base::MakeRefCounted<adblock::FlatbufferConverter>();
+ if (!url.is_valid()) {
+ LOG(ERROR) << "[eyeo] Filter list URL not valid: " << url;
+ return false;
+ }
+ std::string content;
+ if (!base::ReadFileToString(input_path, &content)) {
+ LOG(ERROR) << "[eyeo] Could not open input file " << input_path;
+ return false;
+ }
+ if (input_path.MatchesFinalExtension(FILE_PATH_LITERAL(".gz"))) {
+ if (!compression::GzipUncompress(content, &content)) {
+ LOG(ERROR) << "[eyeo] Could not decompress input file " << input_path;
+ return false;
+ }
+ }
+ std::stringstream input(content);
+ auto converter_result = converter->Convert(input, url, true);
+
+ if (absl::holds_alternative<adblock::ConversionError>(converter_result)) {
+ LOG(ERROR) << "[eyeo] "
+ << absl::get<adblock::ConversionError>(converter_result);
+ return false;
+ }
+
+ if (absl::holds_alternative<GURL>(converter_result)) {
+ LOG(ERROR) << "[eyeo] Filter list redirects. Won't convert";
+ return false;
+ }
+
+ if (!base::WriteFile(
+ output_path,
+ std::string_view(
+ reinterpret_cast<const char*>(
+ absl::get<std::unique_ptr<adblock::FlatbufferData>>(
+ converter_result)
+ ->data()),
+ absl::get<std::unique_ptr<adblock::FlatbufferData>>(
+ converter_result)
+ ->size()))) {
+ LOG(ERROR) << "[eyeo] Could not write output file " << output_path;
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ base::AtExitManager exit_manager;
+ base::CommandLine::Init(argc, argv);
+ auto* command_line = base::CommandLine::ForCurrentProcess();
+
+ logging::LoggingSettings logging_settings;
+ logging_settings.logging_dest = logging::LOG_TO_STDERR;
+ logging::InitLogging(logging_settings);
+
+ const auto positional_arguments = command_line->GetArgs();
+ if (positional_arguments.size() != 3u) {
+ LOG(ERROR) << "[eyeo] Usage: " << command_line->GetProgram()
+ << " [INPUT_FILE] [FILTER_LIST_URL] [OUTPUT_FILE]";
+ return 1;
+ }
+
+ // We need to make the path absolute because base::ReadFileToString() fails
+ // for paths with `..` components.
+ const auto input_path =
+ base::MakeAbsoluteFilePath(base::FilePath(positional_arguments[0]));
+
+#if BUILDFLAG(IS_WIN)
+ const auto url = GURL(base::SysWideToUTF8(positional_arguments[1]));
+#else
+ const auto url = GURL(positional_arguments[1]);
+#endif
+ const auto output_path = base::FilePath(positional_arguments[2]);
+
+ if (!Convert(input_path, url, output_path)) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/components/adblock/core/converter/flatbuffer_converter.cc b/components/adblock/core/converter/flatbuffer_converter.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/flatbuffer_converter.cc
@@ -0,0 +1,153 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+
+#include <fstream>
+#include <iostream>
+#include <string_view>
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/converter/parser/content_filter.h"
+#include "components/adblock/core/converter/parser/filter_classifier.h"
+#include "components/adblock/core/converter/parser/metadata.h"
+#include "components/adblock/core/converter/parser/snippet_filter.h"
+#include "components/adblock/core/converter/parser/url_filter.h"
+#include "components/adblock/core/converter/serializer/flatbuffer_serializer.h"
+
+namespace adblock {
+
+namespace {
+size_t GetActualSeparatorLength(const std::string_view filter_str,
+ FilterType filter_type) {
+ if (filter_type == FilterType::Remove ||
+ filter_type == FilterType::InlineCss) {
+ if (base::StartsWith(filter_str, kElemHideFilterSeparator)) {
+ return 2;
+ }
+ return 3;
+ }
+ if (filter_type == FilterType::ElemHide) {
+ return 2;
+ }
+ return 3;
+}
+
+constexpr char kCommentPrefix[] = "!";
+constexpr size_t kMaxSeparatorLength = 3u;
+
+} // namespace
+
+FlatbufferConverter::FlatbufferConverter() = default;
+FlatbufferConverter::~FlatbufferConverter() = default;
+
+ConversionResult FlatbufferConverter::Convert(std::istream& filter_stream,
+ GURL subscription_url,
+ bool allow_privileged) {
+ if (!filter_stream) {
+ return ConversionError("Invalid filter stream");
+ }
+
+ auto metadata = Metadata::FromStream(filter_stream);
+ if (!metadata.has_value()) {
+ return ConversionError("Invalid filter list metadata");
+ }
+
+ if (metadata->redirect_url.has_value()) {
+ return metadata->redirect_url.value();
+ }
+
+ FlatbufferSerializer flatbuffer_serializer(subscription_url,
+ allow_privileged);
+ flatbuffer_serializer.SerializeMetadata(std::move(metadata.value()));
+ std::string line;
+ while (std::getline(filter_stream, line)) {
+ ConvertFilter(line, flatbuffer_serializer);
+ }
+
+ return flatbuffer_serializer.GetSerializedSubscription();
+}
+
+std::unique_ptr<FlatbufferData> FlatbufferConverter::Convert(
+ const std::vector<std::string>& filters,
+ GURL subscription_url,
+ bool allow_privileged) {
+ FlatbufferSerializer flatbuffer_serializer(subscription_url,
+ allow_privileged);
+ for (const auto& filter : filters) {
+ ConvertFilter(filter, flatbuffer_serializer);
+ }
+ return flatbuffer_serializer.GetSerializedSubscription();
+}
+
+void FlatbufferConverter::ConvertFilter(
+ const std::string& line,
+ FlatbufferSerializer& flatbuffer_serializer) {
+ const std::string_view filter_str =
+ base::TrimWhitespaceASCII(line, base::TRIM_ALL);
+ if (base::StartsWith(filter_str, kCommentPrefix) || filter_str.empty()) {
+ return;
+ }
+
+ auto separator_pos = filter_str.find('#');
+ FilterType filter_type = FilterType::Url;
+ if (separator_pos != std::string::npos) {
+ filter_type = FilterClassifier::Classify(filter_str.substr(separator_pos));
+ }
+
+ switch (filter_type) {
+ case FilterType::ElemHide:
+ case FilterType::ElemHideException:
+ case FilterType::ElemHideEmulation:
+ case FilterType::Remove:
+ case FilterType::InlineCss:
+ if (auto content_filter = ContentFilter::FromString(
+ filter_str.substr(0, separator_pos), filter_type,
+ filter_str.substr(
+ separator_pos +
+ (GetActualSeparatorLength(filter_str.substr(separator_pos),
+ filter_type))))) {
+ flatbuffer_serializer.SerializeContentFilter(
+ std::move(content_filter.value()));
+ } else {
+ VLOG(1) << "[eyeo] Invalid content filter: " << line;
+ }
+ break;
+ case FilterType::Snippet:
+ if (auto snippet_filter = SnippetFilter::FromString(
+ filter_str.substr(0, separator_pos),
+ filter_str.substr(separator_pos + kMaxSeparatorLength))) {
+ flatbuffer_serializer.SerializeSnippetFilter(
+ std::move(snippet_filter.value()));
+ } else {
+ VLOG(1) << "[eyeo] Invalid snippet filter: " << line;
+ }
+ break;
+ case FilterType::Url:
+ if (auto url_filter = UrlFilter::FromString(
+ std::string(filter_str.data(), filter_str.size()))) {
+ flatbuffer_serializer.SerializeUrlFilter(std::move(url_filter.value()));
+ } else {
+ VLOG(1) << "[eyeo] Invalid url filter: " << line;
+ }
+ break;
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/flatbuffer_converter.h b/components/adblock/core/converter/flatbuffer_converter.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/flatbuffer_converter.h
@@ -0,0 +1,63 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_
+
+#include <istream>
+#include <memory>
+
+#include "base/memory/ref_counted.h"
+#include "base/types/strong_alias.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+using ConversionError =
+ base::StrongAlias<class ConversionErrorTag, std::string>;
+// Conversion can yield valid FlatbufferData, a redirect URL or an error:
+using ConversionResult =
+ absl::variant<std::unique_ptr<FlatbufferData>, GURL, ConversionError>;
+
+class FlatbufferSerializer;
+class FlatbufferConverter
+ : public base::RefCountedThreadSafe<FlatbufferConverter> {
+ public:
+ FlatbufferConverter();
+
+ ConversionResult Convert(std::istream& filter_stream,
+ GURL subscription_url,
+ bool allow_privileged);
+ std::unique_ptr<FlatbufferData> Convert(
+ const std::vector<std::string>& filters,
+ GURL subscription_url,
+ bool allow_privileged);
+
+ protected:
+ friend class base::RefCountedThreadSafe<FlatbufferConverter>;
+ virtual ~FlatbufferConverter();
+
+ private:
+ void ConvertFilter(const std::string& line,
+ FlatbufferSerializer& flatbuffer_serializer);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_
diff --git a/components/adblock/core/converter/parser/BUILD.gn b/components/adblock/core/converter/parser/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/BUILD.gn
@@ -0,0 +1,67 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("parser") {
+ sources = [
+ "content_filter.cc",
+ "content_filter.h",
+ "domain_option.cc",
+ "domain_option.h",
+ "filter_classifier.cc",
+ "filter_classifier.h",
+ "metadata.cc",
+ "metadata.h",
+ "snippet_filter.cc",
+ "snippet_filter.h",
+ "snippet_tokenizer.cc",
+ "snippet_tokenizer.h",
+ "url_filter.cc",
+ "url_filter.h",
+ "url_filter_options.cc",
+ "url_filter_options.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core/common",
+ "//url",
+ ]
+
+ deps = [
+ "//third_party/icu/",
+ "//third_party/re2",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/content_filter_test.cc",
+ "test/domain_option_test.cc",
+ "test/filter_classifier_test.cc",
+ "test/metadata_test.cc",
+ "test/snippet_filter_test.cc",
+ "test/snippet_tokenizer_test.cc",
+ "test/url_filter_options_test.cc",
+ "test/url_filter_test.cc",
+ ]
+
+ deps = [
+ ":parser",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/adblock/core/converter/parser/content_filter.cc b/components/adblock/core/converter/parser/content_filter.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/content_filter.cc
@@ -0,0 +1,100 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/content_filter.h"
+
+#include "base/logging.h"
+
+namespace adblock {
+
+static constexpr char kDomainSeparator[] = ",";
+
+// static
+absl::optional<ContentFilter> ContentFilter::FromString(
+ std::string_view domain_list,
+ FilterType filter_type,
+ std::string_view selector) {
+ DCHECK(filter_type == FilterType::ElemHide ||
+ filter_type == FilterType::ElemHideException ||
+ filter_type == FilterType::ElemHideEmulation ||
+ filter_type == FilterType::Remove ||
+ filter_type == FilterType::InlineCss);
+ if (selector.empty()) {
+ VLOG(1) << "[eyeo] Content filters require selector";
+ return {};
+ }
+
+ DomainOption domains =
+ DomainOption::FromString(domain_list, kDomainSeparator);
+
+ if (filter_type == FilterType::ElemHideEmulation ||
+ filter_type == FilterType::Remove ||
+ filter_type == FilterType::InlineCss) {
+ // ElemHideEmulation filters require that the domains have
+ // at least one subdomain or is localhost
+ domains.RemoveDomainsWithNoSubdomain();
+ if (domains.GetIncludeDomains().empty()) {
+ VLOG(1) << "[eyeo] ElemHideEmulation, Remove and InlineCss "
+ "filters require include domains.";
+ return {};
+ }
+ } else if (selector.size() < 3 && domains.UnrestrictedByDomain()) {
+ VLOG(1) << "[eyeo] Content filter is not specific enough. Must be longer "
+ "than 2 characters or restricted by domain.";
+ return {};
+ }
+
+ std::string_view modifier = "";
+ if (filter_type == FilterType::Remove ||
+ filter_type == FilterType::InlineCss) {
+ auto modifier_pos = selector.find_last_of('{');
+ if (modifier_pos == std::string_view::npos) {
+ VLOG(1) << "[eyeo] Mailformed modifier.";
+ return {};
+ }
+
+ modifier = selector.substr(modifier_pos);
+
+ selector.remove_suffix(modifier.length());
+ selector = base::TrimWhitespaceASCII(selector, base::TRIM_TRAILING);
+ if (selector.empty()) {
+ VLOG(1) << "[eyeo] modifier requires a selector.";
+ return {};
+ }
+
+ // Remove leading '{' and trailing '}'. base::TrimSTring(modifier, "{}",
+ // base::TRIM_ALL) might invalidate the encolsed CSS.
+ modifier.remove_prefix(1);
+ modifier.remove_suffix(1);
+ modifier = base::TrimWhitespaceASCII(modifier, base::TRIM_ALL);
+ }
+
+ return ContentFilter(filter_type, selector, modifier, std::move(domains));
+}
+
+ContentFilter::ContentFilter(FilterType type,
+ std::string_view selector,
+ std::string_view modifier,
+ DomainOption domains)
+ : type(type),
+ selector(selector.data(), selector.size()),
+ modifier(modifier.data(), modifier.size()),
+ domains(std::move(domains)) {}
+ContentFilter::ContentFilter(const ContentFilter& other) = default;
+ContentFilter::~ContentFilter() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/content_filter.h b/components/adblock/core/converter/parser/content_filter.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/content_filter.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_
+
+#include <string>
+#include <string_view>
+
+#include "components/adblock/core/converter/parser/domain_option.h"
+#include "components/adblock/core/converter/parser/filter_classifier.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace adblock {
+
+class ContentFilter {
+ public:
+ static absl::optional<ContentFilter> FromString(std::string_view domain_list,
+ FilterType type,
+ std::string_view selector);
+
+ ContentFilter(const ContentFilter& other);
+ ~ContentFilter();
+
+ const FilterType type;
+ const std::string selector;
+ const std::string modifier;
+ const DomainOption domains;
+
+ private:
+ ContentFilter(FilterType type,
+ std::string_view selector,
+ std::string_view modifier,
+ DomainOption domains);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_
diff --git a/components/adblock/core/converter/parser/domain_option.cc b/components/adblock/core/converter/parser/domain_option.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/domain_option.cc
@@ -0,0 +1,146 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/domain_option.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/converter/parser/content_filter.h"
+
+namespace adblock {
+
+namespace {
+
+void RemoveDuplicates(std::vector<std::string_view>& data) {
+ sort(data.begin(), data.end());
+ auto unique_end = unique(data.begin(), data.end());
+ data.erase(unique_end, data.end());
+}
+
+// This is a simplified check if a domain is valid. Corners are cut
+// to have minimal performance impact.
+void RemoveInvalidDomainStrings(std::vector<std::string>& data) {
+ constexpr static std::string_view kValidDomainChars{
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."};
+ data.erase(std::remove_if(
+ data.begin(), data.end(),
+ [](const auto& it) {
+ if (!base::ContainsOnlyChars(it, kValidDomainChars) ||
+ it.find("..") != std::string::npos) {
+ VLOG(1) << "[eyeo] Rejecting domain option: " << it;
+ return true;
+ }
+ return false;
+ }),
+ data.end());
+}
+
+} // namespace
+
+DomainOption::DomainOption() {}
+
+// static
+DomainOption DomainOption::FromString(std::string_view domains_list,
+ std::string_view separator) {
+ static const std::string_view kExclusionPrefix = "~";
+ auto lower_domains_list = base::ToLowerASCII(domains_list);
+ auto domains =
+ base::SplitStringPiece(lower_domains_list, separator,
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ // Remove any trailing dots (e.g. "example.com." -> "example.com").
+ for (auto& domain : domains) {
+ domain = base::TrimString(domain, ".", base::TRIM_TRAILING);
+ }
+
+ // Remove trailing wildcard (e.g. "example.*" -> "example."). This works in
+ // concert with DomainSplitter to match wildcard TLDs.
+ // Domains that ends with a dot ("example.") should match any TLD
+ // ("example.com", "example.co.uk", etc.).
+ for (auto& domain : domains) {
+ domain = base::TrimString(domain, "*", base::TRIM_TRAILING);
+ }
+
+ RemoveDuplicates(domains);
+
+ const auto first_include_domain_it = std::partition(
+ domains.begin(), domains.end(), [](std::string_view domain) {
+ return base::StartsWith(domain, kExclusionPrefix);
+ });
+
+ std::vector<std::string> exclude_domains(domains.begin(),
+ first_include_domain_it);
+ std::vector<std::string> include_domains(first_include_domain_it,
+ domains.end());
+
+ // Remove the ~ prefix that indicates an exclude domain.
+ for (auto& domain : exclude_domains) {
+ base::RemoveChars(domain, kExclusionPrefix, &domain);
+ }
+
+ RemoveInvalidDomainStrings(include_domains);
+ RemoveInvalidDomainStrings(exclude_domains);
+
+ return DomainOption(std::move(exclude_domains), std::move(include_domains));
+}
+
+const std::vector<std::string>& DomainOption::GetExcludeDomains() const {
+ return exclude_domains_;
+}
+
+const std::vector<std::string>& DomainOption::GetIncludeDomains() const {
+ return include_domains_;
+}
+
+void DomainOption::RemoveDomainsWithNoSubdomain() {
+ exclude_domains_.erase(
+ std::remove_if(exclude_domains_.begin(), exclude_domains_.end(),
+ [](auto it) { return !HasSubdomainOrLocalhost(it); }),
+ exclude_domains_.end());
+
+ include_domains_.erase(
+ std::remove_if(include_domains_.begin(), include_domains_.end(),
+ [](auto it) { return !HasSubdomainOrLocalhost(it); }),
+ include_domains_.end());
+}
+
+bool DomainOption::UnrestrictedByDomain() const {
+ return std::ranges::count_if(exclude_domains_, &HasSubdomainOrLocalhost) ==
+ 0 &&
+ std::ranges::count_if(include_domains_, &HasSubdomainOrLocalhost) == 0;
+}
+
+DomainOption::DomainOption(std::vector<std::string> exclude_domains,
+ std::vector<std::string> include_domains)
+ : exclude_domains_(std::move(exclude_domains)),
+ include_domains_(std::move(include_domains)) {}
+DomainOption::DomainOption(const DomainOption& other) = default;
+DomainOption::DomainOption(DomainOption&& other) = default;
+DomainOption& DomainOption::operator=(const DomainOption& other) = default;
+DomainOption& DomainOption::operator=(DomainOption&& other) = default;
+DomainOption::~DomainOption() = default;
+
+// static
+bool DomainOption::HasSubdomainOrLocalhost(std::string_view domain) {
+ return (domain == "localhost") ||
+ (domain.find(".") != std::string_view::npos);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/domain_option.h b/components/adblock/core/converter/parser/domain_option.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/domain_option.h
@@ -0,0 +1,62 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace adblock {
+
+class DomainOption {
+ public:
+ DomainOption();
+
+ static DomainOption FromString(std::string_view domains_list,
+ std::string_view separator);
+
+ DomainOption(const DomainOption& other);
+ DomainOption(DomainOption&& other);
+ DomainOption& operator=(const DomainOption& other);
+ DomainOption& operator=(DomainOption&& other);
+ ~DomainOption();
+
+ // Domains that end with a '.' should match any public registrar that follows,
+ // for example "test." should match "test.com" and "test.co.uk" (but not
+ // "test.domain.com").
+ // Domains that end with any other character should be matched exactly.
+ const std::vector<std::string>& GetExcludeDomains() const;
+ const std::vector<std::string>& GetIncludeDomains() const;
+
+ void RemoveDomainsWithNoSubdomain();
+ bool UnrestrictedByDomain() const;
+
+ private:
+ DomainOption(std::vector<std::string> exclude_domains,
+ std::vector<std::string> include_domains);
+
+ static bool HasSubdomainOrLocalhost(std::string_view domain);
+
+ std::vector<std::string> exclude_domains_;
+ std::vector<std::string> include_domains_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_
diff --git a/components/adblock/core/converter/parser/filter_classifier.cc b/components/adblock/core/converter/parser/filter_classifier.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/filter_classifier.cc
@@ -0,0 +1,52 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/filter_classifier.h"
+
+#include "third_party/re2/src/re2/re2.h"
+
+namespace adblock {
+
+// static
+FilterType FilterClassifier::Classify(std::string_view filter) {
+ if (!filter.empty() && filter.back() == '}' &&
+ (base::StartsWith(filter, kElemHideFilterSeparator) ||
+ base::StartsWith(filter, kElemHideEmulationFilterSeparator))) {
+ static re2::RE2 remove_re("\\{\\s*remove\\s*:\\s*true\\s*;\\s*\\}$");
+ if (re2::RE2::PartialMatch(filter, remove_re)) {
+ return FilterType::Remove;
+ } else {
+ return FilterType::InlineCss;
+ }
+ }
+ if (base::StartsWith(filter, kElemHideFilterSeparator)) {
+ return FilterType::ElemHide;
+ }
+ if (base::StartsWith(filter, kElemHideExceptionFilterSeparator)) {
+ return FilterType::ElemHideException;
+ }
+ if (base::StartsWith(filter, kElemHideEmulationFilterSeparator)) {
+ return FilterType::ElemHideEmulation;
+ }
+ if (base::StartsWith(filter, kSnippetFilterSeparator)) {
+ return FilterType::Snippet;
+ }
+
+ return FilterType::Url;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/filter_classifier.h b/components/adblock/core/converter/parser/filter_classifier.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/filter_classifier.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_
+
+#include <string_view>
+
+#include "base/strings/string_util.h"
+
+namespace adblock {
+
+static constexpr char kElemHideFilterSeparator[] = "##";
+static constexpr char kElemHideExceptionFilterSeparator[] = "#@#";
+static constexpr char kElemHideEmulationFilterSeparator[] = "#?#";
+static constexpr char kSnippetFilterSeparator[] = "#$#";
+
+enum class FilterType {
+ ElemHide,
+ ElemHideException,
+ ElemHideEmulation,
+ Remove,
+ InlineCss,
+ Snippet,
+ Url,
+};
+
+class FilterClassifier {
+ public:
+ static FilterType Classify(std::string_view filter);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_
diff --git a/components/adblock/core/converter/parser/metadata.cc b/components/adblock/core/converter/parser/metadata.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/metadata.cc
@@ -0,0 +1,145 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/metadata.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace adblock {
+
+// Parses the stream line by line until it finds comments. After the first non
+// comment line any upcoming comments will be skipped.
+// static
+absl::optional<Metadata> Metadata::FromStream(std::istream& filter_stream) {
+ static re2::RE2 comment_re("^!\\s*(.*?)\\s*:\\s*(.*)");
+
+ std::string homepage;
+ std::string title;
+ std::string version;
+ absl::optional<GURL> redirect_url;
+ base::TimeDelta expires = kDefaultExpirationInterval;
+
+ std::string line;
+ std::getline(filter_stream, line);
+ if (!IsValidAdblockHeader(line)) {
+ VLOG(1) << "[eyeo] Invalid filter list. Should start with [Adblock Plus "
+ "<x>.<y>].";
+ return {};
+ }
+
+ std::string key, value;
+ // Process stream until the line is a comment
+ auto position_in_stream = filter_stream.tellg();
+ while (std::getline(filter_stream, line)) {
+ base::TrimWhitespaceASCII(line, base::TRIM_ALL, &line);
+ if (!re2::RE2::FullMatch(line, comment_re, &key, &value)) {
+ break;
+ }
+
+ key = base::ToLowerASCII(key);
+ if (key == "homepage") {
+ homepage = value;
+ } else if (key == "redirect") {
+ auto url = GURL(value);
+ if (url.is_valid()) {
+ redirect_url = url;
+ } else {
+ VLOG(1) << "[eyeo] Invalid redirect URL: " << value
+ << ". Will not redirect.";
+ }
+ } else if (key == "title") {
+ title = value;
+ } else if (key == "version") {
+ version = value;
+ } else if (key == "expires") {
+ expires = ParseExpirationTime(value);
+ }
+
+ position_in_stream = filter_stream.tellg();
+ }
+
+ // NOTE: Rewind stream after last header line
+ filter_stream.seekg(position_in_stream, std::ios_base::beg);
+
+ return Metadata(std::move(homepage), std::move(title), std::move(version),
+ std::move(redirect_url), std::move(expires));
+}
+
+// static
+Metadata Metadata::Default() {
+ Metadata metadata;
+ return metadata;
+}
+
+Metadata::Metadata(std::string homepage,
+ std::string title,
+ std::string version,
+ absl::optional<GURL> redirect_url,
+ base::TimeDelta expires)
+ : homepage(std::move(homepage)),
+ title(std::move(title)),
+ version(std::move(version)),
+ redirect_url(std::move(redirect_url)),
+ expires(std::move(expires)) {}
+
+Metadata::Metadata() : expires(kDefaultExpirationInterval) {}
+Metadata::Metadata(const Metadata& other) = default;
+Metadata::Metadata(Metadata&& other) = default;
+Metadata::~Metadata() = default;
+
+// static
+bool Metadata::IsValidAdblockHeader(const std::string& adblock_header) {
+ static re2::RE2 adblock_header_re("^\\[Adblock.*\\]");
+ std::string adblock_header_trimmed;
+
+ base::TrimWhitespaceASCII(adblock_header, base::TRIM_ALL,
+ &adblock_header_trimmed);
+ if (!re2::RE2::FullMatch(re2::StringPiece(adblock_header_trimmed.data(),
+ adblock_header_trimmed.size()),
+ adblock_header_re)) {
+ return false;
+ }
+ return true;
+}
+
+// NOTE: This is done by the logic described here:
+// https://eyeo.gitlab.io/adblockplus/abc/core-spec/#appendix-filter-list-syntax
+// static
+base::TimeDelta Metadata::ParseExpirationTime(
+ const std::string& expiration_value) {
+ static re2::RE2 expiration_time_re("\\s*([0-9]+)\\s*(h)?.*");
+ std::string expiration_unit;
+ uint64_t expiration_time;
+
+ if (!re2::RE2::FullMatch(expiration_value, expiration_time_re,
+ &expiration_time, &expiration_unit)) {
+ VLOG(1) << "[eyeo] Invalid expiration time format: " << expiration_value
+ << ". Will use default value of "
+ << kDefaultExpirationInterval.InDays() << " days.";
+ return kDefaultExpirationInterval;
+ }
+ return std::clamp(expiration_unit == "h" ? base::Hours(expiration_time)
+ : base::Days(expiration_time),
+ kMinExpirationInterval, kMaxExpirationInterval);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/metadata.h b/components/adblock/core/converter/parser/metadata.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/metadata.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_
+
+#include <istream>
+#include <string>
+
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class Metadata {
+ public:
+ static absl::optional<Metadata> FromStream(std::istream& filter_stream);
+ static Metadata Default();
+
+ Metadata(const Metadata& other);
+ Metadata(Metadata&& other);
+ ~Metadata();
+
+ const std::string homepage;
+ const std::string title;
+ const std::string version;
+ const absl::optional<GURL> redirect_url;
+ const base::TimeDelta expires;
+
+ static constexpr base::TimeDelta kDefaultExpirationInterval = base::Days(5);
+ static constexpr base::TimeDelta kMaxExpirationInterval = base::Days(14);
+ static constexpr base::TimeDelta kMinExpirationInterval = base::Hours(1);
+
+ private:
+ Metadata(std::string homepage,
+ std::string title,
+ std::string version,
+ absl::optional<GURL> redirect_url,
+ base::TimeDelta expires);
+ Metadata();
+
+ static bool IsValidAdblockHeader(const std::string& adblock_header);
+ static base::TimeDelta ParseExpirationTime(
+ const std::string& expiration_value);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_
diff --git a/components/adblock/core/converter/parser/snippet_filter.cc b/components/adblock/core/converter/parser/snippet_filter.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/snippet_filter.cc
@@ -0,0 +1,62 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/snippet_filter.h"
+
+#include "base/logging.h"
+
+namespace adblock {
+
+static constexpr char kDomainSeparator[] = ",";
+
+// static
+absl::optional<SnippetFilter> SnippetFilter::FromString(
+ std::string_view domain_list,
+ std::string_view snippet) {
+ if (snippet.empty()) {
+ VLOG(1) << "[eyeo] Filter has no snippet script.";
+ return {};
+ }
+
+ DomainOption domains =
+ DomainOption::FromString(domain_list, kDomainSeparator);
+ // Snippet filters require that the domains have
+ // at least one subdomain or is localhost
+ domains.RemoveDomainsWithNoSubdomain();
+ if (domains.GetIncludeDomains().empty()) {
+ VLOG(1) << "Snippet "
+ "filters require include domains.";
+ return {};
+ }
+
+ auto snippet_script = SnippetTokenizer::Tokenize(snippet);
+ if (snippet_script.empty()) {
+ VLOG(1) << "Could not tokenize snippet script";
+ return {};
+ }
+
+ return SnippetFilter(std::move(snippet_script), std::move(domains));
+}
+
+SnippetFilter::SnippetFilter(SnippetTokenizer::SnippetScript snippet_script,
+ DomainOption domains)
+ : snippet_script(std::move(snippet_script)), domains(std::move(domains)) {}
+SnippetFilter::SnippetFilter(const SnippetFilter& other) = default;
+SnippetFilter::SnippetFilter(SnippetFilter&& other) = default;
+SnippetFilter::~SnippetFilter() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/snippet_filter.h b/components/adblock/core/converter/parser/snippet_filter.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/snippet_filter.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_
+
+#include <string_view>
+
+#include "components/adblock/core/converter/parser/domain_option.h"
+#include "components/adblock/core/converter/parser/snippet_tokenizer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace adblock {
+
+class SnippetFilter {
+ public:
+ static absl::optional<SnippetFilter> FromString(std::string_view domain_list,
+ std::string_view snippet);
+
+ SnippetFilter(const SnippetFilter& other);
+ SnippetFilter(SnippetFilter&& other);
+ ~SnippetFilter();
+
+ const SnippetTokenizer::SnippetScript snippet_script;
+ const DomainOption domains;
+
+ private:
+ SnippetFilter(SnippetTokenizer::SnippetScript snippet_script,
+ DomainOption domains);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_
diff --git a/components/adblock/core/converter/parser/snippet_tokenizer.cc b/components/adblock/core/converter/parser/snippet_tokenizer.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/snippet_tokenizer.cc
@@ -0,0 +1,103 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/snippet_tokenizer.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "base/third_party/icu/icu_utf.h"
+
+namespace adblock {
+
+SnippetTokenizer::SnippetScript SnippetTokenizer::Tokenize(
+ std::string_view input) {
+ SnippetScript script;
+ std::string token;
+ std::vector<std::string> arguments;
+ bool escape = false;
+ bool quotes_just_closed = false;
+ bool within_quotes = false;
+ for (char ch : input) {
+ if (escape) {
+ AddEscapeChar(token, ch);
+ escape = false;
+ quotes_just_closed = false;
+ } else if (ch == '\\') {
+ escape = true;
+ quotes_just_closed = false;
+ } else if (ch == '\'') {
+ within_quotes = !within_quotes;
+ quotes_just_closed = !within_quotes;
+ } else if (within_quotes || (ch != ';' && !base::IsAsciiWhitespace(ch))) {
+ token += ch;
+ quotes_just_closed = false;
+ } else {
+ AddArgument(arguments, token, quotes_just_closed);
+ if (ch == ';') {
+ AddFunctionCall(script, arguments, within_quotes, escape);
+ }
+ quotes_just_closed = false;
+ }
+ }
+ AddArgument(arguments, token, quotes_just_closed);
+ AddFunctionCall(script, arguments, within_quotes, escape);
+ return script;
+}
+
+void SnippetTokenizer::AddEscapeChar(std::string& token, char ch) {
+ switch (ch) {
+ case 'r':
+ token += '\r';
+ break;
+ case 'n':
+ token += '\n';
+ break;
+ case 't':
+ token += '\t';
+ break;
+ case 'u':
+ token += "\\u";
+ break;
+ default:
+ token += ch;
+ }
+}
+
+void SnippetTokenizer::AddArgument(std::vector<std::string>& arguments,
+ std::string& token,
+ bool quotes_just_closed) {
+ if (quotes_just_closed || !token.empty()) {
+ arguments.push_back(token);
+ token.clear();
+ }
+}
+
+void SnippetTokenizer::AddFunctionCall(SnippetScript& script,
+ std::vector<std::string>& arguments,
+ bool within_quotes,
+ bool escape) {
+ // if within quote whole script is invalid
+ // or if detected escape char but ended
+ if (arguments.empty() || within_quotes || escape) {
+ return;
+ }
+ script.push_back(arguments);
+ arguments.clear();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/snippet_tokenizer.h b/components/adblock/core/converter/parser/snippet_tokenizer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/snippet_tokenizer.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace adblock {
+
+class SnippetTokenizer {
+ public:
+ using SnippetScript = std::vector<std::vector<std::string>>;
+
+ static SnippetScript Tokenize(std::string_view input);
+
+ private:
+ static void AddEscapeChar(std::string& token, char ch);
+ static void AddArgument(std::vector<std::string>& arguments,
+ std::string& token,
+ bool quotes_just_closed);
+ static void AddFunctionCall(SnippetScript& script,
+ std::vector<std::string>& arguments,
+ bool within_quotes,
+ bool escape);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_
diff --git a/components/adblock/core/converter/parser/test/content_filter_test.cc b/components/adblock/core/converter/parser/test/content_filter_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/content_filter_test.cc
@@ -0,0 +1,263 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/content_filter.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using ::testing::UnorderedElementsAre;
+
+TEST(AdblockContentFilterTest, ParseEmptyContentFilter) {
+ EXPECT_FALSE(
+ ContentFilter::FromString("", FilterType::ElemHide, "").has_value());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilter) {
+ auto content_filter = ContentFilter::FromString(
+ "example.org", FilterType::ElemHide,
+ ".testcase-container > .testcase-eh-descendant");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHide);
+ EXPECT_EQ(content_filter->selector,
+ ".testcase-container > .testcase-eh-descendant");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilterWithNonAsciiCharacters) {
+ // Non-ASCII characters are allowed in selectors. They should be preserved.
+ auto content_filter =
+ ContentFilter::FromString("test.com", FilterType::ElemHide, ".ad_bÖx");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHide);
+ EXPECT_EQ(content_filter->selector, ".ad_bÖx");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.com"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilterMultipleDomains) {
+ auto content_filter =
+ ContentFilter::FromString("example.org,~foo.example.org,bar.example.org",
+ FilterType::ElemHide, "test-selector");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHide);
+ EXPECT_EQ(content_filter->selector, "test-selector");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org", "bar.example.org"));
+ EXPECT_THAT(content_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("foo.example.org"));
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilterWithIdSelector) {
+ auto content_filter = ContentFilter::FromString(
+ "example.org", FilterType::ElemHide, "#this_is_an_id");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHide);
+ EXPECT_EQ(content_filter->selector, "#this_is_an_id");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilterWithNoDomains) {
+ auto content_filter =
+ ContentFilter::FromString("", FilterType::ElemHide, "selector");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHide);
+ EXPECT_EQ(content_filter->selector, "selector");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_TRUE(content_filter->domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideFilterWithNoSelector) {
+ ASSERT_FALSE(
+ ContentFilter::FromString("example.org", FilterType::ElemHide, "")
+ .has_value());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideExceptionFilter) {
+ auto content_filter = ContentFilter::FromString(
+ "example.org", FilterType::ElemHideException, "selector");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideException);
+ EXPECT_EQ(content_filter->selector, "selector");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideExceptionFilterWithIdSelector) {
+ auto content_filter = ContentFilter::FromString(
+ "example.org", FilterType::ElemHideException, "#this_is_an_id");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideException);
+ EXPECT_EQ(content_filter->selector, "#this_is_an_id");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideEmulationFilter) {
+ auto content_filter = ContentFilter::FromString(
+ "foo.example.org", FilterType::ElemHideEmulation, "foo");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideEmulation);
+ EXPECT_EQ(content_filter->selector, "foo");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("foo.example.org"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest,
+ ParseElemHideEmulationFilterWithNonAsciiCharacters) {
+ // Non-ASCII characters are allowed in selectors. They should be preserved.
+ auto content_filter = ContentFilter::FromString(
+ "test.com", FilterType::ElemHideEmulation, ".ad_bÖx");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideEmulation);
+ EXPECT_EQ(content_filter->selector, ".ad_bÖx");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.com"));
+ EXPECT_TRUE(content_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockContentFilterTest, ParseElemHideEmulationFilterNoIncludeDomain) {
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org",
+ FilterType::ElemHideEmulation, "foo")
+ .has_value());
+}
+
+TEST(AdblockContentFilterTest,
+ ParseElemHideEmulationFilterDomainsWithNoSubdomainRemoved) {
+ auto content_filter =
+ ContentFilter::FromString("org,example.org,~com,~example-too.org",
+ FilterType::ElemHideEmulation, "foo");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideEmulation);
+ EXPECT_EQ(content_filter->selector, "foo");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_THAT(content_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("example-too.org"));
+}
+
+TEST(AdblockContentFilterTest,
+ ParseElemHideEmulationFilterDomainsWithNoSubdomainRemovedButNotLocalhost) {
+ auto content_filter =
+ ContentFilter::FromString("org,example.org,~localhost,~example-too.org",
+ FilterType::ElemHideEmulation, "foo");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::ElemHideEmulation);
+ EXPECT_EQ(content_filter->selector, "foo");
+ EXPECT_TRUE(content_filter->modifier.empty());
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_THAT(content_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("example-too.org", "localhost"));
+}
+
+TEST(AdblockContentFilterTest, ParseUnspecifcContentFilter) {
+ // Short, non domain-specific filters are disallowed because they could break
+ // a lot of sites by accident:
+ EXPECT_FALSE(
+ ContentFilter::FromString("", FilterType::ElemHide, "li").has_value());
+ EXPECT_FALSE(ContentFilter::FromString("", FilterType::ElemHideException, "p")
+ .has_value());
+
+ // This filter is long enough:
+ EXPECT_TRUE(
+ ContentFilter::FromString("", FilterType::ElemHide, "adv").has_value());
+ // This filter is short ("p") but domain-specific, so in worst case it could
+ // only break example.com.
+ EXPECT_TRUE(
+ ContentFilter::FromString("example.com", FilterType::ElemHide, "p")
+ .has_value());
+}
+
+TEST(AdblockContentFilterTest, ParseRemoveFilter) {
+ auto content_filter = ContentFilter::FromString(
+ "example.org,~localhost", FilterType::Remove, "foo {remove: true;}");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::Remove);
+ EXPECT_EQ(content_filter->selector, "foo");
+ EXPECT_EQ(content_filter->modifier, "remove: true;");
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_THAT(content_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("localhost"));
+}
+
+TEST(AdblockContentFilterTest, ParseInlineCssFilter) {
+ auto content_filter =
+ ContentFilter::FromString("example.org,~localhost", FilterType::InlineCss,
+ "foo { some inline: css; }");
+ ASSERT_TRUE(content_filter.has_value());
+ EXPECT_EQ(content_filter->type, FilterType::InlineCss);
+ EXPECT_EQ(content_filter->selector, "foo");
+ EXPECT_EQ(content_filter->modifier, "some inline: css;");
+ EXPECT_THAT(content_filter->domains.GetIncludeDomains(),
+ UnorderedElementsAre("example.org"));
+ EXPECT_THAT(content_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("localhost"));
+}
+
+TEST(AdblockContentFilterTest,
+ ParseRemoveAndInlineCssFilterWithNoPseudoSelector) {
+ ASSERT_FALSE(
+ ContentFilter::FromString("~foo.example.org", FilterType::Remove, "foo")
+ .has_value());
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org",
+ FilterType::InlineCss, "foo")
+ .has_value());
+}
+
+TEST(AdblockContentFilterTest,
+ ParseRemoveAndInlineCssFilterWithInvalidPseudoSelector) {
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org", FilterType::Remove,
+ "foo remove: true;}")
+ .has_value());
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org",
+ FilterType::InlineCss,
+ "foo inline_css; }")
+ .has_value());
+}
+
+TEST(AdblockContentFilterTest, ParseRemoveAndInlineCssFilterWithNoSelector) {
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org", FilterType::Remove,
+ "{remove: true;}")
+ .has_value());
+ ASSERT_FALSE(ContentFilter::FromString("~foo.example.org",
+ FilterType::InlineCss, "{inline_css;}")
+ .has_value());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/domain_option_test.cc b/components/adblock/core/converter/parser/test/domain_option_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/domain_option_test.cc
@@ -0,0 +1,214 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/domain_option.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using ::testing::UnorderedElementsAre;
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionEmpty) {
+ auto domains = DomainOption::FromString("", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionIncludeSimple) {
+ auto domains = DomainOption::FromString("domain.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionExcludeSimple) {
+ auto domains = DomainOption::FromString("~domain.org", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_THAT(domains.GetExcludeDomains(), UnorderedElementsAre("domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionMultiple) {
+ auto domains = DomainOption::FromString(
+ "domain.org,~exclude-domain.org,test.domain.org,~test.exclude-domain."
+ "org",
+ ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.domain.org", "domain.org"));
+ EXPECT_THAT(
+ domains.GetExcludeDomains(),
+ UnorderedElementsAre("test.exclude-domain.org", "exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionCaseInsensitive) {
+ auto domains = DomainOption::FromString(
+ "DoMain.org,~excLude-doMain.org,test.domain.org,~tESt.exclude-domain."
+ "org",
+ ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.domain.org", "domain.org"));
+ EXPECT_THAT(
+ domains.GetExcludeDomains(),
+ UnorderedElementsAre("test.exclude-domain.org", "exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionMultipleWithWs) {
+ auto domains = DomainOption::FromString(
+ "domain.org , ~exclude-domain.org, test.domain.org "
+ ",~test.exclude-domain.org ",
+ ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.domain.org", "domain.org"));
+ EXPECT_THAT(
+ domains.GetExcludeDomains(),
+ UnorderedElementsAre("test.exclude-domain.org", "exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionIncludeMultiple) {
+ auto domains = DomainOption::FromString("domain.org,test.domain.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("domain.org", "test.domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionExcludeMultiple) {
+ auto domains = DomainOption::FromString(
+ "~exclude-domain.org,~test.exclude-domain.org", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_THAT(
+ domains.GetExcludeDomains(),
+ UnorderedElementsAre("exclude-domain.org", "test.exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, RemoveDomainsWithNoSubdomain) {
+ auto domains = DomainOption::FromString(
+ "org,include-domain.org,~com,~exclude-domain.org", ",");
+ domains.RemoveDomainsWithNoSubdomain();
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("include-domain.org"));
+ EXPECT_THAT(domains.GetExcludeDomains(),
+ UnorderedElementsAre("exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, RemoveDomainsWithNoSubdomainButLocalhost) {
+ auto domains = DomainOption::FromString(
+ "localhost,include-domain.org,~localhost,~exclude-domain.org", ",");
+ domains.RemoveDomainsWithNoSubdomain();
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("include-domain.org", "localhost"));
+ EXPECT_THAT(domains.GetExcludeDomains(),
+ UnorderedElementsAre("exclude-domain.org", "localhost"));
+}
+
+TEST(AdblockDomainOptionTest, ParseDomainOptionWithDuplications) {
+ auto domains = DomainOption::FromString(
+ "domain.org,~exclude-domain.org,test.domain.org,~test.exclude-domain.org,"
+ "domain.org,~exclude-domain.org,test.domain.org",
+ ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("test.domain.org", "domain.org"));
+ EXPECT_THAT(
+ domains.GetExcludeDomains(),
+ UnorderedElementsAre("test.exclude-domain.org", "exclude-domain.org"));
+}
+
+TEST(AdblockDomainOptionTest, TrailingDot) {
+ auto domains = DomainOption::FromString("domain.org.", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, TrailingWildcard) {
+ auto domains = DomainOption::FromString("domain.org*", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, TrailingDotAndWildcard) {
+ auto domains = DomainOption::FromString("domain.org.*", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("domain.org."));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, TrailingWildcardAndDot) {
+ auto domains = DomainOption::FromString("domain.org*.", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, SlashAtTheEnd) {
+ auto domains = DomainOption::FromString("domain.org/", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, CaretAtTheEnd) {
+ auto domains = DomainOption::FromString("domain.org^", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, InvalidDomainCharacters) {
+ auto domains =
+ DomainOption::FromString("dom_ain.org,ex@mple.org,test.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(), UnorderedElementsAre("test.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, DoubleDots) {
+ auto domains = DomainOption::FromString("domain..org", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, DoubleComma) {
+ {
+ auto domains = DomainOption::FromString("domain.org,,example.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("domain.org", "example.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+ }
+ {
+ auto domains = DomainOption::FromString("domain.org,,", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+ }
+ {
+ auto domains = DomainOption::FromString(",,domain.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("domain.org"));
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+ }
+}
+
+TEST(AdblockDomainOptionTest, UrlInsteadDomain) {
+ auto domains = DomainOption::FromString("https://example.com", ",");
+ EXPECT_TRUE(domains.GetIncludeDomains().empty());
+ EXPECT_TRUE(domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockDomainOptionTest, SameDomainIncludedAndExcluded) {
+ auto domains = DomainOption::FromString(
+ "some-domain.org,some-domain.org,~some-domain.org,~some-domain.org", ",");
+ EXPECT_THAT(domains.GetIncludeDomains(),
+ UnorderedElementsAre("some-domain.org"));
+ EXPECT_THAT(domains.GetExcludeDomains(),
+ UnorderedElementsAre("some-domain.org"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/filter_classifier_test.cc b/components/adblock/core/converter/parser/test/filter_classifier_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/filter_classifier_test.cc
@@ -0,0 +1,80 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/filter_classifier.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideFilter) {
+ EXPECT_EQ(FilterType::ElemHide, FilterClassifier::Classify("##selectors"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideFilterWithRemove) {
+ EXPECT_EQ(FilterType::Remove,
+ FilterClassifier::Classify("##selectors{remove:true;}"));
+ EXPECT_EQ(FilterType::Remove,
+ FilterClassifier::Classify("##selectors { remove : true; }"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideFilterWithInlineCss) {
+ EXPECT_EQ(FilterType::InlineCss,
+ FilterClassifier::Classify("##selectors{some_inline css}"));
+ EXPECT_EQ(FilterType::InlineCss,
+ FilterClassifier::Classify("##selectors { some in line _css}"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideExceptionFilter) {
+ EXPECT_EQ(FilterType::ElemHideException,
+ FilterClassifier::Classify("#@#selectors"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideEmulationFilter) {
+ EXPECT_EQ(FilterType::ElemHideEmulation,
+ FilterClassifier::Classify("#?#advanced-selectors()"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyElemHideEmulationFilterWithRemove) {
+ EXPECT_EQ(FilterType::Remove, FilterClassifier::Classify(
+ "#?#advanced-selectors(){remove:true;}"));
+ EXPECT_EQ(FilterType::Remove,
+ FilterClassifier::Classify(
+ "#?#advanced-selectors() { remove : true; }"));
+}
+
+TEST(AdblockFilterClassifierTest,
+ ClassifyElemHideEmulationFilterWithInlineCss) {
+ EXPECT_EQ(
+ FilterType::InlineCss,
+ FilterClassifier::Classify("#?#advanced-selectors(){some_inline css}"));
+ EXPECT_EQ(FilterType::InlineCss,
+ FilterClassifier::Classify(
+ "#?#advanced-selectors(){ some in line _css}"));
+}
+
+TEST(AdblockFilterClassifierTest, ClassifyEmptyInput) {
+ EXPECT_EQ(FilterType::Url, FilterClassifier::Classify(""));
+}
+
+TEST(AdblockFilterClassifierTest, ClasifyFilterWithoutSeparator) {
+ EXPECT_EQ(FilterType::Url,
+ FilterClassifier::Classify("filter_wo_separator_is_url_filter"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/metadata_test.cc b/components/adblock/core/converter/parser/test/metadata_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/metadata_test.cc
@@ -0,0 +1,198 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/metadata.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockParserMetadataTest : public testing::Test {
+ public:
+ absl::optional<Metadata> ParseHeader(std::string header) {
+ // Without [Adblock Plus 2.0], the file is not a valid filter list.
+ header = "[Adblock Plus 2.0]\n" + header;
+ std::stringstream input(std::move(header));
+ return Metadata::FromStream(input);
+ }
+};
+
+TEST_F(AdblockParserMetadataTest, EmptyInput) {
+ std::stringstream input("");
+ EXPECT_FALSE(Metadata::FromStream(input).has_value());
+}
+
+TEST_F(AdblockParserMetadataTest, NoMandatoryAdblockHeader) {
+ std::stringstream input("! Title: EasyList");
+ EXPECT_FALSE(Metadata::FromStream(input).has_value());
+}
+
+TEST_F(AdblockParserMetadataTest, InvalidAdblockHeader) {
+ std::stringstream input("[Abdlock Puls]");
+ EXPECT_FALSE(Metadata::FromStream(input).has_value());
+}
+
+TEST_F(AdblockParserMetadataTest, ShortestValidAdblockHeader) {
+ std::stringstream input("[Adblock]");
+ auto metadata = Metadata::FromStream(input);
+ EXPECT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->homepage, "");
+ EXPECT_FALSE(metadata->redirect_url.has_value());
+ EXPECT_EQ(metadata->title, "");
+ EXPECT_EQ(metadata->version, "");
+ EXPECT_EQ(metadata->expires.InDays(),
+ Metadata::kDefaultExpirationInterval.InDays());
+}
+
+TEST_F(AdblockParserMetadataTest, MandatoryAdblockHeaderPresent) {
+ auto metadata = ParseHeader("");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->homepage, "");
+ EXPECT_FALSE(metadata->redirect_url.has_value());
+ EXPECT_EQ(metadata->title, "");
+ EXPECT_EQ(metadata->version, "");
+ EXPECT_EQ(metadata->expires.InDays(),
+ Metadata::kDefaultExpirationInterval.InDays());
+}
+
+TEST_F(AdblockParserMetadataTest, HomepageIsSet) {
+ auto metadata = ParseHeader("! Homepage: https://easylist.to/");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->homepage, "https://easylist.to/");
+}
+
+TEST_F(AdblockParserMetadataTest, RedirectUrlIsSet) {
+ auto metadata = ParseHeader("! Redirect: https://redirect-easylist.to/");
+ ASSERT_TRUE(metadata.has_value());
+ ASSERT_TRUE(metadata->redirect_url.has_value());
+ EXPECT_EQ(metadata->redirect_url.value().spec(),
+ "https://redirect-easylist.to/");
+}
+
+TEST_F(AdblockParserMetadataTest, InvalidRedirectUrlIsNotSet) {
+ auto metadata = ParseHeader("! Homepage: https//invalid_redirect_url.to/");
+ ASSERT_TRUE(metadata.has_value());
+ ASSERT_FALSE(metadata->redirect_url.has_value());
+}
+
+TEST_F(AdblockParserMetadataTest, TitleIsSet) {
+ auto metadata = ParseHeader("! Title: EasyList");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->title, "EasyList");
+}
+
+TEST_F(AdblockParserMetadataTest, VersionIsSet) {
+ auto metadata = ParseHeader("! Version: 202204050843");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->version, "202204050843");
+}
+
+TEST_F(AdblockParserMetadataTest, ExpirationTimeIsSetHoursExplicit) {
+ auto metadata = ParseHeader("! Expires: 2 hours (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InHours(), 2);
+}
+
+TEST_F(AdblockParserMetadataTest, ExpirationTimeIsSetHoursImplicit) {
+ auto metadata = ParseHeader("! Expires: 2 horse (sea)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InHours(), 2);
+}
+
+TEST_F(AdblockParserMetadataTest, ExpirationTimeIsSetHoursDoubleDigit) {
+ auto metadata = ParseHeader("! Expires: 48 hours (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(), 2);
+}
+
+TEST_F(AdblockParserMetadataTest, ExpirationTimeIsSetDaysExplicit) {
+ auto metadata = ParseHeader("! Expires: 2 days (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(), 2);
+}
+
+TEST_F(AdblockParserMetadataTest, ExpirationTimeIsSetDaysImplicit) {
+ auto metadata = ParseHeader("! Expires: 2 not a horse");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(), 2);
+}
+
+TEST_F(AdblockParserMetadataTest, InvalidExpirationTimeNotParsed) {
+ auto metadata = ParseHeader("! Expires: two days (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(),
+ Metadata::kDefaultExpirationInterval.InDays());
+}
+
+TEST_F(AdblockParserMetadataTest, NegativeExpirationTimeNotParsed) {
+ auto metadata = ParseHeader("! Expires: -1 days (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(),
+ Metadata::kDefaultExpirationInterval.InDays());
+}
+
+TEST_F(AdblockParserMetadataTest, InvalidExpirationTimeUpperLimit) {
+ auto metadata = ParseHeader("! Expires: 15");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InDays(),
+ Metadata::kMaxExpirationInterval.InDays());
+}
+
+TEST_F(AdblockParserMetadataTest, InvalidExpirationTimeLowerLimit) {
+ auto metadata = ParseHeader(
+ "! Expires: 0 how do you throw a space party? You planet. (hours)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->expires.InHours(),
+ Metadata::kMinExpirationInterval.InHours());
+}
+
+TEST_F(AdblockParserMetadataTest, AllMembersSet) {
+ auto metadata = ParseHeader(
+ "! Homepage: https://easylist.to/\n"
+ "! Redirect: https://redirect-easylist.to/\n"
+ "! Title: EasyList\n"
+ "! Version: 202204050843\n"
+ "! Expires: 2 hours (update frequency)");
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->homepage, "https://easylist.to/");
+ ASSERT_TRUE(metadata->redirect_url.has_value());
+ EXPECT_EQ(metadata->redirect_url.value().spec(),
+ "https://redirect-easylist.to/");
+ EXPECT_EQ(metadata->title, "EasyList");
+ EXPECT_EQ(metadata->version, "202204050843");
+ EXPECT_EQ(metadata->expires.InMilliseconds(), 2 * 3600000);
+}
+
+TEST_F(AdblockParserMetadataTest, ParsingStopsAtFirstNotHeaderLine) {
+ std::stringstream input(
+ "[Adblock Plus 2.0]\n"
+ "! Title: EasyList\n"
+ "Definitely NOT a header line\n"
+ "! Version: 202204050843");
+
+ auto metadata = Metadata::FromStream(input);
+ ASSERT_TRUE(metadata.has_value());
+ EXPECT_EQ(metadata->title, "EasyList");
+ EXPECT_EQ(metadata->version, "");
+
+ // Reading next line from input returns first not header line
+ std::string line;
+ std::getline(input, line);
+ EXPECT_EQ(line, "Definitely NOT a header line");
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/snippet_filter_test.cc b/components/adblock/core/converter/parser/test/snippet_filter_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/snippet_filter_test.cc
@@ -0,0 +1,79 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/snippet_filter.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
+TEST(AdblockSnippetFilterTest, ParseEmptySnippetFilter) {
+ EXPECT_FALSE(SnippetFilter::FromString("", "").has_value());
+}
+
+TEST(AdblockSnippetFilterTest, ParseSnippetFilterEmptySnippet) {
+ EXPECT_FALSE(SnippetFilter::FromString("test.com", "").has_value());
+}
+
+TEST(AdblockSnippetFilterTest, ParseSnippetFilter) {
+ auto snippet_filter = SnippetFilter::FromString("test.com", "snippet");
+ ASSERT_TRUE(snippet_filter.has_value());
+ ASSERT_EQ(snippet_filter->snippet_script.size(), 1u);
+ ASSERT_EQ(snippet_filter->snippet_script.at(0).size(), 1u);
+ EXPECT_EQ(snippet_filter->snippet_script.at(0).at(0), "snippet");
+ EXPECT_THAT(snippet_filter->domains.GetIncludeDomains(),
+ ElementsAre("test.com"));
+ EXPECT_TRUE(snippet_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockSnippetFilterTest, ParseSnippetFilterSnippetGetsTokenized) {
+ auto snippet_filter =
+ SnippetFilter::FromString("test.com", "snippet log Test");
+ ASSERT_TRUE(snippet_filter.has_value());
+ ASSERT_EQ(snippet_filter->snippet_script.size(), 1u);
+ ASSERT_EQ(snippet_filter->snippet_script.at(0).size(), 3u);
+ EXPECT_EQ(snippet_filter->snippet_script.at(0).at(0), "snippet");
+ EXPECT_EQ(snippet_filter->snippet_script.at(0).at(1), "log");
+ EXPECT_EQ(snippet_filter->snippet_script.at(0).at(2), "Test");
+ EXPECT_THAT(snippet_filter->domains.GetIncludeDomains(),
+ ElementsAre("test.com"));
+ EXPECT_TRUE(snippet_filter->domains.GetExcludeDomains().empty());
+}
+
+TEST(AdblockSnippetFilterTest, ParseSnippetFilterNoIncludeDomain) {
+ EXPECT_FALSE(SnippetFilter::FromString("~test.com", "snippet").has_value());
+}
+
+TEST(AdblockSnippetFilterTest,
+ ParseSnippetFilterDomainsWithNoSubdomainRemoved) {
+ auto snippet_filter = SnippetFilter::FromString(
+ "org,example.org,~localhost,~example-too.org", "snippet");
+ ASSERT_TRUE(snippet_filter.has_value());
+ ASSERT_EQ(snippet_filter->snippet_script.size(), 1u);
+ ASSERT_EQ(snippet_filter->snippet_script.at(0).size(), 1u);
+ EXPECT_EQ(snippet_filter->snippet_script.at(0).at(0), "snippet");
+ EXPECT_THAT(snippet_filter->domains.GetIncludeDomains(),
+ ElementsAre("example.org"));
+ EXPECT_THAT(snippet_filter->domains.GetExcludeDomains(),
+ UnorderedElementsAre("example-too.org", "localhost"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/snippet_tokenizer_test.cc b/components/adblock/core/converter/parser/test/snippet_tokenizer_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/snippet_tokenizer_test.cc
@@ -0,0 +1,141 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/snippet_tokenizer.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using ::testing::ElementsAreArray;
+
+class AdblockSnippetTokenizerTest : public testing::Test {
+ public:
+ void Validate(const std::string& input,
+ const SnippetTokenizer::SnippetScript& output) {
+ auto script = SnippetTokenizer::Tokenize(input);
+ ASSERT_EQ(script.size(), output.size());
+ for (size_t n = 0, cnt = script.size(); n != cnt; ++n) {
+ EXPECT_THAT(script[n], ElementsAreArray(output[n]));
+ }
+ }
+};
+
+TEST_F(AdblockSnippetTokenizerTest, NoArguments) {
+ Validate("foo", {{"foo"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, OneArgument) {
+ Validate("foo 1", {{"foo", "1"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, TwoArguments) {
+ Validate("foo 1 Hello", {{"foo", "1", "Hello"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentWithSpace) {
+ Validate("foo Hello\\ world", {{"foo", "Hello world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentQuoted) {
+ Validate("foo 'Hello world'", {{"foo", "Hello world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentWithQuote) {
+ Validate("foo 'Hello \\'world\\''", {{"foo", "Hello 'world'"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentWithEscapedSemicolon) {
+ Validate("foo TL\\;DR", {{"foo", "TL;DR"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentWithQuotedSemicolon) {
+ Validate("foo 'TL;DR'", {{"foo", "TL;DR"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, ArgumentWithEscapeSequences) {
+ Validate("foo yin\\tyang\\n", {{"foo", "yin\tyang\n"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, MultipleCommands) {
+ Validate("foo; bar", {{"foo"}, {"bar"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, MultipleCommandsWithArguments) {
+ Validate("foo 1 Hello; bar world! #",
+ {{"foo", "1", "Hello"}, {"bar", "world!", "#"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, MultipleCommandsWithQuotation) {
+ Validate(
+ "foo 1 'Hello, \\'Tommy\\'!' ;bar Hi!\\ How\\ are\\ you? "
+ "http://example.com",
+ {{"foo", "1", "Hello, 'Tommy'!"},
+ {"bar", "Hi! How are you?", "http://example.com"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, SpecialCharacters) {
+ Validate("fo\\'\\ \\ \\\t\\\n\\;o 1 2 3; 'b a r' 1 2",
+ {{"fo' \t\n;o", "1", "2", "3"}, {"b a r", "1", "2"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, UnicodeStaysAsIs) {
+ Validate("foo \\u0062\\ud83d\\ude42r", {{"foo", "\\u0062\\ud83d\\ude42r"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, MultipleCommandsNoArguments) {
+ Validate("foo; ;;; ; ; bar 1", {{"foo"}, {"bar", "1"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, MultipleCommandsBlankArguments) {
+ Validate("foo '' ''", {{"foo", "", ""}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, QuotedSpaceWithinArgument) {
+ Validate("foo Hello' 'world", {{"foo", "Hello world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, QuotedComaWithinArgument) {
+ Validate("foo Hello','world", {{"foo", "Hello,world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, QuotedCharsWithinArgument) {
+ Validate("foo Hello', 'world", {{"foo", "Hello, world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, QuotedStartOfArgument) {
+ Validate("foo 'Hello, 'world", {{"foo", "Hello, world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, QuotedEndOfArgument) {
+ Validate("foo Hello', world'", {{"foo", "Hello, world"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, NoClosingQuote) {
+ Validate("foo 'Hello, world", {});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, NoClosingQuoteLastCommand) {
+ Validate("foo Hello; bar 'How are you?", {{"foo", "Hello"}});
+}
+
+TEST_F(AdblockSnippetTokenizerTest, EndingWithBackslash) {
+ Validate("foo Hello; bar 'How are you?' \\", {{"foo", "Hello"}});
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/url_filter_options_test.cc b/components/adblock/core/converter/parser/test/url_filter_options_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/url_filter_options_test.cc
@@ -0,0 +1,258 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/url_filter_options.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
+namespace adblock {
+
+TEST(AdblockUrlFilterOptionsTest, DefaultOptions) {
+ UrlFilterOptions options;
+ EXPECT_FALSE(options.IsMatchCase());
+ EXPECT_FALSE(options.IsPopup());
+ EXPECT_TRUE(options.IsSubresource());
+ EXPECT_EQ(options.ThirdParty(), UrlFilterOptions::ThirdPartyOption::Ignore);
+ EXPECT_EQ(options.ContentTypes(), ContentType::Default);
+ EXPECT_FALSE(options.Rewrite().has_value());
+ EXPECT_TRUE(options.Domains().GetIncludeDomains().empty());
+ EXPECT_TRUE(options.Domains().GetExcludeDomains().empty());
+ EXPECT_TRUE(options.Sitekeys().empty());
+ EXPECT_FALSE(options.Csp().has_value());
+ EXPECT_FALSE(options.Headers().has_value());
+ EXPECT_TRUE(options.ExceptionTypes().empty());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseEmptyListNoValuesSet) {
+ auto options = UrlFilterOptions::FromString("");
+ ASSERT_TRUE(options.has_value());
+ EXPECT_FALSE(options->IsMatchCase());
+ EXPECT_FALSE(options->IsPopup());
+ EXPECT_TRUE(options->IsSubresource());
+ EXPECT_EQ(options->ThirdParty(), UrlFilterOptions::ThirdPartyOption::Ignore);
+ EXPECT_EQ(options->ContentTypes(), ContentType::Default);
+ EXPECT_FALSE(options->Rewrite().has_value());
+ EXPECT_TRUE(options->Domains().GetIncludeDomains().empty());
+ EXPECT_TRUE(options->Domains().GetExcludeDomains().empty());
+ EXPECT_TRUE(options->Sitekeys().empty());
+ EXPECT_FALSE(options->Csp().has_value());
+ EXPECT_FALSE(options->Headers().has_value());
+ EXPECT_TRUE(options->ExceptionTypes().empty());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseMatchCase) {
+ EXPECT_TRUE(UrlFilterOptions::FromString("match-case")->IsMatchCase());
+ EXPECT_FALSE(UrlFilterOptions::FromString("~match-case")->IsMatchCase());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParsePopupOption) {
+ auto options = UrlFilterOptions::FromString("popup");
+ EXPECT_TRUE(options->IsPopup());
+ EXPECT_FALSE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseThirdParty) {
+ auto options = UrlFilterOptions::FromString("third-party");
+ EXPECT_EQ(options->ThirdParty(),
+ UrlFilterOptions::ThirdPartyOption::ThirdPartyOnly);
+ options = UrlFilterOptions::FromString("~third-party");
+ EXPECT_EQ(options->ThirdParty(),
+ UrlFilterOptions::ThirdPartyOption::FirstPartyOnly);
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseRewriteOptions) {
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-text")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankText);
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-css")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankCss);
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-js")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankJs);
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-html")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankHtml);
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-mp3")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankMp3);
+ EXPECT_EQ(UrlFilterOptions::FromString("rewrite=abp-resource:blank-mp4")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankMp4);
+ EXPECT_EQ(
+ UrlFilterOptions::FromString("rewrite=abp-resource:1x1-transparent-gif")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_TransparentGif1x1);
+ EXPECT_EQ(
+ UrlFilterOptions::FromString("rewrite=abp-resource:2x2-transparent-png")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_TransparentPng2x2);
+ EXPECT_EQ(
+ UrlFilterOptions::FromString("rewrite=abp-resource:3x2-transparent-png")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_TransparentPng3x2);
+ EXPECT_EQ(
+ UrlFilterOptions::FromString("rewrite=abp-resource:32x32-transparent-png")
+ ->Rewrite()
+ .value(),
+ UrlFilterOptions::RewriteOption::AbpResource_TransparentPng32x32);
+ EXPECT_FALSE(
+ UrlFilterOptions::FromString("rewrite=abp-resource:32x32-transparent-png")
+ ->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseInvalidRewriteOptions) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("rewrite").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("rewrite=").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("rewrite=invalid-rewrite-option")
+ .has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseDomainOption) {
+ auto options = UrlFilterOptions::FromString(
+ "domain=domain.com|~exclude-domain.com|whatever.org");
+ ASSERT_TRUE(options.has_value());
+ EXPECT_THAT(options->Domains().GetIncludeDomains(),
+ UnorderedElementsAre("domain.com", "whatever.org"));
+ EXPECT_THAT(options->Domains().GetExcludeDomains(),
+ ElementsAre("exclude-domain.com"));
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseDomainsInvalid) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("domain").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("domain=").has_value());
+ EXPECT_FALSE(
+ UrlFilterOptions::FromString("domain=https://example.com").has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParsingOptionsIsCaseInsensitive) {
+ auto options = UrlFilterOptions::FromString("~ThIRd-PaRtY");
+ ASSERT_TRUE(options.has_value());
+ EXPECT_EQ(options->ThirdParty(),
+ UrlFilterOptions::ThirdPartyOption::FirstPartyOnly);
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseSitekeyOption) {
+ auto options = UrlFilterOptions::FromString("sitekey=b|A|D|c");
+ ASSERT_TRUE(options.has_value());
+ // NOTE: alphabetically ordered, uppercase
+ EXPECT_THAT(options->Sitekeys(), ElementsAre(SiteKey{"A"}, SiteKey{"B"},
+ SiteKey{"C"}, SiteKey{"D"}));
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseInvalidSitekeyOption) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("sitekey").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("sitekey=").has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseCspOption) {
+ auto options = UrlFilterOptions::FromString("csp=script-src: 'self'");
+ ASSERT_TRUE(options.has_value());
+ auto csp = options->Csp();
+ ASSERT_TRUE(csp.has_value());
+ EXPECT_EQ(csp.value(), "script-src: 'self'");
+ EXPECT_FALSE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseEmptyCspOption) {
+ auto options = UrlFilterOptions::FromString("csp");
+ ASSERT_TRUE(options.has_value());
+ auto csp = options->Csp();
+ ASSERT_TRUE(csp.has_value());
+ EXPECT_EQ(csp.value(), "");
+ EXPECT_FALSE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseInvalidCspOption) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("csp=report-uri").has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseHeaderOption) {
+ auto options =
+ UrlFilterOptions::FromString("header=X-Frame-Options=sameorigin");
+ ASSERT_TRUE(options.has_value());
+ auto header = options->Headers();
+ ASSERT_TRUE(header.has_value());
+ EXPECT_EQ(header.value(), "X-Frame-Options=sameorigin");
+ EXPECT_FALSE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseEmptyHeaderOption) {
+ auto options = UrlFilterOptions::FromString("header");
+ ASSERT_TRUE(options.has_value());
+ auto header = options->Headers();
+ ASSERT_TRUE(header.has_value());
+ EXPECT_EQ(header.value(), "");
+ EXPECT_FALSE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, InvalidOptionDoesNotGetParsed) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("invalid_option").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("invalid=").has_value());
+ EXPECT_FALSE(UrlFilterOptions::FromString("invalid=option").has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseMultipleOptions) {
+ auto options = UrlFilterOptions::FromString("csp, third-party");
+ ASSERT_TRUE(options.has_value());
+ auto csp = options->Csp();
+ ASSERT_TRUE(csp.has_value());
+ EXPECT_EQ(csp.value(), "");
+ EXPECT_EQ(options->ThirdParty(),
+ UrlFilterOptions::ThirdPartyOption::ThirdPartyOnly);
+}
+
+TEST(AdblockUrlFilterOptionsTest, InvalidOptionMakesOptionsInvalid) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("csp, invalid-option").has_value());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseInvalidOption) {
+ EXPECT_FALSE(UrlFilterOptions::FromString("invalid_option"));
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseInverseContentType) {
+ auto options = UrlFilterOptions::FromString("~other, other");
+ ASSERT_TRUE(options.has_value());
+ auto content_types = options->ContentTypes();
+ EXPECT_TRUE(content_types & ContentType::Other);
+ EXPECT_TRUE(options->IsSubresource());
+}
+
+TEST(AdblockUrlFilterOptionsTest, ParseExceptionTypes) {
+ auto options =
+ UrlFilterOptions::FromString("document, elemhide, generichide");
+ ASSERT_TRUE(options.has_value());
+ EXPECT_THAT(options->ExceptionTypes(),
+ ElementsAre(UrlFilterOptions::ExceptionType::Document,
+ UrlFilterOptions::ExceptionType::Elemhide,
+ UrlFilterOptions::ExceptionType::Generichide));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/test/url_filter_test.cc b/components/adblock/core/converter/parser/test/url_filter_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/test/url_filter_test.cc
@@ -0,0 +1,163 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/url_filter.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAre;
+
+namespace adblock {
+
+TEST(AdblockUrlFilterTest, ParseEmptyUrlFilter) {
+ EXPECT_FALSE(UrlFilter::FromString("").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseInvalidUrlFilter) {
+ EXPECT_FALSE(UrlFilter::FromString("@@").has_value());
+ EXPECT_FALSE(UrlFilter::FromString("$valid_option").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseAllowingUrlFilter) {
+ auto url_filter = UrlFilter::FromString("@@allowing_filter");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_TRUE(url_filter->is_allowing);
+ EXPECT_EQ(url_filter->pattern, "allowing_filter");
+}
+
+TEST(AdblockUrlFilterTest, ParseUrlFilterWithOptions) {
+ auto url_filter = UrlFilter::FromString(
+ "pattern$csp=script-src: 'none',domain=example.org|~example.com");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_FALSE(url_filter->is_allowing);
+ EXPECT_EQ(url_filter->pattern, "pattern");
+ ASSERT_TRUE(url_filter->options.Csp().has_value());
+ EXPECT_EQ(url_filter->options.Csp(), "script-src: 'none'");
+ EXPECT_THAT(url_filter->options.Domains().GetIncludeDomains(),
+ ElementsAre("example.org"));
+ EXPECT_THAT(url_filter->options.Domains().GetExcludeDomains(),
+ ElementsAre("example.com"));
+}
+
+TEST(AdblockUrlFilterTest, ParseUrlFilterWithAnInvalidOption) {
+ EXPECT_FALSE(UrlFilter::FromString("pattern$invalid_option").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseBlockingCspFilterWithNoDirectives) {
+ EXPECT_FALSE(UrlFilter::FromString("pattern$csp").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseAllowingCspFilterWithNoDirectives) {
+ auto url_filter = UrlFilter::FromString("@@pattern$csp");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_TRUE(url_filter->is_allowing);
+ EXPECT_EQ(url_filter->pattern, "pattern");
+ ASSERT_TRUE(url_filter->options.Csp().has_value());
+ EXPECT_EQ(url_filter->options.Csp(), "");
+}
+
+TEST(AdblockUrlFilterTest, ParseBlockingHeaderFilterWithNoDirectives) {
+ EXPECT_FALSE(UrlFilter::FromString("pattern$header").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseRewriteFilterWithDomain) {
+ auto url_filter = UrlFilter::FromString(
+ "||pattern$rewrite=abp-resource:blank-css,domain=example.hu");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_FALSE(url_filter->is_allowing);
+ EXPECT_EQ(url_filter->pattern, "||pattern");
+ ASSERT_TRUE(url_filter->options.Rewrite().has_value());
+ EXPECT_EQ(url_filter->options.Rewrite(),
+ UrlFilterOptions::RewriteOption::AbpResource_BlankCss);
+}
+
+TEST(AdblockUrlFilterTest, ParseRewriteFilterWithNoDomainOption) {
+ EXPECT_FALSE(UrlFilter::FromString("||pattern$rewrite=abp-resource:blank-css")
+ .has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseRewriteFilterWithNoIncludeDomain) {
+ EXPECT_FALSE(
+ UrlFilter::FromString(
+ "||pattern$rewrite=abp-resource:blank-css,domain=~exclude_domain.com")
+ .has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseRewriteFilterWithThirdParty) {
+ EXPECT_FALSE(UrlFilter::FromString("||pattern$rewrite=abp-resource:blank-css,"
+ "domain=example.com,third-party")
+ .has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseRewriteFilterWithInvalidPatter) {
+ EXPECT_FALSE(UrlFilter::FromString(
+ "pattern$rewrite=abp-resource:blank-css,domain=example.hu")
+ .has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseBlockingFilterWithExceptionType) {
+ EXPECT_FALSE(UrlFilter::FromString("pattern$generichide").has_value());
+}
+
+TEST(AdblockUrlFilterTest, ParseAllowingFilterWithoutExceptionType) {
+ auto url_filter = UrlFilter::FromString("@@pattern");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_TRUE(url_filter->is_allowing);
+ EXPECT_TRUE(url_filter->options.ExceptionTypes().empty());
+}
+
+TEST(AdblockUrlFilterTest, ParseAllowingFilterWithExceptionType) {
+ auto url_filter = UrlFilter::FromString("@@pattern$generichide");
+ ASSERT_TRUE(url_filter.has_value());
+ EXPECT_TRUE(url_filter->is_allowing);
+ EXPECT_THAT(url_filter->options.ExceptionTypes(),
+ ElementsAre(UrlFilterOptions::ExceptionType::Generichide));
+}
+
+TEST(AdblockUrlFilterTest, ParseUnspecifcGenericFilter) {
+ // Filter too short and too generic, could "break the internet":
+ EXPECT_FALSE(UrlFilter::FromString("adv").has_value());
+ // |-anchored filters still too short:
+ EXPECT_FALSE(UrlFilter::FromString("|adv").has_value());
+ EXPECT_FALSE(UrlFilter::FromString("||adv").has_value());
+ // Short filter is content-type-specific but not domain-specific:
+ EXPECT_FALSE(UrlFilter::FromString("n$image").has_value());
+
+ // Short pattern OK because the filter is domain specific:
+ EXPECT_TRUE(UrlFilter::FromString("n$domain=example.com").has_value());
+ // Filter pattern long enough:
+ EXPECT_TRUE(UrlFilter::FromString("advert").has_value());
+ // Filter pattern contains wildcard, allowed to be short:
+ EXPECT_TRUE(UrlFilter::FromString("a*").has_value());
+}
+
+TEST(AdblockUrlFilterTest, RejectInvalidHostAnchoredFilter) {
+ // A host-anchored filter that starts from a protocol is typically a mistake.
+ // The intention was probably ||test.com/ad.jpg, or https://test.com/ad.jpg
+ // (with no ||). But the combination of host-anchoring and protocol is
+ // invalid.
+ EXPECT_FALSE(UrlFilter::FromString("||https://test.com/ad.jpg").has_value());
+ EXPECT_FALSE(UrlFilter::FromString("||://test.com/ad.jpg").has_value());
+
+ // We shouldn't discard *all* host-anchored filters with a protocol in the
+ // pattern. The filter below is valid.
+ EXPECT_TRUE(UrlFilter::FromString("||test.com/path=https://test.com/ad.jpg")
+ .has_value());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/url_filter.cc b/components/adblock/core/converter/parser/url_filter.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/url_filter.cc
@@ -0,0 +1,232 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/url_filter.h"
+
+#include <string_view>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/common/regex_filter_pattern.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace adblock {
+namespace {
+
+// Converts patterns like ||abc.com/aa|bb into ||abc.com/aa%7Cbb.
+// Non-anchor pipe characters must be escaped to properly match components of
+// a URL. GURL escapes "|" as seen in url/url_canon_path.cc
+// Pipe characters in anchor position (beginning & end) must be left as-is, in
+// order for the tokenizer to see them as anchors.
+std::string SanitizePipeCharacters(std::string pattern) {
+ auto piece = std::string_view(pattern);
+ // Skip up to 2 leading | characters, they are treated as anchors. These
+ // may not be replaced by the escaped variant.
+ int number_of_left_anchors = 0;
+ if (base::StartsWith(piece, "|")) {
+ number_of_left_anchors++;
+ piece.remove_prefix(1);
+ }
+ if (base::StartsWith(piece, "|")) {
+ number_of_left_anchors++;
+ piece.remove_prefix(1);
+ }
+ // Skip up to one trailing | characters, this is the right anchor.
+ bool pattern_has_right_anchor = base::EndsWith(piece, "|");
+ if (pattern_has_right_anchor) {
+ piece.remove_suffix(1);
+ }
+ if (piece.find('|') == std::string_view::npos) {
+ // The most common case, pattern has no pipe characters apart from anchors.
+ // Avoid allocating new strings, pass the input out.
+ return pattern;
+ }
+ // Escape instances of | the same way GURL does it.
+ std::string output;
+ CHECK(base::ReplaceChars(piece, "|", R"(%7C)", &output));
+ // Re-add the unmodified anchors.
+ for (int i = 0; i < number_of_left_anchors; i++) {
+ output.insert(output.begin(), '|');
+ }
+ if (pattern_has_right_anchor) {
+ output.push_back('|');
+ }
+ return output;
+}
+
+bool IsInvalidHostAnchoredFilter(const std::string& pattern) {
+ // Host anchored filters start with a double pipe.
+ if (!base::StartsWith(pattern, "||")) {
+ return false;
+ }
+
+ // If the pattern after the double pipe contains a protocol, for example
+ // "||http://domain.com", it is invalid. It should have been "||domain.com" or
+ // "http://domain.com".
+ // However, "||domain.com/url=http://example.com" is valid.
+ // Discard filters that start with "||http://" or similar.
+ static re2::RE2 starts_with_protocol(R"(^\|\|\w*:\/\/)");
+ return re2::RE2::PartialMatch(pattern, starts_with_protocol);
+}
+
+} // namespace
+
+static constexpr char kAllowingSymbol[] = "@@";
+static constexpr char kOptionSymbol = '$';
+
+bool IsGenericFilterIsNotSpecificEnough(
+ std::string_view filter_str,
+ const absl::optional<UrlFilterOptions>& options) {
+ if (options.has_value() && (!options->Domains().GetExcludeDomains().empty() ||
+ !options->Domains().GetIncludeDomains().empty() ||
+ !options->Sitekeys().empty())) {
+ return false;
+ }
+ const size_t kMinLength = 4;
+ const auto trimmed_filter_str =
+ base::TrimString(filter_str, "|", base::TRIM_LEADING);
+ return trimmed_filter_str.size() < kMinLength &&
+ trimmed_filter_str.find('*') == std::string::npos;
+}
+
+// static
+absl::optional<UrlFilter> UrlFilter::FromString(std::string filter_str) {
+ // For debugging purposes, we may store the original, unparsed filter text in
+ // the flatbuffer. This costs memory, so we only do it if the switch is
+ // enabled.
+ static const bool store_filter_text =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kStoreFilterText);
+ std::string original_filter_text = store_filter_text ? filter_str : "";
+ absl::optional<UrlFilterOptions> options;
+ bool is_allowing = base::StartsWith(filter_str, kAllowingSymbol);
+ if (is_allowing) {
+ filter_str.erase(0, 2);
+ }
+
+ // TODO(DPD-1277): Support filters that contain multiple '$'
+ size_t option_selector_it = filter_str.rfind(kOptionSymbol);
+ if (option_selector_it != std::string::npos &&
+ !ExtractRegexFilterFromPattern(filter_str)) {
+ std::string option_list = filter_str.substr(option_selector_it + 1);
+ options = UrlFilterOptions::FromString(option_list);
+
+ if (!options.has_value()) {
+ return {};
+ }
+
+ if (options->Csp().has_value() && options->Csp().value().empty() &&
+ !is_allowing) {
+ VLOG(1) << "[eyeo] Invalid CSP filter. Blocking CSP filter requires "
+ "directives";
+ return {};
+ }
+
+ if (options->Headers().has_value() && options->Headers().value().empty() &&
+ !is_allowing) {
+ VLOG(1) << "[eyeo] Invalid header filter. Blocking header filter "
+ "requires directives";
+ return {};
+ }
+
+ if (!options->IsSubresource() && !options->ExceptionTypes().empty() &&
+ !is_allowing) {
+ VLOG(1) << "[eyeo] Exception options can only be used with allowing "
+ "filters";
+ return {};
+ }
+
+ filter_str.erase(option_selector_it);
+ }
+
+ if (filter_str.empty() && !options.has_value()) {
+ return {};
+ }
+
+ if (!ExtractRegexFilterFromPattern(filter_str)) {
+ // It's rare, but some filters contain pipe characters ("|") that are not
+ // anchors but are instead integral parts of the URL they intend to match.
+ // GURL escapes "|"" characters and we need to similarly escape such
+ // occurrences in the filter.
+ filter_str = SanitizePipeCharacters(std::move(filter_str));
+
+ // Most filters are case-insensitive, we may lowercase them along with
+ // lowercasing the URL during matching. This simplifies and speeds up the
+ // matching algorithm. Do not lowercase case-sensitive filters.
+ if ((!options || !options->IsMatchCase())) {
+ filter_str = base::ToLowerASCII(filter_str);
+ }
+ }
+
+ if (options.has_value() && options->Rewrite().has_value()) {
+ if (options->ThirdParty() ==
+ UrlFilterOptions::ThirdPartyOption::ThirdPartyOnly) {
+ VLOG(1) << "[eyeo] Rewrite filter must not be used together with the "
+ "third-party filter option";
+ return {};
+ }
+
+ if (!base::StartsWith(filter_str, "||") && filter_str != "*" &&
+ !filter_str.empty()) {
+ VLOG(1) << "[eyeo] Rewrite filter pattern must either be a star (*) "
+ "or start with a domain anchor double pipe (||)";
+ return {};
+ }
+
+ if (options->Domains().GetIncludeDomains().empty() &&
+ options->ThirdParty() == UrlFilterOptions::ThirdPartyOption::Ignore) {
+ VLOG(1) << "[eyeo] Rewrite filter must be restricted to at least one "
+ "domain using the domain filter option or have ~third-party "
+ "option";
+ return {};
+ }
+ }
+
+ if (IsInvalidHostAnchoredFilter(filter_str)) {
+ VLOG(1) << "[eyeo] Invalid host anchored filter: " << filter_str;
+ return {};
+ }
+
+ if (IsGenericFilterIsNotSpecificEnough(filter_str, options)) {
+ VLOG(1) << "[eyeo] Generic url filter is not specific enough. Must be "
+ "longer than 3 characters or domain-specific.";
+ return {};
+ }
+
+ if (!options.has_value()) {
+ options = UrlFilterOptions();
+ }
+
+ return UrlFilter(is_allowing, std::move(filter_str),
+ std::move(options.value()), std::move(original_filter_text));
+}
+
+UrlFilter::UrlFilter(bool is_allowing,
+ std::string pattern,
+ UrlFilterOptions options,
+ std::string original_filter_text)
+ : is_allowing(is_allowing),
+ pattern(std::move(pattern)),
+ options(std::move(options)),
+ original_filter_text(std::move(original_filter_text)) {}
+UrlFilter::UrlFilter(const UrlFilter& other) = default;
+UrlFilter::UrlFilter(UrlFilter&& other) = default;
+UrlFilter::~UrlFilter() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/url_filter.h b/components/adblock/core/converter/parser/url_filter.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/url_filter.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_
+
+#include <string>
+
+#include "components/adblock/core/converter/parser/url_filter_options.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace adblock {
+
+class UrlFilter {
+ public:
+ static absl::optional<UrlFilter> FromString(std::string filter_str);
+
+ UrlFilter(const UrlFilter& other);
+ UrlFilter(UrlFilter&& other);
+ ~UrlFilter();
+
+ const bool is_allowing;
+ const std::string pattern;
+ const UrlFilterOptions options;
+ const std::string original_filter_text; // Empty, unless for debugging
+
+ private:
+ UrlFilter(bool is_allowing,
+ std::string pattern,
+ UrlFilterOptions options,
+ std::string original_filter_text);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_
diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/components/adblock/core/converter/parser/url_filter_options.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/url_filter_options.cc
@@ -0,0 +1,267 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/parser/url_filter_options.h"
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace adblock {
+
+using SiteKeys = std::vector<SiteKey>;
+
+static constexpr char kDomainOrSitekeySeparator[] = "|";
+static constexpr char kInverseSymbol = '~';
+
+// static
+absl::optional<UrlFilterOptions> UrlFilterOptions::FromString(
+ const std::string& option_list) {
+ bool is_match_case = false;
+ bool is_popup_filter = false;
+ bool is_subresource = false;
+ ThirdPartyOption third_party = ThirdPartyOption::Ignore;
+ absl::optional<RewriteOption> rewrite;
+ DomainOption domains;
+ SiteKeys sitekeys;
+ absl::optional<std::string> csp;
+ absl::optional<std::string> headers;
+ uint32_t content_types = 0;
+ std::set<ExceptionType> exception_types;
+
+ bool is_inverse_option;
+ std::string key, value;
+ for (auto& option : base::SplitString(option_list, ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY)) {
+ if (option.empty()) {
+ continue;
+ }
+
+ is_inverse_option = option.front() == kInverseSymbol;
+ if (is_inverse_option) {
+ option.erase(0, 1);
+ }
+
+ size_t delimiter_pos = option.find('=');
+ if (delimiter_pos != std::string::npos) {
+ key = option.substr(0, delimiter_pos);
+ value = option.substr(delimiter_pos + 1);
+ } else {
+ key = option;
+ }
+
+ key = base::ToLowerASCII(key);
+ base::RemoveChars(key, base::kWhitespaceASCII, &key);
+
+ if (key == "match-case") {
+ is_match_case = !is_inverse_option;
+ } else if (key == "popup") {
+ is_popup_filter = true;
+ } else if (key == "third-party") {
+ third_party = !is_inverse_option ? ThirdPartyOption::ThirdPartyOnly
+ : ThirdPartyOption::FirstPartyOnly;
+ } else if (key == "rewrite") {
+ rewrite = ParseRewrite(value);
+ if (!rewrite.has_value()) {
+ VLOG(1) << "[eyeo] Invalid rewrite filter value: " << value;
+ return {};
+ }
+ } else if (key == "domain") {
+ if (value.empty()) {
+ VLOG(1) << "[eyeo] Domain option has to have a value.";
+ return {};
+ }
+ domains = DomainOption::FromString(value, kDomainOrSitekeySeparator);
+ if (domains.UnrestrictedByDomain()) {
+ VLOG(1) << "[eyeo] Domain option has no valid domain.";
+ return {};
+ }
+ } else if (key == "sitekey") {
+ if (value.empty()) {
+ VLOG(1) << "[eyeo] Sitekey option has to have a value.";
+ return {};
+ }
+ sitekeys = ParseSitekeys(value);
+ } else if (key == "csp") {
+ if (!IsValidCsp(value)) {
+ VLOG(1) << "[eyeo] Invalid CSP filter directives: " << value;
+ return {};
+ }
+ csp = value;
+ } else if (key == "header") {
+ ParseHeaders(value);
+ headers = value;
+ } else {
+ ContentType content_type = ContentTypeFromString(key);
+ if (content_type != ContentType::Unknown) {
+ is_subresource = true;
+ if (is_inverse_option) {
+ if (content_types == 0) {
+ content_types = ContentType::Default;
+ }
+
+ content_types &= ~content_type;
+ } else {
+ content_types |= content_type;
+ }
+ continue;
+ }
+
+ auto exception_type = ExceptionTypeFromString(key);
+ if (exception_type) {
+ // NOTE: Inverse exception types are not supported
+ exception_types.emplace(exception_type.value());
+ continue;
+ }
+
+ VLOG(1) << "[eyeo] Unknown filter option: " << key;
+ return {};
+ }
+ }
+
+ if (exception_types.empty() && !is_popup_filter && !csp.has_value() &&
+ !rewrite.has_value() && !headers.has_value()) {
+ is_subresource = true;
+ }
+
+ if (content_types == 0) {
+ content_types = ContentType::Default;
+ }
+
+ return UrlFilterOptions(is_match_case, is_popup_filter, is_subresource,
+ third_party, content_types, std::move(rewrite),
+ std::move(domains), std::move(sitekeys),
+ std::move(csp), std::move(headers),
+ std::move(exception_types));
+}
+
+UrlFilterOptions::UrlFilterOptions()
+ : is_match_case_(false),
+ is_popup_filter_(false),
+ is_subresource_(true),
+ third_party_(ThirdPartyOption::Ignore),
+ content_types_(ContentType::Default) {}
+
+// static
+absl::optional<UrlFilterOptions::RewriteOption> UrlFilterOptions::ParseRewrite(
+ const std::string& rewrite_value) {
+ if (rewrite_value == "abp-resource:blank-text") {
+ return RewriteOption::AbpResource_BlankText;
+ } else if (rewrite_value == "abp-resource:blank-css") {
+ return RewriteOption::AbpResource_BlankCss;
+ } else if (rewrite_value == "abp-resource:blank-js") {
+ return RewriteOption::AbpResource_BlankJs;
+ } else if (rewrite_value == "abp-resource:blank-html") {
+ return RewriteOption::AbpResource_BlankHtml;
+ } else if (rewrite_value == "abp-resource:blank-mp3") {
+ return RewriteOption::AbpResource_BlankMp3;
+ } else if (rewrite_value == "abp-resource:blank-mp4") {
+ return RewriteOption::AbpResource_BlankMp4;
+ } else if (rewrite_value == "abp-resource:1x1-transparent-gif") {
+ return RewriteOption::AbpResource_TransparentGif1x1;
+ } else if (rewrite_value == "abp-resource:2x2-transparent-png") {
+ return RewriteOption::AbpResource_TransparentPng2x2;
+ } else if (rewrite_value == "abp-resource:3x2-transparent-png") {
+ return RewriteOption::AbpResource_TransparentPng3x2;
+ } else if (rewrite_value == "abp-resource:32x32-transparent-png") {
+ return RewriteOption::AbpResource_TransparentPng32x32;
+ } else {
+ return {};
+ }
+}
+
+// static
+SiteKeys UrlFilterOptions::ParseSitekeys(const std::string& sitekey_value) {
+ SiteKeys sitekeys;
+ for (auto& sitekey : base::SplitString(
+ base::ToUpperASCII(sitekey_value), kDomainOrSitekeySeparator,
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
+ sitekeys.emplace_back(std::move(sitekey));
+ }
+ std::sort(sitekeys.begin(), sitekeys.end());
+ return sitekeys;
+}
+
+// static
+bool UrlFilterOptions::IsValidCsp(const std::string& csp_value) {
+ static re2::RE2 invalid_csp(
+ "(;|^) "
+ "?(base-uri|referrer|report-to|report-uri|upgrade-insecure-requests)\\b");
+
+ return !(re2::RE2::PartialMatch(
+ re2::StringPiece(csp_value.data(), csp_value.size()), invalid_csp));
+}
+
+// static
+void UrlFilterOptions::ParseHeaders(std::string& headers_value) {
+ // replace \x2c with actual ,
+ static re2::RE2 r1("([^\\\\])\\\\x2c");
+ re2::RE2::GlobalReplace(&headers_value, r1, "\\1,");
+
+ // remove extra escape for \\x2c which left
+ static re2::RE2 r2("\\\\x2c");
+ re2::RE2::GlobalReplace(&headers_value, r2, "x2c");
+}
+
+// static
+absl::optional<UrlFilterOptions::ExceptionType>
+UrlFilterOptions::ExceptionTypeFromString(const std::string& exception_type) {
+ if (exception_type == "document") {
+ return ExceptionType::Document;
+ } else if (exception_type == "genericblock") {
+ return ExceptionType::Genericblock;
+ } else if (exception_type == "elemhide") {
+ return ExceptionType::Elemhide;
+ } else if (exception_type == "generichide") {
+ return ExceptionType::Generichide;
+ }
+ return {};
+}
+
+UrlFilterOptions::UrlFilterOptions(
+ const bool is_match_case,
+ const bool is_popup_filter,
+ const bool is_subresource,
+ const ThirdPartyOption third_party,
+ const uint32_t content_types,
+ const absl::optional<RewriteOption> rewrite,
+ const DomainOption domains,
+ const SiteKeys sitekeys,
+ const absl::optional<std::string> csp,
+ const absl::optional<std::string> headers,
+ const std::set<ExceptionType> exception_types)
+ : is_match_case_(is_match_case),
+ is_popup_filter_(is_popup_filter),
+ is_subresource_(is_subresource),
+ third_party_(third_party),
+ content_types_(content_types),
+ rewrite_(std::move(rewrite)),
+ domains_(std::move(domains)),
+ sitekeys_(std::move(sitekeys)),
+ csp_(std::move(csp)),
+ headers_(std::move(headers)),
+ exception_types_(std::move(exception_types)) {}
+UrlFilterOptions::UrlFilterOptions(const UrlFilterOptions& other) = default;
+UrlFilterOptions::UrlFilterOptions(UrlFilterOptions&& other) = default;
+UrlFilterOptions& UrlFilterOptions::operator=(const UrlFilterOptions& other) =
+ default;
+UrlFilterOptions& UrlFilterOptions::operator=(UrlFilterOptions&& other) =
+ default;
+UrlFilterOptions::~UrlFilterOptions() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/parser/url_filter_options.h b/components/adblock/core/converter/parser/url_filter_options.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/parser/url_filter_options.h
@@ -0,0 +1,122 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/converter/parser/domain_option.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace adblock {
+
+class UrlFilterOptions {
+ public:
+ enum class ThirdPartyOption {
+ Ignore,
+ ThirdPartyOnly,
+ FirstPartyOnly,
+ };
+
+ enum class RewriteOption {
+ AbpResource_BlankText,
+ AbpResource_BlankCss,
+ AbpResource_BlankJs,
+ AbpResource_BlankHtml,
+ AbpResource_BlankMp3,
+ AbpResource_BlankMp4,
+ AbpResource_TransparentGif1x1,
+ AbpResource_TransparentPng2x2,
+ AbpResource_TransparentPng3x2,
+ AbpResource_TransparentPng32x32,
+ };
+
+ enum class ExceptionType {
+ Document,
+ Elemhide,
+ Generichide,
+ Genericblock,
+ };
+
+ static absl::optional<UrlFilterOptions> FromString(
+ const std::string& option_list);
+
+ UrlFilterOptions();
+ UrlFilterOptions(const UrlFilterOptions& other);
+ UrlFilterOptions(UrlFilterOptions&& other);
+ UrlFilterOptions& operator=(const UrlFilterOptions& other);
+ UrlFilterOptions& operator=(UrlFilterOptions&& other);
+ ~UrlFilterOptions();
+
+ inline bool IsMatchCase() const { return is_match_case_; }
+ inline bool IsPopup() const { return is_popup_filter_; }
+ inline bool IsSubresource() const { return is_subresource_; }
+ inline ThirdPartyOption ThirdParty() const { return third_party_; }
+ inline const absl::optional<RewriteOption>& Rewrite() const {
+ return rewrite_;
+ }
+ inline const DomainOption& Domains() const { return domains_; }
+ inline const std::vector<SiteKey>& Sitekeys() const { return sitekeys_; }
+ inline const absl::optional<std::string>& Csp() const { return csp_; }
+ inline const absl::optional<std::string>& Headers() const { return headers_; }
+ inline uint32_t ContentTypes() const { return content_types_; }
+ inline const std::set<ExceptionType>& ExceptionTypes() const {
+ return exception_types_;
+ }
+
+ private:
+ UrlFilterOptions(bool is_match_case,
+ bool is_popup_filter,
+ bool is_subresource,
+ ThirdPartyOption third_party,
+ uint32_t content_types,
+ absl::optional<RewriteOption> rewrite,
+ DomainOption domains,
+ std::vector<SiteKey> sitekeys,
+ absl::optional<std::string> csp,
+ absl::optional<std::string> headers,
+ std::set<ExceptionType> exception_types);
+
+ static absl::optional<RewriteOption> ParseRewrite(
+ const std::string& rewrite_value);
+ static std::vector<SiteKey> ParseSitekeys(const std::string& sitekey_value);
+ static bool IsValidCsp(const std::string& csp_value);
+ static void ParseHeaders(std::string& headers_value);
+ static absl::optional<ExceptionType> ExceptionTypeFromString(
+ const std::string& exception_type);
+
+ bool is_match_case_;
+ bool is_popup_filter_;
+ bool is_subresource_;
+ ThirdPartyOption third_party_;
+ uint32_t content_types_;
+ absl::optional<RewriteOption> rewrite_;
+ DomainOption domains_;
+ std::vector<SiteKey> sitekeys_;
+ absl::optional<std::string> csp_;
+ absl::optional<std::string> headers_;
+ std::set<ExceptionType> exception_types_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_
diff --git a/components/adblock/core/converter/serializer/BUILD.gn b/components/adblock/core/converter/serializer/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/BUILD.gn
@@ -0,0 +1,46 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("serializer") {
+ sources = [
+ "filter_keyword_extractor.cc",
+ "filter_keyword_extractor.h",
+ "flatbuffer_serializer.cc",
+ "flatbuffer_serializer.h",
+ "serializer.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core:schema",
+ "//components/adblock/core/common",
+ "//third_party/re2",
+ "//url",
+ ]
+
+ deps = [ "//components/adblock/core/converter/parser" ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "test/filter_keyword_extractor_test.cc" ]
+
+ deps = [
+ ":serializer",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/adblock/core/converter/serializer/filter_keyword_extractor.cc b/components/adblock/core/converter/serializer/filter_keyword_extractor.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/filter_keyword_extractor.cc
@@ -0,0 +1,63 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/serializer/filter_keyword_extractor.h"
+
+#include <algorithm>
+#include <cctype>
+
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/keyword_extractor_utils.h"
+#include "third_party/re2/src/re2/re2.h"
+
+namespace adblock {
+
+absl::optional<std::string> FilterKeywordExtractor::GetNextKeyword() {
+ std::string current_keyword;
+ do {
+ // In case that we are extracting keyword to store a filter
+ // we need to be careful as only one keyword will be used.
+ // So a keyword at the end of the filter might mismatch with keyword
+ // when trying to fetch the filter
+ // for example:
+ // a filter ||domain.cc/in_discovery should not retrieve "discovery" as a
+ // keyword because when we have a valid to block url like this one
+ // domain.cc/in_discovery5 returns with "discovery5" as
+ // one of the extracted keywords instead of "discovery"
+ static const re2::RE2 filter_keyword_extractor(
+ "([^a-zA-Z0-9%*][a-zA-Z0-9%]{2,})");
+ static const re2::RE2 has_a_following_keyword("(^[^a-zA-Z0-9%*])");
+ static const re2::RE2 following_keyword_consume("(^[a-zA-Z0-9%*]*)");
+ if (!RE2::FindAndConsume(&input_, filter_keyword_extractor,
+ &current_keyword)) {
+ return absl::nullopt;
+ }
+ if (!RE2::PartialMatch(input_, has_a_following_keyword)) {
+ RE2::Consume(&input_, following_keyword_consume);
+ current_keyword.clear();
+ continue;
+ }
+ current_keyword = current_keyword.substr(1);
+ } while (utils::IsBadKeyword(current_keyword));
+ return base::ToLowerASCII(current_keyword);
+}
+
+FilterKeywordExtractor::FilterKeywordExtractor(std::string_view url)
+ : input_(url.data(), url.size()), end_of_last_keyword_(input_.begin()) {}
+FilterKeywordExtractor::~FilterKeywordExtractor() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/serializer/filter_keyword_extractor.h b/components/adblock/core/converter/serializer/filter_keyword_extractor.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/filter_keyword_extractor.h
@@ -0,0 +1,59 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_
+
+#include <string>
+#include <string_view>
+
+#include "absl/types/optional.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Keywords allow selecting filters that could potentially match a URL faster
+// than an exhaustive search.
+// This is how it works:
+//
+// 1. A filter pattern is split into keywords via GetNextKeyword()
+// like so:
+// ||content.adblockplus.com/ad
+// becomes:
+// "content", "adblockplus"
+// - "com" is skipped because it's a very common component
+// - "ad" is skipped, explanation in .cc
+//
+// 2. Once we have keywords that describe the filter, the longest or most unique
+// keyword gets chosen to index the filter within the flatbuffer. In this case,
+// "adblockplus".
+class FilterKeywordExtractor {
+ public:
+ explicit FilterKeywordExtractor(std::string_view url);
+ ~FilterKeywordExtractor();
+ absl::optional<std::string> GetNextKeyword();
+
+ private:
+ re2::StringPiece input_;
+ re2::StringPiece::iterator end_of_last_keyword_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_
diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer.cc b/components/adblock/core/converter/serializer/flatbuffer_serializer.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/flatbuffer_serializer.cc
@@ -0,0 +1,490 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/serializer/flatbuffer_serializer.h"
+
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/regex_filter_pattern.h"
+#include "components/adblock/core/converter/parser/filter_classifier.h"
+#include "components/adblock/core/converter/serializer/filter_keyword_extractor.h"
+
+namespace adblock {
+
+class Buffer : public FlatbufferData {
+ public:
+ explicit Buffer(flatbuffers::DetachedBuffer&& buffer)
+ : buffer_(std::move(buffer)) {}
+
+ const uint8_t* data() const override { return buffer_.data(); }
+
+ size_t size() const override { return buffer_.size(); }
+
+ const base::span<const uint8_t> span() const override {
+ return base::as_byte_span(buffer_);
+ }
+
+ private:
+ flatbuffers::DetachedBuffer buffer_;
+};
+
+FlatbufferSerializer::FlatbufferSerializer(GURL subscription_url,
+ bool allow_privileged)
+ : subscription_url_(subscription_url), allow_privileged_(allow_privileged) {
+ SerializeMetadata(Metadata::Default());
+}
+
+FlatbufferSerializer::~FlatbufferSerializer() = default;
+
+std::unique_ptr<FlatbufferData>
+FlatbufferSerializer::GetSerializedSubscription() {
+ auto subscription = flat::CreateSubscription(
+ builder_, metadata_, WriteUrlFilterIndex(url_subresource_block_),
+ WriteUrlFilterIndex(url_subresource_allow_),
+ WriteUrlFilterIndex(url_popup_block_),
+ WriteUrlFilterIndex(url_popup_allow_),
+ WriteUrlFilterIndex(url_document_allow_),
+ WriteUrlFilterIndex(url_elemhide_allow_),
+ WriteUrlFilterIndex(url_generichide_allow_),
+ WriteUrlFilterIndex(url_genericblock_allow_),
+ WriteUrlFilterIndex(url_csp_block_), WriteUrlFilterIndex(url_csp_allow_),
+ WriteUrlFilterIndex(url_rewrite_block_),
+ WriteUrlFilterIndex(url_rewrite_allow_),
+ WriteUrlFilterIndex(url_header_block_),
+ WriteUrlFilterIndex(url_header_allow_),
+ WriteElemhideFilterIndex(elemhide_index_),
+ WriteElemhideFilterIndex(elemhide_emulation_index_),
+ WriteElemhideFilterIndex(elemhide_exception_index_),
+ WriteRemoveFilterIndex(remove_index_),
+ WriteInlineCssFilterIndex(inline_css_index_),
+ WriteSnippetFilterIndex(snippet_index_));
+
+ builder_.Finish(subscription, flat::SubscriptionIdentifier());
+ return std::make_unique<Buffer>(builder_.Release());
+}
+
+void FlatbufferSerializer::SerializeMetadata(const Metadata metadata) {
+ metadata_ = flat::CreateSubscriptionMetadata(
+ builder_, builder_.CreateString(CurrentSchemaVersion()),
+ builder_.CreateString(subscription_url_.spec()),
+ builder_.CreateString(metadata.homepage),
+ builder_.CreateString(metadata.title),
+ builder_.CreateString(metadata.version),
+ metadata.expires.InMilliseconds());
+}
+
+void FlatbufferSerializer::SerializeContentFilter(
+ const ContentFilter content_filter) {
+ switch (content_filter.type) {
+ case FilterType::ElemHide:
+ AddElemhideFilterForDomains(elemhide_index_,
+ content_filter.domains.GetIncludeDomains(),
+ CreateElemHideFilter(content_filter));
+ break;
+ case FilterType::ElemHideException:
+ AddElemhideFilterForDomains(elemhide_exception_index_,
+ content_filter.domains.GetIncludeDomains(),
+ CreateElemHideFilter(content_filter));
+ break;
+ case FilterType::ElemHideEmulation:
+ AddElemhideFilterForDomains(elemhide_emulation_index_,
+ content_filter.domains.GetIncludeDomains(),
+ CreateElemHideFilter(content_filter));
+ break;
+ case FilterType::Remove:
+ AddRemoveFilterForDomains(remove_index_,
+ content_filter.domains.GetIncludeDomains(),
+ CreateRemoveFilter(content_filter));
+ break;
+ case FilterType::InlineCss:
+ if (!allow_privileged_) {
+ VLOG(1) << "[eyeo] Inline CSS filters not allowed";
+ break;
+ }
+ AddInlineCssFilterForDomains(inline_css_index_,
+ content_filter.domains.GetIncludeDomains(),
+ CreateInlineCssFilter(content_filter));
+ break;
+ default:
+ break;
+ }
+}
+
+void FlatbufferSerializer::SerializeSnippetFilter(
+ const SnippetFilter snippet_filter) {
+ if (!allow_privileged_) {
+ VLOG(1) << "[eyeo] Snippet filters not allowed";
+ return;
+ }
+
+ std::vector<flatbuffers::Offset<adblock::flat::SnippetFunctionCall>> offsets;
+ offsets.reserve(snippet_filter.snippet_script.size());
+ for (const auto& cur : snippet_filter.snippet_script) {
+ offsets.push_back(flat::CreateSnippetFunctionCall(
+ builder_, builder_.CreateSharedString(cur.front()),
+ builder_.CreateVectorOfStrings(++cur.begin(), cur.end())));
+ }
+
+ auto offset = flat::CreateSnippetFilter(
+ builder_, {},
+ CreateVectorOfSharedStrings(snippet_filter.domains.GetExcludeDomains()),
+ builder_.CreateVector(offsets));
+ AddSnippetFilterForDomains(
+ snippet_index_, snippet_filter.domains.GetIncludeDomains(), offset);
+}
+
+void FlatbufferSerializer::SerializeUrlFilter(const UrlFilter url_filter) {
+ const auto& options = url_filter.options;
+ if (!allow_privileged_ && options.Headers().has_value()) {
+ VLOG(1) << "[eyeo] Header filters not allowed";
+ return;
+ }
+
+ auto offset = flat::CreateUrlFilter(
+ builder_, builder_.CreateString(url_filter.original_filter_text),
+ builder_.CreateString(url_filter.pattern), options.IsMatchCase(),
+ options.ContentTypes(), ThirdPartyOptionToFb(options.ThirdParty()),
+ CreateVectorOfSharedStringsFromSitekeys(options.Sitekeys()),
+ CreateVectorOfSharedStrings(options.Domains().GetIncludeDomains()),
+ CreateVectorOfSharedStrings(options.Domains().GetExcludeDomains()),
+ options.Rewrite().has_value()
+ ? flat::CreateRewrite(builder_,
+ RewriteOptionToFb(options.Rewrite().value()))
+ : flatbuffers::Offset<flat::Rewrite>(),
+ options.Csp().has_value()
+ ? builder_.CreateSharedString(options.Csp().value())
+ : flatbuffers::Offset<flatbuffers::String>(),
+ options.Headers().has_value()
+ ? builder_.CreateSharedString(options.Headers().value())
+ : flatbuffers::Offset<flatbuffers::String>());
+
+ const absl::optional<std::string_view> keyword_pattern =
+ ExtractRegexFilterFromPattern(url_filter.pattern).has_value()
+ ? absl::optional<std::string_view>()
+ : url_filter.pattern;
+
+ if (options.Headers().has_value()) {
+ AddUrlFilterToIndex(
+ url_filter.is_allowing ? url_header_allow_ : url_header_block_,
+ keyword_pattern, offset);
+ return;
+ }
+
+ if (options.IsPopup()) {
+ AddUrlFilterToIndex(
+ url_filter.is_allowing ? url_popup_allow_ : url_popup_block_,
+ keyword_pattern, offset);
+ }
+
+ if (options.Csp().has_value()) {
+ AddUrlFilterToIndex(
+ url_filter.is_allowing ? url_csp_allow_ : url_csp_block_,
+ keyword_pattern, offset);
+ }
+
+ if (options.Rewrite().has_value()) {
+ AddUrlFilterToIndex(
+ url_filter.is_allowing ? url_rewrite_allow_ : url_rewrite_block_,
+ keyword_pattern, offset);
+ }
+
+ if (options.IsSubresource()) {
+ AddUrlFilterToIndex(url_filter.is_allowing ? url_subresource_allow_
+ : url_subresource_block_,
+ keyword_pattern, offset);
+ }
+
+ for (auto exception_type : options.ExceptionTypes()) {
+ switch (exception_type) {
+ case UrlFilterOptions::ExceptionType::Genericblock:
+ AddUrlFilterToIndex(url_genericblock_allow_, keyword_pattern, offset);
+ break;
+ case UrlFilterOptions::ExceptionType::Generichide:
+ AddUrlFilterToIndex(url_generichide_allow_, keyword_pattern, offset);
+ break;
+ case UrlFilterOptions::ExceptionType::Document:
+ AddUrlFilterToIndex(url_document_allow_, keyword_pattern, offset);
+ break;
+ case UrlFilterOptions::ExceptionType::Elemhide:
+ AddUrlFilterToIndex(url_elemhide_allow_, keyword_pattern, offset);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void FlatbufferSerializer::AddUrlFilterToIndex(
+ UrlFilterIndex& index,
+ absl::optional<std::string_view> pattern_text,
+ flatbuffers::Offset<flat::UrlFilter> filter) {
+ const auto keyword =
+ pattern_text ? FindCandidateKeyword(index, *pattern_text) : "";
+ index[keyword].push_back(filter);
+}
+
+void FlatbufferSerializer::AddElemhideFilterForDomains(
+ ElemhideIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::ElemHideFilter> filter) const {
+ if (include_domains.empty()) {
+ // This is a generic filter, we add those under "" index.
+ index[""].push_back(filter);
+ } else {
+ // Index this filter under each domain it is included for.
+ for (const auto& domain : include_domains) {
+ index[domain].push_back(filter);
+ }
+ }
+}
+
+void FlatbufferSerializer::AddRemoveFilterForDomains(
+ RemoveFilterIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::RemoveFilter> filter) const {
+ // Include domains can not be empty for remove filters
+ DCHECK(!include_domains.empty());
+ for (const auto& domain : include_domains) {
+ index[domain].push_back(filter);
+ }
+}
+
+void FlatbufferSerializer::AddInlineCssFilterForDomains(
+ InlineCssFilterIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::InlineCssFilter> filter) const {
+ // Include domains can not be empty for inline CSS filters
+ DCHECK(!include_domains.empty());
+ for (const auto& domain : include_domains) {
+ index[domain].push_back(filter);
+ }
+}
+
+void FlatbufferSerializer::AddSnippetFilterForDomains(
+ SnippetIndex& index,
+ const std::vector<std::string>& domains,
+ flatbuffers::Offset<flat::SnippetFilter> filter) const {
+ for (const auto& domain : domains) {
+ index[domain].push_back(filter);
+ }
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
+FlatbufferSerializer::CreateVectorOfSharedStrings(
+ const std::vector<std::string>& strings) {
+ std::vector<flatbuffers::Offset<flatbuffers::String>> shared_strings;
+ std::transform(
+ strings.begin(), strings.end(), std::back_inserter(shared_strings),
+ [&](const std::string& s) { return builder_.CreateSharedString(s); });
+ return builder_.CreateVector(std::move(shared_strings));
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
+FlatbufferSerializer::CreateVectorOfSharedStringsFromSitekeys(
+ const std::vector<SiteKey>& sitekeys) {
+ std::vector<flatbuffers::Offset<flatbuffers::String>> shared_strings;
+ std::transform(
+ sitekeys.begin(), sitekeys.end(), std::back_inserter(shared_strings),
+ [&](const SiteKey& s) { return builder_.CreateSharedString(s.value()); });
+ return builder_.CreateVector(std::move(shared_strings));
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::UrlFiltersByKeyword>>>
+FlatbufferSerializer::WriteUrlFilterIndex(const UrlFilterIndex& index) {
+ std::vector<flatbuffers::Offset<flat::UrlFiltersByKeyword>> offsets;
+ offsets.reserve(index.size());
+
+ for (const auto& cur : index) {
+ offsets.push_back(flat::CreateUrlFiltersByKeyword(
+ builder_, builder_.CreateSharedString(cur.first),
+ builder_.CreateVector(cur.second)));
+ }
+
+ return builder_.CreateVector(offsets);
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::ElemHideFiltersByDomain>>>
+FlatbufferSerializer::WriteElemhideFilterIndex(const ElemhideIndex& index) {
+ std::vector<flatbuffers::Offset<flat::ElemHideFiltersByDomain>> offsets;
+ offsets.reserve(index.size());
+
+ for (const auto& cur : index) {
+ offsets.push_back(flat::CreateElemHideFiltersByDomain(
+ builder_, builder_.CreateSharedString(cur.first),
+ builder_.CreateVector(cur.second)));
+ }
+ // Filters must be sorted (by domain), in order for LookupByKey() to work
+ // correctly. This can be also achieved by making ElemhideIndex an ordered
+ // map, but profiling shows sorting an unordered_map at the end is faster by
+ // about 15% (on exceptionrules.txt).
+ return builder_.CreateVectorOfSortedTables(offsets.data(), offsets.size());
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::RemoveFiltersByDomain>>>
+FlatbufferSerializer::WriteRemoveFilterIndex(const RemoveFilterIndex& index) {
+ std::vector<flatbuffers::Offset<flat::RemoveFiltersByDomain>> offsets;
+ offsets.reserve(index.size());
+
+ for (const auto& cur : index) {
+ offsets.push_back(flat::CreateRemoveFiltersByDomain(
+ builder_, builder_.CreateSharedString(cur.first),
+ builder_.CreateVector(cur.second)));
+ }
+
+ return builder_.CreateVector(offsets);
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::InlineCssFiltersByDomain>>>
+FlatbufferSerializer::WriteInlineCssFilterIndex(
+ const InlineCssFilterIndex& index) {
+ std::vector<flatbuffers::Offset<flat::InlineCssFiltersByDomain>> offsets;
+ offsets.reserve(index.size());
+
+ for (const auto& cur : index) {
+ offsets.push_back(flat::CreateInlineCssFiltersByDomain(
+ builder_, builder_.CreateSharedString(cur.first),
+ builder_.CreateVector(cur.second)));
+ }
+
+ return builder_.CreateVector(offsets);
+}
+
+flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::SnippetFiltersByDomain>>>
+FlatbufferSerializer::WriteSnippetFilterIndex(const SnippetIndex& index) {
+ std::vector<flatbuffers::Offset<flat::SnippetFiltersByDomain>> offsets;
+ offsets.reserve(index.size());
+
+ for (const auto& cur : index) {
+ offsets.push_back(flat::CreateSnippetFiltersByDomain(
+ builder_, builder_.CreateSharedString(cur.first),
+ builder_.CreateVector(cur.second)));
+ }
+ return builder_.CreateVector(offsets);
+}
+
+std::string FlatbufferSerializer::FindCandidateKeyword(UrlFilterIndex& index,
+ std::string_view value) {
+ FilterKeywordExtractor keyword_extractor(value);
+ size_t last_size = std::numeric_limits<size_t>::max();
+ std::string keyword;
+ while (auto current_keyword = keyword_extractor.GetNextKeyword()) {
+ std::string candidate = *current_keyword;
+ auto it = index.find(candidate);
+ auto size = it != index.end() ? it->second.size() : 0;
+
+ if (size < last_size ||
+ (size == last_size && candidate.size() > keyword.size())) {
+ last_size = size;
+ keyword = candidate;
+ }
+ }
+ return keyword;
+}
+
+flatbuffers::Offset<flat::ElemHideFilter>
+FlatbufferSerializer::CreateElemHideFilter(
+ const ContentFilter& content_filter) {
+ return flat::CreateElemHideFilter(
+ builder_, {},
+ content_filter.type == FilterType::ElemHideEmulation
+ ? builder_.CreateString(content_filter.selector.data(),
+ content_filter.selector.size())
+ : builder_.CreateString(EscapeSelector(content_filter.selector)),
+ CreateVectorOfSharedStrings(content_filter.domains.GetExcludeDomains()));
+}
+
+flatbuffers::Offset<flat::RemoveFilter>
+FlatbufferSerializer::CreateRemoveFilter(const ContentFilter& content_filter) {
+ return flat::CreateRemoveFilter(
+ builder_, {},
+ builder_.CreateString(content_filter.selector.data(),
+ content_filter.selector.size()),
+ CreateVectorOfSharedStrings(content_filter.domains.GetExcludeDomains()));
+}
+
+flatbuffers::Offset<flat::InlineCssFilter>
+FlatbufferSerializer::CreateInlineCssFilter(
+ const ContentFilter& content_filter) {
+ return flat::CreateInlineCssFilter(
+ builder_, {},
+ builder_.CreateString(content_filter.selector.data(),
+ content_filter.selector.size()),
+ builder_.CreateString(content_filter.modifier.data(),
+ content_filter.modifier.size()),
+ CreateVectorOfSharedStrings(content_filter.domains.GetExcludeDomains()));
+}
+
+// static
+std::string FlatbufferSerializer::EscapeSelector(
+ const std::string_view& value) {
+ std::string escaped;
+ base::ReplaceChars(value, "{", "\\7b ", &escaped);
+ base::ReplaceChars(escaped, "}", "\\7d ", &escaped);
+ return escaped;
+}
+
+// static
+flat::ThirdParty FlatbufferSerializer::ThirdPartyOptionToFb(
+ UrlFilterOptions::ThirdPartyOption option) {
+ if (option == UrlFilterOptions::ThirdPartyOption::ThirdPartyOnly) {
+ return flat::ThirdParty_ThirdPartyOnly;
+ }
+ if (option == UrlFilterOptions::ThirdPartyOption::FirstPartyOnly) {
+ return flat::ThirdParty_FirstPartyOnly;
+ }
+ return flat::ThirdParty_Ignore;
+}
+
+// static
+flat::AbpResource FlatbufferSerializer::RewriteOptionToFb(
+ UrlFilterOptions::RewriteOption option) {
+ switch (option) {
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankText:
+ return flat::AbpResource_BlankText;
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankCss:
+ return flat::AbpResource_BlankCss;
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankJs:
+ return flat::AbpResource_BlankJs;
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankHtml:
+ return flat::AbpResource_BlankHtml;
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankMp3:
+ return flat::AbpResource_BlankMp3;
+ case UrlFilterOptions::RewriteOption::AbpResource_BlankMp4:
+ return flat::AbpResource_BlankMp4;
+ case UrlFilterOptions::RewriteOption::AbpResource_TransparentGif1x1:
+ return flat::AbpResource_TransparentGif1x1;
+ case UrlFilterOptions::RewriteOption::AbpResource_TransparentPng2x2:
+ return flat::AbpResource_TransparentPng2x2;
+ case UrlFilterOptions::RewriteOption::AbpResource_TransparentPng3x2:
+ return flat::AbpResource_TransparentPng3x2;
+ case UrlFilterOptions::RewriteOption::AbpResource_TransparentPng32x32:
+ return flat::AbpResource_TransparentPng32x32;
+ default:
+ NOTREACHED();
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer.h b/components/adblock/core/converter/serializer/flatbuffer_serializer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/flatbuffer_serializer.h
@@ -0,0 +1,161 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/converter/parser/content_filter.h"
+#include "components/adblock/core/converter/parser/metadata.h"
+#include "components/adblock/core/converter/parser/snippet_filter.h"
+#include "components/adblock/core/converter/parser/url_filter.h"
+#include "components/adblock/core/converter/parser/url_filter_options.h"
+#include "components/adblock/core/converter/serializer/serializer.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class FlatbufferSerializer final : public Serializer {
+ public:
+ explicit FlatbufferSerializer(GURL subscription_url, bool allow_privileged);
+ ~FlatbufferSerializer() override;
+
+ std::unique_ptr<FlatbufferData> GetSerializedSubscription();
+
+ void SerializeMetadata(const Metadata metadata) override;
+ void SerializeContentFilter(const ContentFilter content_filter) override;
+ void SerializeSnippetFilter(const SnippetFilter snippet_filter) override;
+ void SerializeUrlFilter(const UrlFilter url_filter) override;
+
+ private:
+ using UrlFilterIndex =
+ std::map<std::string, std::vector<flatbuffers::Offset<flat::UrlFilter>>>;
+ using ElemhideIndex = std::unordered_map<
+ std::string,
+ std::vector<flatbuffers::Offset<flat::ElemHideFilter>>>;
+ using SnippetIndex =
+ std::map<std::string,
+ std::vector<flatbuffers::Offset<flat::SnippetFilter>>>;
+ using RemoveFilterIndex =
+ std::map<std::string,
+ std::vector<flatbuffers::Offset<flat::RemoveFilter>>>;
+ using InlineCssFilterIndex =
+ std::map<std::string,
+ std::vector<flatbuffers::Offset<flat::InlineCssFilter>>>;
+
+ void AddUrlFilterToIndex(UrlFilterIndex& index,
+ absl::optional<std::string_view> pattern_text,
+ flatbuffers::Offset<flat::UrlFilter> filter);
+ void AddElemhideFilterForDomains(
+ ElemhideIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::ElemHideFilter> filter) const;
+ void AddRemoveFilterForDomains(
+ RemoveFilterIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::RemoveFilter> filter) const;
+ void AddInlineCssFilterForDomains(
+ InlineCssFilterIndex& index,
+ const std::vector<std::string>& include_domains,
+ flatbuffers::Offset<flat::InlineCssFilter> filter) const;
+ void AddSnippetFilterForDomains(
+ SnippetIndex& index,
+ const std::vector<std::string>& domains,
+ flatbuffers::Offset<flat::SnippetFilter> filter) const;
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
+ CreateVectorOfSharedStrings(const std::vector<std::string>& strings);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
+ CreateVectorOfSharedStringsFromSitekeys(const std::vector<SiteKey>& sitekeys);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::UrlFiltersByKeyword>>>
+ WriteUrlFilterIndex(const UrlFilterIndex& index);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::ElemHideFiltersByDomain>>>
+ WriteElemhideFilterIndex(const ElemhideIndex& index);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::RemoveFiltersByDomain>>>
+ WriteRemoveFilterIndex(const RemoveFilterIndex& index);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::InlineCssFiltersByDomain>>>
+ WriteInlineCssFilterIndex(const InlineCssFilterIndex& index);
+
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flat::SnippetFiltersByDomain>>>
+ WriteSnippetFilterIndex(const SnippetIndex& index);
+
+ std::string FindCandidateKeyword(UrlFilterIndex& index,
+ std::string_view value);
+
+ flatbuffers::Offset<flat::ElemHideFilter> CreateElemHideFilter(
+ const ContentFilter& content_filter);
+ flatbuffers::Offset<flat::RemoveFilter> CreateRemoveFilter(
+ const ContentFilter& content_filter);
+ flatbuffers::Offset<flat::InlineCssFilter> CreateInlineCssFilter(
+ const ContentFilter& content_filter);
+
+ static std::string EscapeSelector(const std::string_view& value);
+
+ static flat::ThirdParty ThirdPartyOptionToFb(
+ UrlFilterOptions::ThirdPartyOption option);
+ static flat::AbpResource RewriteOptionToFb(
+ UrlFilterOptions::RewriteOption option);
+
+ GURL subscription_url_;
+ bool allow_privileged_ = false;
+ flatbuffers::FlatBufferBuilder builder_;
+ flatbuffers::Offset<flat::SubscriptionMetadata> metadata_;
+ UrlFilterIndex url_subresource_block_;
+ UrlFilterIndex url_subresource_allow_;
+ UrlFilterIndex url_popup_block_;
+ UrlFilterIndex url_popup_allow_;
+ UrlFilterIndex url_document_allow_;
+ UrlFilterIndex url_elemhide_allow_;
+ UrlFilterIndex url_generichide_allow_;
+ UrlFilterIndex url_genericblock_allow_;
+ UrlFilterIndex url_csp_block_;
+ UrlFilterIndex url_csp_allow_;
+ UrlFilterIndex url_rewrite_block_;
+ UrlFilterIndex url_rewrite_allow_;
+ UrlFilterIndex url_header_allow_;
+ UrlFilterIndex url_header_block_;
+ ElemhideIndex elemhide_exception_index_;
+ ElemhideIndex elemhide_index_;
+ ElemhideIndex elemhide_emulation_index_;
+ RemoveFilterIndex remove_index_;
+ InlineCssFilterIndex inline_css_index_;
+ SnippetIndex snippet_index_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_
diff --git a/components/adblock/core/converter/serializer/serializer.h b/components/adblock/core/converter/serializer/serializer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/serializer.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_
+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_
+
+#include <memory>
+
+namespace adblock {
+
+class ContentFilter;
+class Metadata;
+class SnippetFilter;
+class UrlFilter;
+
+class Serializer {
+ public:
+ virtual ~Serializer() = default;
+ virtual void SerializeMetadata(const Metadata metadata) = 0;
+ virtual void SerializeContentFilter(const ContentFilter content_filter) = 0;
+ virtual void SerializeSnippetFilter(const SnippetFilter snippet_filter) = 0;
+ virtual void SerializeUrlFilter(const UrlFilter url_filter) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_
diff --git a/components/adblock/core/converter/serializer/test/filter_keyword_extractor_test.cc b/components/adblock/core/converter/serializer/test/filter_keyword_extractor_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/serializer/test/filter_keyword_extractor_test.cc
@@ -0,0 +1,68 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/converter/serializer/filter_keyword_extractor.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockFilterKeywordExtractor, NoKeywordExtractedFromEmptyInput) {
+ FilterKeywordExtractor extractor("");
+ EXPECT_EQ(extractor.GetNextKeyword(), absl::nullopt);
+}
+
+TEST(AdblockFilterKeywordExtractor, DoesNotExtractCommonKeywords) {
+ FilterKeywordExtractor extractor("||https://domain.com/script.js");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords, testing::ElementsAre("domain", "script"));
+}
+
+TEST(AdblockFilterKeywordExtractor, SingleLetterKeywordsSkipped) {
+ FilterKeywordExtractor extractor("||a.com");
+ EXPECT_EQ(extractor.GetNextKeyword(), absl::nullopt);
+}
+
+TEST(AdblockFilterKeywordExtractor, DoesNotExtractLastKeywords) {
+ FilterKeywordExtractor extractor("||domain.cc/in_discovery");
+ // This filter should match "http://domain.cc/in_discovery5". Because the
+ // Converter only stores the longest keyword per filter, we don't want the
+ // trailing "discovery" component to "win", as it would not match longer
+ // keywords extracted from requests. We skip the trailing keyword when
+ // extracting for filter, but we include it when extracting from request.
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords, testing::ElementsAre("domain", "cc", "in"));
+}
+
+TEST(AdblockKeywordExtractor, DoesNotExtractWildcardKeyword) {
+ FilterKeywordExtractor extractor("/path1/test*iles/path2/file.js");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords,
+ testing::ElementsAre("path1", "path2", "file"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/converter/test/flatbuffer_converter_perftest.cc b/components/adblock/core/converter/test/flatbuffer_converter_perftest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/converter/test/flatbuffer_converter_perftest.cc
@@ -0,0 +1,83 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sstream>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace adblock {
+
+namespace {
+constexpr char kMetricRuntime[] = ".runtime";
+
+std::string GetTestName() {
+ auto* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ return std::string(test_info->test_suite_name()) + "." + test_info->name();
+}
+} // namespace
+
+class AdblockConverterPerfTest : public testing::Test {
+ public:
+ void SetUp() override {
+ converter_ = base::MakeRefCounted<FlatbufferConverter>();
+ }
+ void MeasureConversionTime(std::string filename) {
+ base::FilePath source_file;
+ ASSERT_TRUE(
+ base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_file));
+ source_file = source_file.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("adblock")
+ .AppendASCII(filename);
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(source_file, &content));
+ ASSERT_TRUE(compression::GzipUncompress(content, &content));
+ std::stringstream input(std::move(content));
+ perf_test::PerfResultReporter reporter(GetTestName(), filename.c_str());
+ reporter.RegisterImportantMetric(kMetricRuntime, "ms");
+ base::ElapsedTimer timer;
+ auto buffer = converter_->Convert(input, CustomFiltersUrl(), true);
+ ASSERT_TRUE(
+ absl::holds_alternative<std::unique_ptr<FlatbufferData>>(buffer));
+ reporter.AddResult(kMetricRuntime,
+ static_cast<size_t>(timer.Elapsed().InMilliseconds()));
+ }
+
+ private:
+ scoped_refptr<FlatbufferConverter> converter_ =
+ base::MakeRefCounted<FlatbufferConverter>();
+};
+
+TEST_F(AdblockConverterPerfTest, ConvertEasylistTime) {
+ MeasureConversionTime("easylist.txt.gz");
+}
+
+TEST_F(AdblockConverterPerfTest, ConvertExceptionrulesTime) {
+ MeasureConversionTime("exceptionrules.txt.gz");
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/features.cc b/components/adblock/core/features.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/features.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/features.h"
+
+namespace adblock {
+
+BASE_FEATURE(kAdblockPlusFeature,
+ "AdblockPlus",
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
+} // namespace adblock
diff --git a/components/adblock/core/features.h b/components/adblock/core/features.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/features.h
@@ -0,0 +1,31 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_FEATURES_H_
+#define COMPONENTS_ADBLOCK_CORE_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace adblock {
+
+// Controls whether ad-blocking feature is enabled.
+BASE_DECLARE_FEATURE(kAdblockPlusFeature);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_FEATURES_H_
diff --git a/components/adblock/core/net/BUILD.gn b/components/adblock/core/net/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/BUILD.gn
@@ -0,0 +1,68 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("net") {
+ sources = [
+ "adblock_request_throttle.h",
+ "adblock_request_throttle_impl.cc",
+ "adblock_request_throttle_impl.h",
+ "adblock_resource_request.h",
+ "adblock_resource_request_impl.cc",
+ "adblock_resource_request_impl.h",
+ ]
+
+ deps = [ "//components/adblock/core/common" ]
+
+ public_deps = [
+ "//base",
+ "//components/keyed_service/core",
+ "//net",
+ "//services/network/public/cpp",
+ "//url:url",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/mock_adblock_request_throttle.cc",
+ "test/mock_adblock_request_throttle.h",
+ "test/mock_adblock_resource_request.cc",
+ "test/mock_adblock_resource_request.h",
+ ]
+
+ public_deps = [
+ ":net",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/adblock_request_throttle_impl_test.cc",
+ "test/adblock_resource_request_impl_test.cc",
+ ]
+
+ deps = [
+ ":net",
+ ":test_support",
+ "//components/adblock/core/common",
+ "//net:test_support",
+ "//services/network:test_support",
+ ]
+}
diff --git a/components/adblock/core/net/adblock_request_throttle.h b/components/adblock/core/net/adblock_request_throttle.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_request_throttle.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_H_
+
+#include "base/functional/callback.h"
+#include "base/time/time.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace adblock {
+
+// Centralized throttle to prohibit network requests from executing too early
+// after browser startup or in other situations that might require a delay.
+class AdblockRequestThrottle : public KeyedService {
+ public:
+ // Runs |callback| when requests become allowed, or immediately if they're
+ // already allowed.
+ virtual void RunWhenRequestsAllowed(base::OnceClosure callback) = 0;
+
+ // Starts a timer that will allow requests to be made after |delay|.
+ // Typically called once, shortly after browser startup, but can be called
+ // multiple times to extend or shorten the delay - this would mostly be used
+ // for browser tests.
+ // |delay| of zero means requests are allowed immediately.
+ virtual void AllowRequestsAfter(base::TimeDelta delay) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_H_
diff --git a/components/adblock/core/net/adblock_request_throttle_impl.cc b/components/adblock/core/net/adblock_request_throttle_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_request_throttle_impl.cc
@@ -0,0 +1,71 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/adblock_request_throttle_impl.h"
+
+namespace adblock {
+
+AdblockRequestThrottleImpl::AdblockRequestThrottleImpl() {
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+}
+
+AdblockRequestThrottleImpl::~AdblockRequestThrottleImpl() {
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+}
+
+void AdblockRequestThrottleImpl::RunWhenRequestsAllowed(
+ base::OnceClosure callback) {
+ if (!AreRequestsAllowed()) {
+ pending_callbacks_.push_back(std::move(callback));
+ } else {
+ std::move(callback).Run();
+ }
+}
+
+void AdblockRequestThrottleImpl::AllowRequestsAfter(base::TimeDelta delay) {
+ timer_.Stop();
+ if (delay.is_zero()) {
+ MaybeRunDeferredCallbacks();
+ return;
+ }
+ timer_.Start(FROM_HERE, delay,
+ base::BindRepeating(
+ &AdblockRequestThrottleImpl::MaybeRunDeferredCallbacks,
+ base::Unretained(this)));
+}
+
+void AdblockRequestThrottleImpl::OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType connection_type) {
+ MaybeRunDeferredCallbacks();
+}
+
+bool AdblockRequestThrottleImpl::AreRequestsAllowed() const {
+ // We must be online and past the initial delay.
+ return !net::NetworkChangeNotifier::IsOffline() && !timer_.IsRunning();
+}
+
+void AdblockRequestThrottleImpl::MaybeRunDeferredCallbacks() {
+ if (!AreRequestsAllowed()) {
+ return;
+ }
+ for (auto& callback : pending_callbacks_) {
+ std::move(callback).Run();
+ }
+ pending_callbacks_.clear();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/net/adblock_request_throttle_impl.h b/components/adblock/core/net/adblock_request_throttle_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_request_throttle_impl.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_IMPL_H_
+
+#include "base/functional/callback.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/adblock/core/net/adblock_request_throttle.h"
+#include "net/base/network_change_notifier.h"
+
+namespace adblock {
+
+class AdblockRequestThrottleImpl final
+ : public AdblockRequestThrottle,
+ public net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+ AdblockRequestThrottleImpl();
+ ~AdblockRequestThrottleImpl() final;
+
+ void RunWhenRequestsAllowed(base::OnceClosure callback) final;
+
+ void AllowRequestsAfter(base::TimeDelta delay) final;
+
+ // NetworkChangeObserver:
+ void OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType connection_type) final;
+
+ private:
+ bool AreRequestsAllowed() const;
+ void MaybeRunDeferredCallbacks();
+
+ base::OneShotTimer timer_;
+ std::vector<base::OnceClosure> pending_callbacks_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_REQUEST_THROTTLE_IMPL_H_
diff --git a/components/adblock/core/net/adblock_resource_request.h b/components/adblock/core/net/adblock_resource_request.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_resource_request.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_H_
+
+#include "base/files/file_path.h"
+#include "base/functional/callback.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// State machine of a download request of a single resource (GET or HEAD).
+// It implements observing network state and retries.
+class AdblockResourceRequest {
+ public:
+ // Controls retry behavior when download failed.
+ enum class RetryPolicy {
+ // Will retry with a progressive back-off until download succeeded.
+ RetryUntilSucceeded,
+ // Will only try to download resource once.
+ DoNotRetry,
+ };
+
+ using ResponseCallback = base::RepeatingCallback<void(
+ const GURL& subscription_url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers)>;
+ enum class Method { GET, HEAD };
+
+ virtual ~AdblockResourceRequest() = default;
+
+ virtual void Start(GURL url,
+ Method method,
+ ResponseCallback response_callback,
+ RetryPolicy retry_policy = RetryPolicy::DoNotRetry,
+ const std::string extra_query_params = "") = 0;
+ virtual void Redirect(GURL redirect_url,
+ const std::string extra_query_params = "") = 0;
+
+ virtual size_t GetNumberOfRedirects() const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_H_
diff --git a/components/adblock/core/net/adblock_resource_request_impl.cc b/components/adblock/core/net/adblock_resource_request_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_resource_request_impl.cc
@@ -0,0 +1,217 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/adblock_resource_request_impl.h"
+
+#include "base/strings/escape.h"
+#include "base/strings/strcat.h"
+#include "base/task/thread_pool.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/app_info.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace adblock {
+namespace {
+
+const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("adblock_resource_request", R"(
+ semantics {
+ sender: "AdblockResourceRequest"
+ description:
+ "A request to download ad-blocking related resource. "
+ trigger:
+ "Interval or when user selects a new filter list source"
+ data:
+ "Version (timestamp) of the filter list, if present. "
+ "Application name (ex. Chromium) "
+ "Application version (93.0.4572.0) "
+ destination: WEBSITE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "You enable or disable this feature via 'Ad blocking' setting."
+ policy_exception_justification: "Not implemented."
+ })");
+
+GURL BuildUrlWithParams(const GURL& url, const std::string extra_query_params) {
+ std::string query = base::StrCat(
+ {"addonName=", "eyeo-chromium-sdk", "&addonVersion=", "2.0.0",
+ "&application=", base::EscapeQueryParamValue(AppInfo::Get().name, true),
+ "&applicationVersion=",
+ base::EscapeQueryParamValue(AppInfo::Get().version, true), "&platform=",
+ base::EscapeQueryParamValue(AppInfo::Get().client_os, true),
+ "&platformVersion=", "1.0"});
+
+ if (!extra_query_params.empty()) {
+ query += "&";
+ query += extra_query_params;
+ }
+
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ return url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+AdblockResourceRequestImpl::AdblockResourceRequestImpl(
+ const net::BackoffEntry::Policy* backoff_policy,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ AdblockRequestThrottle* request_throttle)
+ : backoff_entry_(std::make_unique<net::BackoffEntry>(backoff_policy)),
+ url_loader_factory_(url_loader_factory),
+ request_throttle_(request_throttle),
+ retry_timer_(std::make_unique<base::OneShotTimer>()),
+ number_of_redirects_(0) {}
+
+AdblockResourceRequestImpl::~AdblockResourceRequestImpl() = default;
+
+void AdblockResourceRequestImpl::Start(GURL url,
+ Method method,
+ ResponseCallback response_callback,
+ RetryPolicy retry_policy,
+ const std::string extra_query_params) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!IsStarted()) << "Start() called twice";
+ url_ = BuildUrlWithParams(url, extra_query_params);
+ method_ = method;
+ retry_policy_ = retry_policy;
+ response_callback_ = std::move(response_callback);
+ StartWhenRequestsAllowed();
+}
+
+void AdblockResourceRequestImpl::Redirect(
+ GURL redirect_url,
+ const std::string extra_query_params) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsStarted()) << "Redirect() called before Start()";
+ DCHECK(url_ != redirect_url) << "Invalid redirect. Same URL";
+ VLOG(1) << "[eyeo] Will redirect " << url_ << " to " << redirect_url;
+ ++number_of_redirects_;
+ url_ = BuildUrlWithParams(redirect_url, extra_query_params);
+ StartWhenRequestsAllowed();
+}
+
+size_t AdblockResourceRequestImpl::GetNumberOfRedirects() const {
+ return number_of_redirects_;
+}
+
+void AdblockResourceRequestImpl::StartWhenRequestsAllowed() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ request_throttle_->RunWhenRequestsAllowed(base::BindOnce(
+ &AdblockResourceRequestImpl::StartInternal, weak_factory_.GetWeakPtr()));
+}
+
+void AdblockResourceRequestImpl::StartInternal() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("eyeo", "Downloading resource", this, "url",
+ url_.spec(), "method", MethodToString());
+ if (!url_loader_factory_) {
+ // This happens in unit tests that have no network. The request will hang
+ // indefinitely.
+ return;
+ }
+ VLOG(1) << "[eyeo] Downloading " << url_;
+ auto request = std::make_unique<network::ResourceRequest>();
+ request->url = url_;
+ request->method = MethodToString();
+ request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+ loader_ =
+ network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
+
+ if (method_ == Method::GET) {
+ loader_->DownloadToTempFile(
+ url_loader_factory_.get(),
+ base::BindOnce(&AdblockResourceRequestImpl::OnDownloadFinished,
+ // Unretained is safe because destruction of |this| will
+ // remove |loader_| and will abort the callback.
+ base::Unretained(this)));
+ } else {
+ loader_->DownloadHeadersOnly(
+ url_loader_factory_.get(),
+ base::BindOnce(&AdblockResourceRequestImpl::OnHeadersReceived,
+ base::Unretained(this)));
+ }
+}
+
+void AdblockResourceRequestImpl::Retry() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsStarted()) << "Retry() called before Start()";
+ if (!url_loader_factory_) {
+ // This happens in unit tests that have no network.
+ return;
+ }
+ backoff_entry_->InformOfRequest(false);
+ VLOG(1) << "[eyeo] Will retry downloading " << url_ << " in "
+ << backoff_entry_->GetTimeUntilRelease();
+ retry_timer_->Start(
+ FROM_HERE, backoff_entry_->GetTimeUntilRelease(),
+ base::BindOnce(&AdblockResourceRequestImpl::StartWhenRequestsAllowed,
+ // Unretained is safe because destruction of |this| will
+ // remove |retry_timer_| and abort the callback.
+ base::Unretained(this)));
+}
+
+void AdblockResourceRequestImpl::OnDownloadFinished(
+ base::FilePath downloaded_file) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo", "Downloading resource", this);
+
+ if (downloaded_file.empty() &&
+ retry_policy_ == RetryPolicy::RetryUntilSucceeded) {
+ Retry();
+ return;
+ }
+
+ GURL::Replacements strip_query;
+ strip_query.ClearQuery();
+ GURL url = url_.ReplaceComponents(strip_query);
+ response_callback_.Run(
+ url, std::move(downloaded_file),
+ loader_->ResponseInfo() ? loader_->ResponseInfo()->headers : nullptr);
+ // response_callback_ may delete this, do not call any member variables now.
+}
+
+void AdblockResourceRequestImpl::OnHeadersReceived(
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo", "Downloading resource", this);
+
+ if (!headers && retry_policy_ == RetryPolicy::RetryUntilSucceeded) {
+ Retry();
+ return;
+ }
+
+ response_callback_.Run(GURL(), base::FilePath(), headers);
+ // response_callback_ may delete this, do not call any member variables now.
+}
+
+const char* AdblockResourceRequestImpl::MethodToString() const {
+ return method_ == Method::GET ? net::HttpRequestHeaders::kGetMethod
+ : net::HttpRequestHeaders::kHeadMethod;
+}
+
+bool AdblockResourceRequestImpl::IsStarted() const {
+ // url_ gets set in Start() and never reset
+ return !url_.is_empty();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/net/adblock_resource_request_impl.h b/components/adblock/core/net/adblock_resource_request_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/adblock_resource_request_impl.h
@@ -0,0 +1,78 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "components/adblock/core/net/adblock_request_throttle.h"
+#include "components/adblock/core/net/adblock_resource_request.h"
+#include "net/base/backoff_entry.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+namespace adblock {
+
+class AdblockResourceRequestImpl final : public AdblockResourceRequest {
+ public:
+ AdblockResourceRequestImpl(
+ const net::BackoffEntry::Policy* backoff_policy,
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+ AdblockRequestThrottle* request_throttle);
+ ~AdblockResourceRequestImpl() final;
+ void Start(GURL url,
+ Method method,
+ ResponseCallback response_callback,
+ RetryPolicy retry_policy = RetryPolicy::DoNotRetry,
+ const std::string extra_query_params = "") final;
+ void Redirect(GURL redirect_url,
+ const std::string extra_query_params = "") final;
+
+ size_t GetNumberOfRedirects() const final;
+
+ private:
+ bool IsStarted() const;
+ void StartWhenRequestsAllowed();
+ void StartInternal();
+ void Retry();
+ void OnDownloadFinished(base::FilePath downloaded_file);
+ void OnHeadersReceived(scoped_refptr<net::HttpResponseHeaders> headers);
+ const char* MethodToString() const;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ std::unique_ptr<net::BackoffEntry> backoff_entry_;
+ scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+ raw_ptr<AdblockRequestThrottle> request_throttle_;
+ GURL url_;
+ Method method_ = Method::GET;
+ RetryPolicy retry_policy_;
+ ResponseCallback response_callback_;
+ std::unique_ptr<network::SimpleURLLoader> loader_;
+ std::unique_ptr<base::OneShotTimer> retry_timer_;
+ size_t number_of_redirects_;
+ base::WeakPtrFactory<AdblockResourceRequestImpl> weak_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_IMPL_H_
diff --git a/components/adblock/core/net/test/adblock_request_throttle_impl_test.cc b/components/adblock/core/net/test/adblock_request_throttle_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/adblock_request_throttle_impl_test.cc
@@ -0,0 +1,133 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/adblock_request_throttle_impl.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/functional/callback_helpers.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "net/base/mock_network_change_notifier.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockRequestThrottleImplTest : public testing::Test {
+ public:
+ AdblockRequestThrottleImplTest() {
+ request_throttle_.AllowRequestsAfter(kInitialDelay);
+ }
+
+ void SetOffline() {
+ network_change_notifier_->SetConnectionTypeAndNotifyObservers(
+ net::NetworkChangeNotifier::CONNECTION_NONE);
+ }
+
+ void SetOnline() {
+ network_change_notifier_->SetConnectionTypeAndNotifyObservers(
+ net::NetworkChangeNotifier::CONNECTION_ETHERNET);
+ }
+
+ const base::TimeDelta kInitialDelay = base::Seconds(30);
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ std::unique_ptr<net::test::MockNetworkChangeNotifier>
+ network_change_notifier_ = net::test::MockNetworkChangeNotifier::Create();
+ AdblockRequestThrottleImpl request_throttle_;
+};
+
+TEST_F(AdblockRequestThrottleImplTest, CallbackDelayedUntilRequestsAllowed) {
+ base::MockOnceClosure callback;
+ // kInitialDelay is 30 seconds, so the callback should not be run immediately.
+ EXPECT_CALL(callback, Run()).Times(0);
+ request_throttle_.RunWhenRequestsAllowed(callback.Get());
+
+ // Advance time by half od kInitialDelay, the callback should still not be
+ // run.
+ task_environment_.FastForwardBy(kInitialDelay / 2);
+
+ // Advance time by the remaining half of kInitialDelay, the callback should be
+ // run now.
+ EXPECT_CALL(callback, Run()).Times(1);
+ task_environment_.FastForwardBy(kInitialDelay / 2);
+}
+
+TEST_F(AdblockRequestThrottleImplTest, CheckDelayedIfOffline) {
+ SetOffline();
+ base::MockOnceClosure callback;
+ // The callback should not be run if the browser is offline.
+ EXPECT_CALL(callback, Run()).Times(0);
+ request_throttle_.RunWhenRequestsAllowed(callback.Get());
+
+ // Advance time by kInitialDelay, the callback should still not be run.
+ task_environment_.FastForwardBy(kInitialDelay);
+
+ // Set the browser online, the callback should be run now.
+ EXPECT_CALL(callback, Run()).Times(1);
+ SetOnline();
+}
+
+TEST_F(AdblockRequestThrottleImplTest,
+ GoingOnlineBeforeInitialDelayDoesNotTriggerPendingCallbacks) {
+ SetOffline();
+ base::MockOnceClosure callback;
+ // The callback should not be run if the browser is offline.
+ EXPECT_CALL(callback, Run()).Times(0);
+ request_throttle_.RunWhenRequestsAllowed(callback.Get());
+
+ // Advance time by half of kInitialDelay, the callback should still not be
+ // run.
+ task_environment_.FastForwardBy(kInitialDelay / 2);
+
+ // Set the browser online, the callback should still not be run.
+ SetOnline();
+
+ // Advance time by the remaining half of kInitialDelay, the callback should be
+ // run now.
+ EXPECT_CALL(callback, Run()).Times(1);
+ task_environment_.FastForwardBy(kInitialDelay / 2);
+}
+
+TEST_F(AdblockRequestThrottleImplTest,
+ CallbackRunImmediatelyIfRequestsAllowed) {
+ base::MockOnceClosure callback;
+ // AllowRequestsAfter(base::Seconds(0)) should allow the callback to run
+ // immediately.
+ EXPECT_CALL(callback, Run()).Times(1);
+ request_throttle_.AllowRequestsAfter(base::Seconds(0));
+ request_throttle_.RunWhenRequestsAllowed(callback.Get());
+}
+
+TEST_F(AdblockRequestThrottleImplTest, PendingCallbacksRunAfterOverride) {
+ base::MockOnceClosure callback1;
+ base::MockOnceClosure callback2;
+ // RunWhenRequestsAllowed() should queue the callbacks.
+ EXPECT_CALL(callback1, Run()).Times(0);
+ EXPECT_CALL(callback2, Run()).Times(0);
+ request_throttle_.RunWhenRequestsAllowed(callback1.Get());
+ request_throttle_.RunWhenRequestsAllowed(callback2.Get());
+
+ // AllowRequestsAfter(base::Seconds(0)) should run all pending callbacks.
+ EXPECT_CALL(callback1, Run()).Times(1);
+ EXPECT_CALL(callback2, Run()).Times(1);
+ request_throttle_.AllowRequestsAfter(base::Seconds(0));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/net/test/adblock_resource_request_impl_test.cc b/components/adblock/core/net/test/adblock_resource_request_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/adblock_resource_request_impl_test.cc
@@ -0,0 +1,414 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/adblock_resource_request_impl.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/files/file_util.h"
+#include "base/functional/callback_helpers.h"
+#include "base/strings/escape.h"
+#include "base/strings/strcat.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/app_info.h"
+#include "components/adblock/core/net/test/mock_adblock_request_throttle.h"
+#include "net/base/mock_network_change_notifier.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockResourceRequestImplTest
+ : public testing::TestWithParam<AdblockResourceRequest::Method> {
+ public:
+ void SetUp() final {
+ SetOnline();
+ ongoing_request_ = std::make_unique<AdblockResourceRequestImpl>(
+ &kRetryBackoffPolicy, test_shared_url_loader_factory_,
+ &mock_request_throttle_);
+ }
+
+ std::string_view MethodAsString(AdblockResourceRequest::Method method) {
+ return method == AdblockResourceRequest::Method::GET
+ ? net::HttpRequestHeaders::kGetMethod
+ : net::HttpRequestHeaders::kHeadMethod;
+ }
+
+ void VerifyRequestSent(AdblockResourceRequest::Method method) {
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.url,
+ UrlWithExpectedParams(kUrl));
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.method,
+ MethodAsString(method));
+ }
+
+ void VerifyRequestSent() { VerifyRequestSent(GetParam()); }
+
+ void SetOffline() { mock_request_throttle_.requests_allowed_ = false; }
+
+ void SetOnline() {
+ mock_request_throttle_.OverrideDelayImmediatelyForTesting();
+ }
+
+ const GURL UrlWithExpectedParams(const GURL& url,
+ const std::string& extra_query_params = "") {
+ std::string query = base::StrCat(
+ {"addonName=", "eyeo-chromium-sdk", "&addonVersion=", "2.0.0",
+ "&application=",
+ base::EscapeQueryParamValue(AppInfo::Get().name, true),
+ "&applicationVersion=",
+ base::EscapeQueryParamValue(AppInfo::Get().version, true),
+ "&platform=",
+ base::EscapeQueryParamValue(AppInfo::Get().client_os, true),
+ "&platformVersion=", "1.0"});
+
+ if (!extra_query_params.empty()) {
+ query += "&";
+ query += extra_query_params;
+ }
+
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ return url.ReplaceComponents(replacements);
+ }
+
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ MockAdblockRequestThrottle mock_request_throttle_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_{
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)};
+ const GURL kUrl{"https://url.com/filter"};
+ const net::BackoffEntry::Policy kRetryBackoffPolicy = {
+ 0, // Number of initial errors to ignore.
+ 5000, // Initial delay in ms.
+ 2.0, // Factor by which the waiting time will be multiplied.
+ 0, // Fuzzing percentage.
+ 10000, // Maximum delay in ms.
+ -1, // Never discard the entry.
+ false, // Use initial delay.
+ };
+ std::unique_ptr<AdblockResourceRequestImpl> ongoing_request_;
+};
+
+TEST_P(AdblockResourceRequestImplTest,
+ RequestDeferredUntilConnectionAvailable) {
+ SetOffline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ // Download did not start yet.
+ EXPECT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ SetOnline();
+
+ // Request started.
+ VerifyRequestSent();
+}
+
+TEST_P(AdblockResourceRequestImplTest,
+ RequestConnectionAvailableTriggersDownloadsOnlyAfterStart) {
+ SetOffline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+
+ // Download did not start yet.
+ EXPECT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ SetOnline();
+
+ // Download did not start yet.
+ EXPECT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ // Request started.
+ VerifyRequestSent();
+}
+
+TEST_P(AdblockResourceRequestImplTest, RequestCompletedSuccessfully) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ VerifyRequestSent();
+
+ const std::string content = "downloaded content";
+
+ auto header_response = network::CreateURLResponseHead(net::HTTP_OK);
+ header_response->headers->AddHeader("Date", "Today");
+
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .WillOnce([&](const GURL, base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ ASSERT_TRUE(headers);
+ EXPECT_TRUE(headers->HasHeaderValue("Date", "Today"));
+ // We expect a downloaded_file in GET mode, HEAD requests deliver
+ // only headers.
+ if (GetParam() == AdblockResourceRequest::Method::GET) {
+ std::string content_in_file;
+ EXPECT_TRUE(
+ base::ReadFileToString(downloaded_file, &content_in_file));
+ EXPECT_EQ(content_in_file, content);
+ } else {
+ EXPECT_TRUE(downloaded_file.empty());
+ }
+ });
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl), network::URLLoaderCompletionStatus(net::OK),
+ std::move(header_response), content);
+ task_environment_.RunUntilIdle();
+ // No additional tasks are expected.
+ EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
+}
+
+TEST_P(AdblockResourceRequestImplTest, RetriesDeferredProgressively) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(
+ kUrl, GetParam(), response_callback.Get(),
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded);
+
+ VerifyRequestSent();
+
+ // Response callback not called since the download failed.
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .Times(0);
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl),
+ network::URLLoaderCompletionStatus(net::ERR_ABORTED), nullptr, "");
+
+ task_environment_.RunUntilIdle();
+ // A retry attempt task has been posted.
+ EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 1u);
+ // The delay matches the retry policy
+ EXPECT_EQ(task_environment_.NextMainThreadPendingTaskDelay().InMilliseconds(),
+ kRetryBackoffPolicy.initial_delay_ms);
+
+ // Fast-forward time until the retry task is executed.
+ task_environment_.FastForwardBy(
+ task_environment_.NextMainThreadPendingTaskDelay());
+
+ // A retry request was sent.
+ VerifyRequestSent();
+ // The response comes back, with a net::Err which, again,
+ // results in a retry.
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl),
+ network::URLLoaderCompletionStatus(net::ERR_ABORTED), nullptr, "");
+
+ task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 1u);
+ // The delay is now multiplied, according to backoff policy.
+ EXPECT_EQ(task_environment_.NextMainThreadPendingTaskDelay().InMilliseconds(),
+ kRetryBackoffPolicy.initial_delay_ms *
+ kRetryBackoffPolicy.multiply_factor);
+ // Fast-forward time until the retry task is executed.
+ task_environment_.FastForwardBy(
+ task_environment_.NextMainThreadPendingTaskDelay());
+ // A retry request was sent.
+ VerifyRequestSent();
+}
+
+TEST_P(AdblockResourceRequestImplTest, RequestCancelledDuringRetry) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(
+ kUrl, GetParam(), response_callback.Get(),
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded);
+
+ VerifyRequestSent();
+
+ // Response callback not called since the download failed.
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .Times(0);
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl),
+ network::URLLoaderCompletionStatus(net::ERR_ABORTED), nullptr, "");
+
+ task_environment_.RunUntilIdle();
+ // A retry attempt task has been posted.
+ EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 1u);
+ // The delay matches the retry policy
+ EXPECT_EQ(task_environment_.NextMainThreadPendingTaskDelay().InMilliseconds(),
+ kRetryBackoffPolicy.initial_delay_ms);
+
+ // We now cancel the download by destroying ongoing_request_.
+ ongoing_request_.reset();
+
+ // Fast-forward time until the retry task was scheduled to execute.
+ task_environment_.FastForwardBy(
+ task_environment_.NextMainThreadPendingTaskDelay());
+
+ // A retry request was not sent, as the request is cancelled.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+}
+
+TEST_P(AdblockResourceRequestImplTest, RedirectCallStartsDownload) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ // Redirect counter is 0 by default
+ EXPECT_EQ(ongoing_request_->GetNumberOfRedirects(), 0u);
+
+ VerifyRequestSent();
+
+ const GURL kRedirectUrl{"https://redirect_url.com"};
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .WillOnce([&](const GURL&, base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ // The response callback triggers a Redirect()
+ ongoing_request_->Redirect(kRedirectUrl);
+ });
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl).spec(), "content");
+ task_environment_.RunUntilIdle();
+
+ // Redirect counter is incremented by 1
+ EXPECT_EQ(ongoing_request_->GetNumberOfRedirects(), 1u);
+
+ // A redirect request was sent with the redirect URL and same method
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.url,
+ UrlWithExpectedParams(kRedirectUrl));
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.method,
+ MethodAsString(GetParam()));
+}
+
+TEST_P(AdblockResourceRequestImplTest, RequestCancelledAfterStarting) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ VerifyRequestSent();
+
+ // We now cancel the download by destroying ongoing_request_.
+ ongoing_request_.reset();
+ // The response callback will not be called after the request has been
+ // cancelled...
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .Times(0);
+ // ... even when the response arrives.
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl).spec(), "content");
+ task_environment_.RunUntilIdle();
+}
+
+TEST_P(AdblockResourceRequestImplTest, ExtraQueryParamsAddedForRequest) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+
+ const std::string extra_query_param = "extra_key=extra_value";
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get(),
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ extra_query_param);
+
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.url,
+ UrlWithExpectedParams(kUrl, extra_query_param));
+
+ task_environment_.RunUntilIdle();
+}
+
+TEST_P(AdblockResourceRequestImplTest,
+ ExtraQueryParamsAddedForRedirectedRequest) {
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, GetParam(), response_callback.Get());
+
+ VerifyRequestSent();
+
+ const GURL kRedirectUrl{"https://redirect_url.com"};
+ const std::string extra_query_param = "extra_key=extra_value";
+ EXPECT_CALL(response_callback, Run(testing::_, testing::_, testing::_))
+ .WillOnce([&](const GURL&, base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ // The response callback triggers a Redirect()
+ ongoing_request_->Redirect(kRedirectUrl, extra_query_param);
+ });
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl).spec(), "content");
+ task_environment_.RunUntilIdle();
+
+ // A redirect request was sent with query parameters
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ EXPECT_EQ(test_url_loader_factory_.GetPendingRequest(0)->request.url,
+ UrlWithExpectedParams(kRedirectUrl, extra_query_param));
+}
+
+TEST_F(AdblockResourceRequestImplTest,
+ ResponseCallbackCalledWithTrimmedUrlGET) {
+ SetOnline();
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, AdblockResourceRequest::Method::GET,
+ response_callback.Get());
+
+ VerifyRequestSent(AdblockResourceRequest::Method::GET);
+
+ EXPECT_CALL(response_callback, Run(kUrl, testing::_, testing::_)).Times(1);
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl).spec(), "content");
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockResourceRequestImplTest,
+ ResponseCallbackCalledWithTrimmedUrlHEAD) {
+ base::MockCallback<AdblockResourceRequest::ResponseCallback>
+ response_callback;
+ ongoing_request_->Start(kUrl, AdblockResourceRequest::Method::HEAD,
+ response_callback.Get());
+
+ VerifyRequestSent(AdblockResourceRequest::Method::HEAD);
+
+ EXPECT_CALL(response_callback, Run(GURL(), testing::_, testing::_)).Times(1);
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ UrlWithExpectedParams(kUrl).spec(), "content");
+ task_environment_.RunUntilIdle();
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockResourceRequestImplTest,
+ testing::Values(AdblockResourceRequest::Method::GET,
+ AdblockResourceRequest::Method::HEAD));
+
+} // namespace adblock
diff --git a/components/adblock/core/net/test/mock_adblock_request_throttle.cc b/components/adblock/core/net/test/mock_adblock_request_throttle.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/mock_adblock_request_throttle.cc
@@ -0,0 +1,45 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/test/mock_adblock_request_throttle.h"
+
+namespace adblock {
+
+MockAdblockRequestThrottle::MockAdblockRequestThrottle() = default;
+
+MockAdblockRequestThrottle::~MockAdblockRequestThrottle() = default;
+
+void MockAdblockRequestThrottle::RunWhenRequestsAllowed(
+ base::OnceClosure callback) {
+ if (!requests_allowed_) {
+ pending_callbacks_.push_back(std::move(callback));
+ } else {
+ std::move(callback).Run();
+ }
+}
+
+void MockAdblockRequestThrottle::AllowRequestsAfter(base::TimeDelta delay) {}
+
+void MockAdblockRequestThrottle::OverrideDelayImmediatelyForTesting() {
+ requests_allowed_ = true;
+ for (auto& callback : pending_callbacks_) {
+ std::move(callback).Run();
+ }
+ pending_callbacks_.clear();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/net/test/mock_adblock_request_throttle.h b/components/adblock/core/net/test/mock_adblock_request_throttle.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/mock_adblock_request_throttle.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_REQUEST_THROTTLE_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_REQUEST_THROTTLE_H_
+
+#include <vector>
+
+#include "components/adblock/core/net/adblock_request_throttle.h"
+
+namespace adblock {
+
+class MockAdblockRequestThrottle : public AdblockRequestThrottle {
+ public:
+ MockAdblockRequestThrottle();
+ ~MockAdblockRequestThrottle() override;
+ void RunWhenRequestsAllowed(base::OnceClosure callback) override;
+ void AllowRequestsAfter(base::TimeDelta delay) override;
+
+ void OverrideDelayImmediatelyForTesting();
+
+ bool requests_allowed_ = false;
+ std::vector<base::OnceClosure> pending_callbacks_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_REQUEST_THROTTLE_H_
diff --git a/components/adblock/core/net/test/mock_adblock_resource_request.cc b/components/adblock/core/net/test/mock_adblock_resource_request.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/mock_adblock_resource_request.cc
@@ -0,0 +1,25 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/net/test/mock_adblock_resource_request.h"
+
+namespace adblock {
+
+MockAdblockResourceRequest::MockAdblockResourceRequest() = default;
+MockAdblockResourceRequest::~MockAdblockResourceRequest() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/net/test/mock_adblock_resource_request.h b/components/adblock/core/net/test/mock_adblock_resource_request.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/net/test/mock_adblock_resource_request.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_RESOURCE_REQUEST_H_
+#define COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_RESOURCE_REQUEST_H_
+
+#include "components/adblock/core/net/adblock_resource_request.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockAdblockResourceRequest : public NiceMock<AdblockResourceRequest> {
+ public:
+ MockAdblockResourceRequest();
+ ~MockAdblockResourceRequest() override;
+ MOCK_METHOD(void,
+ Start,
+ (GURL url,
+ Method method,
+ ResponseCallback response_callback,
+ RetryPolicy retry_policy,
+ const std::string extra_query_params),
+ (override));
+ MOCK_METHOD(void,
+ Redirect,
+ (GURL redirect_url, const std::string extra_query_params),
+ (override));
+ MOCK_METHOD(size_t, GetNumberOfRedirects, (), (const, override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_RESOURCE_REQUEST_H_
diff --git a/components/adblock/core/resources/.gitignore b/components/adblock/core/resources/.gitignore
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/.gitignore
@@ -0,0 +1 @@
+snippets
diff --git a/components/adblock/core/resources/BUILD.gn b/components/adblock/core/resources/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/BUILD.gn
@@ -0,0 +1,95 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import("//build/compiled_action.gni")
+import("//tools/grit/grit_rule.gni")
+
+# Converts text-format filter lists into flatbuffers using a standalone
+# converter tool.
+template("make_preloaded_subscription") {
+ compiled_action(target_name) {
+ tool = "//components/adblock/core/converter:adblock_flatbuffer_converter"
+ inputs = [ invoker.input ]
+ outputs = [ invoker.output ]
+ args = [
+ rebase_path(invoker.input, root_build_dir),
+ invoker.url,
+ rebase_path(invoker.output, root_build_dir),
+ ]
+ }
+}
+
+# Note, url is *not* used to download the list during build time, only to
+# identify the subscription. Consider it metadata.
+make_preloaded_subscription("make_easylist") {
+ input = "//components/adblock/core/resources/easylist.txt.gz"
+ url = "https://easylist-downloads.adblockplus.org/easylist.txt"
+ output = "${target_gen_dir}/easylist.fb"
+}
+
+make_preloaded_subscription("make_exceptionrules") {
+ input = "//components/adblock/core/resources/exceptionrules.txt.gz"
+ url = "https://easylist-downloads.adblockplus.org/exceptionrules.txt"
+ output = "${target_gen_dir}/exceptionrules.fb"
+}
+
+make_preloaded_subscription("make_anticv") {
+ input = "//components/adblock/core/resources/anticv.txt.gz"
+ url = "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt"
+ output = "${target_gen_dir}/anticv.fb"
+}
+
+action("prepare_snippets") {
+ script = "//components/adblock/core/resources/snippets_deps.py"
+ if (is_debug) {
+ _snippet_lib = "//components/adblock/core/resources/snippets/dist/isolated-first-all.source.jst"
+ } else {
+ _snippet_lib = "//components/adblock/core/resources/snippets/dist/isolated-first-all.jst"
+ }
+ _snippet_deps =
+ "//components/adblock/core/resources/snippets/dist/dependencies.jst"
+ inputs = [
+ _snippet_deps,
+ _snippet_lib,
+ ]
+ outputs = [
+ "${target_gen_dir}/snippets.jst",
+ "${target_gen_dir}/snippets-xpath3-dep.jst",
+ ]
+ args = [
+ "--deps",
+ rebase_path(_snippet_deps, root_build_dir),
+ "--lib",
+ rebase_path(_snippet_lib, root_build_dir),
+ "--output",
+ rebase_path("${target_gen_dir}", root_build_dir),
+ ]
+}
+
+grit("adblock_resources") {
+ source = "adblock_resources.grd"
+ outputs = [
+ "grit/adblock_resources.h",
+ "adblock_resources.pak",
+ ]
+ deps = [
+ ":make_anticv",
+ ":make_easylist",
+ ":make_exceptionrules",
+ ":prepare_snippets",
+ ]
+ output_dir = "$root_gen_dir/components/adblock/core/resources"
+}
diff --git a/components/adblock/core/resources/adblock_resources.grd b/components/adblock/core/resources/adblock_resources.grd
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/adblock_resources.grd
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ This file is part of eyeo Chromium SDK,
+ Copyright (C) 2006-present eyeo GmbH
+
+ eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 3 as
+ published by the Free Software Foundation.
+
+ eyeo Chromium SDK is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+-->
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+ <outputs>
+ <output filename="grit/adblock_resources.h" type="rc_header">
+ <emit emit_type="prepend">
+ </emit>
+ </output>
+ <output filename="adblock_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_ADBLOCK_ELEMHIDE_JS" file="elemhide.js" type="BINDATA" />
+ <include name="IDR_ADBLOCK_ELEMHIDE_FOR_SELECTOR_JS" file="elemhide_for_selector.jst" type="BINDATA" />
+ <include name="IDR_ADBLOCK_ELEMHIDE_EMU_JS" file="elemhideemu.jst" type="BINDATA" />
+ <include name="IDR_ADBLOCK_SNIPPETS_JS" file="${root_gen_dir}/components/adblock/core/resources/snippets.jst" use_base_dir="false" type="BINDATA" compress="gzip" />
+ <include name="IDR_ADBLOCK_SNIPPETS_XPATH3_DEP_JS" file="${root_gen_dir}/components/adblock/core/resources/snippets-xpath3-dep.jst" use_base_dir="false" type="BINDATA" compress="gzip" />
+ <include name="IDR_ADBLOCK_FLATBUFFER_EASYLIST" file="${root_gen_dir}/components/adblock/core/resources/easylist.fb" use_base_dir="false" type="BINDATA" compress="gzip" />
+ <include name="IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES" file="${root_gen_dir}/components/adblock/core/resources/exceptionrules.fb" use_base_dir="false" type="BINDATA" compress="gzip" />
+ <include name="IDR_ADBLOCK_FLATBUFFER_ANTICV" file="${root_gen_dir}/components/adblock/core/resources/anticv.fb" use_base_dir="false" type="BINDATA" compress="gzip" />
+ </includes>
+ </release>
+</grit>
diff --git a/components/adblock/core/resources/elemhide.js b/components/adblock/core/resources/elemhide.js
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/elemhide.js
@@ -0,0 +1,43 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+function hideElement(element)
+{
+ function doHide()
+ {
+ let propertyName = "display";
+ let propertyValue = "none";
+ if (element.localName == "frame")
+ {
+ propertyName = "visibility";
+ propertyValue = "hidden";
+ }
+
+ if (element.style.getPropertyValue(propertyName) != propertyValue ||
+ element.style.getPropertyPriority(propertyName) != "important")
+ element.style.setProperty(propertyName, propertyValue, "important");
+ }
+
+ doHide();
+
+ new MutationObserver(doHide).observe(
+ element, {
+ attributes: true,
+ attributeFilter: ["style"]
+ }
+ );
+}
diff --git a/components/adblock/core/resources/elemhide_for_selector.jst b/components/adblock/core/resources/elemhide_for_selector.jst
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/elemhide_for_selector.jst
@@ -0,0 +1,53 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// {{filename_with_query}} and {{url}} will be replaced with actual variable values
+
+if (typeof(elemhideForSelector) !== typeof(Function))
+{
+ function elemhideForSelector(url, selector)
+ {
+ let elements = document.querySelectorAll(selector);
+ if (elements.length > 0)
+ {
+ for (let element of elements)
+ {
+ if (element.src == url)
+ {
+ hideElement(element);
+ }
+ }
+ }
+ else
+ {
+ console.debug("Nothing found for: " + selector);
+ }
+ }
+}
+
+((url_to_hide, filename_to_hide) => {
+ if (document.readyState == "loading")
+ {
+ document.addEventListener("DOMContentLoaded", () => {
+ elemhideForSelector(url_to_hide, "[src$='" + filename_to_hide + "'], [srcset$='" + filename_to_hide + "']");
+ });
+ }
+ else
+ {
+ elemhideForSelector(url_to_hide, "[src$='" + filename_to_hide + "'], [srcset$='" + filename_to_hide + "']");
+ }
+})("{{url}}", "{{filename_with_query}}");
\ No newline at end of file
diff --git a/components/adblock/core/resources/elemhideemu.jst b/components/adblock/core/resources/elemhideemu.jst
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/elemhideemu.jst
@@ -0,0 +1,1410 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+
+/*
+ * This template is used for JavaScript code generation in element_hider_impl.cc
+ *
+ * Concatenated files from adblockpluscore
+ * (https://gitlab.com/eyeo/adblockplus/abc/adblockpluscore/-/tags/0.5.1):
+ *
+ * lib/common.js
+ * lib/patterns.js
+ * lib/content/elemHideEmulation.js
+ *
+ * The concatenation is an emulation of the `require` statement. All dependencies
+ * required by elemHideEmulation.js are pasted before it.
+ * The files were refined: commented out `require` statements and `export`-ing.
+ *
+ * The application of the element hiding emulation is at the end of this file.
+ */
+
+
+// common.js ------------------------------
+
+
+let textToRegExp =
+/**
+ * Converts raw text into a regular expression string
+ * @param {string} text the string to convert
+ * @return {string} regular expression representation of the text
+ * @package
+ */
+text => text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
+
+const regexpRegexp = /^\/(.*)\/([imu]*)$/;
+
+/**
+ * Make a regular expression from a text argument.
+ *
+ * If it can be parsed as a regular expression, parse it and the flags.
+ *
+ * @param {string} text the text argument.
+ *
+ * @return {?RegExp} a RegExp object or null in case of error.
+ */
+function makeRegExpParameter(text) {
+ let [, source, flags] = regexpRegexp.exec(text) || [null, textToRegExp(text)];
+
+ try {
+ return new RegExp(source, flags);
+ }
+ catch (e) {
+ return null;
+ }
+};
+
+function splitSelector(selector) {
+ if (!selector.includes(","))
+ return [selector];
+
+ let selectors = [];
+ let start = 0;
+ let level = 0;
+ let sep = "";
+
+ for (let i = 0; i < selector.length; i++) {
+ let chr = selector[i];
+
+ // ignore escaped characters
+ if (chr == "\\") {
+ i++;
+ }
+ // don't split within quoted text
+ else if (chr == sep) {
+ sep = ""; // e.g. [attr=","]
+ }
+ else if (sep == "") {
+ if (chr == '"' || chr == "'") {
+ sep = chr;
+ }
+ // don't split between parentheses
+ else if (chr == "(") {
+ level++; // e.g. :matches(div,span)
+ }
+ else if (chr == ")") {
+ level = Math.max(0, level - 1);
+ }
+ else if (chr == "," && level == 0) {
+ selectors.push(selector.substring(start, i));
+ start = i + 1;
+ }
+ }
+ }
+
+ selectors.push(selector.substring(start));
+ return selectors;
+};
+
+function findTargetSelectorIndex(selector) {
+ let index = 0;
+ let whitespace = 0;
+ let scope = [];
+
+ // Start from the end of the string and go character by character, where each
+ // character is a Unicode code point.
+ for (let character of [...selector].reverse()) {
+ let currentScope = scope[scope.length - 1];
+
+ if (character == "'" || character == "\"") {
+ // If we're already within the same type of quote, close the scope;
+ // otherwise open a new scope.
+ if (currentScope == character)
+ scope.pop();
+ else
+ scope.push(character);
+ }
+ else if (character == "]" || character == ")") {
+ // For closing brackets and parentheses, open a new scope only if we're
+ // not within a quote. Within quotes these characters should have no
+ // meaning.
+ if (currentScope != "'" && currentScope != "\"")
+ scope.push(character);
+ }
+ else if (character == "[") {
+ // If we're already within a bracket, close the scope.
+ if (currentScope == "]")
+ scope.pop();
+ }
+ else if (character == "(") {
+ // If we're already within a parenthesis, close the scope.
+ if (currentScope == ")")
+ scope.pop();
+ }
+ else if (!currentScope) {
+ // At the top level (not within any scope), count the whitespace if we've
+ // encountered it. Otherwise if we've hit one of the combinators,
+ // terminate here; otherwise if we've hit a non-colon character,
+ // terminate here.
+ if (/\s/.test(character))
+ whitespace++;
+ else if ((character == ">" || character == "+" || character == "~") ||
+ (whitespace > 0 && character != ":"))
+ break;
+ }
+
+ // Zero out the whitespace count if we've entered a scope.
+ if (scope.length > 0)
+ whitespace = 0;
+
+ // Increment the index by the size of the character. Note that for Unicode
+ // composite characters (like emoji) this will be more than one.
+ index += character.length;
+ }
+
+ return selector.length - index + whitespace;
+}
+
+/**
+ * Qualifies a CSS selector with a qualifier, which may be another CSS selector
+ * or an empty string. For example, given the selector "div.bar" and the
+ * qualifier "#foo", this function returns "div#foo.bar".
+ * @param {string} selector The selector to qualify.
+ * @param {string} qualifier The qualifier with which to qualify the selector.
+ * @returns {string} The qualified selector.
+ * @package
+ */
+function qualifySelector(selector, qualifier) {
+ let qualifiedSelector = "";
+
+ let qualifierTargetSelectorIndex = findTargetSelectorIndex(qualifier);
+ let [, qualifierType = ""] =
+ /^([a-z][a-z-]*)?/i.exec(qualifier.substring(qualifierTargetSelectorIndex));
+
+ for (let sub of splitSelector(selector)) {
+ sub = sub.trim();
+
+ qualifiedSelector += ", ";
+
+ let index = findTargetSelectorIndex(sub);
+
+ // Note that the first group in the regular expression is optional. If it
+ // doesn't match (e.g. "#foo::nth-child(1)"), type will be an empty string.
+ let [, type = "", rest] =
+ /^([a-z][a-z-]*)?\*?(.*)/i.exec(sub.substring(index));
+
+ if (type == qualifierType)
+ type = "";
+
+ // If the qualifier ends in a combinator (e.g. "body #foo>"), we put the
+ // type and the rest of the selector after the qualifier
+ // (e.g. "body #foo>div.bar"); otherwise (e.g. "body #foo") we merge the
+ // type into the qualifier (e.g. "body div#foo.bar").
+ if (/[\s>+~]$/.test(qualifier))
+ qualifiedSelector += sub.substring(0, index) + qualifier + type + rest;
+ else
+ qualifiedSelector += sub.substring(0, index) + type + qualifier + rest;
+ }
+
+ // Remove the initial comma and space.
+ return qualifiedSelector.substring(2);
+};
+
+
+// end of common.js ------------------------------
+
+// patterns.js ------------------------------
+
+
+/**
+ * Regular expression used to match the `^` suffix in an otherwise literal
+ * pattern.
+ * @type {RegExp}
+ */
+let separatorRegExp = /[\x00-\x24\x26-\x2C\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]/;
+
+let filterToRegExp =
+/**
+ * Converts filter text into regular expression string
+ * @param {string} text as in Filter()
+ * @return {string} regular expression representation of filter text
+ * @package
+ */
+function filterToRegExp(text) {
+ // remove multiple wildcards
+ text = text.replace(/\*+/g, "*");
+
+ // remove leading wildcard
+ if (text[0] == "*")
+ text = text.substring(1);
+
+ // remove trailing wildcard
+ if (text[text.length - 1] == "*")
+ text = text.substring(0, text.length - 1);
+
+ return text
+ // remove anchors following separator placeholder
+ .replace(/\^\|$/, "^")
+ // escape special symbols
+ .replace(/\W/g, "\\$&")
+ // replace wildcards by .*
+ .replace(/\\\*/g, ".*")
+ // process separator placeholders (all ANSI characters but alphanumeric
+ // characters and _%.-)
+ .replace(/\\\^/g, `(?:${separatorRegExp.source}|$)`)
+ // process extended anchor at expression start
+ .replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?:[^\\/]+\\.)?")
+ // process anchor at expression start
+ .replace(/^\\\|/, "^")
+ // process anchor at expression end
+ .replace(/\\\|$/, "$");
+};
+
+
+// end of patterns.js ------------------------------
+
+
+// elemHideEmulation.js ------------------------------
+
+
+//const {makeRegExpParameter, splitSelector,
+// qualifySelector} = require("../common");
+//const {filterToRegExp} = require("../patterns");
+
+const DEFAULT_MIN_INVOCATION_INTERVAL = 2000;
+let minInvocationInterval = DEFAULT_MIN_INVOCATION_INTERVAL;
+const DEFAULT_MAX_SYNCHRONOUS_PROCESSING_TIME = 50;
+let maxSynchronousProcessingTime = DEFAULT_MAX_SYNCHRONOUS_PROCESSING_TIME;
+
+let abpSelectorRegexp = /:(-abp-[\w-]+|has|has-text|xpath|not)\(/;
+
+function toCSSStyleDeclaration(value) {
+ return Object.assign(document.createElement("test"), {style: value}).style;
+}
+
+/**
+ * Creates a new IdleDeadline.
+ *
+ * Note: This function is synchronous and does NOT request an idle
+ * callback.
+ *
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline}.
+ * @return {IdleDeadline}
+ */
+function newIdleDeadline() {
+ let startTime = performance.now();
+ return {
+ didTimeout: false,
+ timeRemaining() {
+ let elapsed = performance.now() - startTime;
+ let remaining = maxSynchronousProcessingTime - elapsed;
+ return Math.max(0, remaining);
+ }
+ };
+}
+
+/**
+ * Returns a promise that is resolved when the browser is next idle.
+ *
+ * This is intended to be used for long running tasks on the UI thread
+ * to allow other UI events to process.
+ *
+ * @return {Promise.<IdleDeadline>}
+ * A promise that is fulfilled when you can continue processing
+ */
+function yieldThread() {
+ return new Promise(resolve => {
+ if (typeof requestIdleCallback === "function") {
+ requestIdleCallback(resolve);
+ }
+ else {
+ setTimeout(() => {
+ resolve(newIdleDeadline());
+ }, 0);
+ }
+ });
+}
+
+
+function getCachedPropertyValue(object, name, defaultValueFunc = () => {}) {
+ let value = object[name];
+ if (typeof value == "undefined")
+ Object.defineProperty(object, name, {value: value = defaultValueFunc()});
+ return value;
+}
+
+/**
+ * Return position of node from parent.
+ * @param {Node} node the node to find the position of.
+ * @return {number} One-based index like for :nth-child(), or 0 on error.
+ */
+function positionInParent(node) {
+ let index = 0;
+ for (let child of node.parentNode.children) {
+ if (child == node)
+ return index + 1;
+
+ index++;
+ }
+
+ return 0;
+}
+
+function makeSelector(node, selector = "") {
+ if (node == null)
+ return null;
+ if (!node.parentElement) {
+ let newSelector = ":root";
+ if (selector)
+ newSelector += " > " + selector;
+ return newSelector;
+ }
+ let idx = positionInParent(node);
+ if (idx > 0) {
+ let newSelector = `${node.tagName}:nth-child(${idx})`;
+ if (selector)
+ newSelector += " > " + selector;
+ return makeSelector(node.parentElement, newSelector);
+ }
+
+ return selector;
+}
+
+function parseSelectorContent(content, startIndex) {
+ let parens = 1;
+ let quote = null;
+ let i = startIndex;
+ for (; i < content.length; i++) {
+ let c = content[i];
+ if (c == "\\") {
+ // Ignore escaped characters
+ i++;
+ }
+ else if (quote) {
+ if (c == quote)
+ quote = null;
+ }
+ else if (c == "'" || c == '"') {
+ quote = c;
+ }
+ else if (c == "(") {
+ parens++;
+ }
+ else if (c == ")") {
+ parens--;
+ if (parens == 0)
+ break;
+ }
+ }
+
+ if (parens > 0)
+ return null;
+ return {text: content.substring(startIndex, i), end: i};
+}
+
+/**
+ * Stringified style objects
+ * @typedef {Object} StringifiedStyle
+ * @property {string} style CSS style represented by a string.
+ * @property {string[]} subSelectors selectors the CSS properties apply to.
+ */
+
+/**
+ * Produce a string representation of the stylesheet entry.
+ * @param {CSSStyleRule} rule the CSS style rule.
+ * @return {StringifiedStyle} the stringified style.
+ */
+function stringifyStyle(rule) {
+ let styles = [];
+ for (let i = 0; i < rule.style.length; i++) {
+ let property = rule.style.item(i);
+ let value = rule.style.getPropertyValue(property);
+ let priority = rule.style.getPropertyPriority(property);
+ styles.push(`${property}: ${value}${priority ? " !" + priority : ""};`);
+ }
+ styles.sort();
+ return {
+ style: styles.join(" "),
+ subSelectors: splitSelector(rule.selectorText)
+ };
+}
+
+let scopeSupported = null;
+
+function tryQuerySelector(subtree, selector, all) {
+ let elements = null;
+ try {
+ elements = all ? subtree.querySelectorAll(selector) :
+ subtree.querySelector(selector);
+ scopeSupported = true;
+ }
+ catch (e) {
+ // Edge doesn't support ":scope"
+ scopeSupported = false;
+ }
+ return elements;
+}
+
+/**
+ * Query selector.
+ *
+ * If it is relative, will try :scope.
+ *
+ * @param {Node} subtree the element to query selector
+ * @param {string} selector the selector to query
+ * @param {bool} [all=false] true to perform querySelectorAll()
+ *
+ * @returns {?(Node|NodeList)} result of the query. null in case of error.
+ */
+function scopedQuerySelector(subtree, selector, all) {
+ if (selector[0] == ">") {
+ selector = ":scope" + selector;
+ if (scopeSupported) {
+ return all ? subtree.querySelectorAll(selector) :
+ subtree.querySelector(selector);
+ }
+ if (scopeSupported == null)
+ return tryQuerySelector(subtree, selector, all);
+ return null;
+ }
+ return all ? subtree.querySelectorAll(selector) :
+ subtree.querySelector(selector);
+}
+
+function scopedQuerySelectorAll(subtree, selector) {
+ return scopedQuerySelector(subtree, selector, true);
+}
+
+class PlainSelector {
+ constructor(selector) {
+ this._selector = selector;
+ this.maybeDependsOnAttributes = /[#.:]|\[.+\]/.test(selector);
+ this.maybeContainsSiblingCombinators = /[~+]/.test(selector);
+ }
+
+ /**
+ * Generator function returning a pair of selector string and subtree.
+ * @param {string} prefix the prefix for the selector.
+ * @param {Node} subtree the subtree we work on.
+ * @param {Node[]} [targets] the nodes we are interested in.
+ */
+ *getSelectors(prefix, subtree, targets) {
+ yield [prefix + this._selector, subtree];
+ }
+}
+
+const incompletePrefixRegexp = /[\s>+~]$/;
+
+class NotSelector {
+ constructor(selectors) {
+ this._innerPattern = new Pattern(selectors);
+ }
+
+ get dependsOnStyles() {
+ return this._innerPattern.dependsOnStyles;
+ }
+
+ get dependsOnCharacterData() {
+ return this._innerPattern.dependsOnCharacterData;
+ }
+
+ get maybeDependsOnAttributes() {
+ return this._innerPattern.maybeDependsOnAttributes;
+ }
+
+ *getSelectors(prefix, subtree, targets) {
+ for (let element of this.getElements(prefix, subtree, targets))
+ yield [makeSelector(element), element];
+ }
+
+ /**
+ * Generator function returning selected elements.
+ * @param {string} prefix the prefix for the selector.
+ * @param {Node} subtree the subtree we work on.
+ * @param {Node[]} [targets] the nodes we are interested in.
+ */
+ *getElements(prefix, subtree, targets) {
+ let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
+ prefix + "*" : prefix;
+ let elements = scopedQuerySelectorAll(subtree, actualPrefix);
+ if (elements) {
+ for (let element of elements) {
+ // If the element is neither an ancestor nor a descendant of one of the
+ // targets, we can skip it.
+ if (targets && !targets.some(target => element.contains(target) ||
+ target.contains(element))) {
+ yield null;
+ continue;
+ }
+
+ if (!this._innerPattern.matches(element, subtree))
+ yield element;
+
+ yield null;
+ }
+ }
+ }
+
+ setStyles(styles) {
+ this._innerPattern.setStyles(styles);
+ }
+}
+
+class HasSelector {
+ constructor(selectors) {
+ this._innerPattern = new Pattern(selectors);
+ }
+
+ get dependsOnStyles() {
+ return this._innerPattern.dependsOnStyles;
+ }
+
+ get dependsOnCharacterData() {
+ return this._innerPattern.dependsOnCharacterData;
+ }
+
+ get maybeDependsOnAttributes() {
+ return this._innerPattern.maybeDependsOnAttributes;
+ }
+
+ *getSelectors(prefix, subtree, targets) {
+ for (let element of this.getElements(prefix, subtree, targets))
+ yield [makeSelector(element), element];
+ }
+
+ /**
+ * Generator function returning selected elements.
+ * @param {string} prefix the prefix for the selector.
+ * @param {Node} subtree the subtree we work on.
+ * @param {Node[]} [targets] the nodes we are interested in.
+ */
+ *getElements(prefix, subtree, targets) {
+ let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
+ prefix + "*" : prefix;
+ let elements = scopedQuerySelectorAll(subtree, actualPrefix);
+ if (elements) {
+ for (let element of elements) {
+ // If the element is neither an ancestor nor a descendant of one of the
+ // targets, we can skip it.
+ if (targets && !targets.some(target => element.contains(target) ||
+ target.contains(element))) {
+ yield null;
+ continue;
+ }
+
+ for (let selector of this._innerPattern.evaluate(element, targets)) {
+ if (selector == null)
+ yield null;
+ else if (scopedQuerySelector(element, selector))
+ yield element;
+ }
+
+ yield null;
+ }
+ }
+ }
+
+ setStyles(styles) {
+ this._innerPattern.setStyles(styles);
+ }
+}
+
+class XPathSelector {
+ constructor(textContent) {
+ this.dependsOnCharacterData = true;
+ this.maybeDependsOnAttributes = true;
+
+ let evaluator = new XPathEvaluator();
+ this._expression = evaluator.createExpression(textContent, null);
+ }
+
+ *getSelectors(prefix, subtree, targets) {
+ for (let element of this.getElements(prefix, subtree, targets))
+ yield [makeSelector(element), element];
+ }
+
+ *getElements(prefix, subtree, targets) {
+ let {ORDERED_NODE_SNAPSHOT_TYPE: flag} = XPathResult;
+ let elements = prefix ? scopedQuerySelectorAll(subtree, prefix) : [subtree];
+ for (let parent of elements) {
+ let result = this._expression.evaluate(parent, flag, null);
+ for (let i = 0, {snapshotLength} = result; i < snapshotLength; i++)
+ yield result.snapshotItem(i);
+ }
+ }
+}
+
+class ContainsSelector {
+ constructor(textContent) {
+ this.dependsOnCharacterData = true;
+
+ this._regexp = makeRegExpParameter(textContent);
+ }
+
+ *getSelectors(prefix, subtree, targets) {
+ for (let element of this.getElements(prefix, subtree, targets))
+ yield [makeSelector(element), subtree];
+ }
+
+ *getElements(prefix, subtree, targets) {
+ let actualPrefix = (!prefix || incompletePrefixRegexp.test(prefix)) ?
+ prefix + "*" : prefix;
+
+ let elements = scopedQuerySelectorAll(subtree, actualPrefix);
+
+ if (elements) {
+ let lastRoot = null;
+ for (let element of elements) {
+ // For a filter like div:-abp-contains(Hello) and a subtree like
+ // <div id="a"><div id="b"><div id="c">Hello</div></div></div>
+ // we're only interested in div#a
+ if (lastRoot && lastRoot.contains(element)) {
+ yield null;
+ continue;
+ }
+
+ lastRoot = element;
+
+ if (targets && !targets.some(target => element.contains(target) ||
+ target.contains(element))) {
+ yield null;
+ continue;
+ }
+
+ if (this._regexp && this._regexp.test(element.textContent))
+ yield element;
+ else
+ yield null;
+ }
+ }
+ }
+}
+
+class PropsSelector {
+ constructor(propertyExpression) {
+ this.dependsOnStyles = true;
+ this.maybeDependsOnAttributes = true;
+
+ let regexpString;
+ if (propertyExpression.length >= 2 && propertyExpression[0] == "/" &&
+ propertyExpression[propertyExpression.length - 1] == "/")
+ regexpString = propertyExpression.slice(1, -1);
+ else
+ regexpString = filterToRegExp(propertyExpression);
+
+ this._regexp = new RegExp(regexpString, "i");
+
+ this._subSelectors = [];
+ }
+
+ *getSelectors(prefix, subtree, targets) {
+ for (let subSelector of this._subSelectors) {
+ if (subSelector.startsWith("*") &&
+ !incompletePrefixRegexp.test(prefix))
+ subSelector = subSelector.substring(1);
+
+ yield [qualifySelector(subSelector, prefix), subtree];
+ }
+ }
+
+ setStyles(styles) {
+ this._subSelectors = [];
+ for (let style of styles) {
+ if (this._regexp.test(style.style)) {
+ for (let subSelector of style.subSelectors) {
+ let idx = subSelector.lastIndexOf("::");
+ if (idx != -1)
+ subSelector = subSelector.substring(0, idx);
+
+ this._subSelectors.push(subSelector);
+ }
+ }
+ }
+ }
+}
+
+class Pattern {
+ constructor(selectors, text) {
+ this.selectors = selectors;
+ this.text = text;
+ }
+
+ get dependsOnStyles() {
+ return getCachedPropertyValue(
+ this, "_dependsOnStyles", () => this.selectors.some(
+ selector => selector.dependsOnStyles
+ )
+ );
+ }
+
+ get maybeDependsOnAttributes() {
+ // Observe changes to attributes if either there's a plain selector that
+ // looks like an ID selector, class selector, or attribute selector in one
+ // of the patterns (e.g. "a[href='https://example.com/']")
+ // or there's a properties selector nested inside a has selector
+ // (e.g. "div:-abp-has(:-abp-properties(color: blue))")
+ return getCachedPropertyValue(
+ this, "_maybeDependsOnAttributes", () => this.selectors.some(
+ selector => selector.maybeDependsOnAttributes ||
+ (selector instanceof HasSelector &&
+ selector.dependsOnStyles)
+ )
+ );
+ }
+
+ get dependsOnCharacterData() {
+ // Observe changes to character data only if there's a contains selector in
+ // one of the patterns.
+ return getCachedPropertyValue(
+ this, "_dependsOnCharacterData", () => this.selectors.some(
+ selector => selector.dependsOnCharacterData
+ )
+ );
+ }
+
+ get maybeContainsSiblingCombinators() {
+ return getCachedPropertyValue(
+ this, "_maybeContainsSiblingCombinators", () => this.selectors.some(
+ selector => selector.maybeContainsSiblingCombinators
+ )
+ );
+ }
+
+ matchesMutationTypes(mutationTypes) {
+ let mutationTypeMatchMap = getCachedPropertyValue(
+ this, "_mutationTypeMatchMap", () => new Map([
+ // All types of DOM-dependent patterns are affected by mutations of
+ // type "childList".
+ ["childList", true],
+ ["attributes", this.maybeDependsOnAttributes],
+ ["characterData", this.dependsOnCharacterData]
+ ])
+ );
+
+ for (let mutationType of mutationTypes) {
+ if (mutationTypeMatchMap.get(mutationType))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Generator function returning CSS selectors for all elements that
+ * match the pattern.
+ *
+ * This allows transforming from selectors that may contain custom
+ * :-abp- selectors to pure CSS selectors that can be used to select
+ * elements.
+ *
+ * The selectors returned from this function may be invalidated by DOM
+ * mutations.
+ *
+ * @param {Node} subtree the subtree we work on
+ * @param {Node[]} [targets] the nodes we are interested in. May be
+ * used to optimize search.
+ */
+ *evaluate(subtree, targets) {
+ let selectors = this.selectors;
+ function* evaluateInner(index, prefix, currentSubtree) {
+ if (index >= selectors.length) {
+ yield prefix;
+ return;
+ }
+ for (let [selector, element] of selectors[index].getSelectors(
+ prefix, currentSubtree, targets
+ )) {
+ if (selector == null)
+ yield null;
+ else
+ yield* evaluateInner(index + 1, selector, element);
+ }
+ // Just in case the getSelectors() generator above had to run some heavy
+ // document.querySelectorAll() call which didn't produce any results, make
+ // sure there is at least one point where execution can pause.
+ yield null;
+ }
+ yield* evaluateInner(0, "", subtree);
+ }
+
+ /**
+ * Checks if a pattern matches a specific element
+ * @param {Node} [target] the element we're interested in checking for
+ * matches on.
+ * @param {Node} subtree the subtree we work on
+ * @return {bool}
+ */
+ matches(target, subtree) {
+ let targetFilter = [target];
+ if (this.maybeContainsSiblingCombinators)
+ targetFilter = null;
+
+ let selectorGenerator = this.evaluate(subtree, targetFilter);
+ for (let selector of selectorGenerator) {
+ if (selector && target.matches(selector))
+ return true;
+ }
+ return false;
+ }
+
+ setStyles(styles) {
+ for (let selector of this.selectors) {
+ if (selector.dependsOnStyles)
+ selector.setStyles(styles);
+ }
+ }
+}
+
+function extractMutationTypes(mutations) {
+ let types = new Set();
+
+ for (let mutation of mutations) {
+ types.add(mutation.type);
+
+ // There are only 3 types of mutations: "attributes", "characterData", and
+ // "childList".
+ if (types.size == 3)
+ break;
+ }
+
+ return types;
+}
+
+function extractMutationTargets(mutations) {
+ if (!mutations)
+ return null;
+
+ let targets = new Set();
+
+ for (let mutation of mutations) {
+ if (mutation.type == "childList") {
+ // When new nodes are added, we're interested in the added nodes rather
+ // than the parent.
+ for (let node of mutation.addedNodes)
+ targets.add(node);
+ if (mutation.removedNodes.length > 0)
+ targets.add(mutation.target);
+ }
+ else {
+ targets.add(mutation.target);
+ }
+ }
+
+ return [...targets];
+}
+
+function filterPatterns(patterns, {stylesheets, mutations}) {
+ if (!stylesheets && !mutations)
+ return patterns.slice();
+
+ let mutationTypes = mutations ? extractMutationTypes(mutations) : null;
+
+ return patterns.filter(
+ pattern => (stylesheets && pattern.dependsOnStyles) ||
+ (mutations && pattern.matchesMutationTypes(mutationTypes))
+ );
+}
+
+function shouldObserveAttributes(patterns) {
+ return patterns.some(pattern => pattern.maybeDependsOnAttributes);
+}
+
+function shouldObserveCharacterData(patterns) {
+ return patterns.some(pattern => pattern.dependsOnCharacterData);
+}
+
+function shouldObserveStyles(patterns) {
+ return patterns.some(pattern => pattern.dependsOnStyles);
+}
+
+/**
+ * @callback hideElemsFunc
+ * @param {Node[]} elements Elements on the page that should be hidden
+ * @param {string[]} elementFilters
+ * The filter text that caused the elements to be hidden
+ */
+
+/**
+ * @callback unhideElemsFunc
+ * @param {Node[]} elements Elements on the page that should be hidden
+ */
+
+
+/**
+ * Manages the front-end processing of element hiding emulation filters.
+ */
+class ElemHideEmulation {
+
+ constructor() {
+ this._filteringInProgress = false;
+ this._nextFilteringScheduled = false;
+ this._lastInvocation = -minInvocationInterval;
+ this._scheduledProcessing = null;
+
+ this.document = document;
+ this.observer = new MutationObserver(this.observe.bind(this));
+ this.hiddenElements = new Set();
+ }
+
+ isSameOrigin(stylesheet) {
+ try {
+ return new URL(stylesheet.href).origin == this.document.location.origin;
+ }
+ catch (e) {
+ // Invalid URL, assume that it is first-party.
+ return true;
+ }
+ }
+
+ /**
+ * Parse the selector
+ * @param {string} selector the selector to parse
+ * @return {Array} selectors is an array of objects,
+ * or null in case of errors.
+ */
+ parseSelector(selector) {
+ if (selector.length == 0)
+ return [];
+
+ let match = abpSelectorRegexp.exec(selector);
+ if (!match)
+ return [new PlainSelector(selector)];
+
+ let selectors = [];
+ if (match.index > 0)
+ selectors.push(new PlainSelector(selector.substring(0, match.index)));
+
+ let startIndex = match.index + match[0].length;
+ let content = parseSelectorContent(selector, startIndex);
+ if (!content) {
+ console.warn(new SyntaxError("Failed to parse Adblock Plus " +
+ `selector ${selector} ` +
+ "due to unmatched parentheses."));
+ return null;
+ }
+ if (match[1] == "-abp-properties") {
+ selectors.push(new PropsSelector(content.text));
+ }
+ else if (match[1] == "-abp-has" || match[1] == "has") {
+ let hasSelectors = this.parseSelector(content.text);
+ if (hasSelectors == null)
+ return null;
+ selectors.push(new HasSelector(hasSelectors));
+ }
+ else if (match[1] == "-abp-contains" || match[1] == "has-text") {
+ selectors.push(new ContainsSelector(content.text));
+ }
+ else if (match[1] === "xpath") {
+ try {
+ selectors.push(new XPathSelector(content.text));
+ }
+ catch ({message}) {
+ console.warn(
+ new SyntaxError(
+ "Failed to parse Adblock Plus " +
+ `selector ${selector}, invalid ` +
+ `xpath: ${content.text} ` +
+ `error: ${message}.`
+ )
+ );
+
+ return null;
+ }
+ }
+ else if (match[1] == "not") {
+ let notSelectors = this.parseSelector(content.text);
+ if (notSelectors == null)
+ return null;
+
+ // if all of the inner selectors are PlainSelectors, then we
+ // don't actually need to use our selector at all. We're better
+ // off delegating to the browser :not implementation.
+ if (notSelectors.every(s => s instanceof PlainSelector))
+ selectors.push(new PlainSelector(`:not(${content.text})`));
+ else
+ selectors.push(new NotSelector(notSelectors));
+ }
+ else {
+ // this is an error, can't parse selector.
+ console.warn(new SyntaxError("Failed to parse Adblock Plus " +
+ `selector ${selector}, invalid ` +
+ `pseudo-class :${match[1]}().`));
+ return null;
+ }
+
+ let suffix = this.parseSelector(selector.substring(content.end + 1));
+ if (suffix == null)
+ return null;
+
+ selectors.push(...suffix);
+
+ if (selectors.length == 1 && selectors[0] instanceof ContainsSelector) {
+ console.warn(new SyntaxError("Failed to parse Adblock Plus " +
+ `selector ${selector}, can't ` +
+ "have a lonely :-abp-contains()."));
+ return null;
+ }
+ return selectors;
+ }
+
+ /**
+ * Reads the rules out of CSS stylesheets
+ * @param {CSSStyleSheet[]} [stylesheets] The list of stylesheets to
+ * read.
+ * @return {CSSStyleRule[]}
+ */
+ _readCssRules(stylesheets) {
+ let cssStyles = [];
+
+ for (let stylesheet of stylesheets || []) {
+ // Explicitly ignore third-party stylesheets to ensure consistent behavior
+ // between Firefox and Chrome.
+ if (!this.isSameOrigin(stylesheet))
+ continue;
+
+ let rules;
+ try {
+ rules = stylesheet.cssRules;
+ }
+ catch (e) {
+ // On Firefox, there is a chance that an InvalidAccessError
+ // get thrown when accessing cssRules. Just skip the stylesheet
+ // in that case.
+ // See https://searchfox.org/mozilla-central/rev/f65d7528e34ef1a7665b4a1a7b7cdb1388fcd3aa/layout/style/StyleSheet.cpp#699
+ continue;
+ }
+
+ if (!rules)
+ continue;
+
+ for (let rule of rules) {
+ if (rule.type != rule.STYLE_RULE)
+ continue;
+
+ cssStyles.push(stringifyStyle(rule));
+ }
+ }
+ return cssStyles;
+ }
+
+ /**
+ * Processes the current document and applies all rules to it.
+ * @param {CSSStyleSheet[]} [stylesheets]
+ * The list of new stylesheets that have been added to the document and
+ * made reprocessing necessary. This parameter shouldn't be passed in for
+ * the initial processing, all of document's stylesheets will be considered
+ * then and all rules, including the ones not dependent on styles.
+ * @param {MutationRecord[]} [mutations]
+ * The list of DOM mutations that have been applied to the document and
+ * made reprocessing necessary. This parameter shouldn't be passed in for
+ * the initial processing, the entire document will be considered
+ * then and all rules, including the ones not dependent on the DOM.
+ * @return {Promise}
+ * A promise that is fulfilled once all filtering is completed
+ */
+ async _addSelectors(stylesheets, mutations) {
+ let deadline = newIdleDeadline();
+
+ if (shouldObserveStyles(this.patterns))
+ this._refreshPatternStyles();
+
+ let patternsToCheck = filterPatterns(
+ this.patterns, {stylesheets, mutations}
+ );
+
+ let targets = extractMutationTargets(mutations);
+
+ let elementsToHide = [];
+ let elementsToUnhide = new Set(this.hiddenElements);
+ let elementsToRemove = [];
+ let elementsToApplyCss = [];
+ let cssToApply = [];
+
+ for (let pattern of patternsToCheck) {
+ let evaluationTargets = targets;
+
+ // If the pattern appears to contain any sibling combinators, we can't
+ // easily optimize based on the mutation targets. Since this is a
+ // special case, skip the optimization. By setting it to null here we
+ // make sure we process the entire DOM.
+ if (pattern.maybeContainsSiblingCombinators)
+ evaluationTargets = null;
+
+ let generator = pattern.evaluate(this.document, evaluationTargets);
+ for (let selector of generator) {
+ if (selector != null) {
+ try {
+ for (let element of this.document.querySelectorAll(selector)) {
+ if (pattern.text !== "") {
+ if (pattern.text === "remove()") {
+ elementsToRemove.push(element);
+ } else {
+ elementsToApplyCss.push(element);
+ cssToApply.push(pattern.text);
+ }
+ } else if (!this.hiddenElements.has(element)) {
+ elementsToHide.push(element);
+ } else {
+ elementsToUnhide.delete(element);
+ }
+ }
+ } catch (e) {
+ console.debug("[eyeo] Skipping unsuported selector: " + selector);
+ }
+ }
+
+ if (deadline.timeRemaining() <= 0)
+ deadline = await yieldThread();
+ }
+ }
+ this._hideElems(elementsToHide);
+ this._removeElems(elementsToRemove);
+ this._applyCssToElems(elementsToApplyCss, cssToApply);
+
+ // The search for elements to hide it optimized to find new things
+ // to hide quickly, by not checking all patterns and not checking
+ // the full DOM. That's why we need to do a more thorough check
+ // for each remaining element that might need to be unhidden,
+ // checking all patterns.
+ for (let elem of elementsToUnhide) {
+ if (!elem.isConnected) {
+ // elements that are no longer in the DOM should be unhidden
+ // in case they're ever readded, and then forgotten about so
+ // we don't cause a memory leak.
+ continue;
+ }
+ let matchesAny = this.patterns.some(pattern => pattern.matches(
+ elem, this.document
+ ) && (pattern.text === ""));
+ if (matchesAny)
+ elementsToUnhide.delete(elem);
+
+ if (deadline.timeRemaining() <= 0)
+ deadline = await yieldThread();
+ }
+ this._unhideElems(Array.from(elementsToUnhide));
+ }
+
+ _hideElems(elementsToHide) {
+ for (let elem of elementsToHide) {
+ if (elem.style.display != "none")
+ elem.style.display = "none";
+ this.hiddenElements.add(elem);
+ }
+ }
+
+ _unhideElems(elementsToUnhide) {
+ for (let elem of elementsToUnhide) {
+ if (elem.style.display === "none")
+ elem.style.display = "";
+ this.hiddenElements.delete(elem);
+ }
+ }
+
+ _removeElems(elementsToRemove) {
+ for (let elem of elementsToRemove)
+ elem.remove();
+ }
+
+ _applyCssToElems(elementsToApplyCss, cssToApply) {
+ for (let i=0; i < elementsToApplyCss.length; ++i)
+ elementsToApplyCss[i].style.cssText += cssToApply[i];
+ }
+
+ /**
+ * Performed any scheduled processing.
+ *
+ * This function is asynchronous, and should not be run multiple
+ * times in parallel. The flag `_filteringInProgress` is set and
+ * unset so you can check if it's already running.
+ * @return {Promise}
+ * A promise that is fulfilled once all filtering is completed
+ */
+ async _processFiltering() {
+ if (this._filteringInProgress) {
+ console.warn("ElemHideEmulation scheduling error: " +
+ "Tried to process filtering in parallel.");
+ return;
+ }
+ let params = this._scheduledProcessing || {};
+ this._scheduledProcessing = null;
+ this._filteringInProgress = true;
+ this._nextFilteringScheduled = false;
+ await this._addSelectors(
+ params.stylesheets,
+ params.mutations
+ );
+ this._lastInvocation = performance.now();
+ this._filteringInProgress = false;
+ if (this._scheduledProcessing)
+ this._scheduleNextFiltering();
+ }
+
+ /**
+ * Appends new changes to the list of filters for the next time
+ * filtering is run.
+ * @param {CSSStyleSheet[]} [stylesheets]
+ * new stylesheets to be processed. This parameter should be omitted
+ * for full reprocessing.
+ * @param {MutationRecord[]} [mutations]
+ * new DOM mutations to be processed. This parameter should be omitted
+ * for full reprocessing.
+ */
+ _appendScheduledProcessing(stylesheets, mutations) {
+ if (!this._scheduledProcessing) {
+ // There isn't anything scheduled yet. Make the schedule.
+ this._scheduledProcessing = {stylesheets, mutations};
+ }
+ else if (!stylesheets && !mutations) {
+ // The new request was to reprocess everything, and so any
+ // previous filters are irrelevant.
+ this._scheduledProcessing = {};
+ }
+ else if (this._scheduledProcessing.stylesheets ||
+ this._scheduledProcessing.mutations) {
+ // The previous filters are not to filter everything, so the new
+ // parameters matter. Push them onto the appropriate lists.
+ if (stylesheets) {
+ if (!this._scheduledProcessing.stylesheets)
+ this._scheduledProcessing.stylesheets = [];
+ this._scheduledProcessing.stylesheets.push(...stylesheets);
+ }
+ if (mutations) {
+ if (!this._scheduledProcessing.mutations)
+ this._scheduledProcessing.mutations = [];
+ this._scheduledProcessing.mutations.push(...mutations);
+ }
+ }
+ else {
+ // this._scheduledProcessing is already going to recheck
+ // everything, so no need to do anything here.
+ }
+ }
+
+ /**
+ * Schedule filtering to be processed in the future, or start
+ * processing immediately.
+ *
+ * If processing is already scheduled, this does nothing.
+ */
+ _scheduleNextFiltering() {
+ if (this._nextFilteringScheduled || this._filteringInProgress) {
+ // The next one has already been scheduled. Our new events are
+ // on the queue, so nothing more to do.
+ return;
+ }
+
+ if (this.document.readyState === "loading") {
+ // Document isn't fully loaded yet, so schedule our first
+ // filtering as soon as that's done.
+ this.document.addEventListener(
+ "DOMContentLoaded",
+ () => this._processFiltering(),
+ {once: true}
+ );
+ this._nextFilteringScheduled = true;
+ }
+ else if (performance.now() - this._lastInvocation <
+ minInvocationInterval) {
+ // It hasn't been long enough since our last filter. Set the
+ // timeout for when it's time for that.
+ setTimeout(
+ () => this._processFiltering(),
+ minInvocationInterval - (performance.now() - this._lastInvocation)
+ );
+ this._nextFilteringScheduled = true;
+ }
+ else {
+ // We can actually just start filtering immediately!
+ this._processFiltering();
+ }
+ }
+
+ /**
+ * Re-run filtering either immediately or queued.
+ * @param {CSSStyleSheet[]} [stylesheets]
+ * new stylesheets to be processed. This parameter should be omitted
+ * for full reprocessing.
+ * @param {MutationRecord[]} [mutations]
+ * new DOM mutations to be processed. This parameter should be omitted
+ * for full reprocessing.
+ */
+ queueFiltering(stylesheets, mutations) {
+ this._appendScheduledProcessing(stylesheets, mutations);
+ this._scheduleNextFiltering();
+ }
+
+ _refreshPatternStyles(stylesheet) {
+ let allCssRules = this._readCssRules(this.document.styleSheets);
+ for (let pattern of this.patterns)
+ pattern.setStyles(allCssRules);
+ }
+
+ onLoad(event) {
+ let stylesheet = event.target.sheet;
+ if (stylesheet)
+ this.queueFiltering([stylesheet]);
+ }
+
+ observe(mutations) {
+ this.queueFiltering(null, mutations);
+ }
+
+ apply(patterns) {
+ this.patterns = [];
+ for (let pattern of patterns) {
+ let selectors = this.parseSelector(pattern.selector);
+ if (selectors != null && selectors.length > 0)
+ this.patterns.push(new Pattern(selectors, pattern.text !== undefined ? pattern.text : ""));
+ }
+
+ if (this.patterns.length > 0) {
+ this.queueFiltering();
+
+ let attributes = shouldObserveAttributes(this.patterns);
+ this.observer.observe(
+ this.document,
+ {
+ childList: true,
+ attributes,
+ attributeOldValue: attributes,
+ characterData: shouldObserveCharacterData(this.patterns),
+ subtree: true
+ }
+ );
+ if (shouldObserveStyles(this.patterns)) {
+ let onLoad = this.onLoad.bind(this);
+ if (this.document.readyState === "loading")
+ this.document.addEventListener("DOMContentLoaded", onLoad, true);
+ this.document.addEventListener("load", onLoad, true);
+ }
+ }
+ }
+};
+
+// end of elemHideEmulation.js ------------------------------
+
+/*
+ * elemHidingEmulatedPatterns array definition is generated in element_hider_impl.cc
+ * Example:
+ * [
+ * {selector: "#id_to_hide"},
+ * {selector: "#id_to_remove", text: "remove()"},
+ * {selector: "#id_to_apply_style", text: "background-color: #00FF00!important;"},
+ * ];
+*/
+
+let elemHidingEmulatedPatterns = [{{elemHidingEmulatedPatternsDef}}];
+
+// adopted from applyElemHideEmulation function in:
+// https://gitlab.com/eyeo/adblockplus/adblockpluscore/blob/master/test/browser/elemHideEmulation.js
+
+let elemHideEmulation = new ElemHideEmulation();
+
+elemHideEmulation.apply(elemHidingEmulatedPatterns);
diff --git a/components/adblock/core/resources/snippets_deps.py b/components/adblock/core/resources/snippets_deps.py
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/resources/snippets_deps.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import os.path
+import shutil
+
+
+class SnippetsUtils:
+
+ def __init__(self, args):
+ self.deps_input_file = args.deps
+ self.deps_output_file = args.output + '/snippets-xpath3-dep.jst'
+ self.script_input_file = args.lib
+ self.script_output_file = args.output + '/snippets.jst'
+
+ # Reads the dependencies.jst file distributed with snippets library
+ # and extracts from it the code which is required to support xpath3 snippet filters.
+ # The extracted code is copied into the location pointed out by output_file argument.
+ def prepare_deps(self):
+ # Create file so it exists for sure
+ with open(self.deps_output_file, 'w', encoding='utf8') as out_file:
+ lines_to_write = []
+ start_tag = '/**! Start hide-if-matches-xpath3 dependency !**/'
+ end_tag = '/**! End hide-if-matches-xpath3 dependency !**/'
+ between_tags = False
+ with open(self.deps_input_file, 'r', encoding='utf8') as in_file:
+ for line in in_file:
+ if line.strip() == start_tag:
+ between_tags = True
+ continue
+ if line.strip() == end_tag:
+ between_tags = False
+ continue
+ if between_tags:
+ lines_to_write.append(line)
+ out_file.writelines(lines_to_write)
+
+ def prepare_script(self):
+ shutil.copy(self.script_input_file, self.script_output_file)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ description='Prepares snippets library for BAS')
+ parser.add_argument('--deps',
+ type=str,
+ help='snippet library dependencies')
+ parser.add_argument('--lib',
+ type=str,
+ help='main script for snippet library')
+ parser.add_argument('--output',
+ type=str,
+ help='output folder for final content')
+ main = SnippetsUtils(parser.parse_args())
+ main.prepare_deps()
+ main.prepare_script()
diff --git a/components/adblock/core/resources/update.sh b/components/adblock/core/resources/update.sh
new file mode 100755
--- /dev/null
+++ b/components/adblock/core/resources/update.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+# This script downloads filter lists that get bundled with the browser binary.
+# Bundled filter lists allow out-of-the-box ad-filtering and serve as backup in
+# case the browser needs to navigate to a website without having downloaded
+# the desired filter list from the Internet yet.
+#
+# The browser will replace these bundled filter lists by ones downloaded from
+# the Internet as soon as possible.
+
+# We use minified lists to reduce resource bundle size and speed up startup.
+# We don't care about perfect ad-filtering quality as we expect these to be
+# replaced very soon.
+wget https://easylist-downloads.adblockplus.org/easylist-minified.txt -O easylist.txt
+gzip -f easylist.txt
+
+wget https://easylist-downloads.adblockplus.org/exceptionrules-minimal.txt -O exceptionrules.txt
+gzip -f exceptionrules.txt
+
+wget https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt -O anticv.txt
+gzip -f anticv.txt
diff --git a/components/adblock/core/schema/filter_list_schema.fbs b/components/adblock/core/schema/filter_list_schema.fbs
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/schema/filter_list_schema.fbs
@@ -0,0 +1,190 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+namespace adblock.flat;
+file_identifier "ABP2";
+
+// Filter types
+// ============
+
+enum AbpResource: byte {
+ BlankText,
+ BlankCss,
+ BlankJs,
+ BlankHtml,
+ BlankMp3,
+ BlankMp4,
+ TransparentGif1x1,
+ TransparentPng2x2,
+ TransparentPng3x2,
+ TransparentPng32x32
+}
+
+table Rewrite {
+ replace_with: AbpResource;
+}
+
+table Header {
+ header: string (required);
+ pattern: string;
+}
+
+enum ThirdParty: byte {
+ Ignore,
+ FirstPartyOnly,
+ ThirdPartyOnly,
+}
+
+enum ResourceType: uint32 {
+ Other = 1,
+ Script = 2,
+ Image = 4,
+ Stylesheet = 8,
+ Object = 16,
+ Subdocument = 32,
+ WebSocket = 128,
+ WebRtc = 256,
+ Ping = 1024,
+ XmlHttpRequest = 2048,
+ Media = 16384,
+ Font = 32768,
+ WebBundle = 65536
+}
+
+// usage note: you figure out if this is blocking or allowing based on if
+// it's stored in a 'block' or 'allow' list.
+table UrlFilter {
+ filter_text: string;
+ pattern: string (required);
+ match_case: bool;
+ resource_type: uint32; // this is a bitset mask of ResourceTypes
+ third_party: ThirdParty = Ignore;
+ sitekeys: [string];
+ include_domains: [string];
+ exclude_domains: [string];
+ rewrite: Rewrite;
+ csp_filter: string;
+ header_filter: string;
+ header: Header;
+}
+
+// usage note: you figure out if this is blocking or allowing based on if
+// it's stored in a 'block' or 'allow' list. You also need to use
+// where it's stored to determine its domains, and whether it needs elem
+// hide emulation or not.
+table ElemHideFilter {
+ filter_text: string;
+ selector: string (required);
+ exclude_domains: [string];
+}
+
+table RemoveFilter {
+ filter_text: string;
+ selector: string (required);
+ exclude_domains: [string];
+}
+
+table InlineCssFilter {
+ filter_text: string;
+ selector: string (required);
+ css: string (required);
+ exclude_domains: [string];
+}
+
+table SnippetFunctionCall {
+ command: string (required);
+ arguments: [string];
+}
+
+table SnippetFilter {
+ filter_text: string;
+ exclude_domains: [string];
+ script: [SnippetFunctionCall];
+}
+
+
+// Indexes
+// =======
+
+table UrlFiltersByKeyword {
+ keyword: string (key);
+ filter: [UrlFilter];
+}
+
+// encoder note: the same ElemHideFilter may appear in multiple
+// domains. Ensure that the same offset is stored rather than reencoding
+// the filter multiple times.
+table ElemHideFiltersByDomain {
+ domain: string (key);
+ filter: [ElemHideFilter];
+}
+
+table RemoveFiltersByDomain {
+ domain: string (key);
+ filter: [RemoveFilter];
+}
+
+table InlineCssFiltersByDomain {
+ domain: string (key);
+ filter: [InlineCssFilter];
+}
+
+// encoder note: the same SnippetFilter may appear in multiple
+// domains. Ensure that the same offset is stored rather than reencoding
+// the filter multiple times.
+table SnippetFiltersByDomain {
+ domain: string (key);
+ filter: [SnippetFilter];
+}
+
+
+// Root
+// ====
+
+table SubscriptionMetadata {
+ flatbuffers_schema_version: string;
+ url: string;
+ homepage: string;
+ title: string;
+ version: string;
+ expires: uint64;
+}
+
+table Subscription {
+ metadata: SubscriptionMetadata;
+ url_subresource_block: [UrlFiltersByKeyword];
+ url_subresource_allow: [UrlFiltersByKeyword];
+ url_popup_block: [UrlFiltersByKeyword];
+ url_popup_allow: [UrlFiltersByKeyword];
+ url_document_allow: [UrlFiltersByKeyword];
+ url_elemhide_allow: [UrlFiltersByKeyword];
+ url_generichide_allow: [UrlFiltersByKeyword];
+ url_genericblock_allow: [UrlFiltersByKeyword];
+ url_csp_block: [UrlFiltersByKeyword];
+ url_csp_allow: [UrlFiltersByKeyword];
+ url_rewrite_block: [UrlFiltersByKeyword];
+ url_rewrite_allow: [UrlFiltersByKeyword];
+ url_header_block: [UrlFiltersByKeyword];
+ url_header_allow: [UrlFiltersByKeyword];
+ elemhide: [ElemHideFiltersByDomain];
+ elemhide_emulation: [ElemHideFiltersByDomain];
+ elemhide_exception: [ElemHideFiltersByDomain];
+ remove: [RemoveFiltersByDomain];
+ inline_css: [InlineCssFiltersByDomain];
+ snippet: [SnippetFiltersByDomain];
+}
+
+root_type Subscription;
diff --git a/components/adblock/core/schema/schema_hash.h b/components/adblock/core/schema/schema_hash.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/schema/schema_hash.h
@@ -0,0 +1,30 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Generated by chrome/tools/build/sha256_file.py
+// !! DO NOT EDIT !!
+
+#include <stdint.h>
+
+#include <array>
+
+#ifndef GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_
+#define GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_
+
+extern const std::array<uint8_t, 32> kSha256_filter_list_schema_generated_h;
+
+#endif // GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_
diff --git a/components/adblock/core/session_stats.h b/components/adblock/core/session_stats.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/session_stats.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_
+#define COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_
+
+#include <map>
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+/**
+ * @brief Stores statistics about blocked and allowed URLs
+ * in current session (runtime).
+ * Lives in the UI thread.
+ */
+class SessionStats : public KeyedService {
+ public:
+ virtual std::map<GURL, long> GetSessionAllowedResourcesCount() const = 0;
+
+ virtual std::map<GURL, long> GetSessionBlockedResourcesCount() const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_
diff --git a/components/adblock/core/sitekey_storage.h b/components/adblock/core/sitekey_storage.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/sitekey_storage.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_
+#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "net/http/http_response_headers.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+/**
+ * @brief Parses response headers in search for AdblockPlus sitekeys and stores
+ * them.
+ * Some filters can only be applied on pages that provide a valid sitekey.
+ * Storage is not persistent.
+ * Lives in the UI thread.
+ */
+class SitekeyStorage : public KeyedService {
+ public:
+ // Attempts to extract a sitekey from |headers|. If successful, the sitekey
+ // is added to storage and can be retrieved by |FindSiteKeyForAnyUrl|.
+ virtual void ProcessResponseHeaders(
+ const GURL& request_url,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ const std::string& user_agent) = 0;
+
+ virtual absl::optional<std::pair<GURL, SiteKey>> FindSiteKeyForAnyUrl(
+ const std::vector<GURL>& urls) const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_
diff --git a/components/adblock/core/sitekey_storage_impl.cc b/components/adblock/core/sitekey_storage_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/sitekey_storage_impl.cc
@@ -0,0 +1,164 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/sitekey_storage_impl.h"
+
+#include "absl/types/optional.h"
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "crypto/openssl_util.h"
+#include "crypto/signature_verifier.h"
+#include "net/http/http_response_headers.h"
+
+namespace adblock {
+namespace {
+
+SiteKey GetSitekeyHeader(
+ const scoped_refptr<net::HttpResponseHeaders>& headers) {
+ size_t iterator = 0;
+ std::string name;
+ std::string value;
+ while (headers->EnumerateHeaderLines(&iterator, &name, &value)) {
+ std::transform(name.begin(), name.end(), name.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+ if (name == adblock::kSiteKeyHeaderKey) {
+ return SiteKey{value};
+ }
+ }
+ return {};
+}
+
+} // namespace
+
+SitekeyStorageImpl::SitekeyStorageImpl() = default;
+
+SitekeyStorageImpl::~SitekeyStorageImpl() = default;
+
+void SitekeyStorageImpl::ProcessResponseHeaders(
+ const GURL& request_url,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ const std::string& user_agent) {
+ if (user_agent.empty()) {
+ LOG(WARNING) << "[eyeo] No user agent info";
+ return;
+ }
+
+ auto site_key = GetSitekeyHeader(headers);
+ if (site_key.value().empty()) {
+ VLOG(1) << "[eyeo] No site key header";
+ return;
+ }
+
+ ProcessSiteKey(request_url, site_key, user_agent);
+}
+
+absl::optional<std::pair<GURL, SiteKey>>
+SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector<GURL>& urls) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (const auto& url : urls) {
+ auto elem = url_to_sitekey_map_.find(url);
+ if (elem != url_to_sitekey_map_.cend()) {
+ return {*elem};
+ }
+ }
+ return {};
+}
+
+void SitekeyStorageImpl::ProcessSiteKey(const GURL& request_url,
+ const SiteKey& site_key,
+ const std::string& user_agent) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!site_key.value().empty());
+ auto site_key_pair = FindSiteKeyForAnyUrl({request_url});
+ if (site_key_pair.has_value() &&
+ site_key.value() == site_key_pair->second.value()) {
+ DVLOG(3) << "[eyeo] Public key already stored for url: "
+ << site_key_pair->first;
+ return;
+ }
+
+ GURL url = request_url.GetAsReferrer();
+ TRACE_EVENT1("eyeo", "ProcessSiteKeyImpl", "url", request_url.spec());
+ size_t delimiter = site_key.value().find("_");
+ if ((delimiter == std::string::npos) ||
+ (delimiter >= (site_key.value().length() - 1))) {
+ LOG(ERROR) << "[eyeo] Wrong format of site key header value: "
+ << site_key.value();
+ return;
+ }
+
+ std::string public_key = site_key.value().substr(0, delimiter);
+ std::string public_key_stripped = public_key.substr(0, public_key.find("=="));
+ std::string signature = site_key.value().substr(delimiter + 1);
+ DVLOG(1) << "[eyeo] Found site key header, public key: " << public_key
+ << ", signature: " << signature;
+
+ auto path_with_query = url.path();
+ if (url.has_query()) {
+ path_with_query += "?" + url.query();
+ }
+ DLOG(INFO) << "[eyeo] Calling IsSitekeySignatureValid(publicKey, signature, "
+ "uri, host,"
+ " userAgent) with arguments: ("
+ << public_key << ", " << signature << ", " << path_with_query
+ << ", " << url.host() << ", " << user_agent << ")";
+
+ std::string data = path_with_query + '\0' + url.host() + '\0' + user_agent;
+ if (IsSitekeySignatureValid(public_key, signature, data) &&
+ !request_url.is_empty() && request_url.is_valid() &&
+ !site_key.value().empty()) {
+ url_to_sitekey_map_[url] = SiteKey{public_key_stripped};
+ } else {
+ LOG(ERROR) << "[eyeo] Sitekey verification failed";
+ }
+}
+
+bool SitekeyStorageImpl::IsSitekeySignatureValid(
+ const std::string& public_key_b64,
+ const std::string& signature_b64,
+ const std::string& data) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::string signature;
+ if (!base::Base64Decode(signature_b64, &signature,
+ base::Base64DecodePolicy::kForgiving)) {
+ DLOG(WARNING) << "[eyeo] Signature decode failed";
+ return false;
+ }
+
+ std::string public_key;
+ if (!base::Base64Decode(public_key_b64, &public_key,
+ base::Base64DecodePolicy::kForgiving)) {
+ DLOG(WARNING) << "[eyeo] Public key decode failed";
+ return false;
+ }
+
+ crypto::SignatureVerifier verifier;
+ if (verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ base::as_bytes(base::span(signature)),
+ base::as_bytes(base::span(public_key)))) {
+ verifier.VerifyUpdate(base::as_bytes(base::span(data)));
+ return verifier.VerifyFinal();
+ }
+
+ DLOG(WARNING) << "[eyeo] Verifier initialization failed";
+ return false;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/sitekey_storage_impl.h b/components/adblock/core/sitekey_storage_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/sitekey_storage_impl.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_
+
+#include <map>
+
+#include "absl/types/optional.h"
+
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/sitekey_storage.h"
+
+namespace adblock {
+
+class SitekeyStorageImpl final : public SitekeyStorage {
+ public:
+ SitekeyStorageImpl();
+ ~SitekeyStorageImpl() final;
+
+ void ProcessResponseHeaders(
+ const GURL& request_url,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ const std::string& user_agent) final;
+
+ absl::optional<std::pair<GURL, SiteKey>> FindSiteKeyForAnyUrl(
+ const std::vector<GURL>& urls) const final;
+
+ private:
+ void ProcessSiteKey(const GURL& request_url,
+ const SiteKey& site_key,
+ const std::string& user_agent);
+
+ bool IsSitekeySignatureValid(const std::string& public_key_b64,
+ const std::string& signature_b64,
+ const std::string& data) const;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ std::map<GURL, SiteKey> url_to_sitekey_map_;
+ base::WeakPtrFactory<SitekeyStorageImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_
diff --git a/components/adblock/core/subscription/BUILD.gn b/components/adblock/core/subscription/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/BUILD.gn
@@ -0,0 +1,186 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+source_set("subscription") {
+ sources = [
+ "conversion_executors.h",
+ "domain_splitter.cc",
+ "domain_splitter.h",
+ "filtering_configuration_maintainer.h",
+ "filtering_configuration_maintainer_impl.cc",
+ "filtering_configuration_maintainer_impl.h",
+ "installed_subscription.cc",
+ "installed_subscription.h",
+ "installed_subscription_impl.cc",
+ "installed_subscription_impl.h",
+ "pattern_matcher.cc",
+ "pattern_matcher.h",
+ "preloaded_subscription_provider.h",
+ "preloaded_subscription_provider_impl.cc",
+ "preloaded_subscription_provider_impl.h",
+ "recommended_subscription_installer.h",
+ "recommended_subscription_installer_impl.cc",
+ "recommended_subscription_installer_impl.h",
+ "recommended_subscription_parser.cc",
+ "recommended_subscription_parser.h",
+ "regex_matcher.cc",
+ "regex_matcher.h",
+ "subscription.cc",
+ "subscription.h",
+ "subscription_collection.h",
+ "subscription_collection_impl.cc",
+ "subscription_collection_impl.h",
+ "subscription_config.cc",
+ "subscription_config.h",
+ "subscription_downloader.h",
+ "subscription_downloader_impl.cc",
+ "subscription_downloader_impl.h",
+ "subscription_persistent_metadata.h",
+ "subscription_persistent_metadata_impl.cc",
+ "subscription_persistent_metadata_impl.h",
+ "subscription_persistent_storage.h",
+ "subscription_persistent_storage_impl.cc",
+ "subscription_persistent_storage_impl.h",
+ "subscription_service.h",
+ "subscription_service_impl.cc",
+ "subscription_service_impl.h",
+ "subscription_validator.h",
+ "subscription_validator_impl.cc",
+ "subscription_validator_impl.h",
+ "url_keyword_extractor.cc",
+ "url_keyword_extractor.h",
+ ]
+
+ deps = [
+ "//components/adblock/core/common:utils",
+ "//components/adblock/core/converter",
+ "//components/adblock/core/resources:adblock_resources",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/adblock/core:schema",
+ "//components/adblock/core/common",
+ "//components/adblock/core/common",
+ "//components/adblock/core/configuration",
+ "//components/adblock/core/net",
+ "//components/keyed_service/core",
+ "//components/prefs",
+ "//third_party/abseil-cpp:absl",
+ "//third_party/re2",
+ "//url:url",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/load_gzipped_test_file.cc",
+ "test/load_gzipped_test_file.h",
+ "test/mock_conversion_executors.cc",
+ "test/mock_conversion_executors.h",
+ "test/mock_filtering_configuration_maintainer.cc",
+ "test/mock_filtering_configuration_maintainer.h",
+ "test/mock_installed_subscription.cc",
+ "test/mock_installed_subscription.h",
+ "test/mock_subscription.cc",
+ "test/mock_subscription.h",
+ "test/mock_subscription_collection.cc",
+ "test/mock_subscription_collection.h",
+ "test/mock_subscription_downloader.cc",
+ "test/mock_subscription_downloader.h",
+ "test/mock_subscription_persistent_metadata.cc",
+ "test/mock_subscription_persistent_metadata.h",
+ "test/mock_subscription_service.cc",
+ "test/mock_subscription_service.h",
+ ]
+
+ public_deps = [
+ ":subscription",
+ "//components/adblock/core/configuration:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ deps = [ "//third_party/zlib/google:compression_utils" ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "test/domain_splitter_test.cc",
+ "test/filtering_configuration_maintainer_impl_test.cc",
+ "test/installed_subscription_impl_csp_test.cc",
+ "test/installed_subscription_impl_elemhide_test.cc",
+ "test/installed_subscription_impl_header_test.cc",
+ "test/installed_subscription_impl_list_converter_test.cc",
+ "test/installed_subscription_impl_metadata_test.cc",
+ "test/installed_subscription_impl_rewrite_test.cc",
+ "test/installed_subscription_impl_snippets_test.cc",
+ "test/installed_subscription_impl_test_base.cc",
+ "test/installed_subscription_impl_test_base.h",
+ "test/installed_subscription_impl_url_test.cc",
+ "test/pattern_matcher_test.cc",
+ "test/preloaded_subscription_provider_impl_test.cc",
+ "test/recommended_subscription_installer_impl_test.cc",
+ "test/recommended_subscription_parser_test.cc",
+ "test/subscription_collection_impl_test.cc",
+ "test/subscription_downloader_impl_test.cc",
+ "test/subscription_persistent_metadata_impl_test.cc",
+ "test/subscription_persistent_storage_impl_test.cc",
+ "test/subscription_service_impl_test.cc",
+ "test/subscription_validator_impl_test.cc",
+ "test/url_keyword_extractor_test.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ "//components/adblock/core",
+ "//components/adblock/core/common:test_support",
+ "//components/adblock/core/configuration:test_support",
+ "//components/adblock/core/converter",
+ "//components/adblock/core/net:test_support",
+ "//components/adblock/core/resources:adblock_resources",
+ "//components/prefs:test_support",
+ "//components/sync_preferences:test_support",
+ "//net:test_support",
+ "//services/network:test_support",
+ "//testing/gtest",
+ ]
+}
+
+source_set("perf_tests") {
+ testonly = true
+ sources = [
+ "test/pattern_matcher_perftest.cc",
+ "test/regex_matcher_perftest.cc",
+ ]
+
+ deps = [
+ ":subscription",
+ ":test_support",
+ "//base",
+ "//components/adblock/core",
+ "//testing/gtest",
+ "//testing/perf",
+ ]
+
+ data = [
+ "//components/test/data/adblock/40_regex_patterns.txt.gz",
+ "//components/test/data/adblock/5000_patterns.txt.gz",
+ "//components/test/data/adblock/5000_urls.txt.gz",
+ ]
+}
diff --git a/components/adblock/core/subscription/conversion_executors.h b/components/adblock/core/subscription/conversion_executors.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/conversion_executors.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+
+#include "url/gurl.h"
+
+namespace adblock {
+
+class ConversionExecutors {
+ public:
+ // Synchronous
+ virtual scoped_refptr<InstalledSubscription> ConvertCustomFilters(
+ const std::vector<std::string>& filters) const = 0;
+ // Asynchronous
+ virtual void ConvertFilterListFile(
+ const GURL& subscription_url,
+ const base::FilePath& path,
+ base::OnceCallback<void(ConversionResult)> result_callback) const = 0;
+
+ virtual ~ConversionExecutors() = default;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_
diff --git a/components/adblock/core/subscription/domain_splitter.cc b/components/adblock/core/subscription/domain_splitter.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/domain_splitter.cc
@@ -0,0 +1,74 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/domain_splitter.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+
+namespace adblock {
+
+DomainSplitter::DomainSplitter(std::string_view domain)
+ : remaining_domain_(base::TrimString(domain, ".", base::TRIM_ALL)) {
+ // If the domain has a public registry, for "example info.domain.co.uk", we
+ // prepare to also iterate over the domain without the registry, i.e.
+ // "info.domain." in order to match wildcard filters.
+ const size_t registry_length =
+ net::registry_controlled_domains::GetCanonicalHostRegistryLength(
+ domain,
+ net::registry_controlled_domains::UnknownRegistryFilter::
+ EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::PrivateRegistryFilter::
+ EXCLUDE_PRIVATE_REGISTRIES);
+ if (registry_length != 0) {
+ domain_sans_registry_copy_ =
+ domain.substr(0, domain.size() - registry_length);
+ remaining_domain_sans_registry_ = domain_sans_registry_copy_;
+ }
+}
+
+absl::optional<std::string_view> DomainSplitter::FindNextSubdomain() {
+ std::string_view& remaining_domain = GetNonEmptyRemainingDomain();
+ if (remaining_domain.empty()) {
+ // We've exhausted both remaining_domain_ and
+ // remaining_domain_sans_registry_, end iteration.
+ return absl::nullopt;
+ }
+
+ const std::string_view return_value = remaining_domain;
+
+ // Remove the next subdomain from remaining_domain:
+ const size_t next_dot_index = remaining_domain.find('.');
+ if (next_dot_index == std::string_view::npos) {
+ remaining_domain = "";
+ } else {
+ remaining_domain.remove_prefix(next_dot_index + 1);
+ }
+
+ return return_value;
+}
+
+std::string_view& DomainSplitter::GetNonEmptyRemainingDomain() {
+ if (!remaining_domain_.empty()) {
+ return remaining_domain_;
+ }
+ return remaining_domain_sans_registry_;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/domain_splitter.h b/components/adblock/core/subscription/domain_splitter.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/domain_splitter.h
@@ -0,0 +1,56 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_
+
+#include <string_view>
+
+#include "absl/types/optional.h"
+
+namespace adblock {
+
+// When constructed with a full domain like "aaa.bbb.ccc.com", subsequent calls
+// to FindNextSubdomain() will yield "aaa.bbb.ccc.com", "bbb.ccc.com",
+// "ccc.com", "com" and then versions with the TLD removed, for matching
+// wildcard filters: "aaa.bbb.ccc." then "bbb.ccc." then "ccc.". A nullopt is
+// returned when the end of the domain is reached.
+class DomainSplitter {
+ public:
+ // |domain| must outlive this, no copy made.
+ explicit DomainSplitter(std::string_view domain);
+ // Returns reference to part of |domain|.
+ absl::optional<std::string_view> FindNextSubdomain();
+
+ private:
+ std::string_view& GetNonEmptyRemainingDomain();
+ std::string_view remaining_domain_;
+ // HACK - the copy of the string is needed to null-terminate the
+ // "remaining_domain_sans_registry_" member. We need a null-terminated source
+ // string even though string_view has a size() because of
+ // https://github.com/google/flatbuffers/issues/8200.
+ // TODO: remove this hack when flatbuffers is updated -
+ // remaining_domain_sans_registry_ could refer to the string_view passed in
+ // ctor instead and avoid any allocations.
+ std::string domain_sans_registry_copy_;
+ std::string_view remaining_domain_sans_registry_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_
diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer.h b/components/adblock/core/subscription/filtering_configuration_maintainer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/filtering_configuration_maintainer.h
@@ -0,0 +1,55 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/subscription_collection.h"
+
+namespace adblock {
+
+// Maintains a set of subscriptions needed to fulfil filtering requirements of a
+// single FilteringConfiguration.
+// Downloads and installs missing subscriptions, removes no-longer-needed
+// subscriptions, periodically updates installed subscriptions.
+class FilteringConfigurationMaintainer {
+ public:
+ virtual ~FilteringConfigurationMaintainer() = default;
+
+ // Returns a SubscriptionCollection that implements the blocking logic
+ // demanded by a FilteringConfiguration. This becomes part of a
+ // SubscriptionService::Snapshot.
+ virtual std::unique_ptr<SubscriptionCollection> GetSubscriptionCollection()
+ const = 0;
+
+ // Allows inspecting what Subscriptions are currently in use. This includes
+ // ongoing downloads, preloaded subscriptions and installed subscriptions.
+ virtual std::vector<scoped_refptr<Subscription>> GetCurrentSubscriptions()
+ const = 0;
+
+ // Removes all subscriptions with InstalationState::AutoInstalled
+ virtual void RemoveAutoInstalledSubscriptions() = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_
diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc
@@ -0,0 +1,516 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/filtering_configuration_maintainer_impl.h"
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/subscription/subscription_collection_impl.h"
+
+// TODO(mpawlowski): Remove in DPD-1154. This class should not need to know
+// anything about particular subscriptions, it should be generic.
+#include "components/adblock/core/subscription/subscription_config.h"
+
+namespace adblock {
+namespace {
+constexpr base::TimeDelta kDefaultHeadRequestExpirationInterval = base::Days(1);
+
+std::string CreateDomainAllowlistingFilter(const std::string& domain) {
+ return "@@||" + base::ToLowerASCII(domain) +
+ "^$document,domain=" + base::ToLowerASCII(domain);
+}
+} // namespace
+
+class FilteringConfigurationMaintainerImpl::OngoingInstallation final
+ : public Subscription {
+ public:
+ explicit OngoingInstallation(const GURL& url) : url_(url) {}
+
+ GURL GetSourceUrl() const final { return url_; }
+ std::string GetTitle() const final { return {}; }
+ std::string GetCurrentVersion() const final { return {}; }
+ InstallationState GetInstallationState() const final {
+ return InstallationState::Installing;
+ }
+ base::Time GetInstallationTime() const final { return {}; }
+ base::TimeDelta GetExpirationInterval() const final { return {}; }
+
+ private:
+ friend class base::RefCountedThreadSafe<OngoingInstallation>;
+ ~OngoingInstallation() final = default;
+ GURL url_;
+};
+
+FilteringConfigurationMaintainerImpl::FilteringConfigurationMaintainerImpl(
+ FilteringConfiguration* configuration,
+ std::unique_ptr<SubscriptionPersistentStorage> storage,
+ std::unique_ptr<SubscriptionDownloader> downloader,
+ std::unique_ptr<RecommendedSubscriptionInstaller> recommended_installer,
+ std::unique_ptr<PreloadedSubscriptionProvider>
+ preloaded_subscription_provider,
+ std::unique_ptr<TaskScheduler> subscription_updater,
+ ConversionExecutors* conversion_executor,
+ SubscriptionPersistentMetadata* persistent_metadata,
+ SubscriptionUpdatedCallback subscription_updated_callback)
+ : configuration_(std::move(configuration)),
+ storage_(std::move(storage)),
+ downloader_(std::move(downloader)),
+ recommended_installer_(std::move(recommended_installer)),
+ preloaded_subscription_provider_(
+ std::move(preloaded_subscription_provider)),
+ subscription_updater_(std::move(subscription_updater)),
+ conversion_executor_(conversion_executor),
+ persistent_metadata_(persistent_metadata),
+ subscription_updated_callback_(std::move(subscription_updated_callback)) {
+ DCHECK(configuration_->IsEnabled())
+ << "Disabled configurations should not be maintained";
+ configuration_->AddObserver(this);
+}
+
+FilteringConfigurationMaintainerImpl::~FilteringConfigurationMaintainerImpl() {
+ configuration_->RemoveObserver(this);
+}
+
+std::unique_ptr<SubscriptionCollection>
+FilteringConfigurationMaintainerImpl::GetSubscriptionCollection() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::vector<scoped_refptr<InstalledSubscription>> state = current_state_;
+ if (custom_filters_) {
+ state.push_back(custom_filters_);
+ }
+ std::ranges::move(
+ preloaded_subscription_provider_->GetCurrentPreloadedSubscriptions(),
+ std::back_inserter(state));
+ VLOG(2) << "[eyeo] FilteringConfiguration " << configuration_->GetName()
+ << " produces " << state.size() << " subscriptions for Snapshot";
+ return std::make_unique<SubscriptionCollectionImpl>(
+ state, configuration_->GetName());
+}
+
+std::vector<scoped_refptr<Subscription>>
+FilteringConfigurationMaintainerImpl::GetCurrentSubscriptions() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Result will contain the currently installed subscriptions:
+ std::vector<scoped_refptr<Subscription>> result;
+ std::ranges::copy(current_state_, std::back_inserter(result));
+ // And all preloaded subscriptions:
+ auto preloaded_subscriptions =
+ preloaded_subscription_provider_->GetCurrentPreloadedSubscriptions();
+ std::ranges::move(preloaded_subscriptions, std::back_inserter(result));
+ // Also, dummy subscriptions that represent ongoing installations (unless
+ // already present, in which case they'd represent updates).
+ std::ranges::copy_if(
+ ongoing_installations_, std::back_inserter(result),
+ [&](const auto& ongoing_installation) {
+ return std::ranges::find(result, ongoing_installation->GetSourceUrl(),
+ &Subscription::GetSourceUrl) == result.end();
+ });
+ return result;
+}
+
+void FilteringConfigurationMaintainerImpl::RemoveAutoInstalledSubscriptions() {
+ recommended_installer_->RemoveAutoInstalledSubscriptions();
+}
+
+void FilteringConfigurationMaintainerImpl::OnFilterListsChanged(
+ FilteringConfiguration* config) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(config, configuration_);
+ if (status_ == StorageStatus::Initialized) {
+ InstallMissingSubscriptions();
+ RemoveUnneededSubscriptions();
+ }
+}
+
+void FilteringConfigurationMaintainerImpl::OnAllowedDomainsChanged(
+ FilteringConfiguration* config) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(config, configuration_);
+ OnCustomFiltersChanged(config);
+}
+
+void FilteringConfigurationMaintainerImpl::OnCustomFiltersChanged(
+ FilteringConfiguration* config) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(config, configuration_);
+ SetCustomFilters();
+}
+
+bool FilteringConfigurationMaintainerImpl::IsInitialized() const {
+ return status_ == StorageStatus::Initialized;
+}
+
+void FilteringConfigurationMaintainerImpl::InstallMissingSubscriptions() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsInitialized());
+ // Subscriptions that are either installed or being installed:
+ auto installed_subscriptions = GetReadySubscriptions();
+ std::ranges::copy(GetPendingSubscriptions(),
+ std::back_inserter(installed_subscriptions));
+ std::ranges::sort(installed_subscriptions);
+ // Remove duplication in case of ongoing update
+ installed_subscriptions.erase(
+ unique(installed_subscriptions.begin(), installed_subscriptions.end()),
+ installed_subscriptions.end());
+
+ // Subscriptions that are demanded by the FilteringConfiguration:
+ auto demanded_subscriptions = configuration_->GetFilterLists();
+ std::ranges::sort(demanded_subscriptions);
+ // Missing subscriptions is the difference between the two:
+ std::vector<GURL> missing_subscriptions;
+ std::ranges::set_difference(demanded_subscriptions, installed_subscriptions,
+ std::back_inserter(missing_subscriptions));
+ for (const auto& url : missing_subscriptions) {
+ DownloadAndInstallSubscription(url);
+ }
+}
+
+void FilteringConfigurationMaintainerImpl::RemoveUnneededSubscriptions() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsInitialized());
+ // Subscriptions that are either installed or being installed:
+ auto installed_subscriptions = GetReadySubscriptions();
+ std::ranges::copy(GetPendingSubscriptions(),
+ std::back_inserter(installed_subscriptions));
+ std::ranges::sort(installed_subscriptions);
+ // Remove duplication in case of ongoing update
+ installed_subscriptions.erase(
+ unique(installed_subscriptions.begin(), installed_subscriptions.end()),
+ installed_subscriptions.end());
+
+ // Subscriptions that are demanded by the FilteringConfiguration:
+ auto demanded_subscriptions = configuration_->GetFilterLists();
+ std::ranges::sort(demanded_subscriptions);
+ installed_subscriptions.erase(std::unique(installed_subscriptions.begin(),
+ installed_subscriptions.end()),
+ installed_subscriptions.end());
+ // Unneeded subscriptions is the difference between the two:
+ std::vector<GURL> unneeded_subscriptions;
+ std::ranges::set_difference(installed_subscriptions, demanded_subscriptions,
+ std::back_inserter(unneeded_subscriptions));
+ for (const auto& url : unneeded_subscriptions) {
+ UninstallSubscription(url);
+ }
+}
+
+void FilteringConfigurationMaintainerImpl::InitializeStorage() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!IsInitialized());
+ VLOG(1) << "[eyeo] FilteringConfigurationMaintainer starting.";
+ TRACE_EVENT_ASYNC_BEGIN1(
+ "eyeo", "FilteringConfigurationMaintainerImpl::InitializeStorage",
+ TRACE_ID_LOCAL(this), "name", configuration_->GetName());
+ storage_->LoadSubscriptions(
+ base::BindOnce(&FilteringConfigurationMaintainerImpl::StorageInitialized,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FilteringConfigurationMaintainerImpl::StorageInitialized(
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!IsInitialized());
+ DCHECK(current_state_.empty())
+ << "current state was modified before initial state was loaded";
+ current_state_ = std::move(loaded_subscriptions);
+ status_ = StorageStatus::Initialized;
+ // SubscriptionPersistentStorage allows multiple Subscriptions with same URL,
+ // which is a legal transitive state during ex. installing an update.
+ // However, current_state_ should always contain only
+ // one subscription with a given URL. This normally happens automatically by
+ // virtue of |SubscriptionAddedToStorage| calling |UninstallSubscription| but
+ // this invariant might not hold if ex. the application exits after
+ // SubscriptionPersistentStorage stores the update but before
+ // SubscriptionServiceImpl uninstalls the old version. It's difficult to
+ // make installing subscription updates atomic, so solve potential race
+ // condition here:
+ RemoveDuplicateSubscriptions();
+ // Synchronize current state with the demands of the FilteringConfiguration:
+ OnFilterListsChanged(configuration_);
+ OnCustomFiltersChanged(configuration_);
+ // Start periodic updates:
+ subscription_updater_->StartSchedule(
+ base::BindRepeating(&FilteringConfigurationMaintainerImpl::RunUpdateCheck,
+ weak_ptr_factory_.GetWeakPtr()));
+ TRACE_EVENT_ASYNC_END1(
+ "eyeo", "FilteringConfigurationMaintainerImpl::InitializeStorage",
+ TRACE_ID_LOCAL(this), "name", configuration_->GetName());
+}
+
+void FilteringConfigurationMaintainerImpl::RemoveDuplicateSubscriptions() {
+ // std::sort + std::unique is not good for this use case, as we need to
+ // perform actions on the duplicates, not just discard them, and std::unique
+ // leaves moved elements in unspecified state. std::adjacent_find or
+ // std::unique_copy could be used as well, but using a helper std::set seems
+ // simplest.
+ const auto comparator = [](const auto& lhs, const auto& rhs) {
+ return lhs->GetSourceUrl() < rhs->GetSourceUrl();
+ };
+ std::set<scoped_refptr<InstalledSubscription>, decltype(comparator)>
+ unique_subscriptions(comparator);
+ for (auto subscription : current_state_) {
+ if (!unique_subscriptions.insert(subscription).second) {
+ // This element already exists in the set, we found a duplicate.
+ storage_->RemoveSubscription(subscription);
+ }
+ }
+ current_state_.assign(unique_subscriptions.begin(),
+ unique_subscriptions.end());
+}
+
+void FilteringConfigurationMaintainerImpl::RunUpdateCheck() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ VLOG(1) << "[eyeo] Running update check";
+
+ // Run recommended subscription update check first so
+ // we don't update lists that would get removed.
+ if (recommended_installer_) {
+ recommended_installer_->RunUpdateCheck();
+ }
+
+ for (auto& subscription : current_state_) {
+ // Update subscriptions that are expired and aren't already in the process
+ // of installing an update.
+ const auto& url = subscription->GetSourceUrl();
+ if (persistent_metadata_->IsExpired(url) &&
+ std::ranges::find(ongoing_installations_, url,
+ &Subscription::GetSourceUrl) ==
+ ongoing_installations_.end()) {
+ VLOG(1) << "[eyeo] Updating expired subscription " << url;
+ DownloadAndInstallSubscription(url);
+ } else {
+ VLOG(1) << "[eyeo] Skipping update of " << url << ": "
+ << (!persistent_metadata_->IsExpired(url)
+ ? "not expired yet"
+ : "already downloading");
+ }
+ }
+
+ // TODO(mpawlowski): remove after DPD-1154. If Acceptable Ads is not
+ // installed, but it would have been expired, send HEAD request for Acceptable
+ // Ads filter list just to count the user, without the intention of
+ // downloading it.
+ // This is to support legacy behavior.
+ if (configuration_->GetName() == "adblock" &&
+ std::ranges::none_of(GetCurrentSubscriptions(),
+ [](const auto& subscription) {
+ return subscription->GetSourceUrl() ==
+ AcceptableAdsUrl();
+ }) &&
+ persistent_metadata_->IsExpired(AcceptableAdsUrl())) {
+ PingAcceptableAds();
+ }
+}
+
+void FilteringConfigurationMaintainerImpl::DownloadAndInstallSubscription(
+ const GURL& subscription_url) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsInitialized());
+ const bool is_an_update =
+ std::ranges::any_of(current_state_, [&](const auto candidate) {
+ return candidate->GetSourceUrl() == subscription_url;
+ });
+
+ // We do not retry downloading subscription updates, they will be retried
+ // by the TaskScheduler in due time anyway.
+ auto retry_policy =
+ is_an_update ? AdblockResourceRequest::RetryPolicy::DoNotRetry
+ : AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded;
+
+ auto ongoing_installation =
+ base::MakeRefCounted<OngoingInstallation>(subscription_url);
+ ongoing_installations_.insert(ongoing_installation);
+ UpdatePreloadedSubscriptionProvider();
+
+ downloader_->StartDownload(
+ subscription_url, retry_policy,
+ base::BindOnce(
+ &FilteringConfigurationMaintainerImpl::OnSubscriptionDataAvailable,
+ weak_ptr_factory_.GetWeakPtr(), ongoing_installation));
+}
+
+void FilteringConfigurationMaintainerImpl::OnSubscriptionDataAvailable(
+ scoped_refptr<OngoingInstallation> ongoing_installation,
+ std::unique_ptr<FlatbufferData> raw_data) {
+ if (ongoing_installations_.find(ongoing_installation) ==
+ ongoing_installations_.end()) {
+ // Installation was canceled.
+ UpdatePreloadedSubscriptionProvider();
+ return;
+ }
+ if (!raw_data) {
+ // Download failed.
+ ongoing_installations_.erase(ongoing_installation);
+ UpdatePreloadedSubscriptionProvider();
+ return;
+ }
+
+ storage_->StoreSubscription(
+ std::move(raw_data),
+ base::BindOnce(
+ &FilteringConfigurationMaintainerImpl::SubscriptionAddedToStorage,
+ weak_ptr_factory_.GetWeakPtr(), ongoing_installation));
+}
+
+void FilteringConfigurationMaintainerImpl::SubscriptionAddedToStorage(
+ scoped_refptr<OngoingInstallation> ongoing_installation,
+ scoped_refptr<InstalledSubscription> subscription) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (ongoing_installations_.find(ongoing_installation) ==
+ ongoing_installations_.end()) {
+ // Installation was canceled. We must now remove the subscription from
+ // storage. Do not add it to |current_state|.
+ storage_->RemoveSubscription(subscription);
+ UpdatePreloadedSubscriptionProvider();
+ return;
+ }
+ ongoing_installations_.erase(ongoing_installation);
+
+ if (!subscription) {
+ // There was an error adding subscription to storage.
+ LOG(WARNING) << "[eyeo] Failed to add subscription, current number "
+ << "of subscriptions: " << current_state_.size();
+ UpdatePreloadedSubscriptionProvider();
+ return;
+ }
+ // Remove any subscription that already exists with the same URL
+ bool subscription_existed =
+ UninstallSubscriptionInternal(subscription->GetSourceUrl());
+ // Add the new subscription
+ current_state_.push_back(subscription);
+ if (subscription_existed) {
+ VLOG(1) << "[eyeo] Updated subscription " << subscription->GetSourceUrl()
+ << ", current version " << subscription->GetCurrentVersion();
+ } else {
+ VLOG(1) << "[eyeo] Added subscription " << subscription->GetSourceUrl()
+ << ", current number of subscriptions: " << current_state_.size();
+ }
+ UpdatePreloadedSubscriptionProvider();
+ // Notify "observer"
+ subscription_updated_callback_.Run(subscription->GetSourceUrl());
+}
+
+void FilteringConfigurationMaintainerImpl::PingAcceptableAds() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsInitialized());
+ downloader_->DoHeadRequest(
+ AcceptableAdsUrl(),
+ base::BindOnce(&FilteringConfigurationMaintainerImpl::OnHeadRequestDone,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FilteringConfigurationMaintainerImpl::OnHeadRequestDone(
+ const std::string version) {
+ if (version.empty()) {
+ return;
+ }
+ persistent_metadata_->SetVersion(AcceptableAdsUrl(), version);
+ persistent_metadata_->SetExpirationInterval(
+ AcceptableAdsUrl(), kDefaultHeadRequestExpirationInterval);
+}
+
+void FilteringConfigurationMaintainerImpl::UninstallSubscription(
+ const GURL& subscription_url) {
+ DVLOG(1) << "[eyeo] Removing subscription " << subscription_url;
+ if (!UninstallSubscriptionInternal(subscription_url)) {
+ VLOG(1) << "[eyeo] Nothing to remove, subscription not installed "
+ << subscription_url;
+ return;
+ }
+ if (subscription_url != AcceptableAdsUrl()) {
+ // Remove metadata associated with the subscription. Retain (forever)
+ // metadata of the Acceptable Ads subscription even when it's no longer
+ // installed, to allow continued HEAD-only pings for user counting purposes.
+ persistent_metadata_->RemoveMetadata(subscription_url);
+ }
+ UpdatePreloadedSubscriptionProvider();
+ VLOG(1) << "[eyeo] Removed subscription " << subscription_url;
+}
+
+bool FilteringConfigurationMaintainerImpl::UninstallSubscriptionInternal(
+ const GURL& subscription_url) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(IsInitialized());
+ bool subscription_removed = false;
+ auto it = std::ranges::find(current_state_, subscription_url,
+ &Subscription::GetSourceUrl);
+ if (it != current_state_.end()) {
+ storage_->RemoveSubscription(*it);
+ current_state_.erase(it);
+ subscription_removed = true;
+ }
+
+ auto ongoing_installation_it = std::ranges::find(
+ ongoing_installations_, subscription_url, &Subscription::GetSourceUrl);
+ if (ongoing_installation_it != ongoing_installations_.end()) {
+ ongoing_installations_.erase(ongoing_installation_it);
+ DVLOG(1) << "[eyeo] Canceling installation of subscription "
+ << subscription_url;
+ downloader_->CancelDownload(subscription_url);
+ DVLOG(2) << "[eyeo] Canceled installation of subscription "
+ << subscription_url;
+ subscription_removed = true;
+ }
+ return subscription_removed;
+}
+
+void FilteringConfigurationMaintainerImpl::SetCustomFilters() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ std::vector<std::string> filters = configuration_->GetCustomFilters();
+ std::ranges::transform(configuration_->GetAllowedDomains(),
+ std::back_inserter(filters),
+ &CreateDomainAllowlistingFilter);
+ for (const auto& filter : filters) {
+ VLOG(1) << "[eyeo] Setting custom filter: " << filter;
+ }
+ if (filters.empty()) {
+ custom_filters_.reset();
+ return;
+ }
+
+ custom_filters_ = conversion_executor_->ConvertCustomFilters(filters);
+}
+
+void FilteringConfigurationMaintainerImpl::
+ UpdatePreloadedSubscriptionProvider() {
+ preloaded_subscription_provider_->UpdateSubscriptions(
+ GetReadySubscriptions(), GetPendingSubscriptions());
+}
+
+std::vector<GURL> FilteringConfigurationMaintainerImpl::GetReadySubscriptions()
+ const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::vector<GURL> result;
+ result.reserve(current_state_.size());
+ std::ranges::transform(current_state_, std::back_inserter(result),
+ &Subscription::GetSourceUrl);
+ return result;
+}
+
+std::vector<GURL>
+FilteringConfigurationMaintainerImpl::GetPendingSubscriptions() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ std::vector<GURL> result;
+ result.reserve(ongoing_installations_.size());
+ std::ranges::transform(ongoing_installations_, std::back_inserter(result),
+ &Subscription::GetSourceUrl);
+ return result;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h
@@ -0,0 +1,124 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_IMPL_H_
+
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/adblock/core/common/task_scheduler.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/conversion_executors.h"
+#include "components/adblock/core/subscription/filtering_configuration_maintainer.h"
+#include "components/adblock/core/subscription/preloaded_subscription_provider.h"
+#include "components/adblock/core/subscription/recommended_subscription_installer.h"
+#include "components/adblock/core/subscription/subscription_downloader.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+#include "components/adblock/core/subscription/subscription_persistent_storage.h"
+
+namespace adblock {
+
+class FilteringConfigurationMaintainerImpl
+ : public FilteringConfigurationMaintainer,
+ public FilteringConfiguration::Observer {
+ public:
+ using SubscriptionUpdatedCallback =
+ base::RepeatingCallback<void(const GURL&)>;
+ FilteringConfigurationMaintainerImpl(
+ FilteringConfiguration* configuration,
+ std::unique_ptr<SubscriptionPersistentStorage> storage,
+ std::unique_ptr<SubscriptionDownloader> downloader,
+ std::unique_ptr<RecommendedSubscriptionInstaller> recommended_installer,
+ std::unique_ptr<PreloadedSubscriptionProvider>
+ preloaded_subscription_provider,
+ std::unique_ptr<TaskScheduler> subscription_updater,
+ ConversionExecutors* conversion_executor,
+ SubscriptionPersistentMetadata* persistent_metadata,
+ SubscriptionUpdatedCallback subscription_updated_callback);
+ ~FilteringConfigurationMaintainerImpl() override;
+
+ std::unique_ptr<SubscriptionCollection> GetSubscriptionCollection()
+ const final;
+
+ std::vector<scoped_refptr<Subscription>> GetCurrentSubscriptions()
+ const final;
+
+ void RemoveAutoInstalledSubscriptions() final;
+
+ // FilteringConfiguration::Observer:
+ void OnFilterListsChanged(FilteringConfiguration* config) final;
+ void OnAllowedDomainsChanged(FilteringConfiguration* config) final;
+ void OnCustomFiltersChanged(FilteringConfiguration* config) final;
+
+ void InitializeStorage();
+
+ private:
+ enum class StorageStatus {
+ Initialized,
+ Uninitialized,
+ };
+ class OngoingInstallation;
+ bool IsInitialized() const;
+ void InstallMissingSubscriptions();
+ void RemoveUnneededSubscriptions();
+ void StorageInitialized(
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions);
+ void RemoveDuplicateSubscriptions();
+ void RunUpdateCheck();
+ void DownloadAndInstallSubscription(const GURL& subscription_url);
+ void OnSubscriptionDataAvailable(
+ scoped_refptr<OngoingInstallation> ongoing_installation,
+ std::unique_ptr<FlatbufferData> raw_data);
+ void SubscriptionAddedToStorage(
+ scoped_refptr<OngoingInstallation> ongoing_installation,
+ scoped_refptr<InstalledSubscription> subscription);
+ void PingAcceptableAds();
+ void OnHeadRequestDone(const std::string version);
+ void UninstallSubscription(const GURL& subscription_url);
+ bool UninstallSubscriptionInternal(const GURL& subscription_url);
+ void SetCustomFilters();
+ void UpdatePreloadedSubscriptionProvider();
+ std::vector<GURL> GetReadySubscriptions() const;
+ std::vector<GURL> GetPendingSubscriptions() const;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ StorageStatus status_ = StorageStatus::Uninitialized;
+ raw_ptr<FilteringConfiguration> configuration_;
+ std::unique_ptr<SubscriptionPersistentStorage> storage_;
+ std::unique_ptr<SubscriptionDownloader> downloader_;
+ std::unique_ptr<RecommendedSubscriptionInstaller> recommended_installer_;
+ std::unique_ptr<PreloadedSubscriptionProvider>
+ preloaded_subscription_provider_;
+ std::unique_ptr<TaskScheduler> subscription_updater_;
+ raw_ptr<ConversionExecutors> conversion_executor_;
+ // TODO(mpawlowski): Should not need to update metadata after DPD-1154, when
+ // HEAD requests are removed. Move all use of SubscriptionPersistentMetadata
+ // into SubscriptionPersistentStorage.
+ raw_ptr<SubscriptionPersistentMetadata> persistent_metadata_;
+ SubscriptionUpdatedCallback subscription_updated_callback_;
+ std::set<scoped_refptr<OngoingInstallation>> ongoing_installations_;
+ std::vector<scoped_refptr<InstalledSubscription>> current_state_;
+ scoped_refptr<InstalledSubscription> custom_filters_;
+ base::WeakPtrFactory<FilteringConfigurationMaintainerImpl> weak_ptr_factory_{
+ this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_IMPL_H_
diff --git a/components/adblock/core/subscription/installed_subscription.cc b/components/adblock/core/subscription/installed_subscription.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/installed_subscription.cc
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/installed_subscription.h"
+
+namespace adblock {
+
+InstalledSubscription::ContentFiltersData::ContentFiltersData() = default;
+InstalledSubscription::ContentFiltersData::~ContentFiltersData() = default;
+InstalledSubscription::ContentFiltersData::ContentFiltersData(
+ const ContentFiltersData&) = default;
+InstalledSubscription::ContentFiltersData::ContentFiltersData(
+ ContentFiltersData&&) = default;
+InstalledSubscription::ContentFiltersData&
+InstalledSubscription::ContentFiltersData::operator=(
+ const ContentFiltersData&) = default;
+InstalledSubscription::ContentFiltersData&
+InstalledSubscription::ContentFiltersData::operator=(ContentFiltersData&&) =
+ default;
+
+InstalledSubscription::Snippet::Snippet() = default;
+InstalledSubscription::Snippet::Snippet(const Snippet&) = default;
+InstalledSubscription::Snippet::Snippet(Snippet&&) = default;
+InstalledSubscription::Snippet::~Snippet() = default;
+InstalledSubscription::Snippet& InstalledSubscription::Snippet::operator=(
+ const Snippet&) = default;
+InstalledSubscription::Snippet& InstalledSubscription::Snippet::operator=(
+ Snippet&&) = default;
+
+InstalledSubscription::~InstalledSubscription() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/installed_subscription.h b/components/adblock/core/subscription/installed_subscription.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/installed_subscription.h
@@ -0,0 +1,147 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_
+
+#include <cstdint>
+#include <map>
+#include <set>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "absl/types/optional.h"
+
+#include "base/time/time.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/common/header_filter_data.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+enum class SpecialFilterType {
+ // Allows all ads on the frame and its children, overrides any URL blocking
+ // or element hiding:
+ Document,
+ // Disables element hiding on the frame and its children, URL blocking is
+ // still allowed:
+ Elemhide,
+ // Only consider domain-specific URL filters on this frame and its children:
+ Genericblock,
+ // Only consider domain-specific element hiding selectors on this frame and
+ // its children:
+ Generichide,
+};
+enum class FilterCategory { Allowing, Blocking, DomainSpecificBlocking };
+
+// Represents an installed subscription that can be queried for filters.
+class InstalledSubscription : public Subscription {
+ public:
+ struct ContentFiltersData {
+ using Selector = std::string_view;
+ using SelectorWithCss = std::pair<Selector, std::string_view>;
+ using Selectors = std::vector<Selector>;
+ using SelectorsWithCss = std::vector<SelectorWithCss>;
+ ContentFiltersData();
+ ~ContentFiltersData();
+ ContentFiltersData(const ContentFiltersData&);
+ ContentFiltersData(ContentFiltersData&&);
+ ContentFiltersData& operator=(const ContentFiltersData&);
+ ContentFiltersData& operator=(ContentFiltersData&&);
+ // The final set of selectors to apply on a page is |elemhide_selectors|
+ // |remove_selectors| and |inline_css_selectors| each of them with
+ // removed entries from |elemhide_exceptions|. This difference is not
+ // computed by this Subscription because there may be multiple subscriptions
+ // and |elemhide_exceptions| from one subscriptions may remove f.e.
+ // |elemhide_selectors| from another.
+ Selectors elemhide_exceptions;
+ Selectors elemhide_selectors;
+ Selectors remove_selectors;
+ SelectorsWithCss selectors_to_inline_css;
+ };
+
+ class Snippet {
+ public:
+ Snippet();
+ Snippet(const Snippet&);
+ Snippet(Snippet&&);
+ ~Snippet();
+ Snippet& operator=(const Snippet&);
+ Snippet& operator=(Snippet&&);
+ std::string_view command;
+ std::vector<std::string_view> arguments;
+ };
+
+ virtual bool HasUrlFilter(const GURL& url,
+ const std::string& document_domain,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const = 0;
+ virtual bool HasPopupFilter(const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey,
+ FilterCategory category) const = 0;
+ virtual bool HasSpecialFilter(SpecialFilterType type,
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey) const = 0;
+ // CSP filters have a payload: a string that gets injected to a network
+ // response's Content-Security-Policy header. If a filters is found, it will
+ // be append to |results|.
+ virtual void FindCspFilters(const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<std::string_view>& results) const = 0;
+ // Find all rewrite filters matching category.
+ virtual std::set<std::string_view> FindRewriteFilters(
+ const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category) const = 0;
+
+ virtual void FindHeaderFilters(const GURL& url,
+ ContentType content_type,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<HeaderFilterData>& results) const = 0;
+
+ virtual std::vector<Snippet> MatchSnippets(
+ const std::string& document_domain) const = 0;
+
+ virtual ContentFiltersData GetElemhideData(const GURL& url,
+ bool domain_specfic) const = 0;
+ // Note there's no "domain_specific". Emulation filters are always
+ // domain-specific.
+ virtual ContentFiltersData GetElemhideEmulationData(
+ const GURL& url) const = 0;
+
+ // Instructs to remove the file which contains this subscription's data during
+ // destruction. NOP if there is no backing file, when the subscription is
+ // created in-memory.
+ // Operation is atomic and thread-safe. Consecutive calls are NOPs.
+ virtual void MarkForPermanentRemoval() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<InstalledSubscription>;
+ ~InstalledSubscription() override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_
diff --git a/components/adblock/core/subscription/installed_subscription_impl.cc b/components/adblock/core/subscription/installed_subscription_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/installed_subscription_impl.cc
@@ -0,0 +1,638 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_switches.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/common/regex_filter_pattern.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/domain_splitter.h"
+#include "components/adblock/core/subscription/pattern_matcher.h"
+#include "components/adblock/core/subscription/regex_matcher.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/url_keyword_extractor.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/url_constants.h"
+
+namespace adblock {
+namespace {
+
+bool NeedsLowercasing(const std::string& input) {
+ return std::ranges::any_of(
+ input, [](const char c) { return base::IsAsciiUpper(c); });
+}
+
+bool IsThirdParty(const GURL& url, const std::string& domain) {
+ return !net::registry_controlled_domains::SameDomainOrHost(
+ url, GURL(url.scheme() + "://" + domain),
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+}
+
+bool DomainMatches(std::string_view filter_domain,
+ std::string_view document_domain) {
+ if (base::EndsWith(filter_domain, ".") && !document_domain.empty()) {
+ // Trailing dot indicates a wildcard match for any TLD.
+ // Remove the registry from |document_domain| in order for it to also end
+ // in a dot (e.g. "example.com" becomes "example.").
+ const size_t registry_length =
+ net::registry_controlled_domains::GetCanonicalHostRegistryLength(
+ document_domain,
+ net::registry_controlled_domains::UnknownRegistryFilter::
+ EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::PrivateRegistryFilter::
+ EXCLUDE_PRIVATE_REGISTRIES);
+ // Remove the registry from document_domain:
+ document_domain.remove_suffix(registry_length);
+ }
+
+ // document_domain is same as filter_domain:
+ // - document: subdomain.example.com
+ // - filter: subdomain.example.com
+ // Or document_domain ends with ".filter_domain":
+ // - document: subdomain.example.com
+ // - filter: example.com
+ // (document ends with ".example.com")
+ // This logic follow for wildcard filters as well, so:
+ // - document: subdomain.example.
+ // - filter: example.
+
+ return document_domain == filter_domain ||
+ (base::EndsWith(document_domain, filter_domain) &&
+ base::EndsWith(document_domain.substr(
+ 0, document_domain.size() - filter_domain.size()),
+ "."));
+}
+
+bool DomainOnList(
+ std::string_view document_domain,
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>* list) {
+ return std::any_of(list->begin(), list->end(), [&](auto* filter_domain) {
+ return DomainMatches(
+ std::string_view(filter_domain->c_str(), filter_domain->size()),
+ document_domain);
+ });
+}
+
+template <typename T>
+bool IsFilterExcludedByDomain(const T* filter, std::string_view domain) {
+ return
+ // Some exclusions apply on this domain:
+ filter->exclude_domains()->size() > 0 &&
+ // And those exclusions contain |domain| or one of its subdomains:
+ DomainOnList(domain, filter->exclude_domains());
+}
+
+template <typename T>
+InstalledSubscription::ContentFiltersData::Selectors GetSelectorsForDomain(
+ const T* category,
+ std::string_view domain) {
+ TRACE_EVENT1("eyeo", "InstalledSubscriptionImpl::GetSelectorsForDomain",
+ "domain", domain);
+
+ if (!category || !category->filter()) {
+ // No filters found for this domain.
+ return {};
+ }
+
+ InstalledSubscription::ContentFiltersData::Selectors selectors;
+ for (const auto* filter : *category->filter()) {
+ if (IsFilterExcludedByDomain(filter, domain)) {
+ continue;
+ }
+ selectors.emplace_back(filter->selector()->c_str(),
+ filter->selector()->size());
+ }
+
+ return selectors;
+}
+
+InstalledSubscription::ContentFiltersData::SelectorsWithCss
+GetInlineCssDataForDomain(const flat::InlineCssFiltersByDomain* category,
+ std::string_view domain) {
+ TRACE_EVENT1("eyeo", "InstalledSubscriptionImpl::GetInlineCssDataForDomain",
+ "domain", domain);
+
+ if (!category || !category->filter()) {
+ // No filters found for this domain.
+ return {};
+ }
+
+ InstalledSubscription::ContentFiltersData::SelectorsWithCss
+ selectors_to_inline_css;
+ for (const auto* filter : *category->filter()) {
+ if (IsFilterExcludedByDomain(filter, domain)) {
+ continue;
+ }
+ selectors_to_inline_css.emplace_back(std::make_pair(
+ std::string_view(filter->selector()->c_str(),
+ filter->selector()->size()),
+ std::string_view(filter->css()->c_str(), filter->css()->size())));
+ }
+
+ return selectors_to_inline_css;
+}
+
+void LogFoundFilters(const GURL& url,
+ const std::vector<const flat::UrlFilter*>& filters,
+ const flat::Subscription& subscription) {
+ static const bool store_filter_text =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kStoreFilterText);
+ if (!store_filter_text) {
+ return;
+ }
+ for (const auto* filter : filters) {
+ if (filter->filter_text() && filter->filter_text()->size() > 0) {
+ LOG(INFO) << "Found filter for " << url.spec() << " in "
+ << subscription.metadata()->url()->string_view() << ": "
+ << filter->filter_text()->string_view();
+ }
+ }
+}
+
+} // namespace
+
+InstalledSubscriptionImpl::InstalledSubscriptionImpl(
+ std::unique_ptr<FlatbufferData> data,
+ InstallationState installation_state,
+ base::Time installation_time)
+ : buffer_(std::move(data)),
+ installation_state_(installation_state),
+ installation_time_(installation_time),
+ regex_matcher_(std::make_unique<RegexMatcher>()) {
+ DCHECK(buffer_);
+ index_ = flat::GetSubscription(buffer_->data());
+ regex_matcher_->PreBuildRegexPatternsWithNoKeyword(index_);
+}
+
+InstalledSubscriptionImpl::~InstalledSubscriptionImpl() = default;
+
+GURL InstalledSubscriptionImpl::GetSourceUrl() const {
+ return index_->metadata()->url() ? GURL(index_->metadata()->url()->str())
+ : GURL();
+}
+
+std::string InstalledSubscriptionImpl::GetTitle() const {
+ return index_->metadata()->title() ? index_->metadata()->title()->str() : "";
+}
+
+std::string InstalledSubscriptionImpl::GetCurrentVersion() const {
+ return index_->metadata()->version() ? index_->metadata()->version()->str()
+ : "";
+}
+
+Subscription::InstallationState
+InstalledSubscriptionImpl::GetInstallationState() const {
+ return installation_state_;
+}
+
+base::Time InstalledSubscriptionImpl::GetInstallationTime() const {
+ return installation_time_;
+}
+
+base::TimeDelta InstalledSubscriptionImpl::GetExpirationInterval() const {
+ return base::Milliseconds(index_->metadata()->expires());
+}
+
+bool InstalledSubscriptionImpl::HasUrlFilter(const GURL& url,
+ const std::string& document_domain,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const {
+ return !FindInternal(category != FilterCategory::Allowing
+ ? index_->url_subresource_block()
+ : index_->url_subresource_allow(),
+ url, content_type, document_domain, sitekey.value(),
+ category, FindStrategy::FindFirst)
+ .empty();
+}
+
+bool InstalledSubscriptionImpl::HasPopupFilter(
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey,
+ FilterCategory category) const {
+ return !FindInternal(category != FilterCategory::Allowing
+ ? index_->url_popup_block()
+ : index_->url_popup_allow(),
+ url, absl::nullopt, document_domain, sitekey.value(),
+ category, FindStrategy::FindFirst)
+ .empty();
+}
+
+void InstalledSubscriptionImpl::FindCspFilters(
+ const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<std::string_view>& results) const {
+ for (auto* filter : FindInternal(category != FilterCategory::Allowing
+ ? index_->url_csp_block()
+ : index_->url_csp_allow(),
+ url, absl::nullopt, document_domain, "",
+ category, FindStrategy::FindAll)) {
+ DCHECK(category == FilterCategory::Allowing || filter->csp_filter())
+ << "Blocking CSP filter must contain payload";
+ results.insert(filter->csp_filter()
+ ? std::string_view(filter->csp_filter()->c_str(),
+ filter->csp_filter()->size())
+ : std::string_view());
+ }
+}
+
+std::set<std::string_view> InstalledSubscriptionImpl::FindRewriteFilters(
+ const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category) const {
+ std::set<std::string_view> result;
+ for (auto* filter : FindInternal(category != FilterCategory::Allowing
+ ? index_->url_rewrite_block()
+ : index_->url_rewrite_allow(),
+ url, absl::nullopt, document_domain, "",
+ category, FindStrategy::FindAll)) {
+ result.insert(RewriteUrl(filter->rewrite()->replace_with()));
+ }
+ return result;
+}
+
+void InstalledSubscriptionImpl::FindHeaderFilters(
+ const GURL& url,
+ ContentType content_type,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<HeaderFilterData>& results) const {
+ for (auto* filter : FindInternal(category != FilterCategory::Allowing
+ ? index_->url_header_block()
+ : index_->url_header_allow(),
+ url, content_type, document_domain, "",
+ category, FindStrategy::FindAll)) {
+ DCHECK(category == FilterCategory::Allowing || filter->header_filter())
+ << "Blocking header filter must contain header_filter() payload";
+ results.insert({std::string_view(filter->header_filter()->c_str(),
+ filter->header_filter()->size()),
+ GetSourceUrl()});
+ }
+}
+
+bool InstalledSubscriptionImpl::HasSpecialFilter(
+ SpecialFilterType type,
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey) const {
+ const UrlFilterIndex* index = nullptr;
+ switch (type) {
+ case SpecialFilterType::Document:
+ index = index_->url_document_allow();
+ break;
+ case SpecialFilterType::Elemhide:
+ index = index_->url_elemhide_allow();
+ break;
+ case SpecialFilterType::Genericblock:
+ index = index_->url_genericblock_allow();
+ break;
+ case SpecialFilterType::Generichide:
+ index = index_->url_generichide_allow();
+ break;
+ }
+ return !FindInternal(index, url, absl::nullopt, document_domain,
+ sitekey.value(), FilterCategory::Allowing,
+ FindStrategy::FindFirst)
+ .empty();
+}
+
+InstalledSubscription::ContentFiltersData
+InstalledSubscriptionImpl::GetElemhideData(const GURL& url,
+ bool domain_specific) const {
+ ContentFiltersData result;
+ const std::string domain(base::ToLowerASCII(url.host_piece()));
+ if (!domain_specific) {
+ result.elemhide_exceptions =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide_exception()->LookupByKey(""), domain);
+ result.elemhide_selectors =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide()->LookupByKey(""), domain);
+ }
+
+ DomainSplitter domain_splitter(domain);
+ while (auto subdomain = domain_splitter.FindNextSubdomain()) {
+ auto specific_exceptions =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide_exception()->LookupByKey(subdomain->data()),
+ domain);
+ std::move(specific_exceptions.begin(), specific_exceptions.end(),
+ std::back_inserter(result.elemhide_exceptions));
+ auto specific_hide_selectors =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide()->LookupByKey(subdomain->data()), domain);
+ std::move(specific_hide_selectors.begin(), specific_hide_selectors.end(),
+ std::back_inserter(result.elemhide_selectors));
+ }
+
+ return result;
+}
+
+InstalledSubscription::ContentFiltersData
+InstalledSubscriptionImpl::GetElemhideEmulationData(const GURL& url) const {
+ const std::string domain(base::ToLowerASCII(url.host_piece()));
+ ContentFiltersData result;
+ DomainSplitter domain_splitter(domain);
+ while (auto subdomain = domain_splitter.FindNextSubdomain()) {
+ auto elemhide_exceptions =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide_exception()->LookupByKey(subdomain->data()),
+ domain);
+ std::move(elemhide_exceptions.begin(), elemhide_exceptions.end(),
+ std::back_inserter(result.elemhide_exceptions));
+ auto elemhide_selectors =
+ GetSelectorsForDomain<flat::ElemHideFiltersByDomain>(
+ index_->elemhide_emulation()->LookupByKey(subdomain->data()),
+ domain);
+ std::move(elemhide_selectors.begin(), elemhide_selectors.end(),
+ std::back_inserter(result.elemhide_selectors));
+ auto remove_selectors = GetSelectorsForDomain<flat::RemoveFiltersByDomain>(
+ index_->remove()->LookupByKey(subdomain->data()), domain);
+ std::move(remove_selectors.begin(), remove_selectors.end(),
+ std::back_inserter(result.remove_selectors));
+ auto selectors_to_inline_css = GetInlineCssDataForDomain(
+ index_->inline_css()->LookupByKey(subdomain->data()), domain);
+ std::move(selectors_to_inline_css.begin(), selectors_to_inline_css.end(),
+ std::back_inserter(result.selectors_to_inline_css));
+ }
+ return result;
+}
+
+std::vector<const flat::UrlFilter*> InstalledSubscriptionImpl::FindInternal(
+ const UrlFilterIndex* index,
+ const GURL& url,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ FindStrategy strategy) const {
+ if (!index) {
+ // No filters of this type were parsed.
+ return {};
+ }
+ const std::string& normalized_domain =
+ NeedsLowercasing(document_domain) ? base::ToLowerASCII(document_domain)
+ : document_domain;
+ const std::string normalized_sitekey = base::ToUpperASCII(sitekey);
+ const GURL& lowercase_url =
+ NeedsLowercasing(url.spec()) ? GURL(base::ToLowerASCII(url.spec())) : url;
+ const bool is_third_party_request = IsThirdParty(url, document_domain);
+ std::vector<const flat::UrlFilter*> results;
+
+ UrlKeywordExtractor keyword_extractor(lowercase_url.spec());
+ while (auto current_keyword = keyword_extractor.GetNextKeyword()) {
+ FindFiltersForKeyword(index, *current_keyword, url, lowercase_url,
+ content_type, normalized_domain, normalized_sitekey,
+ category, is_third_party_request, strategy, results);
+ if (strategy == FindStrategy::FindFirst && !results.empty()) {
+ LogFoundFilters(url, results, *index_);
+ return results;
+ }
+ }
+
+ FindFiltersForKeyword(index, "", url, lowercase_url, content_type,
+ normalized_domain, normalized_sitekey, category,
+ is_third_party_request, strategy, results);
+ LogFoundFilters(url, results, *index_);
+ return results;
+}
+
+void InstalledSubscriptionImpl::FindFiltersForKeyword(
+ const UrlFilterIndex* index,
+ std::string_view keyword,
+ const GURL& url,
+ const GURL& lowercase_url,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ bool is_third_party_request,
+ FindStrategy strategy,
+ std::vector<const flat::UrlFilter*>& out_results) const {
+ const auto* idx = index->LookupByKey(keyword.data());
+
+ if (!idx) {
+ return;
+ }
+
+ for (const auto* filter : *(idx->filter())) {
+ if (!CandidateFilterViable(filter, content_type, document_domain, sitekey,
+ category, is_third_party_request)) {
+ continue;
+ }
+
+ if (filter->pattern()->size() == 0u) {
+ // This filter applies to all URLs, assuming prior checks passed.
+ out_results.push_back(filter);
+ if (strategy == FindStrategy::FindFirst) {
+ return;
+ }
+ }
+ // During flatbuffer conversion, the pattern is lowercased for
+ // case-insensitive filters, and left in original form for case-sensitive
+ // filters.
+ const std::string_view pattern(filter->pattern()->c_str(),
+ filter->pattern()->size());
+ if (const auto regex_pattern = ExtractRegexFilterFromPattern(pattern)) {
+ if (regex_matcher_->MatchesRegex(*regex_pattern, url,
+ filter->match_case())) {
+ out_results.push_back(filter);
+ if (strategy == FindStrategy::FindFirst) {
+ return;
+ }
+ }
+ } else {
+ const auto& normalized_url = filter->match_case() ? url : lowercase_url;
+ if (DoesPatternMatchUrl(pattern, normalized_url)) {
+ out_results.push_back(filter);
+ if (strategy == FindStrategy::FindFirst) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+bool InstalledSubscriptionImpl::CandidateFilterViable(
+ const flat::UrlFilter* candidate,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ bool is_third_party_request) const {
+ if (content_type && (candidate->resource_type() & *content_type) == 0) {
+ return false;
+ }
+ if (category == FilterCategory::DomainSpecificBlocking &&
+ IsGenericFilter(candidate)) {
+ return false;
+ }
+ if (!CheckThirdParty(candidate, is_third_party_request)) {
+ return false;
+ }
+ if (!IsActiveOnDomain(candidate, document_domain, sitekey)) {
+ return false;
+ }
+ return true;
+}
+
+bool InstalledSubscriptionImpl::CheckThirdParty(
+ const flat::UrlFilter* filter,
+ bool is_third_party_request) const {
+ switch (filter->third_party()) {
+ case flat::ThirdParty_Ignore:
+ // This filter applies to first- and third-party requests requests.
+ return true;
+ case flat::ThirdParty_FirstPartyOnly:
+ // This filter applies only to first-party requests.
+ return !is_third_party_request;
+ case flat::ThirdParty_ThirdPartyOnly:
+ // This filter applies only to third-party requests.
+ return is_third_party_request;
+ }
+}
+
+bool InstalledSubscriptionImpl::IsGenericFilter(
+ const flat::UrlFilter* filter) const {
+ const auto* sitekeys = filter->sitekeys();
+ DCHECK(sitekeys);
+
+ if (sitekeys->size()) {
+ return false;
+ }
+
+ return IsEmptyDomainAllowed(filter->include_domains(),
+ filter->exclude_domains());
+}
+
+bool InstalledSubscriptionImpl::IsActiveOnDomain(
+ const flat::UrlFilter* filter,
+ const std::string& document_domain,
+ const std::string& sitekey) const {
+ const auto* sitekeys = filter->sitekeys();
+ DCHECK(sitekeys);
+
+ if (sitekeys->size() != 0u) {
+ if (std::none_of(
+ sitekeys->begin(), sitekeys->end(), [&sitekey](const auto* it) {
+ return std::string_view(it->c_str(), it->size()) == sitekey;
+ })) {
+ // This filter requires a sitekey, and the one provided doesn't match.
+ return false;
+ }
+ }
+
+ const auto* include_domains = filter->include_domains();
+ const auto* exclude_domains = filter->exclude_domains();
+ return IsActiveOnDomain(document_domain, include_domains, exclude_domains);
+}
+
+bool InstalledSubscriptionImpl::IsActiveOnDomain(
+ const std::string& document_domain,
+ const Domains* include_domains,
+ const Domains* exclude_domains) const {
+ if (IsEmptyDomainAllowed(include_domains, exclude_domains)) {
+ return true;
+ }
+
+ // If |document_domain| matches any exclusion-type mapping for this filter,
+ // the filter may not be applied to this domain.
+ if (exclude_domains && DomainOnList(document_domain, exclude_domains)) {
+ return false;
+ }
+
+ if (include_domains && include_domains->size()) {
+ if (DomainOnList(document_domain, include_domains)) {
+ return true;
+ }
+ return false;
+ }
+
+ // But if there are no include requirements for the filter, only exclude
+ // domains, the filter applies.
+ return true;
+}
+
+bool InstalledSubscriptionImpl::IsEmptyDomainAllowed(
+ const Domains* include_domains,
+ const Domains* exclude_domains) const {
+ const bool has_no_exclude_domains =
+ !exclude_domains || exclude_domains->size() == 0u;
+ return // optimization: instead of checking domains->LookupByKey(""), just
+ // check first element is empty (list is sorted)
+ (!include_domains || !include_domains->size() ||
+ !include_domains->Get(0)->size()) &&
+ has_no_exclude_domains;
+}
+
+std::vector<InstalledSubscription::Snippet>
+InstalledSubscriptionImpl::MatchSnippets(
+ const std::string& document_domain) const {
+ std::vector<InstalledSubscription::Snippet> result;
+ if (!index_->snippet()) {
+ return result;
+ }
+
+ DomainSplitter domain_splitter(document_domain);
+ while (auto subdomain = domain_splitter.FindNextSubdomain()) {
+ const auto* idx = index_->snippet()->LookupByKey(subdomain->data());
+
+ if (!idx) {
+ continue;
+ }
+
+ for (const auto* cur : (*idx->filter())) {
+ if (IsActiveOnDomain(document_domain, nullptr, cur->exclude_domains())) {
+ for (const auto* line : (*cur->script())) {
+ InstalledSubscription::Snippet obj;
+ obj.command = std::string_view(line->command()->c_str(),
+ line->command()->size());
+ obj.arguments.reserve(line->arguments()->size());
+
+ for (const auto* arg : (*line->arguments())) {
+ obj.arguments.emplace_back(arg->c_str(), arg->size());
+ }
+
+ result.push_back(std::move(obj));
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void InstalledSubscriptionImpl::MarkForPermanentRemoval() {
+ buffer_->PermanentlyRemoveSourceOnDestruction();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/installed_subscription_impl.h b/components/adblock/core/subscription/installed_subscription_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/installed_subscription_impl.h
@@ -0,0 +1,147 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_
+
+#include <cstdint>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/memory/raw_ptr.h"
+
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/adblock/core/subscription/regex_matcher.h"
+
+namespace adblock {
+
+// A flatbuffer-based implementation of Subscription.
+class InstalledSubscriptionImpl final : public InstalledSubscription {
+ public:
+ InstalledSubscriptionImpl(std::unique_ptr<FlatbufferData> buffer,
+ InstallationState installation_state,
+ base::Time installation_time);
+ // Subscription
+ GURL GetSourceUrl() const final;
+ std::string GetTitle() const final;
+ std::string GetCurrentVersion() const final;
+ InstallationState GetInstallationState() const final;
+ base::Time GetInstallationTime() const final;
+ base::TimeDelta GetExpirationInterval() const final;
+
+ // InstalledSubscription
+ bool HasUrlFilter(const GURL& url,
+ const std::string& document_domain,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const final;
+ bool HasPopupFilter(const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey,
+ FilterCategory category) const final;
+ bool HasSpecialFilter(SpecialFilterType type,
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey) const final;
+ void FindCspFilters(const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<std::string_view>& results) const final;
+ std::set<std::string_view> FindRewriteFilters(
+ const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category) const final;
+ void FindHeaderFilters(const GURL& url,
+ ContentType content_type,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<HeaderFilterData>& results) const final;
+
+ ContentFiltersData GetElemhideData(const GURL& url,
+ bool domain_specific) const final;
+ ContentFiltersData GetElemhideEmulationData(const GURL& url) const final;
+
+ std::vector<Snippet> MatchSnippets(
+ const std::string& document_domain) const final;
+
+ void MarkForPermanentRemoval() final;
+
+ private:
+ friend class base::RefCountedThreadSafe<InstalledSubscriptionImpl>;
+ ~InstalledSubscriptionImpl() final;
+ enum class FindStrategy {
+ FindFirst,
+ FindAll,
+ };
+
+ using UrlFilterIndex =
+ flatbuffers::Vector<flatbuffers::Offset<flat::UrlFiltersByKeyword>>;
+ using Domains = flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>;
+ // Finds the first filter in |category| that matches the remaining parameters.
+ // Finds all filters in category that matchers the remaining parameters.
+ std::vector<const flat::UrlFilter*> FindInternal(
+ const UrlFilterIndex* index,
+ const GURL& url,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ FindStrategy strategy) const;
+ void FindFiltersForKeyword(
+ const UrlFilterIndex* index,
+ std::string_view keyword,
+ const GURL& url,
+ const GURL& lowercase_url,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ bool is_third_party_request,
+ FindStrategy strategy,
+ std::vector<const flat::UrlFilter*>& out_results) const;
+ bool CandidateFilterViable(const flat::UrlFilter* candidate,
+ absl::optional<ContentType> content_type,
+ const std::string& document_domain,
+ const std::string& sitekey,
+ FilterCategory category,
+ bool is_third_party_request) const;
+ bool IsGenericFilter(const flat::UrlFilter* filter) const;
+ bool CheckThirdParty(const flat::UrlFilter* filter,
+ bool is_third_party_request) const;
+ bool IsActiveOnDomain(const flat::UrlFilter* filter,
+ const std::string& document_domain,
+ const std::string& sitekey) const;
+ bool IsActiveOnDomain(const std::string& document_domain,
+ const Domains* include_domains,
+ const Domains* exclude_domains) const;
+ bool IsEmptyDomainAllowed(const Domains* include_domains,
+ const Domains* exclude_domains) const;
+
+ const std::unique_ptr<FlatbufferData> buffer_;
+ const InstallationState installation_state_;
+ const base::Time installation_time_;
+ raw_ptr<const flat::Subscription> index_ = nullptr;
+ const std::unique_ptr<RegexMatcher> regex_matcher_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_
diff --git a/components/adblock/core/subscription/pattern_matcher.cc b/components/adblock/core/subscription/pattern_matcher.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/pattern_matcher.cc
@@ -0,0 +1,277 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/pattern_matcher.h"
+
+#include "absl/types/optional.h"
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/regex_filter_pattern.h"
+#include "url/third_party/mozilla/url_parse.h"
+
+namespace adblock {
+namespace {
+
+constexpr int kMaxRecursionDepth = 50;
+
+bool CharacterIsValidSeparator(char c) {
+ // The separator character can be anything but a letter, a digit, or one of
+ // the following: _, -, ., %
+ return !base::IsAsciiAlphaNumeric(c) &&
+ std::string_view("_-.%").find(c) == std::string_view::npos;
+}
+
+// Returns if |candidate| (e.g. "https://sub") is a valid start of |url|'s host.
+// If url is "https://sub.domain.com/path" then:
+// Valid candidates:
+// - https://
+// - https://sub.
+// - https://sub.domain.
+// - https://sub.domain.com
+// Invalid candidates:
+// - https://s
+// - https://sub
+// - https://sub.domain.com/
+// - https://sub.domain.com/p
+bool IsValidStartOfHost(std::string_view candidate, const GURL& url) {
+ if (url.has_scheme()) {
+ const auto parsed_url = url.parsed_for_possibly_invalid_spec();
+ const size_t distance_to_host = parsed_url.CountCharactersBefore(
+ url::Parsed::HOST, /*include_delimiter=*/true);
+ if (candidate.size() < distance_to_host) {
+ // If the candidate doesn't start with the url's scheme, it means we've
+ // found a match before we've reached the host portion of the URL, so the
+ // |candidate| cannot be a valid start of the host.
+ // This could happen if we're matching https://sub.domain.com/path with
+ // an invalid filter like:
+ // - ||https://sub.domain.com (candidate is "")
+ // - ||ps://sub.domain.com (candidate is "htt")
+ // - ||://sub.domain.com (candidate is "https")
+ // These filters are generally rejected by the parser, but prior to
+ // DPD-2644 they were allowed and may exist in the flatbuffer.
+ return false;
+ }
+ // Strip the scheme and the separator, to get to the host part of the URL.
+ candidate = candidate.substr(distance_to_host);
+ }
+ return candidate.empty() || candidate == url.host_piece() ||
+ (base::EndsWith(candidate, ".") &&
+ candidate.find_first_of("/") == std::string_view::npos);
+}
+
+class PatternTokenizer {
+ public:
+ explicit PatternTokenizer(std::string_view filter_pattern)
+ : consumed_filter_pattern_(filter_pattern) {}
+
+ std::string_view NextToken() {
+ if (consumed_filter_pattern_.empty()) {
+ return {};
+ }
+ // If the previous call left us on a wildcard character, return it and
+ // and advance to first non-wildcard position.
+ if (consumed_filter_pattern_[0] == '*') {
+ consumed_filter_pattern_ =
+ base::TrimString(consumed_filter_pattern_, "*", base::TRIM_LEADING);
+ return "*";
+ }
+ // If the previous call left us on a ^ separator, return it and advance
+ if (consumed_filter_pattern_[0] == '^') {
+ consumed_filter_pattern_ = consumed_filter_pattern_.substr(1);
+ return "^";
+ }
+ // If the previous call left us on a | anchor (or anchors), return it and
+ // advance to first non-anchor position.
+ if (consumed_filter_pattern_[0] == '|') {
+ const auto token = consumed_filter_pattern_.substr(
+ 0, consumed_filter_pattern_.find_first_not_of("|"));
+ consumed_filter_pattern_ = consumed_filter_pattern_.substr(token.size());
+ return token;
+ }
+
+ // The next token is whatever characters are between current position and
+ // the next separator (or EOF)
+ const auto next_token = consumed_filter_pattern_.substr(
+ 0, consumed_filter_pattern_.find_first_of(kSeparators));
+ // Advance to next token.
+ consumed_filter_pattern_ =
+ consumed_filter_pattern_.substr(next_token.size());
+ return next_token;
+ }
+
+ private:
+ constexpr static std::string_view kSeparators{"*^|"};
+ // The tokenizer consumes |consumed_filter_pattern_| from the left as it
+ // advances. This is cheap, just incrementing the begin index.
+ std::string_view consumed_filter_pattern_;
+};
+
+absl::optional<std::string_view> FindNextTokenInInput(
+ std::string_view consumed_input,
+ PatternTokenizer tokenizer,
+ int recursion_depth);
+
+// Check if |consumed_input| starts with next token from |tokenizer| and
+// continues matching subsequent tokens (recursively).
+bool NextTokenBeginsInput(std::string_view consumed_input,
+ PatternTokenizer tokenizer,
+ int recursion_depth) {
+ if (++recursion_depth > kMaxRecursionDepth) {
+ return false;
+ }
+ const auto token = tokenizer.NextToken();
+ if (token.empty()) {
+ // Matching finished, no more tokens in the filter.
+ return true;
+ }
+ if (token == "^") {
+ // The next character must either be a valid separator, or EOF. "^" matches
+ // either.
+ if (!consumed_input.empty()) {
+ // This is not an EOF, ^ must match a valid separator, followed by
+ // subsequent matching tokens.
+ return CharacterIsValidSeparator(consumed_input[0]) &&
+ NextTokenBeginsInput(consumed_input.substr(1), tokenizer,
+ recursion_depth);
+ }
+ // ^ is a valid match for EOF, but only if there aren't any tokens left
+ // that want to match text.
+ return NextTokenBeginsInput({}, tokenizer, recursion_depth);
+ } else if (token == "*") {
+ // The next characters can be anything, as long as subsequent tokens are
+ // matched further in |consumed_input| (recursively).
+ return FindNextTokenInInput(consumed_input, tokenizer, recursion_depth)
+ .has_value();
+ } else if (token == "|") {
+ // "|" is an end-of-URL anchor, verify we indeed reached end of input.
+ // TODO(mpawlowski) A literal "|"" character can occur in a URL, we should
+ // probably check this as well: DPD-1755.
+ return consumed_input.empty();
+ } else {
+ // The next characters should exactly match the token, and then subsequent
+ // tokens must continue matching the input.
+ if (!base::StartsWith(consumed_input, token)) {
+ return false;
+ }
+ return NextTokenBeginsInput(consumed_input.substr(token.size()), tokenizer,
+ recursion_depth);
+ }
+}
+
+// Returns characters skipped in order to reach next token from |tokenizer|, or
+// nullopt if not found.
+absl::optional<std::string_view> FindNextTokenInInput(
+ std::string_view consumed_input,
+ PatternTokenizer tokenizer,
+ int recursion_depth) {
+ if (++recursion_depth > kMaxRecursionDepth) {
+ return absl::nullopt;
+ }
+ const auto token = tokenizer.NextToken();
+ // We're searching for |token| anywhere inside |consumed_input|, we may skip
+ // any number of characters while we try to find it.
+ DCHECK(token != "*") << "PatternTokenizer failed to handle multiple "
+ "consecutive wildcards in the filter pattern";
+ if (token == "^") {
+ // We're looking for input that matches the ^ separator, followed by next
+ // tokens (recursively).
+ // It is possible that the first separator we find won't be followed by the
+ // correct next token. This is ok, this algorithm cannot be greedy. Keep
+ // skipping characters until we match a separator followed by subsequent
+ // tokens.
+ for (size_t i = 0; i < consumed_input.size(); i++) {
+ if (!CharacterIsValidSeparator(consumed_input[i])) {
+ continue;
+ }
+ if (NextTokenBeginsInput(consumed_input.substr(i + 1), tokenizer,
+ recursion_depth)) {
+ return consumed_input.substr(0, i + 1);
+ }
+ }
+ // Reached the end of the input without matching a valid separator (that was
+ // followed by the right tokens, recursively).
+ // It is OK as long as there are no further tokens that require matching
+ // input. The "^" symbol matches EOF too.
+ return NextTokenBeginsInput(std::string_view(), tokenizer, recursion_depth)
+ ? absl::optional<std::string_view>{consumed_input}
+ : absl::nullopt;
+ } else if (token == "|") {
+ // If we're skipping characters, we can always skip enough to reach the end
+ // anchor.
+ return consumed_input;
+ } else {
+ // The searched token is just ASCII text. Keep searching for occurrences of
+ // it within consumed_input.
+ for (auto match_pos = consumed_input.find(token);
+ match_pos != std::string_view::npos;
+ match_pos = consumed_input.find(token, match_pos + 1)) {
+ if (NextTokenBeginsInput(consumed_input.substr(match_pos + token.size()),
+ tokenizer, recursion_depth)) {
+ return consumed_input.substr(0, match_pos);
+ }
+ // If the first occurrence of token inside consumed_input isn't the right
+ // one, keep looking. Subsequent tokens didn't match, but the algorithm is
+ // not greedy, there might be another match.
+ }
+
+ return absl::nullopt;
+ }
+}
+
+} // namespace
+
+bool DoesPatternMatchUrl(std::string_view filter_pattern, const GURL& url) {
+ DCHECK(!ExtractRegexFilterFromPattern(filter_pattern))
+ << "This function does not support regular expressions filters";
+ const std::string_view input(url.spec());
+ PatternTokenizer tokenizer(filter_pattern);
+ const auto first_token = tokenizer.NextToken();
+ if (first_token == "|") {
+ return NextTokenBeginsInput(input, tokenizer, 0);
+ } else if (first_token == "||") {
+ {
+ // If the next token is *, we discard the start-from-host anchor, behave
+ // as if the filter started from *
+ auto empty_or_wildcard_tokenizer = tokenizer;
+ const auto token = empty_or_wildcard_tokenizer.NextToken();
+ if (token == "*") {
+ return FindNextTokenInInput(input, empty_or_wildcard_tokenizer, 0)
+ .has_value();
+ }
+ // If the next token is empty we have a filter "||" matching any domain.
+ if (token.empty()) {
+ return true;
+ }
+ }
+ const auto skipped_characters = FindNextTokenInInput(input, tokenizer, 0);
+ if (!skipped_characters) {
+ return false;
+ }
+ return IsValidStartOfHost(*skipped_characters, url);
+
+ } else if (first_token == "*") {
+ return FindNextTokenInInput(input, tokenizer, 0).has_value();
+ } else {
+ // Behave as if the first token is a wildcard, recreate tokenizer to restart
+ // from the first token.
+ return FindNextTokenInInput(input, PatternTokenizer(filter_pattern), 0)
+ .has_value();
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/pattern_matcher.h b/components/adblock/core/subscription/pattern_matcher.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/pattern_matcher.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_
+
+#include <string_view>
+
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Returns whether the URL is matched by a filter pattern.
+// Example: filter_pattern "||example.com^" will match url
+// "https://subdomain/example.com/path.png"
+// filter_pattern must NOT be a regex filter
+bool DoesPatternMatchUrl(std::string_view filter_pattern, const GURL& url);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_
diff --git a/components/adblock/core/subscription/preloaded_subscription_provider.h b/components/adblock/core/subscription/preloaded_subscription_provider.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/preloaded_subscription_provider.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_
+
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Provides temporary preloaded subscriptions when needed.
+// Preloaded subscriptions are filter lists bundled with the browser. They can
+// be used to provide some level of ad-filtering while waiting for the download
+// of up-to-date filter lists from the Internet.
+class PreloadedSubscriptionProvider {
+ public:
+ virtual ~PreloadedSubscriptionProvider() = default;
+
+ // The collection of preloaded subscriptions returned by
+ // |GetCurrentPreloadedSubscriptions()| is built by comparing the list of
+ // installed (ie. available) subscriptions with the list of pending (ie.
+ // desired) subscriptions.
+ virtual void UpdateSubscriptions(std::vector<GURL> installed_subscriptions,
+ std::vector<GURL> pending_subscriptions) = 0;
+
+ // Returns preloaded subscriptions that were deemed necessary, based on the
+ // difference between pending and installed subscriptions, to provide relevant
+ // temporary ad-filtering. This may include easylist.txt and
+ // exceptionrules.txt. The subscriptions are kept in memory and released when
+ // no longer needed.
+ virtual std::vector<scoped_refptr<InstalledSubscription>>
+ GetCurrentPreloadedSubscriptions() const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_
diff --git a/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc
@@ -0,0 +1,121 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/preloaded_subscription_provider_impl.h"
+
+#include <string_view>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/pattern.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+
+namespace adblock {
+namespace {
+
+bool HasSubscriptionWithMatchingUrl(const std::vector<GURL>& collection,
+ std::string_view pattern) {
+ return std::find_if(collection.begin(), collection.end(),
+ [pattern](const GURL& url) {
+ return base::MatchPattern(url.spec(), pattern);
+ }) != collection.end();
+}
+
+} // namespace
+
+class PreloadedSubscriptionProviderImpl::SingleSubscriptionProvider {
+ public:
+ explicit SingleSubscriptionProvider(PreloadedSubscriptionInfo info)
+ : info_(info) {}
+
+ void UpdatePreloadedSubscription(
+ const std::vector<GURL>& installed_subscriptions,
+ const std::vector<GURL>& pending_subscriptions) {
+ const bool needs_subscription =
+ HasSubscriptionWithMatchingUrl(pending_subscriptions,
+ info_.url_pattern) &&
+ !HasSubscriptionWithMatchingUrl(installed_subscriptions,
+ info_.url_pattern);
+ if (needs_subscription && !subscription_) {
+ TRACE_EVENT1("eyeo", "Creating preloaded subscription", "url_pattern",
+ info_.url_pattern);
+ subscription_ = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ utils::MakeFlatbufferDataFromResourceBundle(
+ info_.flatbuffer_resource_id),
+ Subscription::InstallationState::Preloaded, base::Time());
+ VLOG(1) << "[eyeo] Preloaded subscription now in use: "
+ << subscription_->GetSourceUrl();
+ } else if (!needs_subscription && subscription_) {
+ VLOG(1) << "[eyeo] Preloaded subscription no longer in use: "
+ << subscription_->GetSourceUrl();
+ subscription_.reset();
+ }
+ }
+
+ scoped_refptr<InstalledSubscription> subscription() const {
+ return subscription_;
+ }
+
+ void Reset() { subscription_.reset(); }
+
+ private:
+ PreloadedSubscriptionInfo info_;
+ scoped_refptr<InstalledSubscription> subscription_;
+};
+
+PreloadedSubscriptionProviderImpl::~PreloadedSubscriptionProviderImpl() =
+ default;
+PreloadedSubscriptionProviderImpl::PreloadedSubscriptionProviderImpl() {
+ for (const auto& info : config::GetPreloadedSubscriptionConfiguration()) {
+ providers_.emplace_back(info);
+ }
+}
+
+void PreloadedSubscriptionProviderImpl::UpdateSubscriptions(
+ std::vector<GURL> installed_subscriptions,
+ std::vector<GURL> pending_subscriptions) {
+ installed_subscriptions_ = std::move(installed_subscriptions);
+ pending_subscriptions_ = std::move(pending_subscriptions);
+ UpdateSubscriptionsInternal();
+}
+
+std::vector<scoped_refptr<InstalledSubscription>>
+PreloadedSubscriptionProviderImpl::GetCurrentPreloadedSubscriptions() const {
+ std::vector<scoped_refptr<InstalledSubscription>> result;
+ for (const auto& provider : providers_) {
+ auto sub = provider.subscription();
+ if (sub) {
+ result.push_back(sub);
+ }
+ }
+ return result;
+}
+
+void PreloadedSubscriptionProviderImpl::UpdateSubscriptionsInternal() {
+ for (auto& provider : providers_) {
+ provider.UpdatePreloadedSubscription(installed_subscriptions_,
+ pending_subscriptions_);
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/preloaded_subscription_provider_impl.h b/components/adblock/core/subscription/preloaded_subscription_provider_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/preloaded_subscription_provider_impl.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_
+
+#include <vector>
+
+#include "components/adblock/core/subscription/preloaded_subscription_provider.h"
+
+namespace adblock {
+
+class PreloadedSubscriptionProviderImpl final
+ : public PreloadedSubscriptionProvider {
+ public:
+ ~PreloadedSubscriptionProviderImpl() final;
+ PreloadedSubscriptionProviderImpl();
+
+ void UpdateSubscriptions(std::vector<GURL> installed_subscriptions,
+ std::vector<GURL> pending_subscriptions) final;
+
+ std::vector<scoped_refptr<InstalledSubscription>>
+ GetCurrentPreloadedSubscriptions() const final;
+
+ private:
+ void UpdateSubscriptionsInternal();
+
+ class SingleSubscriptionProvider;
+ std::vector<GURL> installed_subscriptions_;
+ std::vector<GURL> pending_subscriptions_;
+ std::vector<SingleSubscriptionProvider> providers_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_
diff --git a/components/adblock/core/subscription/recommended_subscription_installer.h b/components/adblock/core/subscription/recommended_subscription_installer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/recommended_subscription_installer.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_H_
+
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Downloads, parses and installs recommended subscriptions based on
+// geolocation.
+class RecommendedSubscriptionInstaller {
+ public:
+ using RecommendedSubscriptionsParsedCallback =
+ base::OnceCallback<void(const std::vector<GURL>&)>;
+
+ virtual ~RecommendedSubscriptionInstaller() {}
+
+ virtual void RunUpdateCheck() = 0;
+ virtual void RemoveAutoInstalledSubscriptions() = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_H_
diff --git a/components/adblock/core/subscription/recommended_subscription_installer_impl.cc b/components/adblock/core/subscription/recommended_subscription_installer_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/recommended_subscription_installer_impl.cc
@@ -0,0 +1,141 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/recommended_subscription_installer_impl.h"
+
+#include "base/functional/callback.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/subscription/recommended_subscription_parser.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/prefs/pref_service.h"
+#include "net/http/http_response_headers.h"
+
+namespace adblock {
+namespace {
+// Subscriptions that are not recommended for 14 days are removed
+constexpr base::TimeDelta kAutoInstalledSubscriptionExpirationInterval =
+ base::Days(14);
+// Auto installed subscriptions are updated every day
+constexpr base::TimeDelta kAutoInstalledSubscriptionUpdateInterval =
+ base::Days(1);
+} // namespace
+
+RecommendedSubscriptionInstallerImpl::RecommendedSubscriptionInstallerImpl(
+ PrefService* pref_service,
+ FilteringConfiguration* configuration,
+ SubscriptionPersistentMetadata* persistent_metadata,
+ ResourceRequestMaker request_maker)
+ : pref_service_(pref_service),
+ configuration_(configuration),
+ persistent_metadata_(persistent_metadata),
+ request_maker_(std::move(request_maker)) {
+ DCHECK(configuration_->GetName() == kAdblockFilteringConfigurationName)
+ << "Recommended subscriptions should only be installed for adblock "
+ "configuration";
+}
+
+RecommendedSubscriptionInstallerImpl::~RecommendedSubscriptionInstallerImpl() {}
+
+void RecommendedSubscriptionInstallerImpl::RunUpdateCheck() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (IsUpdateDue()) {
+ VLOG(1) << "[eyeo] Running recommended subscription update";
+ resource_request_ = request_maker_.Run();
+ resource_request_->Start(
+ RecommendedSubscriptionListUrl(), AdblockResourceRequest::Method::GET,
+ base::BindRepeating(&RecommendedSubscriptionInstallerImpl::
+ OnRecommendationListDownloaded,
+ weak_ptr_factory_.GetWeakPtr()),
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, "");
+ }
+}
+
+bool RecommendedSubscriptionInstallerImpl::IsUpdateDue() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return pref_service_->GetBoolean(
+ common::prefs::kEnableAutoInstalledSubscriptions) &&
+ pref_service_->GetTime(
+ common::prefs::kAutoInstalledSubscriptionsNextUpdateTime) <=
+ base::Time::Now();
+}
+
+void RecommendedSubscriptionInstallerImpl::OnRecommendationListDownloaded(
+ const GURL& url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ VLOG(1) << "[eyeo] Finished downloading recommended subscription list";
+
+ resource_request_.reset();
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&RecommendedSubscriptionParser::FromFile, downloaded_file),
+ base::BindOnce(&RecommendedSubscriptionInstallerImpl::
+ OnRecommendedSubscriptionsParsed,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void RecommendedSubscriptionInstallerImpl::OnRecommendedSubscriptionsParsed(
+ const std::vector<GURL>& recommended_subscriptions) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (const auto& subscription : recommended_subscriptions) {
+ VLOG(1) << "[eyeo] Adding auto installed subscription: " << subscription;
+
+ if (configuration_->IsFilterListPresent(subscription) &&
+ !persistent_metadata_->IsAutoInstalled(subscription)) {
+ VLOG(1) << "[eyeo] Skipping recommended subscription since it's already "
+ "added as not auto installed";
+ continue;
+ }
+
+ // If the list is not present already, subscribe to it.
+ // Adding existing list is NOP so there is no need to check.
+ configuration_->AddFilterList(subscription);
+
+ persistent_metadata_->SetAutoInstalledExpirationInterval(
+ subscription, kAutoInstalledSubscriptionExpirationInterval);
+ }
+
+ for (const auto& filter_list : configuration_->GetFilterLists()) {
+ // Remove auto installed subscription if it's not recommended for a while
+ if (persistent_metadata_->IsAutoInstalledExpired(filter_list)) {
+ VLOG(1) << "[eyeo] Removing auto installed subscription: " << filter_list;
+ configuration_->RemoveFilterList(filter_list);
+ }
+ }
+
+ pref_service_->SetTime(
+ common::prefs::kAutoInstalledSubscriptionsNextUpdateTime,
+ base::Time::Now() + kAutoInstalledSubscriptionUpdateInterval);
+}
+
+void RecommendedSubscriptionInstallerImpl::RemoveAutoInstalledSubscriptions() {
+ for (const auto& filter_list : configuration_->GetFilterLists()) {
+ // Remove auto installed subscription if it's not recommended for a while
+ if (persistent_metadata_->IsAutoInstalled(filter_list)) {
+ VLOG(1) << "[eyeo] Removing auto installed subscription: " << filter_list;
+ configuration_->RemoveFilterList(filter_list);
+ }
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/recommended_subscription_installer_impl.h b/components/adblock/core/subscription/recommended_subscription_installer_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/recommended_subscription_installer_impl.h
@@ -0,0 +1,73 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_IMPL_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/net/adblock_resource_request.h"
+#include "components/adblock/core/subscription/recommended_subscription_installer.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+
+class PrefService;
+
+namespace adblock {
+
+class RecommendedSubscriptionInstallerImpl final
+ : public RecommendedSubscriptionInstaller {
+ public:
+ using ResourceRequestMaker =
+ base::RepeatingCallback<std::unique_ptr<AdblockResourceRequest>()>;
+
+ RecommendedSubscriptionInstallerImpl(
+ PrefService* pref_service,
+ FilteringConfiguration* configuration,
+ SubscriptionPersistentMetadata* persistent_metadata,
+ ResourceRequestMaker request_maker);
+ ~RecommendedSubscriptionInstallerImpl() final;
+
+ void RunUpdateCheck() final;
+ void RemoveAutoInstalledSubscriptions() final;
+
+ private:
+ bool IsUpdateDue() const;
+ void OnRecommendationListDownloaded(
+ const GURL& url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers);
+ void OnRecommendedSubscriptionsParsed(
+ const std::vector<GURL>& recommended_subscriptions);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ raw_ptr<PrefService> pref_service_;
+ raw_ptr<FilteringConfiguration> configuration_;
+ raw_ptr<SubscriptionPersistentMetadata> persistent_metadata_;
+ ResourceRequestMaker request_maker_;
+ RecommendedSubscriptionsParsedCallback
+ on_recommended_subscriptions_available_;
+ std::unique_ptr<AdblockResourceRequest> resource_request_;
+ base::WeakPtrFactory<RecommendedSubscriptionInstallerImpl> weak_ptr_factory_{
+ this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_IMPL_H_
diff --git a/components/adblock/core/subscription/recommended_subscription_parser.cc b/components/adblock/core/subscription/recommended_subscription_parser.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/recommended_subscription_parser.cc
@@ -0,0 +1,82 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/recommended_subscription_parser.h"
+
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/task/thread_pool.h"
+#include "base/values.h"
+
+namespace adblock {
+
+// static
+std::vector<GURL> RecommendedSubscriptionParser::FromFile(
+ base::FilePath downloaded_file) {
+ std::string recommendation_json_string;
+ auto read_result =
+ base::ReadFileToString(downloaded_file, &recommendation_json_string);
+ base::DeleteFile(downloaded_file);
+ if (!read_result) {
+ LOG(ERROR) << "[eyeo] Could not read recommended subscription list";
+ return {};
+ }
+
+ VLOG(1) << "[eyeo] Raw subscription recommendations: "
+ << recommendation_json_string;
+
+ auto recommendation_json = base::JSONReader::ReadAndReturnValueWithError(
+ recommendation_json_string, base::JSON_PARSE_RFC);
+
+ if (!recommendation_json.has_value()) {
+ LOG(ERROR) << "[eyeo] Could not parse recommended subscription list: "
+ << recommendation_json.error().ToString();
+ return {};
+ }
+
+ if (!recommendation_json->is_list()) {
+ LOG(ERROR) << "[eyeo] Invalid recommended subscription data";
+ return {};
+ }
+
+ std::vector<GURL> recommended_subscription_urls;
+ auto& recommended_subscriptions = recommendation_json->GetList();
+ for (auto& recommended_subscription : recommended_subscriptions) {
+ if (!recommended_subscription.is_dict()) {
+ LOG(ERROR) << "[eyeo] Invalid recommended subscription data";
+ continue;
+ }
+
+ std::string* recommended_subscription_url =
+ recommended_subscription.GetDict().FindString("url");
+ if (!recommended_subscription_url) {
+ LOG(ERROR) << "[eyeo] Invalid recommended subscription data";
+ continue;
+ }
+
+ VLOG(1) << "[eyeo] Recommended subscription url: "
+ << *recommended_subscription_url;
+ recommended_subscription_urls.emplace_back(*recommended_subscription_url);
+ }
+
+ return recommended_subscription_urls;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/recommended_subscription_parser.h b/components/adblock/core/subscription/recommended_subscription_parser.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/recommended_subscription_parser.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_PARSER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_PARSER_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class RecommendedSubscriptionParser {
+ public:
+ static std::vector<GURL> FromFile(base::FilePath file_path);
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_PARSER_H_
diff --git a/components/adblock/core/subscription/regex_matcher.cc b/components/adblock/core/subscription/regex_matcher.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/regex_matcher.cc
@@ -0,0 +1,162 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/regex_matcher.h"
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/common/regex_filter_pattern.h"
+#include "re2/re2.h"
+#include "re2/stringpiece.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "unicode/utypes.h"
+
+namespace adblock {
+
+RegexMatcher::RegexMatcher() = default;
+RegexMatcher::~RegexMatcher() = default;
+
+void RegexMatcher::PreBuildRegexPatternsWithNoKeyword(
+ const flat::Subscription* index) {
+ base::ElapsedTimer timer;
+ PreBuildPatternsFrom(index->url_csp_allow());
+ PreBuildPatternsFrom(index->url_csp_block());
+ PreBuildPatternsFrom(index->url_document_allow());
+ PreBuildPatternsFrom(index->url_genericblock_allow());
+ PreBuildPatternsFrom(index->url_header_allow());
+ PreBuildPatternsFrom(index->url_popup_allow());
+ PreBuildPatternsFrom(index->url_popup_block());
+ PreBuildPatternsFrom(index->url_elemhide_allow());
+ PreBuildPatternsFrom(index->url_rewrite_allow());
+ PreBuildPatternsFrom(index->url_rewrite_block());
+ PreBuildPatternsFrom(index->url_subresource_allow());
+ PreBuildPatternsFrom(index->url_subresource_block());
+ VLOG(1) << "Added " << CacheSize() << " precompiled regular expressions in "
+ << timer.Elapsed();
+}
+
+void RegexMatcher::PreBuildRegexPattern(std::string_view regular_expression,
+ bool case_sensitive) {
+ auto re2_pattern = BuildRe2Expression(regular_expression, case_sensitive);
+ if (re2_pattern) {
+ re2_cache_[std::make_pair(regular_expression, case_sensitive)] =
+ std::move(re2_pattern);
+ } else {
+ auto icu_pattern = BuildIcuExpression(regular_expression, case_sensitive);
+ if (!icu_pattern) {
+ LOG(ERROR) << "Even ICU cannot parse this regular expression, "
+ "this should have been caught during parsing. Will "
+ "ignore this filter: "
+ << regular_expression;
+ return;
+ }
+ icu_cache_[std::make_pair(regular_expression, case_sensitive)] =
+ std::move(icu_pattern);
+ }
+}
+
+bool RegexMatcher::MatchesRegex(std::string_view regex_pattern,
+ const GURL& url,
+ bool case_sensitive) const {
+ const std::string_view input = url.spec();
+ const auto cache_key = std::make_pair(regex_pattern, case_sensitive);
+
+ const auto cached_re2_expression = re2_cache_.find(cache_key);
+ if (cached_re2_expression != re2_cache_.end()) {
+ return re2::RE2::PartialMatch(input.data(), *cached_re2_expression->second);
+ }
+
+ const auto cached_icu_expression = icu_cache_.find(cache_key);
+ if (cached_icu_expression != icu_cache_.end()) {
+ const icu::UnicodeString icu_input(input.data(), input.length());
+ UErrorCode status = U_ZERO_ERROR;
+ std::unique_ptr<icu::RegexMatcher> regex_matcher = base::WrapUnique(
+ cached_icu_expression->second->matcher(icu_input, status));
+ bool is_match = regex_matcher->find(0, status);
+ DCHECK(U_SUCCESS(status));
+ return is_match;
+ }
+ VLOG(1) << "Matching a non-prebuilt expression, this will be slow";
+ return utils::RegexMatches(regex_pattern, input, case_sensitive);
+}
+
+void RegexMatcher::PreBuildPatternsFrom(const UrlFilterIndex* index) {
+ if (!index) {
+ return;
+ }
+ const auto* idx = index->LookupByKey("");
+ if (!idx) {
+ return;
+ }
+ for (const auto* filter : *(idx->filter())) {
+ if (CacheSize() >= kMaxPrebuiltPatterns) {
+ return;
+ }
+ if (!filter->pattern()) {
+ continue; // This filter has no keyword because it has an empty pattern.
+ }
+ const std::string_view filter_string(filter->pattern()->c_str(),
+ filter->pattern()->size());
+ const auto regex_string = ExtractRegexFilterFromPattern(filter_string);
+ if (!regex_string) {
+ continue; // This is not a regex filter.
+ }
+ PreBuildRegexPattern(*regex_string, filter->match_case());
+ }
+}
+
+std::unique_ptr<re2::RE2> RegexMatcher::BuildRe2Expression(
+ std::string_view regular_expression,
+ bool case_sensitive) {
+ re2::RE2::Options options;
+ options.set_case_sensitive(case_sensitive);
+ options.set_never_capture(true);
+ options.set_log_errors(false);
+ options.set_encoding(re2::RE2::Options::EncodingLatin1);
+ auto prebuilt_re2 = std::make_unique<re2::RE2>(
+ re2::StringPiece(regular_expression.data(), regular_expression.size()),
+ options);
+ if (!prebuilt_re2->ok()) {
+ return nullptr;
+ }
+ return prebuilt_re2;
+}
+
+std::unique_ptr<icu::RegexPattern> RegexMatcher::BuildIcuExpression(
+ std::string_view regular_expression,
+ bool case_sensitive) {
+ const icu::UnicodeString icu_pattern(regular_expression.data(),
+ regular_expression.length());
+ UErrorCode status = U_ZERO_ERROR;
+ const auto icu_case_sensetive = case_sensitive ? 0u : UREGEX_CASE_INSENSITIVE;
+ std::unique_ptr<icu::RegexPattern> prebuilt_pattern = base::WrapUnique(
+ icu::RegexPattern::compile(icu_pattern, icu_case_sensetive, status));
+ if (U_FAILURE(status) || !prebuilt_pattern) {
+ return nullptr;
+ }
+ return prebuilt_pattern;
+}
+
+int RegexMatcher::CacheSize() const {
+ return re2_cache_.size() + icu_cache_.size();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/regex_matcher.h b/components/adblock/core/subscription/regex_matcher.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/regex_matcher.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_
+
+#include <map>
+#include <string_view>
+
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class RegexMatcher {
+ public:
+ RegexMatcher();
+ ~RegexMatcher();
+ RegexMatcher(const RegexMatcher&) = delete;
+ RegexMatcher(RegexMatcher&&) = delete;
+ RegexMatcher& operator=(const RegexMatcher&) = delete;
+ RegexMatcher& operator=(RegexMatcher&&) = delete;
+
+ // Max number of patterns that PreBuildRegexPatternsWithNoKeyword() will
+ // build and store in memory. If there are more regex patterns in |index|,
+ // they will not be pre-built and MatchesRegex() will build them on demand.
+ static constexpr int kMaxPrebuiltPatterns = 500;
+
+ // Regex patterns that have no keyword attached must be matched to every URL.
+ // There are typically few of them and they are matched very often, so
+ // pre-build them.
+ void PreBuildRegexPatternsWithNoKeyword(const flat::Subscription* index);
+ void PreBuildRegexPattern(std::string_view regular_expression,
+ bool case_sensitive);
+
+ bool MatchesRegex(std::string_view regex_pattern,
+ const GURL& url,
+ bool case_sensitive) const;
+
+ private:
+ using UrlFilterIndex =
+ flatbuffers::Vector<flatbuffers::Offset<flat::UrlFiltersByKeyword>>;
+ void PreBuildPatternsFrom(const UrlFilterIndex* index);
+ std::unique_ptr<re2::RE2> BuildRe2Expression(
+ std::string_view regular_expression,
+ bool case_sensitive);
+ std::unique_ptr<icu::RegexPattern> BuildIcuExpression(
+ std::string_view regular_expression,
+ bool case_sensitive);
+ int CacheSize() const;
+
+ using CacheKey = std::tuple<std::string_view, bool>;
+ std::map<CacheKey, std::unique_ptr<re2::RE2>> re2_cache_;
+ std::map<CacheKey, std::unique_ptr<icu::RegexPattern>> icu_cache_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_
diff --git a/components/adblock/core/subscription/subscription.cc b/components/adblock/core/subscription/subscription.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription.cc
@@ -0,0 +1,24 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription.h"
+
+namespace adblock {
+
+Subscription::~Subscription() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription.h b/components/adblock/core/subscription/subscription.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Represents a single filter list, ex. Easylist French or Acceptable Ads.
+// Read-only and thread-safe.
+class Subscription : public base::RefCountedThreadSafe<Subscription> {
+ public:
+ enum class InstallationState {
+ // Subscription is installed and in use.
+ Installed,
+ // Subscription comes from geolocation recommendation and may be
+ // auto-removed on recommendation change. It's installed and in use.
+ AutoInstalled,
+ // A preloaded version of this subscription is in use, a full version is
+ // likely being downloaded from the Internet.
+ Preloaded,
+ // Subscription is being downloaded and not yet in use. No preloaded
+ // substitute is available.
+ Installing,
+ // State is unknown when FilteringConfiguration is disabled.
+ Unknown,
+ };
+ // Returns the URL of the text version of the subscription, ex.
+ // https://easylist-downloads.adblockplus.org/easylist.txt.
+ // Note that this may be different than the URL from which the subscription
+ // was downloaded.
+ virtual GURL GetSourceUrl() const = 0;
+
+ // Returns the value of the `! Title:` field of the filter list, ex. "EasyList
+ // Germany+EasyList". This is an optional field and may be empty.
+ virtual std::string GetTitle() const = 0;
+
+ // Returns the value of the `! Version:` field of the filter list, ex.
+ // "202108191121". This is an optional field and may be empty.
+ virtual std::string GetCurrentVersion() const = 0;
+
+ // Returns whether this subscription is installed and in use, or whether it's
+ // still being downloaded.
+ virtual InstallationState GetInstallationState() const = 0;
+
+ // Returns the time the subscription was installed or last updated.
+ // Only valid when GetInstallationState() returns Installed, otherwise zero.
+ virtual base::Time GetInstallationTime() const = 0;
+
+ // Returns amount of time until subscription expires.
+ // Typically, update checks are performed once per expiration interval.
+ virtual base::TimeDelta GetExpirationInterval() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<Subscription>;
+ virtual ~Subscription();
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_
diff --git a/components/adblock/core/subscription/subscription_collection.h b/components/adblock/core/subscription/subscription_collection.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_collection.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_
+
+#include <set>
+#include <string_view>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/containers/span.h"
+#include "base/values.h"
+#include "components/adblock/core/common/content_type.h"
+#include "components/adblock/core/common/header_filter_data.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Represents a collection of all currently active Subscriptions and allows
+// bulk queries to be made towards all of them.
+// Represents a snapshot of a state of the browser.
+// Cheap to create and copy, non-mutable and thread-safe.
+class SubscriptionCollection {
+ public:
+ virtual ~SubscriptionCollection() = default;
+
+ // Name of the FilteringConfiguration this collection represents
+ virtual const std::string& GetFilteringConfigurationName() const = 0;
+
+ virtual absl::optional<GURL> FindBySubresourceFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const = 0;
+ virtual absl::optional<GURL> FindByPopupFilter(
+ const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey,
+ FilterCategory category) const = 0;
+ virtual absl::optional<GURL> FindByAllowFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const = 0;
+ virtual absl::optional<GURL> FindBySpecialFilter(
+ SpecialFilterType filter_type,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const = 0;
+
+ virtual InstalledSubscription::ContentFiltersData GetElementHideData(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const = 0;
+ virtual InstalledSubscription::ContentFiltersData GetElementHideEmulationData(
+ const GURL& frame_url) const = 0;
+
+ virtual base::Value::List GenerateSnippets(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy) const = 0;
+
+ virtual std::set<std::string_view> GetCspInjections(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const = 0;
+
+ virtual std::set<std::string_view> GetRewriteFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ FilterCategory category) const = 0;
+
+ virtual std::set<HeaderFilterData> GetHeaderFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ FilterCategory category) const = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_
diff --git a/components/adblock/core/subscription/subscription_collection_impl.cc b/components/adblock/core/subscription/subscription_collection_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_collection_impl.cc
@@ -0,0 +1,382 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_collection_impl.h"
+
+#include <algorithm>
+#include <numeric>
+#include <string>
+
+namespace adblock {
+namespace {
+
+std::string DocumentDomain(const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) {
+ return frame_hierarchy.empty() ? request_url.host()
+ : frame_hierarchy[0].host();
+}
+
+InstalledSubscription::ContentFiltersData MaybeReduceSelectors(
+ InstalledSubscription::ContentFiltersData& combined_selectors) {
+ if (combined_selectors.elemhide_exceptions.empty()) {
+ // Nothing to reduce.
+ return combined_selectors;
+ }
+ // Populate result with blocking selectors.
+ InstalledSubscription::ContentFiltersData final_selectors;
+ final_selectors.elemhide_selectors =
+ std::move(combined_selectors.elemhide_selectors);
+ final_selectors.remove_selectors =
+ std::move(combined_selectors.remove_selectors);
+ final_selectors.selectors_to_inline_css =
+ std::move(combined_selectors.selectors_to_inline_css);
+ // Remove exceptions.
+ for (auto* selectors_collection : {&final_selectors.elemhide_selectors,
+ &final_selectors.remove_selectors}) {
+ if (selectors_collection->empty()) {
+ continue;
+ }
+ selectors_collection->erase(
+ std::remove_if(
+ selectors_collection->begin(), selectors_collection->end(),
+ [&](const auto& selector) {
+ return std::find(combined_selectors.elemhide_exceptions.begin(),
+ combined_selectors.elemhide_exceptions.end(),
+ selector) !=
+ combined_selectors.elemhide_exceptions.end();
+ }),
+ selectors_collection->end());
+ }
+ if (!final_selectors.selectors_to_inline_css.empty()) {
+ final_selectors.selectors_to_inline_css.erase(
+ std::remove_if(
+ final_selectors.selectors_to_inline_css.begin(),
+ final_selectors.selectors_to_inline_css.end(),
+ [&](const auto& selector_with_css) {
+ return std::find(combined_selectors.elemhide_exceptions.begin(),
+ combined_selectors.elemhide_exceptions.end(),
+ selector_with_css.first) !=
+ combined_selectors.elemhide_exceptions.end();
+ }),
+ final_selectors.selectors_to_inline_css.end());
+ }
+ return final_selectors;
+}
+
+bool SubscriptionContainsSpecialFilter(
+ const scoped_refptr<adblock::InstalledSubscription> subscription,
+ SpecialFilterType filter_type,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) {
+ for (auto it = frame_hierarchy.begin(); it < frame_hierarchy.end(); ++it) {
+ const GURL& current_url = *it;
+ const std::string& current_domain = std::next(it) != frame_hierarchy.end()
+ ? std::next(it)->host()
+ : current_url.host();
+ if (subscription->HasSpecialFilter(filter_type, current_url, current_domain,
+ sitekey)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool HasAllowFilter(
+ const scoped_refptr<adblock::InstalledSubscription> subscription,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) {
+ if (subscription->HasUrlFilter(
+ request_url, DocumentDomain(request_url, frame_hierarchy),
+ content_type, sitekey, FilterCategory::Allowing)) {
+ return true;
+ }
+ if (SubscriptionContainsSpecialFilter(subscription,
+ SpecialFilterType::Document,
+ frame_hierarchy, sitekey)) {
+ return true;
+ }
+ return false;
+}
+
+bool HasSpecialFilter(
+ const scoped_refptr<adblock::InstalledSubscription> subscription,
+ SpecialFilterType filter_type,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) {
+ if (subscription->HasSpecialFilter(
+ filter_type, request_url,
+ DocumentDomain(request_url, frame_hierarchy), sitekey)) {
+ return true;
+ }
+ return SubscriptionContainsSpecialFilter(subscription, filter_type,
+ frame_hierarchy, sitekey);
+}
+
+} // namespace
+
+SubscriptionCollectionImpl::SubscriptionCollectionImpl(
+ std::vector<scoped_refptr<InstalledSubscription>> current_state,
+ const std::string& configuration_name)
+ : subscriptions_(std::move(current_state)),
+ configuration_name_(configuration_name) {}
+
+SubscriptionCollectionImpl::~SubscriptionCollectionImpl() = default;
+SubscriptionCollectionImpl::SubscriptionCollectionImpl(
+ const SubscriptionCollectionImpl&) = default;
+SubscriptionCollectionImpl::SubscriptionCollectionImpl(
+ SubscriptionCollectionImpl&&) = default;
+SubscriptionCollectionImpl& SubscriptionCollectionImpl::operator=(
+ const SubscriptionCollectionImpl&) = default;
+SubscriptionCollectionImpl& SubscriptionCollectionImpl::operator=(
+ SubscriptionCollectionImpl&&) = default;
+
+const std::string& SubscriptionCollectionImpl::GetFilteringConfigurationName()
+ const {
+ return configuration_name_;
+}
+
+absl::optional<GURL> SubscriptionCollectionImpl::FindBySubresourceFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const {
+ const auto subscription = std::find_if(
+ subscriptions_.begin(), subscriptions_.end(),
+ [&](const auto& subscription) {
+ return subscription->HasUrlFilter(
+ request_url, DocumentDomain(request_url, frame_hierarchy),
+ content_type, sitekey, category);
+ });
+ if (subscription != subscriptions_.end()) {
+ return (*subscription)->GetSourceUrl();
+ }
+ return absl::nullopt;
+}
+
+absl::optional<GURL> SubscriptionCollectionImpl::FindByPopupFilter(
+ const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey,
+ FilterCategory category) const {
+ const auto subscription =
+ std::find_if(subscriptions_.begin(), subscriptions_.end(),
+ [&](const auto& subscription) {
+ return subscription->HasPopupFilter(
+ popup_url, DocumentDomain(popup_url, frame_hierarchy),
+ sitekey, category);
+ });
+ if (subscription != subscriptions_.end()) {
+ return (*subscription)->GetSourceUrl();
+ }
+ return absl::nullopt;
+}
+
+absl::optional<GURL> SubscriptionCollectionImpl::FindByAllowFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const {
+ for (const auto& subscription : subscriptions_) {
+ if (HasAllowFilter(subscription, request_url, frame_hierarchy, content_type,
+ sitekey)) {
+ return (*subscription).GetSourceUrl();
+ }
+ }
+ return absl::nullopt;
+}
+
+absl::optional<GURL> SubscriptionCollectionImpl::FindBySpecialFilter(
+ SpecialFilterType filter_type,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const {
+ for (const auto& subscription : subscriptions_) {
+ if (HasSpecialFilter(subscription, filter_type, request_url,
+ frame_hierarchy, sitekey)) {
+ return (*subscription).GetSourceUrl();
+ }
+ }
+ return absl::nullopt;
+}
+
+InstalledSubscription::ContentFiltersData
+SubscriptionCollectionImpl::GetElementHideData(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const {
+ const bool domain_specific = !!FindBySpecialFilter(
+ SpecialFilterType::Generichide, frame_url, frame_hierarchy, sitekey);
+
+ InstalledSubscription::ContentFiltersData combined_selectors;
+ for (const auto& subscription : subscriptions_) {
+ auto selectors = subscription->GetElemhideData(frame_url, domain_specific);
+ std::move(selectors.elemhide_exceptions.begin(),
+ selectors.elemhide_exceptions.end(),
+ std::back_inserter(combined_selectors.elemhide_exceptions));
+ std::move(selectors.elemhide_selectors.begin(),
+ selectors.elemhide_selectors.end(),
+ std::back_inserter(combined_selectors.elemhide_selectors));
+ }
+ return MaybeReduceSelectors(combined_selectors);
+}
+
+InstalledSubscription::ContentFiltersData
+SubscriptionCollectionImpl::GetElementHideEmulationData(
+ const GURL& frame_url) const {
+ InstalledSubscription::ContentFiltersData combined_selectors;
+ for (const auto& subscription : subscriptions_) {
+ auto selectors = subscription->GetElemhideEmulationData(frame_url);
+ std::move(selectors.elemhide_exceptions.begin(),
+ selectors.elemhide_exceptions.end(),
+ std::back_inserter(combined_selectors.elemhide_exceptions));
+ std::move(selectors.elemhide_selectors.begin(),
+ selectors.elemhide_selectors.end(),
+ std::back_inserter(combined_selectors.elemhide_selectors));
+ std::move(selectors.remove_selectors.begin(),
+ selectors.remove_selectors.end(),
+ std::back_inserter(combined_selectors.remove_selectors));
+ std::move(selectors.selectors_to_inline_css.begin(),
+ selectors.selectors_to_inline_css.end(),
+ std::back_inserter(combined_selectors.selectors_to_inline_css));
+ }
+ return MaybeReduceSelectors(combined_selectors);
+}
+
+base::Value::List SubscriptionCollectionImpl::GenerateSnippets(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy) const {
+ base::Value::List snippets;
+ auto document_domain = DocumentDomain(frame_url, frame_hierarchy);
+
+ for (const auto& subscription : subscriptions_) {
+ auto matched = subscription->MatchSnippets(document_domain);
+ for (const auto& snippet : matched) {
+ base::Value::List call;
+ call.Append(base::Value(snippet.command));
+ for (const auto& arg : snippet.arguments) {
+ call.Append(base::Value(arg));
+ }
+ snippets.Append(std::move(call));
+ }
+ }
+
+ return snippets;
+}
+
+std::set<std::string_view> SubscriptionCollectionImpl::GetCspInjections(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const {
+ std::set<std::string_view> blocking_filters{};
+ std::set<std::string_view> allowing_filters{};
+ for (const auto& subscription : subscriptions_) {
+ subscription->FindCspFilters(request_url,
+ DocumentDomain(request_url, frame_hierarchy),
+ FilterCategory::Blocking, blocking_filters);
+ }
+ if (blocking_filters.empty()) {
+ return {};
+ }
+
+ // If blocking filters found, check if can be overruled by allowing filters.
+ for (const auto& subscription : subscriptions_) {
+ // There may exist an allowing rule for this request and its immediate
+ // parent frame. We also check for document-wide allowing filters.
+ if (HasSpecialFilter(subscription, SpecialFilterType::Document, request_url,
+ frame_hierarchy, SiteKey())) {
+ return {};
+ }
+ subscription->FindCspFilters(request_url,
+ DocumentDomain(request_url, frame_hierarchy),
+ FilterCategory::Allowing, allowing_filters);
+ }
+
+ // Remove overruled filters.
+ for (const auto& a_f : allowing_filters) {
+ if (a_f.empty()) {
+ return {};
+ }
+ blocking_filters.erase(a_f);
+ }
+ if (blocking_filters.empty()) {
+ return {};
+ }
+
+ // Last chance to avoid blocking: maybe there is a Genericblock filter and
+ // we should re-search for domain-specific filters only?
+ if (std::ranges::any_of(subscriptions_, [&](const auto& sub) {
+ return HasSpecialFilter(sub, SpecialFilterType::Genericblock,
+ request_url, frame_hierarchy, SiteKey());
+ })) {
+ // This is a relatively rare case - we should have searched for
+ // domain-specific filters only.
+ std::set<std::string_view> domain_specific_blocking{};
+ for (const auto& subscription : subscriptions_) {
+ subscription->FindCspFilters(
+ request_url, DocumentDomain(request_url, frame_hierarchy),
+ FilterCategory::DomainSpecificBlocking, domain_specific_blocking);
+ // There is a domain-specific blocking filter. No point in
+ // searching for a domain-specific allowing filter, since the
+ // previous search for non-specific allowing filters would have found
+ // it.
+ }
+ if (!domain_specific_blocking.empty()) {
+ for (const auto& a_f : allowing_filters) {
+ if (a_f.empty()) {
+ return {};
+ }
+ domain_specific_blocking.erase(a_f);
+ }
+ }
+
+ return domain_specific_blocking;
+ }
+
+ return blocking_filters;
+}
+
+std::set<std::string_view> SubscriptionCollectionImpl::GetRewriteFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ FilterCategory category) const {
+ std::set<std::string_view> result;
+ for (const auto& subscription : subscriptions_) {
+ const auto filters = subscription->FindRewriteFilters(
+ request_url, DocumentDomain(request_url, frame_hierarchy), category);
+ result.insert(filters.begin(), filters.end());
+ }
+ return result;
+}
+
+std::set<HeaderFilterData> SubscriptionCollectionImpl::GetHeaderFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ FilterCategory category) const {
+ std::set<HeaderFilterData> filters{};
+ for (const auto& subscription : subscriptions_) {
+ subscription->FindHeaderFilters(
+ request_url, content_type, DocumentDomain(request_url, frame_hierarchy),
+ category, filters);
+ }
+ return filters;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_collection_impl.h b/components/adblock/core/subscription/subscription_collection_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_collection_impl.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_
+
+#include <string_view>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/subscription_collection.h"
+
+namespace adblock {
+
+class SubscriptionCollectionImpl final : public SubscriptionCollection {
+ public:
+ explicit SubscriptionCollectionImpl(
+ std::vector<scoped_refptr<InstalledSubscription>> current_state,
+ const std::string& configuration_name);
+ ~SubscriptionCollectionImpl() final;
+ SubscriptionCollectionImpl(const SubscriptionCollectionImpl&);
+ SubscriptionCollectionImpl(SubscriptionCollectionImpl&&);
+ SubscriptionCollectionImpl& operator=(const SubscriptionCollectionImpl&);
+ SubscriptionCollectionImpl& operator=(SubscriptionCollectionImpl&&);
+
+ const std::string& GetFilteringConfigurationName() const final;
+
+ absl::optional<GURL> FindBySubresourceFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category) const final;
+
+ absl::optional<GURL> FindByPopupFilter(
+ const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey,
+ FilterCategory category) const final;
+
+ absl::optional<GURL> FindByAllowFilter(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey) const final;
+
+ absl::optional<GURL> FindBySpecialFilter(
+ SpecialFilterType filter_type,
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const final;
+
+ InstalledSubscription::ContentFiltersData GetElementHideData(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey) const final;
+ InstalledSubscription::ContentFiltersData GetElementHideEmulationData(
+ const GURL& frame_url) const final;
+ base::Value::List GenerateSnippets(
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy) const final;
+ std::set<std::string_view> GetCspInjections(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy) const final;
+ std::set<std::string_view> GetRewriteFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ FilterCategory category) const final;
+
+ std::set<HeaderFilterData> GetHeaderFilters(
+ const GURL& request_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ FilterCategory category) const final;
+
+ private:
+ std::vector<scoped_refptr<InstalledSubscription>> subscriptions_;
+ std::string configuration_name_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_
diff --git a/components/adblock/core/subscription/subscription_config.cc b/components/adblock/core/subscription/subscription_config.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_config.cc
@@ -0,0 +1,426 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_config.h"
+
+#include <map>
+
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "net/base/url_util.h"
+
+namespace {
+int g_port_for_testing = 0;
+
+std::string GetHost() {
+ GURL url("https://easylist-downloads.adblockplus.org");
+ if (!g_port_for_testing) {
+ return url.spec();
+ }
+ GURL::Replacements replacements;
+ const std::string port_str = base::NumberToString(g_port_for_testing);
+ replacements.SetPortStr(port_str);
+ return url.ReplaceComponents(replacements).spec();
+}
+} // namespace
+
+namespace adblock {
+
+const GURL& AdblockBaseFilterListUrl() {
+ static GURL kAdblockBaseFilterListUrl(GetHost());
+ return kAdblockBaseFilterListUrl;
+}
+
+const GURL& AcceptableAdsUrl() {
+ static GURL kAcceptableAds(GetHost() + "exceptionrules.txt");
+ return kAcceptableAds;
+}
+
+const GURL& AntiCVUrl() {
+ static GURL kAntiCV(GetHost() + "abp-filters-anti-cv.txt");
+ return kAntiCV;
+}
+
+const GURL& DefaultSubscriptionUrl() {
+ static GURL kEasylistUrl(GetHost() + "easylist.txt");
+ return kEasylistUrl;
+}
+
+const GURL& RecommendedSubscriptionListUrl() {
+ static GURL kRecommendedSubscriptionListUrl(GetHost() +
+ "recommendations.json");
+ return kRecommendedSubscriptionListUrl;
+}
+
+KnownSubscriptionInfo::KnownSubscriptionInfo() = default;
+KnownSubscriptionInfo::~KnownSubscriptionInfo() = default;
+KnownSubscriptionInfo::KnownSubscriptionInfo(const KnownSubscriptionInfo&) =
+ default;
+KnownSubscriptionInfo::KnownSubscriptionInfo(KnownSubscriptionInfo&&) = default;
+KnownSubscriptionInfo& KnownSubscriptionInfo::operator=(
+ const KnownSubscriptionInfo&) = default;
+KnownSubscriptionInfo& KnownSubscriptionInfo::operator=(
+ KnownSubscriptionInfo&&) = default;
+
+KnownSubscriptionInfo::KnownSubscriptionInfo(
+ GURL url_param,
+ std::string title_param,
+ std::vector<std::string> languages_param,
+ SubscriptionUiVisibility ui_visibility_param,
+ SubscriptionFirstRunBehavior first_run_param,
+ SubscriptionPrivilegedFilterStatus privileged_status_param)
+ : url(url_param),
+ title(title_param),
+ languages(languages_param),
+ ui_visibility(ui_visibility_param),
+ first_run(first_run_param),
+ privileged_status(privileged_status_param) {}
+
+const std::vector<KnownSubscriptionInfo>& config::GetKnownSubscriptions() {
+ // The current list of subscriptions can be downloaded from:
+ // https://gitlab.com/eyeo/filterlists/subscriptionlist/-/archive/master/subscriptionlist-master.tar.gz
+ // The archive contains files with subscription definitions. The ones we're
+ // interested in are ones that declare a [recommendation] keyword in either
+ // the "list" or "variant" key.
+ // Here's a script that parses the archive into a subscriptions.json:
+ // https://gitlab.com/eyeo/adblockplus/abc/adblockpluscore/-/blob/next/build/updateSubscriptions.js
+ // The list isn't updated very often. If it starts to become a burden to
+ // align the C++ representation, better to update it manually because it also
+ // contains visibility and first run behavior options.
+ static std::vector<KnownSubscriptionInfo> recommendations = {
+ {DefaultSubscriptionUrl(),
+ "EasyList",
+ {"en"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Subscribe,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "abpindo.txt"),
+ "ABPindo",
+ {"id", "ms"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "abpvn.txt"),
+ "ABPVN List",
+ {"vi"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "bulgarian_list.txt"),
+ "Bulgarian list",
+ {"bg"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "dandelion_sprouts_nordic_filters.txt"),
+ "Dandelion Sprout's Nordic Filters",
+ {"no", "nb", "nn", "da", "is", "fo", "kl"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistchina.txt"),
+ "EasyList China",
+ {"zh"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistczechslovak.txt"),
+ "EasyList Czech and Slovak",
+ {"cs", "sk"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistdutch.txt"),
+ "EasyList Dutch",
+ {"nl"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistgermany.txt"),
+ "EasyList Germany",
+ {"de"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "israellist.txt"),
+ "EasyList Hebrew",
+ {"he"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "hufilter.txt"),
+ "EasyList Hungarian",
+ {"hu"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistitaly.txt"),
+ "EasyList Italy",
+ {"it"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistlithuania.txt"),
+ "EasyList Lithuania",
+ {"lt"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistpolish.txt"),
+ "EasyList Polish",
+ {"pl"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistportuguese.txt"),
+ "EasyList Portuguese",
+ {"pt"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easylistspanish.txt"),
+ "EasyList Spanish",
+ {"es"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "global-filters.txt"),
+ "Global Filters",
+ {"th", "el", "sl", "hr", "sr", "bs", "fil"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "indianlist.txt"),
+ "IndianList",
+ {"bn", "gu", "hi", "pa", "as", "mr", "ml", "te", "kn", "or", "ne", "si",
+ "ta", "mai"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "japanese-filters.txt"),
+ "Japanese Filters",
+ {"ja"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "koreanlist.txt"),
+ "KoreanList",
+ {"ko"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "latvianlist.txt"),
+ "Latvian List",
+ {"lv"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "liste_ar.txt"),
+ "Liste AR",
+ {"ar"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "liste_fr.txt"),
+ "Liste FR",
+ {"ar", "fr"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "rolist.txt"),
+ "ROList",
+ {"ro"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "ruadlist.txt"),
+ "RuAdList",
+ {"ru", "uk", "uz", "kk"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "turkish-filters.txt"),
+ "Turkish Filters",
+ {"tr"},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {AcceptableAdsUrl(),
+ "Acceptable Ads",
+ {},
+ SubscriptionUiVisibility::Invisible,
+ SubscriptionFirstRunBehavior::Subscribe,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {AntiCVUrl(),
+ "ABP filters",
+ {},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Subscribe,
+ SubscriptionPrivilegedFilterStatus::Allowed},
+ {GURL(GetHost() + "i_dont_care_about_cookies.txt"),
+ "I don't care about cookies",
+ {},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + ""
+ "fanboy-notifications.txt"),
+ "Fanboy's Notifications Blocking List",
+ {},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "easyprivacy.txt"),
+ "EasyPrivacy",
+ {},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {GURL(GetHost() + "fanboy-social.txt"),
+ "Fanboy's Social Blocking List",
+ {},
+ SubscriptionUiVisibility::Visible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Forbidden},
+ {CustomFiltersUrl(),
+ "User filters",
+ {},
+ SubscriptionUiVisibility::Invisible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Allowed},
+ {TestPagesSubscriptionUrl(),
+ "ABP Test filters",
+ {},
+ SubscriptionUiVisibility::Invisible,
+ SubscriptionFirstRunBehavior::Ignore,
+ SubscriptionPrivilegedFilterStatus::Allowed}
+
+ // You can customize subscriptions available on first run and in settings
+ // here. Items are displayed in settings in order declared here. See
+ // components/adblock/docs/integration-how-to.md, section 'How to change
+ // the default filter lists?'. For example:
+
+ // clang-format off
+ /*
+ {"https://domain.com/subscription.txt", // URL
+ "My Custom Filters", // Display name for settings
+ {}, // Supported languages list, considered for
+ // SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch
+ SubscriptionUiVisibility::Visible, // Should the app show a subscription in the settings
+ SubscriptionFirstRunBehavior::Subscribe, // Should the app subscribe on first run
+ SubscriptionPrivilegedFilterStatus::Forbidden // Allow or forbid snippets and header filters
+ },
+ */
+ // clang-format on
+
+ };
+
+ return recommendations;
+}
+
+bool config::AllowPrivilegedFilters(const GURL& url) {
+#if !defined(NDEBUG)
+ // Enable priviliged filters from locally hosted filter lists in debug builds.
+ // e.g. locally delployed testpages.
+ if (net::IsLocalhost(url)) {
+ return true;
+ }
+#endif
+
+ for (const auto& cur : GetKnownSubscriptions()) {
+ if (cur.url == url) {
+ return cur.privileged_status ==
+ SubscriptionPrivilegedFilterStatus::Allowed;
+ }
+ }
+
+ return false;
+}
+
+const std::vector<PreloadedSubscriptionInfo>&
+config::GetPreloadedSubscriptionConfiguration() {
+ static const std::vector<PreloadedSubscriptionInfo> preloaded_subscriptions =
+ {{"*easylist.txt", IDR_ADBLOCK_FLATBUFFER_EASYLIST},
+ {"*exceptionrules.txt", IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES},
+ {"*abp-filters-anti-cv.txt", IDR_ADBLOCK_FLATBUFFER_ANTICV}};
+ return preloaded_subscriptions;
+}
+
+void SetFilterListServerPortForTesting(int port_for_testing) {
+ g_port_for_testing = port_for_testing;
+}
+
+const std::vector<std::string_view>& config::MaybeSplitCombinedAdblockList(
+ const GURL& filter_list) {
+ static std::vector<std::string_view> EMPTY_VALUE;
+ static std::string_view VALUE_EASYLIST_EN = "easylist.txt";
+ static std::map<std::string_view, std::vector<std::string_view>>
+ filter_lists_map = {
+ {"abpindo+easylist.txt", {"abpindo.txt", VALUE_EASYLIST_EN}},
+ {"abpvn+easylist", {"abpvn.txt", VALUE_EASYLIST_EN}},
+ {"bulgarian_list+easylist.",
+ {"bulgarian_list.txt", VALUE_EASYLIST_EN}},
+ {"dandelion_sprouts_nordic_filters+easylist.txt",
+ {"dandelion_sprouts_nordic_filters.txt", VALUE_EASYLIST_EN}},
+ {"easylistchina+easylist.txt",
+ {"easylistchina.txt", VALUE_EASYLIST_EN}},
+ {"easylistczechslovak+easylist.txt",
+ {"easylistczechslovak.txt", VALUE_EASYLIST_EN}},
+ {"easylistdutch+easylist.txt",
+ {"easylistdutch.txt", VALUE_EASYLIST_EN}},
+ {"easylistgermany+easylist.txt",
+ {"easylistgermany.txt", VALUE_EASYLIST_EN}},
+ {"israellist+easylist.txt", {"israellist.txt", VALUE_EASYLIST_EN}},
+ {"hufilter+easylist.txt", {"hufilter.txt", VALUE_EASYLIST_EN}},
+ {"easylistitaly+easylist.txt",
+ {"easylistitaly.txt", VALUE_EASYLIST_EN}},
+ {"easylistlithuania+easylist.txt",
+ {"easylistlithuania.txt", VALUE_EASYLIST_EN}},
+ {"easylistpolish+easylist.txt",
+ {"easylistpolish.txt", VALUE_EASYLIST_EN}},
+ {"easylistportuguese+easylist.txt",
+ {"easylistportuguese.txt", VALUE_EASYLIST_EN}},
+ {"easylistspanish+easylist.txt",
+ {"easylistspanish.txt", VALUE_EASYLIST_EN}},
+ {"global-filters+easylist.txt",
+ {"global-filters.txt", VALUE_EASYLIST_EN}},
+ {"indianlist+easylist.txt", {"indianlist.txt", VALUE_EASYLIST_EN}},
+ {"japanese+easylist.txt", {"japanese.txt", VALUE_EASYLIST_EN}},
+ {"koreanlist+easylist.txt", {"koreanlist.txt", VALUE_EASYLIST_EN}},
+ {"latvianlist+easylist.txt", {"latvianlist.txt", VALUE_EASYLIST_EN}},
+ {"liste_ar+liste_fr+easylist.txt",
+ {"liste_ar.txt", "liste_fr.txt", VALUE_EASYLIST_EN}},
+ {"liste_fr+easylist.txt", {"liste_fr.txt", VALUE_EASYLIST_EN}},
+ {"rolist+easylist.txt", {"rolist.txt", VALUE_EASYLIST_EN}},
+ {"ruadlist+easylist.txt", {"ruadlist.txt", VALUE_EASYLIST_EN}},
+ {"turkish+easylist.txt", {"turkish.txt", VALUE_EASYLIST_EN}},
+ };
+ if (filter_list.host() != AdblockBaseFilterListUrl().host()) {
+ // This method works only for ad block maintained lists.
+ return EMPTY_VALUE;
+ }
+ auto path =
+ base::TrimString(filter_list.path_piece(), "/", base::TRIM_LEADING);
+ if (filter_lists_map.find(path) == filter_lists_map.end()) {
+ return EMPTY_VALUE;
+ }
+ return filter_lists_map[path];
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_config.h b/components/adblock/core/subscription/subscription_config.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_config.h
@@ -0,0 +1,131 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_
+
+#include <string_view>
+#include <vector>
+
+#include "url/gurl.h"
+
+namespace adblock {
+
+const GURL& AdblockBaseFilterListUrl();
+const GURL& AcceptableAdsUrl();
+const GURL& AntiCVUrl();
+const GURL& DefaultSubscriptionUrl();
+const GURL& RecommendedSubscriptionListUrl();
+
+// Sets the port used by the embedded http server required for browser tests.
+// Must be called before the first call to GetKnownSubscriptions().
+void SetFilterListServerPortForTesting(int port_for_testing);
+
+enum class SubscriptionUiVisibility { Visible, Invisible };
+
+enum class SubscriptionFirstRunBehavior {
+ // Download and install as soon as possible.
+ Subscribe,
+ // Download and install as soon as possible but only if the device's region
+ // matches one of the |languages| defined in KnownSubscriptionInfo.
+ SubscribeIfLocaleMatch,
+ // Do not install automatically.
+ Ignore
+};
+
+// Privileged filters include:
+// - Snippet filters
+// - Header filters
+enum class SubscriptionPrivilegedFilterStatus { Allowed, Forbidden };
+
+// Description of a subscription that's known to exist in the Internet.
+// Can be used to populate a list of proposed or recommended subscriptions in
+// UI.
+struct KnownSubscriptionInfo {
+ KnownSubscriptionInfo();
+ KnownSubscriptionInfo(GURL url,
+ std::string title,
+ std::vector<std::string> languages,
+ SubscriptionUiVisibility ui_visibility,
+ SubscriptionFirstRunBehavior first_run,
+ SubscriptionPrivilegedFilterStatus privileged_status);
+ ~KnownSubscriptionInfo();
+ KnownSubscriptionInfo(const KnownSubscriptionInfo&);
+ KnownSubscriptionInfo(KnownSubscriptionInfo&&);
+ KnownSubscriptionInfo& operator=(const KnownSubscriptionInfo&);
+ KnownSubscriptionInfo& operator=(KnownSubscriptionInfo&&);
+
+ GURL url;
+ std::string title;
+ std::vector<std::string> languages;
+ SubscriptionUiVisibility ui_visibility = SubscriptionUiVisibility::Visible;
+ SubscriptionFirstRunBehavior first_run =
+ SubscriptionFirstRunBehavior::Subscribe;
+ SubscriptionPrivilegedFilterStatus privileged_status =
+ SubscriptionPrivilegedFilterStatus::Forbidden;
+};
+
+// Describes an available preloaded subscription that will be used to provide
+// some level of ad-filtering while a desired subscription is being downloaded
+// from the Internet.
+// Preloaded subscriptions are bundled with the browser and stored in the
+// ResourceBundle. They might be out-of-date and have reduced quality, but they
+// allow some level of ad-filtering immediately upon first start.
+struct PreloadedSubscriptionInfo {
+ // Wildcard-aware pattern that matches subscription URL. Examples:
+ // "*easylist.txt" (will match URLs like
+ // https://easylist-downloads.adblockplus.org/easylist.txt or
+ // https://easylist-downloads.adblockplus.org/easylistchina+easylist.txt).
+ // This preloaded subscription will be used as a substitute for a
+ // subscription with a URL that matches |url_pattern|.
+ std::string_view url_pattern;
+
+ // Resource ID containing the binary flatbuffer data that defines this
+ // preloaded subscription. Examples:
+ // IDR_ADBLOCK_FLATBUFFER_EASYLIST
+ int flatbuffer_resource_id;
+};
+
+namespace config {
+
+// Returns the list of all known subscriptions. This list is static
+// and may change with browser updates, but not with filter list updates.
+// The list contains recommendations for all languages.
+const std::vector<KnownSubscriptionInfo>& GetKnownSubscriptions();
+
+// Returns whether a subscription from |url| is allowed to provide
+// privileged filters.
+bool AllowPrivilegedFilters(const GURL& url);
+
+// Returns the configuration of available preloaded subscriptions. Used by
+// PreloadedSubscriptionProvider.
+const std::vector<PreloadedSubscriptionInfo>&
+GetPreloadedSubscriptionConfiguration();
+
+// Check if |filter_list| is a combined easylist english plus locale specific
+// easylist (f.e. "abpindo+easylist.txt") and if so find and return a mapping to
+// a vector of standalone filter list paths (f.e. {"abpindo.txt",
+// "easylist.txt"}). This method works only for |filter_list| hosted at
+// easylist-downloads.adblockplus.org.
+const std::vector<std::string_view>& MaybeSplitCombinedAdblockList(
+ const GURL& filter_list);
+
+} // namespace config
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_
diff --git a/components/adblock/core/subscription/subscription_downloader.h b/components/adblock/core/subscription/subscription_downloader.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_downloader.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_
+
+#include <vector>
+
+#include "base/functional/callback_forward.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/net/adblock_resource_request.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Downloads filter lists from the Internet and converts them into flatbuffers.
+// See also: AdblockRewourceRequest for more details about allowing and
+// retrying downloads.
+class SubscriptionDownloader {
+ public:
+ using DownloadCompletedCallback =
+ base::OnceCallback<void(std::unique_ptr<FlatbufferData>)>;
+
+ // For head requests we only need the parsed version as result
+ using HeadRequestCallback = base::OnceCallback<void(const std::string)>;
+ virtual ~SubscriptionDownloader() = default;
+ // Starts downlading |subscription_url|. |on_finished| will be called with
+ // the converted flatbuffer. |retry_policy| controls failure-handling
+ // behavior. If downloading is disallowed due to current network state, it is
+ // deferred until conditions allow it.
+ virtual void StartDownload(const GURL& subscription_url,
+ AdblockResourceRequest::RetryPolicy retry_policy,
+ DownloadCompletedCallback on_finished) = 0;
+ // Cancels ongoing downloads for matching |url|, including retry attempts or
+ // downloads deferred due to network conditions.
+ virtual void CancelDownload(const GURL& subscription_url) = 0;
+ // Triggers head request on |subscription_url|. |on_finished| will be called
+ // with the parsed date from response headers, or with the empty string if
+ // the request was not successful.
+ virtual void DoHeadRequest(const GURL& subscription_url,
+ HeadRequestCallback on_finished) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_
diff --git a/components/adblock/core/subscription/subscription_downloader_impl.cc b/components/adblock/core/subscription/subscription_downloader_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_downloader_impl.cc
@@ -0,0 +1,279 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_downloader_impl.h"
+
+#include <algorithm>
+#include <fstream>
+#include <functional>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/strings/escape.h"
+#include "base/strings/safe_sprintf.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "net/base/url_util.h"
+#include "net/http/http_response_headers.h"
+
+namespace adblock {
+namespace {
+
+// To retain user anonymity, we clamp the download count sent to subscription
+// servers - any number higher than 4 is reported as just "4+".
+std::string GetClampedDownloadCount(int download_count) {
+ DCHECK_GE(download_count, 0);
+ if (download_count > 4) {
+ return "4+";
+ }
+ return base::NumberToString(download_count);
+}
+
+std::string BuildSubscriptionQueryParams(
+ const GURL& subscription_url,
+ const SubscriptionPersistentMetadata* persistent_metadata,
+ const bool is_disabled) {
+ return base::StrCat(
+ {"lastVersion=",
+ base::EscapeQueryParamValue(
+ persistent_metadata->GetVersion(subscription_url), true),
+ "&disabled=", is_disabled ? "true" : "false", "&downloadCount=",
+ GetClampedDownloadCount(
+ persistent_metadata->GetDownloadSuccessCount(subscription_url)),
+ "&safe=true"});
+}
+
+int GenerateTraceId(const GURL& subscription_url) {
+ return std::hash<std::string>{}(subscription_url.spec());
+}
+
+// converts |date| into abp version format ex: 202107210821
+// in UTC format as necessary for server
+std::string ConvertBaseTimeToABPFilterVersionFormat(const base::Time& date) {
+ base::Time::Exploded exploded;
+ // we receive in GMT and convert to UTC ( which has the same time )
+ date.UTCExplode(&exploded);
+ char buff[16];
+ base::strings::SafeSPrintf(buff, "%04d%02d%02d%02d%02d", exploded.year,
+ exploded.month, exploded.day_of_month,
+ exploded.hour, exploded.minute);
+ return std::string(buff);
+}
+
+} // namespace
+
+SubscriptionDownloaderImpl::SubscriptionDownloaderImpl(
+ SubscriptionRequestMaker request_maker,
+ ConversionExecutors* conversion_executor,
+ SubscriptionPersistentMetadata* persistent_metadata)
+ : request_maker_(std::move(request_maker)),
+ conversion_executor_(conversion_executor),
+ persistent_metadata_(persistent_metadata) {}
+
+SubscriptionDownloaderImpl::~SubscriptionDownloaderImpl() = default;
+
+void SubscriptionDownloaderImpl::StartDownload(
+ const GURL& subscription_url,
+ AdblockResourceRequest::RetryPolicy retry_policy,
+ DownloadCompletedCallback on_finished) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!IsUrlAllowed(subscription_url)) {
+ LOG(WARNING) << "[eyeo] Download from URL not allowed, will not download "
+ << subscription_url;
+ std::move(on_finished).Run(nullptr);
+ return;
+ }
+
+ ongoing_downloads_[subscription_url] =
+ OngoingDownload{request_maker_.Run(), std::move(on_finished)};
+ std::get<ResourceRequestPtr>(ongoing_downloads_[subscription_url])
+ ->Start(
+ subscription_url, AdblockResourceRequest::Method::GET,
+ base::BindRepeating(&SubscriptionDownloaderImpl::OnDownloadFinished,
+ weak_ptr_factory_.GetWeakPtr()),
+ retry_policy,
+ BuildSubscriptionQueryParams(subscription_url, persistent_metadata_,
+ false));
+}
+
+void SubscriptionDownloaderImpl::CancelDownload(const GURL& subscription_url) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ ongoing_downloads_.erase(subscription_url);
+}
+
+void SubscriptionDownloaderImpl::DoHeadRequest(
+ const GURL& subscription_url,
+ HeadRequestCallback on_finished) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (ongoing_ping_) {
+ std::move(std::get<HeadRequestCallback>(*ongoing_ping_)).Run("");
+ }
+
+ ongoing_ping_ = HeadRequest{request_maker_.Run(), std::move(on_finished)};
+ std::get<ResourceRequestPtr>(*ongoing_ping_)
+ ->Start(subscription_url, AdblockResourceRequest::Method::HEAD,
+ base::BindRepeating(
+ &SubscriptionDownloaderImpl::OnHeadersOnlyDownloaded,
+ weak_ptr_factory_.GetWeakPtr()),
+ // A ping may fail. We don't retry, SubscriptionUpdater will send
+ // a new ping in an hour anyway.
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ BuildSubscriptionQueryParams(subscription_url,
+ persistent_metadata_, true));
+}
+
+bool SubscriptionDownloaderImpl::IsUrlAllowed(
+ const GURL& subscription_url) const {
+ if (net::IsLocalhost(subscription_url)) {
+ // We trust all localhost urls, regardless of scheme.
+ return true;
+ }
+ if (!subscription_url.SchemeIs("https") &&
+ !subscription_url.SchemeIs("data")) {
+ return false;
+ }
+ return true;
+}
+
+void SubscriptionDownloaderImpl::OnHeadersOnlyDownloaded(
+ const GURL&,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(ongoing_ping_.has_value());
+
+ std::string version("");
+
+ // Parse date or Date from response headers.
+ if (headers) {
+ if (auto date = headers->GetDateValue()) {
+ version = ConvertBaseTimeToABPFilterVersionFormat(date.value());
+ }
+ }
+
+ std::move(std::get<HeadRequestCallback>(*ongoing_ping_))
+ .Run(std::move(version));
+ ongoing_ping_.reset();
+}
+
+void SubscriptionDownloaderImpl::OnDownloadFinished(
+ const GURL& subscription_url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ auto download_it = ongoing_downloads_.find(subscription_url);
+ DCHECK(download_it != ongoing_downloads_.end());
+
+ if (downloaded_file.empty()) {
+ persistent_metadata_->IncrementDownloadErrorCount(subscription_url);
+ DLOG(WARNING) << "[eyeo] Failed to retrieve content for "
+ << subscription_url << ", will abort";
+ std::move(std::get<DownloadCompletedCallback>(download_it->second))
+ .Run(nullptr);
+ ongoing_downloads_.erase(download_it);
+ return;
+ }
+
+ VLOG(1) << "[eyeo] Finished downloading " << subscription_url
+ << ", starting conversion";
+
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
+ "eyeo", "Converting subscription",
+ TRACE_ID_LOCAL(GenerateTraceId(subscription_url)), "url",
+ subscription_url.spec());
+
+ conversion_executor_->ConvertFilterListFile(
+ subscription_url, downloaded_file,
+ base::BindOnce(&SubscriptionDownloaderImpl::OnConversionFinished,
+ weak_ptr_factory_.GetWeakPtr(), subscription_url));
+}
+
+void SubscriptionDownloaderImpl::OnConversionFinished(
+ const GURL& subscription_url,
+ ConversionResult converter_result) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT_NESTABLE_ASYNC_END0(
+ "eyeo", "Converting subscription",
+ TRACE_ID_LOCAL(GenerateTraceId(subscription_url)));
+ const auto download_it = ongoing_downloads_.find(subscription_url);
+ if (download_it == ongoing_downloads_.end()) {
+ VLOG(1) << "[eyeo] Conversion result discarded, subscription download "
+ "was cancelled.";
+ return;
+ }
+
+ if (absl::holds_alternative<std::unique_ptr<FlatbufferData>>(
+ converter_result)) {
+ VLOG(1) << "[eyeo] Finished converting " << subscription_url
+ << " successfully";
+ std::move(std::get<DownloadCompletedCallback>(download_it->second))
+ .Run(std::move(
+ absl::get<std::unique_ptr<FlatbufferData>>(converter_result)));
+ ongoing_downloads_.erase(download_it);
+ } else if (absl::holds_alternative<GURL>(converter_result)) {
+ const GURL& redirect_url = absl::get<GURL>(converter_result);
+ if (!IsUrlAllowed(redirect_url)) {
+ AbortWithWarning(download_it, "Redirect URL not allowed.");
+ return;
+ }
+ if (redirect_url == subscription_url) {
+ AbortWithWarning(download_it,
+ "Redirect to the same URL is not permitted.");
+ return;
+ }
+ if (std::get<ResourceRequestPtr>(download_it->second)
+ ->GetNumberOfRedirects() >= kMaxNumberOfRedirects) {
+ AbortWithWarning(download_it, "Maximum number of redirects exceeded.");
+ } else {
+ auto ongoing_download = ongoing_downloads_.extract(download_it);
+ ongoing_download.key() = redirect_url;
+ auto redirected_download_it =
+ ongoing_downloads_.insert(std::move(ongoing_download)).position;
+ std::get<ResourceRequestPtr>(redirected_download_it->second)
+ ->Redirect(redirect_url,
+ BuildSubscriptionQueryParams(redirect_url,
+ persistent_metadata_, false));
+ }
+ } else {
+ persistent_metadata_->IncrementDownloadErrorCount(subscription_url);
+ AbortWithWarning(download_it,
+ *absl::get<ConversionError>(converter_result));
+ return;
+ }
+}
+
+void SubscriptionDownloaderImpl::AbortWithWarning(
+ const OngoingDownloadsIt ongoing_download_it,
+ const std::string& warning) {
+ if (ongoing_download_it == ongoing_downloads_.end()) {
+ return;
+ }
+ DLOG(WARNING) << "[eyeo] " << warning << " Aborting download of "
+ << ongoing_download_it->first;
+ std::move(std::get<DownloadCompletedCallback>(ongoing_download_it->second))
+ .Run(nullptr);
+ ongoing_downloads_.erase(ongoing_download_it);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_downloader_impl.h b/components/adblock/core/subscription/subscription_downloader_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_downloader_impl.h
@@ -0,0 +1,97 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include "absl/types/optional.h"
+#include "base/files/file_path.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/core/net/adblock_resource_request.h"
+#include "components/adblock/core/subscription/conversion_executors.h"
+#include "components/adblock/core/subscription/subscription_downloader.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+
+class PrefService;
+
+namespace adblock {
+
+class SubscriptionDownloaderImpl final : public SubscriptionDownloader {
+ public:
+ // Used to create AdblockResourceRequest to implement concurrent HEAD and
+ // GET requests for subscriptions.
+ using SubscriptionRequestMaker =
+ base::RepeatingCallback<std::unique_ptr<AdblockResourceRequest>()>;
+
+ SubscriptionDownloaderImpl(
+ SubscriptionRequestMaker request_maker,
+ ConversionExecutors* conversion_executor,
+ SubscriptionPersistentMetadata* persistent_metadata);
+ ~SubscriptionDownloaderImpl() final;
+ void StartDownload(const GURL& subscription_url,
+ AdblockResourceRequest::RetryPolicy retry_policy,
+ DownloadCompletedCallback on_finished) final;
+ void CancelDownload(const GURL& subscription_url) final;
+ void DoHeadRequest(const GURL& subscription_url,
+ HeadRequestCallback on_finished) final;
+
+ static constexpr int kMaxNumberOfRedirects = 5;
+
+ private:
+ using ResourceRequestPtr = std::unique_ptr<AdblockResourceRequest>;
+ // Represents subscription downloads in progress.
+ using OngoingDownload =
+ std::tuple<ResourceRequestPtr, DownloadCompletedCallback>;
+ using OngoingDownloads = std::map<GURL, OngoingDownload>;
+ using OngoingDownloadsIt = OngoingDownloads::iterator;
+ // There's never more than one concurrent HEAD request - for the
+ // Acceptable Ads subscription, a special case in user counting. This will
+ // be replaced by a dedicated solution for user counting (Telemetry)
+ // eventually.
+ using HeadRequest = std::tuple<ResourceRequestPtr, HeadRequestCallback>;
+
+ bool IsUrlAllowed(const GURL& subscription_url) const;
+ bool IsConnectionAllowed() const;
+ void OnDownloadFinished(const GURL& subscription_url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers);
+ void OnHeadersOnlyDownloaded(const GURL& subscription_url,
+ base::FilePath downloaded_file,
+ scoped_refptr<net::HttpResponseHeaders> headers);
+ void OnConversionFinished(const GURL& subscription_url,
+ ConversionResult converter_result);
+ void AbortWithWarning(const OngoingDownloadsIt ongoing_download_it,
+ const std::string& warning);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ SubscriptionRequestMaker request_maker_;
+ raw_ptr<ConversionExecutors> conversion_executor_;
+ raw_ptr<SubscriptionPersistentMetadata> persistent_metadata_;
+ OngoingDownloads ongoing_downloads_;
+ absl::optional<HeadRequest> ongoing_ping_;
+ base::WeakPtrFactory<SubscriptionDownloaderImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_
diff --git a/components/adblock/core/subscription/subscription_persistent_metadata.h b/components/adblock/core/subscription/subscription_persistent_metadata.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_metadata.h
@@ -0,0 +1,99 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Persistently stores metadata about Subscriptions.
+// Metadata is data about Subscriptions that may not be encoded in the
+// Subscriptions themselves, like number of errors encountered while
+// downloading.
+// Subscription metadata is used to control subscription update behavior and
+// provide data for GET/HEAD query parameters.
+class SubscriptionPersistentMetadata : public KeyedService {
+ public:
+ // Sets the expiration date to Now() + |expires_in|.
+ // Expiration time can be:
+ // - Parsed from the filter list (see Subscription::GetTimeUntilExpires())
+ // - Set to 5 days by default, if expiration time is not specified in filter
+ // list
+ // - Set to 1 day by default for the special case of HEAD-only request.
+ virtual void SetExpirationInterval(const GURL& subscription_url,
+ base::TimeDelta expires_in) = 0;
+ // Sets the last installation time to Now().
+ virtual void SetLastInstallationTime(const GURL& subscription_url) = 0;
+ // The version of a subscription can be:
+ // - parsed from the filter list (see Subscription::GetCurrentVersion())
+ // - for HEAD requests, created by parsing the received "Date" header.
+ // - not set
+ // It is common for custom subscriptions to not have a version available.
+ virtual void SetVersion(const GURL& subscription_url,
+ std::string version) = 0;
+ // Increments the total number of successful downloads.
+ // Successful HEAD-only requests, which don't deliver an actual subscription,
+ // still count towards this number.
+ // Resets the download error count to 0, as it breaks the error streak.
+ virtual void IncrementDownloadSuccessCount(const GURL& subscription_url) = 0;
+ // Increments the number of consecutive download failures, used to determine
+ // whether to fall back to an alternate download URL.
+ // Incrementing the error count does *not* influence the success count.
+ virtual void IncrementDownloadErrorCount(const GURL& subscription_url) = 0;
+ // Returns whether the expiration time (see SetExpirationInterval()) is
+ // earlier than Now().
+ // A subscription for which SetExpirationInterval() was never called is
+ // considered expired, as otherwise it would never be selected for updating.
+ virtual bool IsExpired(const GURL& subscription_url) const = 0;
+ // Returns time of the last installation set by SetLastInstallationTime().
+ virtual base::Time GetLastInstallationTime(
+ const GURL& subscription_url) const = 0;
+ // Returns version set in SetVersion() or "0" when not set.
+ // Subscriptions are allowed to not have a version defined.
+ virtual std::string GetVersion(const GURL& subscription_url) const = 0;
+ // Returns the number of successful downloads of this subscription in the
+ // past.
+ virtual int GetDownloadSuccessCount(const GURL& subscription_url) const = 0;
+ // Returns number of consecutive download errors.
+ virtual int GetDownloadErrorCount(const GURL& subscription_url) const = 0;
+ // Mark the subscription as auto installed. Auto installed subscriptions have
+ // a secondary expiration time, triggering the removal of the subscription if
+ // it expires.
+ virtual void SetAutoInstalledExpirationInterval(
+ const GURL& subscription_url,
+ base::TimeDelta expires_in) = 0;
+ // Returns whether the subscription is auto installed.
+ virtual bool IsAutoInstalled(const GURL& subscription_url) const = 0;
+ // Returns whether the auto installed expiration time (see
+ // SetAutoInstalledExpirationInterval()) is earlier than Now(). A subscription
+ // for which SetAutoInstalledExpirationInterval() was never called is not
+ // considered expired since that would trigger the removal of the subscription
+ virtual bool IsAutoInstalledExpired(const GURL& subscription_url) const = 0;
+
+ // Remove metadata associated with |subscription_url|.
+ virtual void RemoveMetadata(const GURL& subscription_url) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_
diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc
@@ -0,0 +1,224 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h"
+
+#include <string_view>
+
+#include "absl/types/optional.h"
+#include "base/json/values_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/prefs/scoped_user_pref_update.h"
+
+namespace adblock {
+namespace {
+constexpr std::string_view kExpirationTimeKey = "expiration_time";
+constexpr std::string_view kLastInstallationTimeKey = "last_installation_time";
+constexpr std::string_view kVersionKey = "version";
+constexpr std::string_view kDownloadCountKey = "download_count";
+constexpr std::string_view kErrorCountKey = "error_count";
+constexpr std::string_view kAutoInstalledExpirationTimeKey =
+ "auto_installed_expiration_time";
+} // namespace
+
+struct SubscriptionPersistentMetadataImpl::Metadata {
+ base::Time expiration_time;
+ base::Time last_installation_time;
+ std::string version{"0"};
+ int download_count{0};
+ int error_count{0};
+ absl::optional<base::Time> auto_installed_expiration_time{};
+};
+
+SubscriptionPersistentMetadataImpl::SubscriptionPersistentMetadataImpl(
+ PrefService* prefs)
+ : prefs_(prefs) {
+ LoadFromPrefs();
+}
+
+SubscriptionPersistentMetadataImpl::~SubscriptionPersistentMetadataImpl() =
+ default;
+
+void SubscriptionPersistentMetadataImpl::SetExpirationInterval(
+ const GURL& subscription_url,
+ base::TimeDelta expires_in) {
+ const auto now = base::Time::Now();
+ metadata_map_[subscription_url].expiration_time = now + expires_in;
+ UpdatePrefs();
+}
+
+void SubscriptionPersistentMetadataImpl::SetLastInstallationTime(
+ const GURL& subscription_url) {
+ metadata_map_[subscription_url].last_installation_time = base::Time::Now();
+ UpdatePrefs();
+}
+
+void SubscriptionPersistentMetadataImpl::SetVersion(
+ const GURL& subscription_url,
+ std::string version) {
+ metadata_map_[subscription_url].version = std::move(version);
+ UpdatePrefs();
+}
+
+void SubscriptionPersistentMetadataImpl::IncrementDownloadSuccessCount(
+ const GURL& subscription_url) {
+ metadata_map_[subscription_url].download_count++;
+ metadata_map_[subscription_url].error_count = 0;
+ UpdatePrefs();
+}
+
+void SubscriptionPersistentMetadataImpl::IncrementDownloadErrorCount(
+ const GURL& subscription_url) {
+ metadata_map_[subscription_url].error_count++;
+ UpdatePrefs();
+}
+
+bool SubscriptionPersistentMetadataImpl::IsExpired(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return true;
+ }
+ return it->second.expiration_time <= base::Time::Now();
+}
+
+base::Time SubscriptionPersistentMetadataImpl::GetLastInstallationTime(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return base::Time();
+ }
+ return it->second.last_installation_time;
+}
+
+std::string SubscriptionPersistentMetadataImpl::GetVersion(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return "0";
+ }
+ return it->second.version;
+}
+
+int SubscriptionPersistentMetadataImpl::GetDownloadSuccessCount(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return 0;
+ }
+ return it->second.download_count;
+}
+
+int SubscriptionPersistentMetadataImpl::GetDownloadErrorCount(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return 0;
+ }
+ return it->second.error_count;
+}
+
+void SubscriptionPersistentMetadataImpl::SetAutoInstalledExpirationInterval(
+ const GURL& subscription_url,
+ base::TimeDelta expires_in) {
+ const auto now = base::Time::Now();
+ metadata_map_[subscription_url].auto_installed_expiration_time =
+ now + expires_in;
+ UpdatePrefs();
+}
+
+bool SubscriptionPersistentMetadataImpl::IsAutoInstalled(
+ const GURL& subscription_url) const {
+ if (!metadata_map_.count(subscription_url)) {
+ return false;
+ }
+
+ return metadata_map_.at(subscription_url)
+ .auto_installed_expiration_time.has_value();
+}
+
+bool SubscriptionPersistentMetadataImpl::IsAutoInstalledExpired(
+ const GURL& subscription_url) const {
+ auto it = metadata_map_.find(subscription_url);
+ if (it == metadata_map_.end()) {
+ return false;
+ }
+ if (it->second.auto_installed_expiration_time.has_value()) {
+ return it->second.auto_installed_expiration_time.value() <=
+ base::Time::Now();
+ }
+ return false;
+}
+
+void SubscriptionPersistentMetadataImpl::RemoveMetadata(
+ const GURL& subscription_url) {
+ metadata_map_.erase(subscription_url);
+ UpdatePrefs();
+}
+
+void SubscriptionPersistentMetadataImpl::UpdatePrefs() {
+ base::Value::Dict dict;
+ for (const auto& pair : metadata_map_) {
+ base::Value::Dict subscription;
+ subscription.Set(kExpirationTimeKey,
+ TimeToValue(pair.second.expiration_time));
+ subscription.Set(kLastInstallationTimeKey,
+ TimeToValue(pair.second.last_installation_time));
+ subscription.Set(kVersionKey, pair.second.version);
+ subscription.Set(kDownloadCountKey, pair.second.download_count);
+ subscription.Set(kErrorCountKey, pair.second.error_count);
+ if (pair.second.auto_installed_expiration_time.has_value()) {
+ subscription.Set(
+ kAutoInstalledExpirationTimeKey,
+ TimeToValue(pair.second.auto_installed_expiration_time.value()));
+ }
+ dict.Set(pair.first.spec(), std::move(subscription));
+ }
+ prefs_->SetDict(common::prefs::kSubscriptionMetadata, std::move(dict));
+}
+
+void SubscriptionPersistentMetadataImpl::LoadFromPrefs() {
+ const base::Value& dict =
+ prefs_->GetValue(common::prefs::kSubscriptionMetadata);
+ DCHECK(dict.is_dict());
+ for (const auto dict_item : dict.GetDict()) {
+ Metadata subscription;
+ DCHECK(dict_item.second.is_dict());
+ const auto* d = dict_item.second.GetIfDict();
+ subscription.expiration_time =
+ ValueToTime(d->Find(kExpirationTimeKey)).value_or(base::Time());
+ subscription.last_installation_time =
+ ValueToTime(d->Find(kLastInstallationTimeKey)).value_or(base::Time());
+ const auto* version = d->FindString(kVersionKey);
+ if (version) {
+ subscription.version = *version;
+ }
+ subscription.error_count = d->FindInt(kErrorCountKey).value_or(0);
+ subscription.download_count = d->FindInt(kDownloadCountKey).value_or(0);
+ const auto* auto_installed_expiration_time =
+ d->Find(kAutoInstalledExpirationTimeKey);
+ if (auto_installed_expiration_time) {
+ subscription.auto_installed_expiration_time =
+ ValueToTime(auto_installed_expiration_time);
+ }
+ metadata_map_.emplace(dict_item.first, std::move(subscription));
+ }
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.h b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_
+
+#include <map>
+
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+#include "components/prefs/pref_service.h"
+
+namespace adblock {
+
+// Stores persistent subscription metadata in PrefService.
+class SubscriptionPersistentMetadataImpl final
+ : public SubscriptionPersistentMetadata {
+ public:
+ explicit SubscriptionPersistentMetadataImpl(PrefService* prefs);
+ ~SubscriptionPersistentMetadataImpl() final;
+
+ void SetExpirationInterval(const GURL& subscription_url,
+ base::TimeDelta expires_in) final;
+ void SetLastInstallationTime(const GURL& subscription_url) final;
+ void SetVersion(const GURL& subscription_url, std::string version) final;
+ void IncrementDownloadSuccessCount(const GURL& subscription_url) final;
+ void IncrementDownloadErrorCount(const GURL& subscription_url) final;
+
+ bool IsExpired(const GURL& subscription_url) const final;
+ base::Time GetLastInstallationTime(const GURL& subscription_url) const final;
+ std::string GetVersion(const GURL& subscription_url) const final;
+ int GetDownloadSuccessCount(const GURL& subscription_url) const final;
+ int GetDownloadErrorCount(const GURL& subscription_url) const final;
+
+ void SetAutoInstalledExpirationInterval(const GURL& subscription_url,
+ base::TimeDelta expires_in) final;
+ bool IsAutoInstalled(const GURL& subscription_url) const final;
+ bool IsAutoInstalledExpired(const GURL& subscription_url) const final;
+
+ void RemoveMetadata(const GURL& subscription_url) final;
+
+ private:
+ struct Metadata;
+ using MetadataMap = std::map<GURL, Metadata>;
+ void UpdatePrefs();
+ void LoadFromPrefs();
+
+ raw_ptr<PrefService> prefs_;
+ MetadataMap metadata_map_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_
diff --git a/components/adblock/core/subscription/subscription_persistent_storage.h b/components/adblock/core/subscription/subscription_persistent_storage.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_storage.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+
+namespace adblock {
+
+// Provides a persistent, disk-based storage for installed subscription files.
+class SubscriptionPersistentStorage {
+ public:
+ virtual ~SubscriptionPersistentStorage() = default;
+ using LoadCallback = base::OnceCallback<void(
+ std::vector<scoped_refptr<InstalledSubscription>>)>;
+ // Loads subscriptions from a directory on disk and returns them via
+ // |on_loaded|.
+ virtual void LoadSubscriptions(LoadCallback on_loaded) = 0;
+
+ using StoreCallback =
+ base::OnceCallback<void(scoped_refptr<InstalledSubscription>)>;
+ // Stores |raw_data| to disk and returns a Subscription created from
+ // flatbuffer parsed from |raw_data|.
+ // |on_finished| gets called after the store to disk and parsing has finished,
+ // nullptr argument signifies there was an error.
+ // |raw_data| is assumed to be valid against the current flatbuffer schema, it
+ // is not validated internally for performance reasons. Validate flatbuffers
+ // downloaded from the Internet externally.
+ virtual void StoreSubscription(std::unique_ptr<FlatbufferData> raw_data,
+ StoreCallback on_finished) = 0;
+
+ // Removes |subscription|'s file from disk.
+ virtual void RemoveSubscription(
+ scoped_refptr<InstalledSubscription> subscription) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_
diff --git a/components/adblock/core/subscription/subscription_persistent_storage_impl.cc b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc
@@ -0,0 +1,237 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
+#include "base/unguessable_token.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/subscription_validator.h"
+
+namespace adblock {
+namespace {
+
+GURL UrlFromFlatbufferData(const FlatbufferData& flatbuffer) {
+ DCHECK(flat::GetSubscription(flatbuffer.data())->metadata());
+ DCHECK(flat::GetSubscription(flatbuffer.data())->metadata()->url());
+ return GURL(
+ flat::GetSubscription(flatbuffer.data())->metadata()->url()->str());
+}
+
+} // namespace
+
+SubscriptionPersistentStorageImpl::SubscriptionPersistentStorageImpl(
+ base::FilePath base_storage_dir,
+ std::unique_ptr<SubscriptionValidator> validator,
+ SubscriptionPersistentMetadata* persistent_metadata)
+ : base_storage_dir_(std::move(base_storage_dir)),
+ validator_(std::move(validator)),
+ persistent_metadata_(persistent_metadata) {}
+
+SubscriptionPersistentStorageImpl::~SubscriptionPersistentStorageImpl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+// static
+SubscriptionPersistentStorageImpl::LoadedBuffer
+SubscriptionPersistentStorageImpl::WriteSubscription(
+ const base::FilePath& storage_dir,
+ std::unique_ptr<FlatbufferData> raw_data,
+ SubscriptionValidator::StoreTrustedSignatureThreadSafeCallback
+ store_signature) {
+ // To avoid conflict between existing subscription files, generate a new
+ // unique path.
+ base::FilePath subscription_path = storage_dir.AppendASCII(
+ base::UnguessableToken::Create().ToString() + ".fb");
+ // UnguessableToken is a 128-bit, cryptographically-safe random number,
+ // conflicts are less likely than disk failure. The DCHECK is to express
+ // intent.
+ DCHECK(!base::PathExists(subscription_path));
+ if (!base::WriteFile(
+ subscription_path,
+ std::string_view(reinterpret_cast<const char*>(raw_data->data()),
+ raw_data->size()))) {
+ // Disk write failed.
+ return std::make_pair(nullptr, base::FilePath{});
+ }
+ auto buffer = std::make_unique<MemoryMappedFlatbufferData>(subscription_path);
+ if (!buffer->data()) {
+ // Creating the memory-mapped region failed.
+ // TODO(DPD-1278) revert to in-memory buffer?
+ return std::make_pair(nullptr, base::FilePath{});
+ }
+ std::move(store_signature).Run(*buffer, subscription_path);
+ return std::make_pair(std::move(buffer), std::move(subscription_path));
+}
+
+// static
+std::vector<SubscriptionPersistentStorageImpl::LoadedBuffer>
+SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory(
+ const base::FilePath& storage_dir,
+ SubscriptionValidator::IsSignatureValidThreadSafeCallback
+ is_signature_valid) {
+ DLOG(INFO) << "[eyeo] Reading subscriptions from directory";
+ TRACE_EVENT0("eyeo", "ReadSubscriptionsFromDirectory");
+ // Does nothing if directory already exists:
+ base::CreateDirectory(storage_dir);
+
+ std::vector<SubscriptionPersistentStorageImpl::LoadedBuffer> result;
+ base::FileEnumerator enumerator(storage_dir, false /* recursive */,
+ base::FileEnumerator::FILES);
+ // Iterate through |storage_dir| and try to load all files within.
+ for (base::FilePath flatbuffer_path = enumerator.Next();
+ !flatbuffer_path.empty(); flatbuffer_path = enumerator.Next()) {
+ std::string contents;
+
+ TRACE_EVENT_BEGIN1("eyeo", "ReadFileToString", "path",
+ flatbuffer_path.AsUTF8Unsafe());
+ if (!base::ReadFileToString(flatbuffer_path, &contents)) {
+ // File could not be read.
+ base::DeleteFile(flatbuffer_path);
+ continue;
+ }
+ TRACE_EVENT_END1("eyeo", "ReadFileToString", "path",
+ flatbuffer_path.AsUTF8Unsafe());
+ TRACE_EVENT_BEGIN0("eyeo", "VerifySubscriptionBuffer");
+ if (!is_signature_valid.Run(InMemoryFlatbufferData(std::move(contents)),
+ flatbuffer_path)) {
+ // This is not a valid subscription file, remove it.
+ base::DeleteFile(flatbuffer_path);
+ continue;
+ }
+ TRACE_EVENT_END0("eyeo", "VerifySubscriptionBuffer");
+ auto buffer = std::make_unique<MemoryMappedFlatbufferData>(flatbuffer_path);
+ if (!buffer->data()) {
+ // Could not create mapped memory region to file content.
+ // TODO(mpawlowski) revert to in-memory buffer?
+ continue;
+ }
+ result.emplace_back(std::move(buffer), std::move(flatbuffer_path));
+ }
+ DLOG(INFO) << "[eyeo] Finished reading and validating subscriptions. Loaded "
+ << result.size() << " subscriptions.";
+ return result;
+}
+
+void SubscriptionPersistentStorageImpl::LoadSubscriptions(
+ LoadCallback on_loaded) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "eyeo", "SubscriptionPersistentStorageImpl::LoadSubscription",
+ TRACE_ID_LOCAL(this));
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&ReadSubscriptionsFromDirectory, base_storage_dir_,
+ validator_->IsSignatureValid()),
+ base::BindOnce(&SubscriptionPersistentStorageImpl::LoadComplete,
+ weak_ptr_factory.GetWeakPtr(), std::move(on_loaded)));
+}
+
+void SubscriptionPersistentStorageImpl::StoreSubscription(
+ std::unique_ptr<FlatbufferData> raw_data,
+ StoreCallback on_finished) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(&WriteSubscription, base_storage_dir_, std::move(raw_data),
+ validator_->StoreTrustedSignature()),
+ base::BindOnce(&SubscriptionPersistentStorageImpl::SubscriptionStored,
+ weak_ptr_factory.GetWeakPtr(), std::move(on_finished)));
+}
+
+void SubscriptionPersistentStorageImpl::RemoveSubscription(
+ scoped_refptr<InstalledSubscription> subscription) {
+ auto it = backing_file_mapping_.find(subscription);
+ DCHECK(it != backing_file_mapping_.end())
+ << "Attempted to remove subscription not governed by this "
+ "SubscriptionPersistentStorageImpl";
+ validator_->RemoveStoredSignature().Run(it->second);
+ backing_file_mapping_.erase(it);
+ subscription->MarkForPermanentRemoval();
+}
+
+void SubscriptionPersistentStorageImpl::LoadComplete(
+ LoadCallback on_loaded,
+ std::vector<LoadedBuffer> loaded_buffers) {
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions;
+ for (LoadedBuffer& loaded_buffer : loaded_buffers) {
+ const auto url = UrlFromFlatbufferData(*loaded_buffer.first);
+ const auto last_installation_time =
+ persistent_metadata_->GetLastInstallationTime(url);
+ auto installed_subscription =
+ base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(loaded_buffer.first),
+ persistent_metadata_->IsAutoInstalled(url)
+ ? Subscription::InstallationState::AutoInstalled
+ : Subscription::InstallationState::Installed,
+ last_installation_time);
+ backing_file_mapping_[installed_subscription] =
+ std::move(loaded_buffer.second);
+ loaded_subscriptions.push_back(installed_subscription);
+ }
+ TRACE_EVENT_ASYNC_END0("eyeo",
+ "SubscriptionPersistentStorageImpl::LoadSubscription",
+ TRACE_ID_LOCAL(this));
+ std::move(on_loaded).Run(std::move(loaded_subscriptions));
+}
+
+void SubscriptionPersistentStorageImpl::SubscriptionStored(
+ StoreCallback on_finished,
+ LoadedBuffer storage_result) {
+ if (!storage_result.first) {
+ // There was an error storing the subscription.
+ std::move(on_finished).Run(nullptr);
+ return;
+ }
+
+ const auto url = UrlFromFlatbufferData(*storage_result.first);
+ const auto last_installation_time = base::Time::Now();
+ auto installed_subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(storage_result.first),
+ persistent_metadata_->IsAutoInstalled(url)
+ ? Subscription::InstallationState::AutoInstalled
+ : Subscription::InstallationState::Installed,
+ last_installation_time);
+ persistent_metadata_->IncrementDownloadSuccessCount(url);
+ persistent_metadata_->SetLastInstallationTime(url);
+ persistent_metadata_->SetExpirationInterval(
+ url, installed_subscription->GetExpirationInterval());
+ const auto parsed_version = installed_subscription->GetCurrentVersion();
+ if (!parsed_version.empty()) {
+ persistent_metadata_->SetVersion(url, parsed_version);
+ }
+ backing_file_mapping_[installed_subscription] =
+ std::move(storage_result.second);
+ std::move(on_finished).Run(installed_subscription);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_persistent_storage_impl.h b/components/adblock/core/subscription/subscription_persistent_storage_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_persistent_storage_impl.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_
+
+#include <map>
+
+#include "base/files/file_path.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+#include "components/adblock/core/subscription/subscription_persistent_storage.h"
+#include "components/adblock/core/subscription/subscription_validator.h"
+
+namespace adblock {
+
+class SubscriptionPersistentStorageImpl final
+ : public SubscriptionPersistentStorage {
+ public:
+ SubscriptionPersistentStorageImpl(
+ base::FilePath base_storage_dir,
+ std::unique_ptr<SubscriptionValidator> validator,
+ SubscriptionPersistentMetadata* persistent_metadata);
+ ~SubscriptionPersistentStorageImpl() final;
+
+ void LoadSubscriptions(LoadCallback on_loaded) final;
+ void StoreSubscription(std::unique_ptr<FlatbufferData> raw_data,
+ StoreCallback on_finished) final;
+ void RemoveSubscription(
+ scoped_refptr<InstalledSubscription> subscription) final;
+
+ private:
+ using SubscriptionFileMapping =
+ std::map<scoped_refptr<InstalledSubscription>, base::FilePath>;
+ using LoadedBuffer =
+ std::pair<std::unique_ptr<FlatbufferData>, base::FilePath>;
+ static LoadedBuffer WriteSubscription(
+ const base::FilePath& storage_dir,
+ std::unique_ptr<FlatbufferData> raw_data,
+ SubscriptionValidator::StoreTrustedSignatureThreadSafeCallback
+ store_signature);
+ static std::vector<LoadedBuffer> ReadSubscriptionsFromDirectory(
+ const base::FilePath& storage_dir,
+ SubscriptionValidator::IsSignatureValidThreadSafeCallback
+ is_signature_valid);
+ void LoadComplete(LoadCallback on_initialized,
+ std::vector<LoadedBuffer> loaded_buffers);
+ void SubscriptionStored(StoreCallback on_finished,
+ LoadedBuffer storage_result);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ base::FilePath base_storage_dir_;
+ std::unique_ptr<SubscriptionValidator> validator_;
+ raw_ptr<SubscriptionPersistentMetadata> persistent_metadata_;
+ // Maps Subscriptions to files that they access.
+ SubscriptionFileMapping backing_file_mapping_;
+ base::WeakPtrFactory<SubscriptionPersistentStorageImpl> weak_ptr_factory{
+ this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_
diff --git a/components/adblock/core/subscription/subscription_service.h b/components/adblock/core/subscription/subscription_service.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_service.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/observer_list_types.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/subscription_collection.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Maintains a state of available Subscriptions needed for all installed
+// FilteringConfigurations.
+class SubscriptionService : public KeyedService {
+ public:
+ using Snapshot = std::vector<std::unique_ptr<SubscriptionCollection>>;
+ class SubscriptionObserver : public base::CheckedObserver {
+ public:
+ // Called only on successful installation or update of a subscription.
+ // TODO(mpawlowski) add error reporting.
+ virtual void OnSubscriptionInstalled(const GURL& subscription_url) {}
+ // Called on installation of new filtering configuration
+ virtual void OnFilteringConfigurationInstalled(
+ FilteringConfiguration* config) {}
+ virtual void OnFilteringConfigurationUninstalled(
+ std::string_view config_name) {}
+ };
+ // Returns currently available subscriptions installed for |configuration|.
+ // Includes subscriptions that are still being downloaded.
+ virtual std::vector<scoped_refptr<Subscription>> GetCurrentSubscriptions(
+ FilteringConfiguration* configuration) const = 0;
+ // Subscriptions and filters demanded by |configuration| will be installed and
+ // will become part of future Snapshots. SubscriptionService will maintain
+ // subscriptions required by the configuration, download and remove filter
+ // lists as needed.
+ virtual void InstallFilteringConfiguration(
+ std::unique_ptr<FilteringConfiguration> configuration) = 0;
+ // Removes configuration from the list of known configurations and reset its
+ // all persistent data. Use it only when configuration is no longer needed,
+ // otherwise prefer to disable configuration via FilteringConfiguration API.
+ // IMPORTANT: After calling this method any pointer pointing to uninstalled
+ // configuration becomes invalid.
+ virtual void UninstallFilteringConfiguration(
+ std::string_view configuration_name) = 0;
+ // Returns a list of FilteringConfigurations previously installed via
+ // InstallFilteringConfiguration.
+ virtual std::vector<FilteringConfiguration*>
+ GetInstalledFilteringConfigurations() = 0;
+ // Gets a filtering configuration by name.
+ virtual FilteringConfiguration* GetFilteringConfiguration(
+ std::string_view configuration_name) const = 0;
+ // Returns a snapshot of subscriptions as present at the time of calling the
+ // function that can be used to query filters.
+ // The result may be passed between threads, even called
+ // concurrently, and future changes to the installed subscriptions will not
+ // impact it.
+ virtual Snapshot GetCurrentSnapshot() const = 0;
+ virtual void SetAutoInstallEnabled(bool enabled) = 0;
+ virtual bool IsAutoInstallEnabled() const = 0;
+
+ virtual void AddObserver(SubscriptionObserver*) = 0;
+ virtual void RemoveObserver(SubscriptionObserver*) = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_
diff --git a/components/adblock/core/subscription/subscription_service_impl.cc b/components/adblock/core/subscription/subscription_service_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_service_impl.cc
@@ -0,0 +1,265 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_service_impl.h"
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <set>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/parameter_pack.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/subscription/filtering_configuration_maintainer.h"
+#include "components/adblock/core/subscription/subscription_collection.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+
+namespace adblock {
+
+class EmptySubscription : public Subscription {
+ public:
+ explicit EmptySubscription(const GURL& url) : url_(url) {}
+ GURL GetSourceUrl() const override { return url_; }
+ std::string GetTitle() const override { return ""; }
+ std::string GetCurrentVersion() const override { return ""; }
+ InstallationState GetInstallationState() const override {
+ return InstallationState::Unknown;
+ }
+ base::Time GetInstallationTime() const override {
+ return base::Time::UnixEpoch();
+ }
+ base::TimeDelta GetExpirationInterval() const override {
+ return base::TimeDelta();
+ }
+
+ private:
+ ~EmptySubscription() override {}
+ const GURL url_;
+};
+
+SubscriptionServiceImpl::SubscriptionServiceImpl(
+ PrefService* pref_service,
+ FilteringConfigurationMaintainerFactory maintainer_factory,
+ FilteringConfigurationCleaner configuration_cleaner)
+ : pref_service_(pref_service),
+ maintainer_factory_(std::move(maintainer_factory)),
+ configuration_cleaner_(std::move(configuration_cleaner)) {}
+
+SubscriptionServiceImpl::~SubscriptionServiceImpl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto& entry : maintainers_) {
+ entry.first->RemoveObserver(this);
+ }
+}
+
+std::vector<scoped_refptr<Subscription>>
+SubscriptionServiceImpl::GetCurrentSubscriptions(
+ FilteringConfiguration* configuration) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = std::ranges::find_if(maintainers_, [&](const auto& entry) {
+ return entry.first.get() == configuration;
+ });
+ DCHECK(it != maintainers_.end()) << "Cannot get Subscriptions from an "
+ "unregistered FilteringConfiguration";
+
+ // First get the list from FilteringConfiguration which represents actual
+ // settings state but misses subscription metadata (it's just a list of urls).
+ auto urls = it->first->GetFilterLists();
+ std::vector<scoped_refptr<adblock::Subscription>> result;
+ std::ranges::transform(urls, std::back_inserter(result),
+ [](const auto& url) {
+ return base::MakeRefCounted<EmptySubscription>(url);
+ });
+ if (it->second) {
+ // As the list from FilteringConfiguration is lacking metadata, replace
+ // each entry from FilteringConfiguration by respective entry from
+ // maintainer, leaving entries from FilteringConfiguration if there is no
+ // counterpart in maintainer (this can be the case when subscription storage
+ // is not yet initialized).
+ auto maintainer_filter_lists = it->second->GetCurrentSubscriptions();
+ for (size_t i = 0; i < result.size(); ++i) {
+ auto list_it = std::ranges::find_if(
+ maintainer_filter_lists, [&](const auto& entry) {
+ return entry->GetSourceUrl() == result[i]->GetSourceUrl();
+ });
+ if (list_it != maintainer_filter_lists.end()) {
+ result[i] = *list_it;
+ }
+ }
+ }
+ return result;
+}
+
+void SubscriptionServiceImpl::InstallFilteringConfiguration(
+ std::unique_ptr<FilteringConfiguration> configuration) {
+ auto name = configuration->GetName();
+ auto it = std::ranges::find_if(maintainers_, [&name](const auto& entry) {
+ return entry.first.get()->GetName() == name;
+ });
+ if (it != maintainers_.end()) {
+ LOG(WARNING)
+ << "[eyeo] Trying to install configuration with duplicated name: "
+ << name;
+ return;
+ }
+ VLOG(1) << "[eyeo] FilteringConfiguration installed: "
+ << configuration->GetName();
+ configuration->AddObserver(this);
+ std::unique_ptr<FilteringConfigurationMaintainer> maintainer;
+ if (configuration->IsEnabled()) {
+ // Only enabled configurations should be maintained. Disabled configurations
+ // are observed and added to the collection, but a Maintainer will be
+ // created in OnEnabledStateChanged.
+ maintainer = MakeMaintainer(configuration.get());
+ }
+ auto* ptr = configuration.get();
+ maintainers_.insert(
+ std::make_pair(std::move(configuration), std::move(maintainer)));
+ for (auto& observer : observers_) {
+ observer.OnFilteringConfigurationInstalled(ptr);
+ }
+}
+
+void SubscriptionServiceImpl::UninstallFilteringConfiguration(
+ std::string_view configuration_name) {
+ auto it = std::ranges::find_if(maintainers_, [&](const auto& entry) {
+ return entry.first.get()->GetName() == configuration_name;
+ });
+ if (it == maintainers_.end()) {
+ LOG(WARNING) << "[eyeo] Trying to uninstall non existing configuration: "
+ << configuration_name;
+ return;
+ }
+ VLOG(1) << "[eyeo] FilteringConfiguration uninstalled: "
+ << configuration_name;
+ it->first->RemoveObserver(this);
+ it->second.reset();
+ configuration_cleaner_.Run(it->first.get());
+ maintainers_.erase(it);
+ for (auto& observer : observers_) {
+ observer.OnFilteringConfigurationUninstalled(configuration_name);
+ }
+}
+
+std::vector<FilteringConfiguration*>
+SubscriptionServiceImpl::GetInstalledFilteringConfigurations() {
+ std::vector<FilteringConfiguration*> result;
+ std::ranges::transform(maintainers_, std::back_inserter(result),
+ [](const auto& pair) { return pair.first.get(); });
+ return result;
+}
+
+FilteringConfiguration* SubscriptionServiceImpl::GetFilteringConfiguration(
+ std::string_view configuration_name) const {
+ const auto it = std::ranges::find_if(
+ maintainers_, [&configuration_name](const auto& pair) {
+ return pair.first->GetName() == configuration_name;
+ });
+ if (it == maintainers_.end()) {
+ LOG(WARNING)
+ << "[eyeo] Trying to get a pointer to not installed configuration "
+ << configuration_name;
+ return nullptr;
+ }
+ return it->first.get();
+}
+
+SubscriptionService::Snapshot SubscriptionServiceImpl::GetCurrentSnapshot()
+ const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ Snapshot snapshot;
+ for (const auto& entry : maintainers_) {
+ if (!entry.second) {
+ continue; // Configuration is disabled
+ }
+ snapshot.push_back(entry.second->GetSubscriptionCollection());
+ }
+ return snapshot;
+}
+
+void SubscriptionServiceImpl::SetAutoInstallEnabled(bool enabled) {
+ if (!pref_service_) {
+ return;
+ }
+ pref_service_->SetBoolean(common::prefs::kEnableAutoInstalledSubscriptions,
+ enabled);
+ if (!enabled) {
+ for (auto& entry : maintainers_) {
+ entry.second->RemoveAutoInstalledSubscriptions();
+ }
+ }
+}
+
+bool SubscriptionServiceImpl::IsAutoInstallEnabled() const {
+ return pref_service_ != nullptr &&
+ pref_service_->GetBoolean(
+ common::prefs::kEnableAutoInstalledSubscriptions);
+}
+
+void SubscriptionServiceImpl::AddObserver(SubscriptionObserver* o) {
+ observers_.AddObserver(o);
+}
+
+void SubscriptionServiceImpl::RemoveObserver(SubscriptionObserver* o) {
+ observers_.RemoveObserver(o);
+}
+
+void SubscriptionServiceImpl::OnEnabledStateChanged(
+ FilteringConfiguration* config) {
+ auto it = std::ranges::find_if(maintainers_, [&](const auto& entry) {
+ return entry.first.get() == config;
+ });
+ DCHECK(it != maintainers_.end()) << "Received OnEnabledStateChanged from "
+ "unregistered FilteringConfiguration";
+ VLOG(1) << "[eyeo] FilteringConfiguration " << config->GetName()
+ << (config->IsEnabled() ? " enabled" : " disabled");
+ if (config->IsEnabled()) {
+ // Enable the configuration by creating a new
+ // FilteringConfigurationMaintainer. This triggers installing missing
+ // subscriptions etc.
+ it->second = MakeMaintainer(config);
+ } else {
+ // Disable the configuration by removing its
+ // FilteringConfigurationMaintainer. This cancels all related operations and
+ // frees all associated memory.
+ it->second.reset();
+ }
+}
+
+void SubscriptionServiceImpl::OnSubscriptionUpdated(
+ const GURL& subscription_url) {
+ for (auto& observer : observers_) {
+ observer.OnSubscriptionInstalled(subscription_url);
+ }
+}
+
+std::unique_ptr<FilteringConfigurationMaintainer>
+SubscriptionServiceImpl::MakeMaintainer(FilteringConfiguration* configuration) {
+ return maintainer_factory_.Run(
+ configuration,
+ base::BindRepeating(&SubscriptionServiceImpl::OnSubscriptionUpdated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_service_impl.h b/components/adblock/core/subscription/subscription_service_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_service_impl.h
@@ -0,0 +1,101 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/subscription/filtering_configuration_maintainer.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/adblock/core/subscription/preloaded_subscription_provider.h"
+#include "components/adblock/core/subscription/subscription_downloader.h"
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+#include "components/adblock/core/subscription/subscription_persistent_storage.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/prefs/pref_service.h"
+
+namespace adblock {
+
+class SubscriptionServiceImpl final : public SubscriptionService,
+ public FilteringConfiguration::Observer {
+ public:
+ // Used to notify this about updates to installed subscriptions.
+ using SubscriptionUpdatedCallback =
+ base::RepeatingCallback<void(const GURL& subscription_url)>;
+ // Used to create FilteringConfigurationMaintainers for newly installed
+ // FilteringConfigurations.
+ using FilteringConfigurationMaintainerFactory =
+ base::RepeatingCallback<std::unique_ptr<FilteringConfigurationMaintainer>(
+ FilteringConfiguration* configuration,
+ SubscriptionUpdatedCallback observer)>;
+ using FilteringConfigurationCleaner =
+ base::RepeatingCallback<void(FilteringConfiguration* configuration)>;
+ explicit SubscriptionServiceImpl(
+ PrefService* pref_service,
+ FilteringConfigurationMaintainerFactory maintainer_factory,
+ FilteringConfigurationCleaner configuration_cleaner);
+ ~SubscriptionServiceImpl() final;
+
+ // SubscriptionService:
+ std::vector<scoped_refptr<Subscription>> GetCurrentSubscriptions(
+ FilteringConfiguration* configuration) const final;
+ void InstallFilteringConfiguration(
+ std::unique_ptr<FilteringConfiguration> configuration) final;
+ void UninstallFilteringConfiguration(
+ std::string_view configuration_name) final;
+ std::vector<FilteringConfiguration*> GetInstalledFilteringConfigurations()
+ final;
+ FilteringConfiguration* GetFilteringConfiguration(
+ std::string_view configuration_name) const final;
+ Snapshot GetCurrentSnapshot() const final;
+ void SetAutoInstallEnabled(bool enabled) final;
+ bool IsAutoInstallEnabled() const final;
+ void AddObserver(SubscriptionObserver*) final;
+ void RemoveObserver(SubscriptionObserver*) final;
+
+ // FilteringConfiguration::Observer:
+ void OnEnabledStateChanged(FilteringConfiguration* config) final;
+
+ private:
+ void OnSubscriptionUpdated(const GURL& subscription_url);
+ std::unique_ptr<FilteringConfigurationMaintainer> MakeMaintainer(
+ FilteringConfiguration* configuration);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ const raw_ptr<PrefService> pref_service_;
+ FilteringConfigurationMaintainerFactory maintainer_factory_;
+ FilteringConfigurationCleaner configuration_cleaner_;
+ using MaintainersCollection =
+ std::map<std::unique_ptr<FilteringConfiguration>,
+ std::unique_ptr<FilteringConfigurationMaintainer>>;
+ MaintainersCollection maintainers_;
+ base::ObserverList<SubscriptionObserver> observers_;
+ base::WeakPtrFactory<SubscriptionServiceImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_
diff --git a/components/adblock/core/subscription/subscription_validator.h b/components/adblock/core/subscription/subscription_validator.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_validator.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_H_
+
+#include "base/files/file_path.h"
+#include "base/functional/callback_forward.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+
+namespace adblock {
+
+// Validates potentially untrusted Subscriptions read from disk.
+// Is thread-safe, returned callbacks can be called from a background thread.
+class SubscriptionValidator {
+ public:
+ virtual ~SubscriptionValidator() = default;
+ // Verifies if |data| has a signature that matches a previously stored
+ // signature for |path| and whether the schema version is supported. To avoid
+ // race conditions, only the state current for the time of retrieving the
+ // callback is considered, subsequent calls to |StoreTrustedSignature| will
+ // not affect the results. You need to recreate the callback to read new
+ // state.
+ using IsSignatureValidThreadSafeCallback =
+ base::RepeatingCallback<bool(const FlatbufferData& data,
+ const base::FilePath& path)>;
+ virtual IsSignatureValidThreadSafeCallback IsSignatureValid() const = 0;
+
+ // Asynchronously persistently store the signature of |data| associated with
+ // |path|.
+ using StoreTrustedSignatureThreadSafeCallback =
+ base::OnceCallback<void(const FlatbufferData& data,
+ const base::FilePath& path)>;
+ virtual StoreTrustedSignatureThreadSafeCallback StoreTrustedSignature() = 0;
+
+ // Asynchronously removes the signature of file |path| from persistent
+ // storage.
+ using RemoveStoredSignatureThreadSafeCallback =
+ base::OnceCallback<void(const base::FilePath& path)>;
+ virtual RemoveStoredSignatureThreadSafeCallback RemoveStoredSignature() = 0;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_H_
diff --git a/components/adblock/core/subscription/subscription_validator_impl.cc b/components/adblock/core/subscription/subscription_validator_impl.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_validator_impl.cc
@@ -0,0 +1,144 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_validator_impl.h"
+
+#include "base/base64.h"
+#include "base/containers/span.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/task/bind_post_task.h"
+#include "base/task/sequenced_task_runner.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "crypto/sha2.h"
+
+namespace adblock {
+namespace {
+
+std::string ComputeSubscriptionHash(const FlatbufferData& buffer) {
+ return base::Base64Encode(crypto::SHA256Hash(buffer.span()));
+}
+
+// When the schema version used to create installed subscriptions is different
+// from the schema version known by this browser, we should not attempt to read
+// the flatbuffers - we would misinterpret their content.
+// Clear the stored subscription signatures to indicate the files are invalid.
+void ClearSignaturesIfSchemaVersionChanged(
+ PrefService* pref_service,
+ const std::string& current_schema_version) {
+ if (pref_service->GetString(common::prefs::kLastUsedSchemaVersion) !=
+ current_schema_version) {
+ if (!pref_service->FindPreference(common::prefs::kSubscriptionSignatures)
+ ->IsDefaultValue()) {
+ LOG(INFO) << "[eyeo] Schema version has changed, invalidating stored "
+ "subscriptions.";
+ pref_service->ClearPref(common::prefs::kSubscriptionSignatures);
+ }
+ pref_service->SetString(common::prefs::kLastUsedSchemaVersion,
+ current_schema_version);
+ }
+}
+
+bool IsSignatureValidInternal(
+ const base::Value::Dict& initial_subscription_signatures,
+ const FlatbufferData& data,
+ const base::FilePath& path) {
+ const auto* expected_hash = initial_subscription_signatures.FindString(
+ path.BaseName().AsUTF8Unsafe());
+ if (!expected_hash) {
+ DLOG(WARNING) << "[eyeo] " << path << " has no matching signature in prefs";
+ return false;
+ }
+ if (*expected_hash != ComputeSubscriptionHash(data)) {
+ DLOG(WARNING) << "[eyeo] " << path << " has invalid signature in prefs";
+ return false;
+ }
+ return true;
+}
+
+void StoreTrustedSignatureInternal(
+ scoped_refptr<base::TaskRunner> main_task_runner,
+ base::OnceCallback<void(std::string signature, const base::FilePath& path)>
+ signature_receiver,
+ const FlatbufferData& data,
+ const base::FilePath& path) {
+ // Compute the hash on the current, background thread.
+ const auto hash = ComputeSubscriptionHash(data);
+ // Post the hash for storing into the main thread.
+ main_task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(signature_receiver), std::move(hash), path));
+}
+
+} // namespace
+
+SubscriptionValidatorImpl::SubscriptionValidatorImpl(
+ PrefService* pref_service,
+ const std::string& current_schema_version)
+ : pref_service_(pref_service) {
+ ClearSignaturesIfSchemaVersionChanged(pref_service_, current_schema_version);
+}
+
+SubscriptionValidatorImpl::~SubscriptionValidatorImpl() = default;
+
+SubscriptionValidator::IsSignatureValidThreadSafeCallback
+SubscriptionValidatorImpl::IsSignatureValid() const {
+ return base::BindRepeating(
+ &IsSignatureValidInternal,
+ pref_service_->GetDict(common::prefs::kSubscriptionSignatures).Clone());
+}
+
+SubscriptionValidator::StoreTrustedSignatureThreadSafeCallback
+SubscriptionValidatorImpl::StoreTrustedSignature() {
+ return base::BindOnce(
+ &StoreTrustedSignatureInternal,
+ base::SequencedTaskRunner::GetCurrentDefault(),
+ base::BindOnce(
+ &SubscriptionValidatorImpl::StoreTrustedSignatureOnMainThread,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+SubscriptionValidator::RemoveStoredSignatureThreadSafeCallback
+SubscriptionValidatorImpl::RemoveStoredSignature() {
+ return base::BindPostTask(
+ base::SequencedTaskRunner::GetCurrentDefault(),
+ base::BindOnce(
+ &SubscriptionValidatorImpl::RemoveStoredSignatureInMainThread,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SubscriptionValidatorImpl::StoreTrustedSignatureOnMainThread(
+ std::string signature,
+ const base::FilePath& path) {
+ ScopedDictPrefUpdate pref_update(pref_service_,
+ common::prefs::kSubscriptionSignatures);
+ const auto key = path.BaseName().AsUTF8Unsafe();
+ pref_update->Set(key, base::Value(signature));
+}
+
+void SubscriptionValidatorImpl::RemoveStoredSignatureInMainThread(
+ const base::FilePath& path) {
+ ScopedDictPrefUpdate pref_update(pref_service_,
+ common::prefs::kSubscriptionSignatures);
+ const auto key = path.BaseName().AsUTF8Unsafe();
+ pref_update->Remove(key);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/subscription_validator_impl.h b/components/adblock/core/subscription/subscription_validator_impl.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/subscription_validator_impl.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_IMPL_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_IMPL_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/adblock/core/subscription/subscription_validator.h"
+#include "components/prefs/pref_service.h"
+
+class PrefService;
+
+namespace adblock {
+
+// Stores the hash of FlatbufferData in a Tracked Pref.
+class SubscriptionValidatorImpl final : public SubscriptionValidator {
+ public:
+ SubscriptionValidatorImpl(PrefService* pref_service,
+ const std::string& current_schema_version);
+ ~SubscriptionValidatorImpl() final;
+
+ IsSignatureValidThreadSafeCallback IsSignatureValid() const final;
+ StoreTrustedSignatureThreadSafeCallback StoreTrustedSignature() final;
+ RemoveStoredSignatureThreadSafeCallback RemoveStoredSignature() final;
+
+ private:
+ void StoreTrustedSignatureOnMainThread(std::string signature,
+ const base::FilePath& path);
+ void RemoveStoredSignatureInMainThread(const base::FilePath& path);
+
+ raw_ptr<PrefService> pref_service_;
+ base::WeakPtrFactory<SubscriptionValidatorImpl> weak_ptr_factory_{this};
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_IMPL_H_
diff --git a/components/adblock/core/subscription/test/domain_splitter_test.cc b/components/adblock/core/subscription/test/domain_splitter_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/domain_splitter_test.cc
@@ -0,0 +1,64 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/domain_splitter.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockDomainSplitterTest, SimpleDomain) {
+ DomainSplitter splitter("example.com");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "example.com");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "com");
+ // For matching wildcard TLDs:
+ EXPECT_EQ(splitter.FindNextSubdomain(), "example.");
+ EXPECT_EQ(splitter.FindNextSubdomain(), absl::nullopt);
+}
+
+TEST(AdblockDomainSplitterTest, MultiComponentDomain) {
+ DomainSplitter splitter("subdomain.example.com");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "subdomain.example.com");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "example.com");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "com");
+ // For matching wildcard TLDs:
+ EXPECT_EQ(splitter.FindNextSubdomain(), "subdomain.example.");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "example.");
+ EXPECT_EQ(splitter.FindNextSubdomain(), absl::nullopt);
+}
+
+TEST(AdblockDomainSplitterTest, DomainIsRegistrar) {
+ DomainSplitter splitter("gov.uk");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "gov.uk");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "uk");
+ // There are no wildcard TLDs to match because gov.uk is a registrar already.
+ EXPECT_EQ(splitter.FindNextSubdomain(), absl::nullopt);
+}
+
+TEST(AdblockDomainSplitterTest, Localhost) {
+ DomainSplitter splitter("localhost");
+ EXPECT_EQ(splitter.FindNextSubdomain(), "localhost");
+ EXPECT_EQ(splitter.FindNextSubdomain(), absl::nullopt);
+}
+
+TEST(AdblockDomainSplitterTest, EmptyDomain) {
+ DomainSplitter splitter("");
+ EXPECT_EQ(splitter.FindNextSubdomain(), absl::nullopt);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/filtering_configuration_maintainer_impl_test.cc b/components/adblock/core/subscription/test/filtering_configuration_maintainer_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/filtering_configuration_maintainer_impl_test.cc
@@ -0,0 +1,991 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/filtering_configuration_maintainer_impl.h"
+
+#include <iterator>
+#include <memory>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/ranges/algorithm.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/gmock_move_support.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/common/header_filter_data.h"
+#include "components/adblock/core/common/sitekey.h"
+#include "components/adblock/core/common/test/mock_task_scheduler.h"
+#include "components/adblock/core/configuration/test/fake_filtering_configuration.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/adblock/core/subscription/recommended_subscription_installer.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/test/mock_conversion_executors.h"
+#include "components/adblock/core/subscription/test/mock_subscription.h"
+#include "components/adblock/core/subscription/test/mock_subscription_downloader.h"
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+#include "gmock/gmock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+
+namespace adblock {
+namespace {
+
+class FakePersistentStorage final
+ : public NiceMock<SubscriptionPersistentStorage> {
+ public:
+ MOCK_METHOD(void, MockLoadSubscriptions, ());
+
+ void LoadSubscriptions(LoadCallback on_initialized) final {
+ on_initialized_ = std::move(on_initialized);
+ MockLoadSubscriptions();
+ }
+
+ void StoreSubscription(std::unique_ptr<FlatbufferData> raw_data,
+ StoreCallback on_finished) final {
+ store_subscription_calls_.emplace_back(std::move(raw_data),
+ std::move(on_finished));
+ }
+ void RemoveSubscription(
+ scoped_refptr<InstalledSubscription> subscription) final {
+ remove_subscription_calls_.push_back(std::move(subscription));
+ }
+
+ LoadCallback on_initialized_;
+ std::vector<std::pair<std::unique_ptr<FlatbufferData>, StoreCallback>>
+ store_subscription_calls_;
+ std::vector<scoped_refptr<InstalledSubscription>> remove_subscription_calls_;
+};
+
+class FakeBuffer final : public FlatbufferData {
+ public:
+ const uint8_t* data() const final { return nullptr; }
+ size_t size() const final { return 0u; }
+ const base::span<const uint8_t> span() const final { return {}; }
+};
+
+class FakeSubscription final : public InstalledSubscription {
+ public:
+ explicit FakeSubscription(
+ std::string name,
+ InstallationState state = InstallationState::Installed)
+ : name_(std::move(name)), state_(state) {}
+
+ GURL GetSourceUrl() const final {
+ if (GURL{name_}.is_valid()) {
+ return GURL{name_};
+ }
+ return GURL{"https://easylist-downloads.adblockplus.org/" + name_};
+ }
+ std::string GetTitle() const final { return name_; }
+
+ std::string GetCurrentVersion() const final { return name_; }
+
+ InstallationState GetInstallationState() const final { return state_; }
+
+ base::Time GetInstallationTime() const final { return base::Time(); }
+
+ base::TimeDelta GetExpirationInterval() const final { return base::Days(5); }
+
+ bool HasUrlFilter(const GURL& url,
+ const std::string& document_domain,
+ ContentType type,
+ const SiteKey& sitekey,
+ FilterCategory category) const final {
+ return false;
+ }
+ bool HasPopupFilter(const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey,
+ FilterCategory category) const final {
+ return false;
+ }
+ bool HasSpecialFilter(SpecialFilterType type,
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey) const final {
+ return false;
+ }
+ void FindCspFilters(const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<std::string_view>& results) const final {}
+ std::set<std::string_view> FindRewriteFilters(
+ const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category) const final {
+ return {};
+ }
+ void FindHeaderFilters(const GURL& url,
+ ContentType type,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<HeaderFilterData>& results) const final {}
+ ContentFiltersData GetElemhideData(const GURL& url,
+ bool domain_specific) const final {
+ ContentFiltersData result;
+ result.elemhide_selectors = {name_};
+ return result;
+ }
+ ContentFiltersData GetElemhideEmulationData(const GURL& url) const final {
+ return {};
+ }
+
+ std::vector<Snippet> MatchSnippets(
+ const std::string& document_domain) const final {
+ return {};
+ }
+
+ void MarkForPermanentRemoval() final {}
+
+ std::string name_;
+ InstallationState state_;
+
+ private:
+ ~FakeSubscription() final = default;
+};
+
+class MockPreloadedSubscriptionProvider
+ : public NiceMock<PreloadedSubscriptionProvider> {
+ public:
+ MOCK_METHOD(void,
+ UpdateSubscriptions,
+ (std::vector<GURL> installed_subscriptions,
+ std::vector<GURL> pending_subscriptions),
+ (override));
+ MOCK_METHOD(std::vector<scoped_refptr<InstalledSubscription>>,
+ GetCurrentPreloadedSubscriptions,
+ (),
+ (override, const));
+};
+
+class MockRecommendedSubscriptionInstaller
+ : public NiceMock<RecommendedSubscriptionInstaller> {
+ public:
+ MOCK_METHOD(void, RunUpdateCheck, (), (override));
+ MOCK_METHOD(void, RemoveAutoInstalledSubscriptions, (), (override));
+};
+
+} // namespace
+
+class AdblockFilteringConfigurationMaintainerImplTest : public testing::Test {
+ public:
+ void CreateTestee(std::vector<scoped_refptr<InstalledSubscription>>
+ demanded_subscriptions) {
+ filtering_configuration_ = std::make_unique<FakeFilteringConfiguration>();
+ filtering_configuration_->name = "adblock";
+ for (auto& sub : demanded_subscriptions) {
+ filtering_configuration_->AddFilterList(sub->GetSourceUrl());
+ }
+ auto storage = std::make_unique<FakePersistentStorage>();
+ storage_ = storage.get();
+ auto downloader = std::make_unique<MockSubscriptionDownloader>();
+ downloader_ = downloader.get();
+ auto recommended_subscription_installer =
+ std::make_unique<MockRecommendedSubscriptionInstaller>();
+ recommended_subscription_installer_ =
+ recommended_subscription_installer.get();
+ auto preloaded_subscription_provider =
+ std::make_unique<MockPreloadedSubscriptionProvider>();
+ preloaded_subscription_provider_ = preloaded_subscription_provider.get();
+ auto updater = std::make_unique<MockTaskScheduler>();
+ updater_ = updater.get();
+
+ testee_ = std::make_unique<FilteringConfigurationMaintainerImpl>(
+ filtering_configuration_.get(), std::move(storage),
+ std::move(downloader), std::move(recommended_subscription_installer),
+ std::move(preloaded_subscription_provider), std::move(updater),
+ &conversion_executor_, &persistent_metadata_, observer_.Get());
+ testee_->InitializeStorage();
+ }
+
+ void FinishStorageInitialization(
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions) {
+ // When storage becomes initialized, testee starts the update checks
+ // schedule.
+ EXPECT_CALL(*updater_, StartSchedule(_))
+ .WillOnce(testing::SaveArg<0>(&run_update_check_callback_));
+ std::move(storage_->on_initialized_).Run(loaded_subscriptions);
+ }
+
+ void TearDown() override {
+ // Avoid danging pointers during destruction.
+ storage_ = nullptr;
+ preloaded_subscription_provider_ = nullptr;
+ updater_ = nullptr;
+ downloader_ = nullptr;
+ recommended_subscription_installer_ = nullptr;
+ }
+
+ void AddSubscription(
+ scoped_refptr<InstalledSubscription> subscription,
+ AdblockResourceRequest::RetryPolicy expected_retry_policy =
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded) {
+ DCHECK(filtering_configuration_) << "Call CreateTestee() first";
+ const auto url = subscription->GetSourceUrl();
+ // The downloader will be called to fetch the raw_data for subscription.
+ EXPECT_CALL(*downloader_,
+ StartDownload(url, expected_retry_policy, testing::_))
+ .WillOnce([](const GURL&, AdblockResourceRequest::RetryPolicy,
+ base::OnceCallback<void(std::unique_ptr<FlatbufferData>)>
+ callback) {
+ // The downloader responds by running the callback with a new
+ // buffer, simulating a successful download.
+ std::move(callback).Run(std::make_unique<FakeBuffer>());
+ });
+ filtering_configuration_->AddFilterList(url);
+
+ // Storage was asked to store the buffer provided by downloader.
+ ASSERT_EQ(storage_->store_subscription_calls_.size(), 1u);
+ EXPECT_TRUE(storage_->store_subscription_calls_[0].first);
+ // Storage runs the callback provided by SubscriptionService to indicate
+ // store succeeded. This triggers the SubscriptionObserver.
+ EXPECT_CALL(observer_, Run(url));
+ std::move(storage_->store_subscription_calls_[0].second).Run(subscription);
+ storage_->store_subscription_calls_.clear();
+ }
+
+ void RemoveSubscription(scoped_refptr<FakeSubscription> subscription) {
+ DCHECK(filtering_configuration_) << "Call CreateTestee() first";
+ // Simulates a single call to UninstallSubscription that forwards the
+ // subscription to storage_ for removal.
+ EXPECT_CALL(persistent_metadata_,
+ RemoveMetadata(subscription->GetSourceUrl()));
+ filtering_configuration_->RemoveFilterList(subscription->GetSourceUrl());
+ ASSERT_EQ(storage_->remove_subscription_calls_.size(), 1u);
+ EXPECT_EQ(storage_->remove_subscription_calls_[0], subscription);
+ storage_->remove_subscription_calls_.clear();
+ }
+
+ void InitializeTesteeWithNoSubscriptions() {
+ CreateTestee({});
+ FinishStorageInitialization({});
+ }
+
+ const GURL kRequestUrl{"https://domain.com/resource.jpg"};
+ const GURL kParentUrl{"https://domain.com"};
+ const SiteKey kSitekey{"abc"};
+
+ std::unique_ptr<FakeFilteringConfiguration> filtering_configuration_;
+ raw_ptr<FakePersistentStorage> storage_;
+ raw_ptr<MockPreloadedSubscriptionProvider> preloaded_subscription_provider_;
+ raw_ptr<MockTaskScheduler> updater_;
+ raw_ptr<MockSubscriptionDownloader> downloader_;
+ raw_ptr<MockRecommendedSubscriptionInstaller>
+ recommended_subscription_installer_;
+ MockSubscriptionPersistentMetadata persistent_metadata_;
+ MockConversionExecutors conversion_executor_;
+ base::test::TaskEnvironment task_environment_;
+ base::MockCallback<
+ FilteringConfigurationMaintainerImpl::SubscriptionUpdatedCallback>
+ observer_;
+ base::RepeatingClosure run_update_check_callback_;
+ std::unique_ptr<FilteringConfigurationMaintainerImpl> testee_;
+};
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest, Initialization) {
+ std::vector<scoped_refptr<InstalledSubscription>> initial_subscriptions = {
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1"),
+ base::MakeRefCounted<FakeSubscription>("fake_subscription2")};
+ CreateTestee(initial_subscriptions);
+
+ // Before storage is initialized, no subscriptions are returned.
+ EXPECT_TRUE(testee_->GetCurrentSubscriptions().empty());
+
+ // Storage completes initialization, loads two subscriptions.
+ FinishStorageInitialization(initial_subscriptions);
+
+ // The subscriptions provided by storage are visible.
+ EXPECT_THAT(testee_->GetCurrentSubscriptions(),
+ testing::UnorderedElementsAre(initial_subscriptions[0],
+ initial_subscriptions[1]));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest, AddSubscription) {
+ // Storage has no initial subscriptions:
+ InitializeTesteeWithNoSubscriptions();
+
+ // When storage calls its callback, the provided subscription is added to the
+ // service and |on_finished| is triggered with the parsed URL.
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ AddSubscription(fake_subscription1);
+
+ // Added subscription is reflected in |GetCurrentSubscriptions|.
+ EXPECT_THAT(testee_->GetCurrentSubscriptions(),
+ testing::ElementsAre(fake_subscription1));
+
+ // The snapshot has a SubscriptionCollection that queries the added
+ // subscription. We can check whether FakeSubscription's title appears in
+ // Elemhide selectors.
+ auto snapshot = testee_->GetSubscriptionCollection();
+ auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_THAT(selectors, testing::ElementsAre(fake_subscription1->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ AddMultipleSubscriptionsAndRemoveOne) {
+ InitializeTesteeWithNoSubscriptions();
+
+ // Add 3 subscriptions.
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ auto fake_subscription2 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription2");
+ auto fake_subscription3 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription3");
+ AddSubscription(fake_subscription1);
+ AddSubscription(fake_subscription2);
+ AddSubscription(fake_subscription3);
+
+ // Remove middle one.
+ RemoveSubscription(fake_subscription2);
+
+ // Two remaining subscription are reflected in |GetInstalledSubscriptions|.
+ EXPECT_THAT(
+ testee_->GetCurrentSubscriptions(),
+ testing::UnorderedElementsAre(fake_subscription1, fake_subscription3));
+
+ // The snapshot has a SubscriptionCollection that queries the remaining
+ // subscriptions. We can check whether FakeSubscription's title appears in
+ // Elemhide selectors.
+ auto snapshot = testee_->GetSubscriptionCollection();
+ auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_THAT(selectors,
+ testing::UnorderedElementsAre(fake_subscription1->GetTitle(),
+ fake_subscription3->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ SnapshotNotAffectedByFutureAddition) {
+ InitializeTesteeWithNoSubscriptions();
+ // Add one subscription
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ AddSubscription(fake_subscription1);
+
+ // Take snapshot now.
+ auto snapshot = testee_->GetSubscriptionCollection();
+
+ // Add new subscription after snapshot.
+ auto fake_subscription2 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription2");
+ AddSubscription(fake_subscription2);
+
+ // Snapshot only contains the first subscription.
+ auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_THAT(selectors,
+ testing::UnorderedElementsAre(fake_subscription1->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ SnapshotNotAffectedByFutureRemoval) {
+ InitializeTesteeWithNoSubscriptions();
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ auto fake_subscription2 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription2");
+ AddSubscription(fake_subscription1);
+ AddSubscription(fake_subscription2);
+
+ // Take snapshot now.
+ auto snapshot = testee_->GetSubscriptionCollection();
+
+ // Remove second subscription.
+ RemoveSubscription(fake_subscription2);
+
+ // Snapshot still contains both subscriptions.
+ auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_THAT(selectors,
+ testing::UnorderedElementsAre(fake_subscription1->GetTitle(),
+ fake_subscription2->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ UpgradeExistingSubscription) {
+ InitializeTesteeWithNoSubscriptions();
+ auto expired_subscription =
+ base::MakeRefCounted<FakeSubscription>("expired_subscription");
+ auto young_subscription =
+ base::MakeRefCounted<FakeSubscription>("young_subscription");
+ AddSubscription(expired_subscription);
+ AddSubscription(young_subscription);
+
+ // Pretend one of the subscriptions expired.
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(expired_subscription->GetSourceUrl()))
+ .WillRepeatedly(testing::Return(true));
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(young_subscription->GetSourceUrl()))
+ .WillRepeatedly(testing::Return(false));
+ // Even though Acceptable Ads is not installed, its expiration will be checked
+ // to make a HEAD request if needed.
+ EXPECT_CALL(persistent_metadata_, IsExpired(AcceptableAdsUrl()))
+ .WillRepeatedly(testing::Return(false));
+
+ // Expect that the expired subscription will be re-downloaded.
+ EXPECT_CALL(*downloader_,
+ StartDownload(expired_subscription->GetSourceUrl(),
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ testing::_))
+ .WillOnce(base::test::RunOnceCallback<2>(std::make_unique<FakeBuffer>()));
+
+ // The young subscription will not be re-downloaded.
+ EXPECT_CALL(*downloader_, StartDownload(young_subscription->GetSourceUrl(),
+ testing::_, testing::_))
+ .Times(0);
+
+ run_update_check_callback_.Run();
+
+ // In a second run, even though |expired_subscription| might be marked as
+ // expired by persistent_metadata_, there will be no new download since one is
+ // already under way.
+ EXPECT_CALL(*downloader_, StartDownload(expired_subscription->GetSourceUrl(),
+ testing::_, testing::_))
+ .Times(0);
+ run_update_check_callback_.Run();
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ CallOnFilterListsChangedDuringUpgradeExistingSubscriptions) {
+ InitializeTesteeWithNoSubscriptions();
+ auto expired_subscription =
+ base::MakeRefCounted<FakeSubscription>("expired_subscription");
+ auto young_subscription =
+ base::MakeRefCounted<FakeSubscription>("young_subscription");
+ AddSubscription(expired_subscription);
+ AddSubscription(young_subscription);
+
+ // Pretend one of the subscriptions expired.
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(expired_subscription->GetSourceUrl()))
+ .WillRepeatedly(testing::Return(true));
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(young_subscription->GetSourceUrl()))
+ .WillRepeatedly(testing::Return(false));
+ // Even though Acceptable Ads is not installed, its expiration will be checked
+ // to make a HEAD request if needed.
+ EXPECT_CALL(persistent_metadata_, IsExpired(AcceptableAdsUrl()))
+ .WillRepeatedly(testing::Return(false));
+
+ // Expect that the expired subscription will be re-downloaded and call
+ // OnFilterListsChanged during update
+ EXPECT_CALL(*downloader_,
+ StartDownload(expired_subscription->GetSourceUrl(),
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ testing::_))
+ .WillOnce(testing::DoAll(
+ [&]() {
+ testee_->OnFilterListsChanged(filtering_configuration_.get());
+ },
+ base::test::RunOnceCallback<2>(std::make_unique<FakeBuffer>())));
+
+ // The young subscription will not be re-downloaded.
+ EXPECT_CALL(*downloader_, StartDownload(young_subscription->GetSourceUrl(),
+ testing::_, testing::_))
+ .Times(0);
+
+ run_update_check_callback_.Run();
+
+ // In a second run, even though |expired_subscription| might be marked as
+ // expired by persistent_metadata_, there will be no new download since one is
+ // already under way.
+ EXPECT_CALL(*downloader_, StartDownload(expired_subscription->GetSourceUrl(),
+ testing::_, testing::_))
+ .Times(0);
+ run_update_check_callback_.Run();
+
+ // After OnFilterListsChanged, check if number of subscriptions is unchanged
+ // (DPD-2015)
+ EXPECT_EQ(2u, testee_->GetCurrentSubscriptions().size());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ UpdatePingStoresAAversion) {
+ InitializeTesteeWithNoSubscriptions();
+ // Once the update check runs, even though Acceptable Ads is not installed,
+ // pretend its expired. This will trigger a HEAD request.
+ EXPECT_CALL(persistent_metadata_, IsExpired(AcceptableAdsUrl()))
+ .WillRepeatedly(testing::Return(true));
+
+ SubscriptionDownloader::HeadRequestCallback download_completed_callback;
+ EXPECT_CALL(*downloader_, DoHeadRequest(AcceptableAdsUrl(), testing::_))
+ .WillOnce(MoveArg<1>(&download_completed_callback));
+
+ run_update_check_callback_.Run();
+
+ // When the HEAD request finishes, the service will store the parsed version
+ // and the expiration interval.
+ const std::string version("202107210821");
+ EXPECT_CALL(persistent_metadata_, SetVersion(AcceptableAdsUrl(), version));
+ // The next ping should happen in a day.
+ EXPECT_CALL(persistent_metadata_,
+ SetExpirationInterval(AcceptableAdsUrl(), base::Days(1)));
+ std::move(download_completed_callback).Run(version);
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ SendPingOnlyForAdblockConfig) {
+ InitializeTesteeWithNoSubscriptions();
+ EXPECT_CALL(persistent_metadata_, IsExpired(AcceptableAdsUrl()))
+ .WillRepeatedly(testing::Return(true));
+ SubscriptionDownloader::HeadRequestCallback download_completed_callback;
+
+ // For default "adblock" config ping is sent
+ EXPECT_CALL(*downloader_, DoHeadRequest(AcceptableAdsUrl(), testing::_))
+ .WillOnce(MoveArg<1>(&download_completed_callback));
+ run_update_check_callback_.Run();
+
+ // For non "adblock" config ping is not sent
+ filtering_configuration_->name = "crumbs";
+ EXPECT_CALL(*downloader_, DoHeadRequest(AcceptableAdsUrl(), testing::_))
+ .Times(0);
+ run_update_check_callback_.Run();
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ RemoveDuplicatesDuringInitialLoad) {
+ // Storage returns 3 subscriptions in initial load, however there is a
+ // duplicate, due to a race condition or corruption.
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ auto fake_subscription2 =
+ base::MakeRefCounted<FakeSubscription>("unique_subscription");
+ auto fake_subscription3 =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ ASSERT_EQ(fake_subscription1->GetSourceUrl(),
+ fake_subscription3->GetSourceUrl());
+
+ CreateTestee({fake_subscription1, fake_subscription2});
+ FinishStorageInitialization(
+ {fake_subscription1, fake_subscription2, fake_subscription3});
+
+ // Service noticed one subscription is duplicated and it removes one instance
+ // - it is unspecified which.
+ ASSERT_EQ(storage_->remove_subscription_calls_.size(), 1u);
+ EXPECT_EQ(storage_->remove_subscription_calls_[0]->GetSourceUrl(),
+ fake_subscription1->GetSourceUrl());
+
+ // Installed subscriptions do not contain duplicates.
+ std::vector<GURL> current_subscriptions_urls;
+ base::ranges::transform(testee_->GetCurrentSubscriptions(),
+ std::back_inserter(current_subscriptions_urls),
+ [](const auto& sub) { return sub->GetSourceUrl(); });
+ EXPECT_THAT(
+ current_subscriptions_urls,
+ testing::UnorderedElementsAre(fake_subscription1->GetSourceUrl(),
+ fake_subscription2->GetSourceUrl()));
+
+ // ContentFiltersData returned by snapshot do not contain duplicates.
+ const auto snapshot = testee_->GetSubscriptionCollection();
+
+ const auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_EQ(selectors.size(), 2u);
+ EXPECT_THAT(selectors,
+ testing::UnorderedElementsAre(fake_subscription1->GetTitle(),
+ fake_subscription2->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ CancellingInstallationDuringDownload_WithPreloadedFallback) {
+ // Storage has no initial subscriptions:
+ InitializeTesteeWithNoSubscriptions();
+
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ const GURL& url = fake_subscription1->GetSourceUrl();
+
+ // SubscriptionDownloader will be called to fetch the subscription. We will
+ // trigger the response later, after cancelling installation.
+ SubscriptionDownloader::DownloadCompletedCallback download_completed_callback;
+ EXPECT_CALL(*downloader_, StartDownload(url, testing::_, testing::_))
+ .WillOnce(MoveArg<2>(&download_completed_callback));
+
+ // There is a preloaded fallback available for this URL.
+ auto preloaded_subscription = base::MakeRefCounted<FakeSubscription>(
+ "fake_subscription1", Subscription::InstallationState::Preloaded);
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ GetCurrentPreloadedSubscriptions())
+ .WillRepeatedly(
+ testing::Return(std::vector<scoped_refptr<InstalledSubscription>>{
+ preloaded_subscription}));
+
+ // Start installation.
+ filtering_configuration_->AddFilterList(url);
+
+ // We should see the preloaded fallback in GetCurrentSubscriptions().
+ EXPECT_THAT(testee_->GetCurrentSubscriptions(),
+ testing::UnorderedElementsAre(preloaded_subscription));
+
+ // We now uninstall the subscription, this should cancel the download.
+ // The observer is never notified about success.
+ EXPECT_CALL(observer_, Run(testing::_)).Times(0);
+ // The downloader is told to cancel the download.
+ EXPECT_CALL(*downloader_, CancelDownload(url));
+ filtering_configuration_->RemoveFilterList(url);
+
+ // The subscription is no longer listed.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ GetCurrentPreloadedSubscriptions())
+ .WillRepeatedly(
+ testing::Return(std::vector<scoped_refptr<InstalledSubscription>>{}));
+ EXPECT_TRUE(testee_->GetCurrentSubscriptions().empty());
+
+ // Even when the download callback delivers the FakeBuffer, it will not
+ // be sent to storage.
+ std::move(download_completed_callback).Run(std::make_unique<FakeBuffer>());
+ // There are no attempts to store the buffer received from Downloader.
+ EXPECT_TRUE(storage_->store_subscription_calls_.empty());
+ EXPECT_TRUE(testee_->GetCurrentSubscriptions().empty());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ CancellingInstallationDuringStorage_NoFallback) {
+ // Storage has no initial subscriptions:
+ InitializeTesteeWithNoSubscriptions();
+
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("fake_subscription1");
+ const GURL& url = fake_subscription1->GetSourceUrl();
+
+ // There are no preloaded fallback available for this URL.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ GetCurrentPreloadedSubscriptions())
+ .WillRepeatedly(
+ testing::Return(std::vector<scoped_refptr<InstalledSubscription>>{}));
+ // SubscriptionDownloader will be called to fetch the subscription. It is
+ // immediately successful.
+ EXPECT_CALL(*downloader_, StartDownload(url, testing::_, testing::_))
+ .WillOnce(
+ [&](const GURL&, AdblockResourceRequest::RetryPolicy,
+ SubscriptionDownloader::DownloadCompletedCallback callback) {
+ std::move(callback).Run(std::make_unique<FakeBuffer>());
+ });
+
+ // Start installation.
+ filtering_configuration_->AddFilterList(url);
+
+ // The downloader immediately returned a FakeBuffer, it should have been sent
+ // to storage.
+ ASSERT_EQ(storage_->store_subscription_calls_.size(), 1u);
+
+ // We should see the ongoing installation in GetCurrentSubscriptions().
+ const auto current_subscriptions = testee_->GetCurrentSubscriptions();
+ ASSERT_EQ(current_subscriptions.size(), 1u);
+ EXPECT_EQ(current_subscriptions[0]->GetSourceUrl(), url);
+ EXPECT_EQ(current_subscriptions[0]->GetInstallationState(),
+ Subscription::InstallationState::Installing);
+
+ // We now uninstall the subscription, this should cancel the installation.
+ // The observer is never notified about success.
+ EXPECT_CALL(observer_, Run(testing::_)).Times(0);
+ filtering_configuration_->RemoveFilterList(url);
+
+ // The subscription is no longer listed.
+ EXPECT_TRUE(testee_->GetCurrentSubscriptions().empty());
+
+ // Even when the storage callback delivers the Subscription, it will not
+ // be installed in SubscriptionService.
+ std::move(storage_->store_subscription_calls_[0].second)
+ .Run(fake_subscription1);
+ // In fact, the subscription will be scheduled for removal from storage, it
+ // is not desired.
+ ASSERT_EQ(storage_->remove_subscription_calls_.size(), 1u);
+ EXPECT_EQ(storage_->remove_subscription_calls_[0], fake_subscription1);
+
+ EXPECT_TRUE(testee_->GetCurrentSubscriptions().empty());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest, CustomFilterIsAdded) {
+ auto fake_subscription1 =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ InitializeTesteeWithNoSubscriptions();
+ AddSubscription(fake_subscription1);
+
+ std::string filter = "test";
+ std::vector<std::string> filters = {filter};
+ EXPECT_CALL(conversion_executor_, ConvertCustomFilters(filters))
+ .WillOnce(testing::Return(
+ base::MakeRefCounted<FakeSubscription>(CustomFiltersUrl().spec())));
+ filtering_configuration_->AddCustomFilter(filter);
+
+ // The in-memory subscription containing the custom filter is not reported
+ // among current subscriptions, only the subscription added by client is.
+ EXPECT_THAT(testee_->GetCurrentSubscriptions(),
+ testing::UnorderedElementsAre(fake_subscription1));
+
+ // However, the SubscriptionCollection *does* get the custom filter
+ // subscription.
+ auto snapshot = testee_->GetSubscriptionCollection();
+
+ auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_THAT(selectors, testing::UnorderedElementsAre(
+ CustomFiltersUrl().spec(), "subscription"));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ AllowedDomainIsImplementedAsCustomFilter) {
+ InitializeTesteeWithNoSubscriptions();
+ // Allowed domains and user-added custom filters are combined into one list.
+ std::string custom_filter = "test";
+ std::string domain = "examPLE.com";
+ std::string expected_allowing_filter =
+ "@@||example.com^$document,domain=example.com";
+ // We first add a custom filter, converter is called with it only:
+ EXPECT_CALL(conversion_executor_,
+ ConvertCustomFilters(std::vector<std::string>{custom_filter}))
+ .WillOnce(testing::Return(
+ base::MakeRefCounted<FakeSubscription>(CustomFiltersUrl().spec())));
+ filtering_configuration_->AddCustomFilter(custom_filter);
+
+ // We then add an allowed domain, the converter is called with both the custom
+ // filter and the domain filter.
+ EXPECT_CALL(conversion_executor_,
+ ConvertCustomFilters(std::vector<std::string>{
+ custom_filter, expected_allowing_filter}))
+ .WillOnce(testing::Return(
+ base::MakeRefCounted<FakeSubscription>(CustomFiltersUrl().spec())));
+ filtering_configuration_->AddAllowedDomain(domain);
+
+ // When an allowed domain is removed, the custom filter is removed as well.
+ // We're back to just converting the user-added custom filter.
+ EXPECT_CALL(conversion_executor_,
+ ConvertCustomFilters(std::vector<std::string>{custom_filter}))
+ .WillOnce(testing::Return(
+ base::MakeRefCounted<FakeSubscription>(CustomFiltersUrl().spec())));
+ filtering_configuration_->RemoveAllowedDomain(domain);
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ PreloadedSubscriptionProviderUpdatedDuringChanges) {
+ testing::InSequence sequence;
+ InitializeTesteeWithNoSubscriptions();
+ // When starting a download, inform provider about new pending subscription.
+ auto first_subscription =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{},
+ std::vector<GURL>{first_subscription->GetSourceUrl()}));
+
+ // Start download.
+ SubscriptionDownloader::DownloadCompletedCallback download_completed_callback;
+ EXPECT_CALL(*downloader_, StartDownload(testing::_, testing::_, testing::_))
+ .WillOnce(MoveArg<2>(&download_completed_callback));
+ filtering_configuration_->AddFilterList(first_subscription->GetSourceUrl());
+ // When download completes, update the provider about new installed
+ // subscription, and no pending subscriptions.
+ EXPECT_CALL(
+ *preloaded_subscription_provider_,
+ UpdateSubscriptions(std::vector<GURL>{first_subscription->GetSourceUrl()},
+ std::vector<GURL>{}));
+
+ // Download completes.
+ EXPECT_CALL(observer_, Run(first_subscription->GetSourceUrl()));
+ std::move(download_completed_callback).Run(std::make_unique<FakeBuffer>());
+ std::move(storage_->store_subscription_calls_.back().second)
+ .Run(first_subscription);
+
+ // Second subscription added.
+ auto second_subscription =
+ base::MakeRefCounted<FakeSubscription>("subscription2");
+ // Provider updated with both the old installed subscription and the new
+ // ongoing download.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{first_subscription->GetSourceUrl()},
+ std::vector<GURL>{second_subscription->GetSourceUrl()}));
+
+ // Second download starts.
+ EXPECT_CALL(*downloader_, StartDownload(testing::_, testing::_, testing::_))
+ .WillOnce(MoveArg<2>(&download_completed_callback));
+
+ filtering_configuration_->AddFilterList(second_subscription->GetSourceUrl());
+
+ // When second download completes, provider has two installed and zero pending
+ // subscriptions.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{first_subscription->GetSourceUrl(),
+ second_subscription->GetSourceUrl()},
+ std::vector<GURL>{}));
+ EXPECT_CALL(observer_, Run(second_subscription->GetSourceUrl()));
+ std::move(download_completed_callback).Run(std::make_unique<FakeBuffer>());
+ std::move(storage_->store_subscription_calls_.back().second)
+ .Run(second_subscription);
+
+ // First subscription is uninstalled, provider informed about new state
+ // containing only the second subscription.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{second_subscription->GetSourceUrl()},
+ std::vector<GURL>{}));
+ filtering_configuration_->RemoveFilterList(
+ first_subscription->GetSourceUrl());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ PreloadedSubscriptionProviderUpdatedDuringFailedDownload) {
+ testing::InSequence sequence;
+ InitializeTesteeWithNoSubscriptions();
+ // When starting a download, inform provider about new pending subscription.
+ auto first_subscription =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{},
+ std::vector<GURL>{first_subscription->GetSourceUrl()}));
+
+ // Start download.
+ SubscriptionDownloader::DownloadCompletedCallback download_completed_callback;
+ EXPECT_CALL(*downloader_, StartDownload(testing::_, testing::_, testing::_))
+ .WillOnce(MoveArg<2>(&download_completed_callback));
+ filtering_configuration_->AddFilterList(first_subscription->GetSourceUrl());
+ // When download fails, inform the provider about returning to previous state.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(std::vector<GURL>{}, std::vector<GURL>{}));
+
+ // Download fails.
+ std::move(download_completed_callback).Run(nullptr);
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ PreloadedSubscriptionProviderUpdatedWhenInstallationCancelled) {
+ testing::InSequence sequence;
+ InitializeTesteeWithNoSubscriptions();
+ // When starting a download, inform provider about new pending subscription.
+ auto first_subscription =
+ base::MakeRefCounted<FakeSubscription>("subscription");
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(
+ std::vector<GURL>{},
+ std::vector<GURL>{first_subscription->GetSourceUrl()}));
+
+ // Start download.
+ SubscriptionDownloader::DownloadCompletedCallback download_completed_callback;
+ EXPECT_CALL(*downloader_, StartDownload(testing::_, testing::_, testing::_))
+ .WillOnce(MoveArg<2>(&download_completed_callback));
+ filtering_configuration_->AddFilterList(first_subscription->GetSourceUrl());
+ // When installation is cancelled, inform the provider about returning to
+ // previous state.
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ UpdateSubscriptions(std::vector<GURL>{}, std::vector<GURL>{}))
+ .Times(testing::AtLeast(1));
+ filtering_configuration_->RemoveFilterList(
+ first_subscription->GetSourceUrl());
+
+ // Download completes, but the installation was cancelled in the mean time.
+ std::move(download_completed_callback).Run(std::make_unique<FakeBuffer>());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ PreloadedSubscriptionProviderConsultedForSnapshot) {
+ auto subscription_in_service =
+ base::MakeRefCounted<FakeSubscription>("subscription_in_service");
+ auto preloaded_subscription = base::MakeRefCounted<FakeSubscription>(
+ "preloaded_subscription", Subscription::InstallationState::Preloaded);
+ InitializeTesteeWithNoSubscriptions();
+ AddSubscription(subscription_in_service);
+
+ EXPECT_CALL(*preloaded_subscription_provider_,
+ GetCurrentPreloadedSubscriptions())
+ .WillOnce(
+ testing::Return(std::vector<scoped_refptr<InstalledSubscription>>{
+ preloaded_subscription}));
+
+ // Snapshot provides both the subscription in service and the preloaded
+ // subscription returned by provider.
+ const auto snapshot = testee_->GetSubscriptionCollection();
+ const auto selectors =
+ snapshot->GetElementHideData(GURL(), {}, SiteKey()).elemhide_selectors;
+ EXPECT_EQ(selectors.size(), 2u);
+ EXPECT_THAT(selectors, testing::UnorderedElementsAre(
+ subscription_in_service->GetTitle(),
+ preloaded_subscription->GetTitle()));
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ AcceptableAdsMetadataRetained) {
+ auto aa_subscription =
+ base::MakeRefCounted<FakeSubscription>("exceptionrules.txt");
+ auto easylist_subscription =
+ base::MakeRefCounted<FakeSubscription>("easylist.txt");
+ InitializeTesteeWithNoSubscriptions();
+ AddSubscription(aa_subscription);
+ AddSubscription(easylist_subscription);
+
+ // Removing EasyList clears the subscription's metadata.
+ EXPECT_CALL(persistent_metadata_,
+ RemoveMetadata(easylist_subscription->GetSourceUrl()));
+ filtering_configuration_->RemoveFilterList(
+ easylist_subscription->GetSourceUrl());
+
+ // Removing the Acceptable Ads subscription retains metadata, in order to
+ // allow sending continued HEAD-only update-like requests with consistent
+ // expiry date.
+ EXPECT_CALL(persistent_metadata_,
+ RemoveMetadata(aa_subscription->GetSourceUrl()))
+ .Times(0);
+ filtering_configuration_->RemoveFilterList(aa_subscription->GetSourceUrl());
+}
+
+TEST_F(AdblockFilteringConfigurationMaintainerImplTest,
+ RecommendedSubsUpdatedBeforeFilterListUpdateCheck) {
+ auto temporary_subscription =
+ base::MakeRefCounted<FakeSubscription>("temp_sub.txt");
+ auto easylist_subscription =
+ base::MakeRefCounted<FakeSubscription>("easylist.txt");
+ InitializeTesteeWithNoSubscriptions();
+ AddSubscription(temporary_subscription);
+ AddSubscription(easylist_subscription);
+
+ // Recommended subscription update check removes temporary subscription.
+ EXPECT_CALL(*recommended_subscription_installer_, RunUpdateCheck())
+ .WillOnce([&]() { RemoveSubscription(temporary_subscription); });
+
+ // Temporary subscription already removed, will not check for update.
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(temporary_subscription->GetSourceUrl()))
+ .Times(0);
+
+ EXPECT_CALL(persistent_metadata_,
+ IsExpired(easylist_subscription->GetSourceUrl()))
+ .WillRepeatedly(testing::Return(false));
+
+ EXPECT_CALL(persistent_metadata_, IsExpired(AcceptableAdsUrl()))
+ .WillRepeatedly(testing::Return(false));
+
+ run_update_check_callback_.Run();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_csp_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_csp_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_csp_test.cc
@@ -0,0 +1,236 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/rand_util.h"
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+#include "gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplCSPTest
+ : public AdblockInstalledSubscriptionImplTestBase {
+ public:
+};
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, CspFilterForUrl) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'self'
+ )");
+
+ std::set<std::string_view> filters1;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking, filters1);
+ EXPECT_THAT(filters1, testing::UnorderedElementsAre("script-src 'self'"));
+
+ // Different URL, not found.
+ std::set<std::string_view> filters2;
+ subscriptions->FindCspFilters(GURL("https://test.org/resource.jpg"),
+ "test.com", FilterCategory::Blocking, filters2);
+ EXPECT_TRUE(filters2.empty());
+
+ // Allowing filter, not found.
+ std::set<std::string_view> filters3;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Allowing, filters3);
+ EXPECT_TRUE(filters3.empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, MultipleCspFiltersForUrl) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'first'
+ test.com^$csp=script-src 'second'
+ )");
+
+ std::set<std::string_view> filters1;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking, filters1);
+ EXPECT_THAT(filters1, testing::UnorderedElementsAre(
+ std::string_view("script-src 'first'"),
+ std::string_view("script-src 'second'")));
+
+ // Different URL, not found.
+ std::set<std::string_view> filters2;
+ subscriptions->FindCspFilters(GURL("https://test.org/resource.jpg"),
+ "test.com", FilterCategory::Blocking, filters2);
+ EXPECT_TRUE(filters2.empty());
+
+ // Allowing filter, not found.
+ std::set<std::string_view> filters3;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Allowing, filters3);
+ EXPECT_TRUE(filters3.empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, CspFilterForDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ $csp=script-src 'self' '*' 'unsafe-inline',domain=dom-a.com|dom-b.com
+ )");
+
+ std::set<std::string_view> filters1;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "dom-b.com", FilterCategory::Blocking,
+ filters1);
+ EXPECT_THAT(filters1, testing::UnorderedElementsAre(
+ "script-src 'self' '*' 'unsafe-inline'"));
+
+ std::set<std::string_view> filters2;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "dom-a.com", FilterCategory::Blocking,
+ filters2);
+ EXPECT_THAT(filters2, testing::UnorderedElementsAre(
+ "script-src 'self' '*' 'unsafe-inline'"));
+
+ std::set<std::string_view> filters3;
+ subscriptions->FindCspFilters(GURL("https://dom-a.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking, filters3);
+ EXPECT_TRUE(filters3.empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, AllowingCspFilterNoPayload) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'self'
+ @@test.com^$csp
+ )");
+
+ std::set<std::string_view> blocking_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking,
+ blocking_filters);
+ EXPECT_THAT(blocking_filters,
+ testing::UnorderedElementsAre("script-src 'self'"));
+
+ // Allowing filter is found, with an empty payload.
+ std::set<std::string_view> allowing_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Allowing,
+ allowing_filters);
+ EXPECT_THAT(allowing_filters, testing::UnorderedElementsAre(""));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, AllowingCspFilterWithPayload) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'self'
+ @@test.com^$csp=script-src 'self'
+ )");
+
+ std::set<std::string_view> blocking_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking,
+ blocking_filters);
+ EXPECT_THAT(blocking_filters,
+ testing::UnorderedElementsAre("script-src 'self'"));
+
+ // Allowing filter is found, with payload.
+ std::set<std::string_view> allowing_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Allowing,
+ allowing_filters);
+ EXPECT_THAT(allowing_filters,
+ testing::UnorderedElementsAre("script-src 'self'"));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest,
+ MultipleAllowingCspFiltersWithPayload) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'self'
+ @@test.com^$csp=script-src 'first'
+ @@test.com^$csp=script-src 'second'
+ )");
+
+ std::set<std::string_view> expected_blocking{"script-src 'self'"};
+
+ std::set<std::string_view> blocking_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking,
+ blocking_filters);
+ EXPECT_EQ(expected_blocking, blocking_filters);
+
+ // Allowing filter is found, with payload.
+ std::set<std::string_view> expected_allowing{"script-src 'first'",
+ "script-src 'second'"};
+
+ std::set<std::string_view> allowing_filters;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Allowing,
+ allowing_filters);
+ EXPECT_EQ(expected_allowing, allowing_filters);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, DomainSpecificOnlyCspFilter) {
+ // This filter is not domain-specific.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp=script-src 'self'
+ )");
+
+ // It's not found when domain_specific_only = true.
+ std::set<std::string_view> results;
+ subscriptions->FindCspFilters(
+ GURL("https://test.com/resource.jpg"), "test.com",
+ FilterCategory::DomainSpecificBlocking, results);
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest, ThirdPartyCspFilters) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ only-third.com^$csp=only-third,third-party
+ never-third.com^$csp=never-third,~third-party
+ )");
+
+ // only-third is only found when the URL is from a different domain.
+
+ std::set<std::string_view> filters1;
+ subscriptions->FindCspFilters(GURL("https://only-third.com/resource.jpg"),
+ "only-third.com", FilterCategory::Blocking,
+ filters1);
+ EXPECT_TRUE(filters1.empty());
+
+ // never-third is only found when the URL is from the same domain.
+ std::set<std::string_view> filters2;
+ subscriptions->FindCspFilters(GURL("https://never-third.com/resource.jpg"),
+ "never-third.com", FilterCategory::Blocking,
+ filters2);
+ EXPECT_THAT(filters2, testing::UnorderedElementsAre("never-third"));
+
+ std::set<std::string_view> results3;
+ subscriptions->FindCspFilters(GURL("https://never-third.com/resource.jpg"),
+ "different.com", FilterCategory::Blocking,
+ results3);
+ EXPECT_TRUE(results3.empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplCSPTest,
+ BlockingCspFilterWithoutPayloadIgnored) {
+ // It's impossible to say what CSP header should be injected if the filter
+ // doesn't specify.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$csp
+ )");
+
+ std::set<std::string_view> results;
+ subscriptions->FindCspFilters(GURL("https://test.com/resource.jpg"),
+ "test.com", FilterCategory::Blocking, results);
+ EXPECT_TRUE(results.empty());
+}
+
+// TODO(mpawlowski) support multiple CSP filters per URL + frame hierarchy:
+// DPD-1145.
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_elemhide_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_elemhide_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_elemhide_test.cc
@@ -0,0 +1,566 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/installed_subscription.h"
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+#include "gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplElemhideTest
+ : public AdblockInstalledSubscriptionImplTestBase {};
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_generic_selector) {
+ auto subscriptions = ConvertAndLoadRules("##.zad.billboard");
+ auto selectors = subscriptions->GetElemhideData(
+ GURL("https://pl.ign.com/marvels-avengers/41262/news/"
+ "marvels-avengers-kratos-zagra-czarna-pantere"),
+ false);
+ EXPECT_EQ(FilterSelectors(selectors),
+ std::set<std::string_view>({".zad.billboard"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, Elementhide_excludes_sub) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example.org###ad_1
+ example.org###ad_2
+ foo.example.org#@##ad_2
+ )");
+ const auto selectors_1 =
+ subscriptions->GetElemhideData(GURL("http://foo.example.org"), false);
+
+ const auto selectors_2 =
+ subscriptions->GetElemhideData(GURL("http://example.org"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors_1),
+ std::set<std::string_view>({"#ad_1"}));
+ EXPECT_EQ(FilterSelectors(selectors_2),
+ std::set<std::string_view>({"#ad_1", "#ad_2"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_domain_specific) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ! other type of filters
+ /testcasefiles/blocking/addresspart/abptestcasepath/
+ example.org#?#div:-abp-properties(width: 213px)
+ ! element hiding selectors
+ ###testcase-eh-id
+ example.org###testcase-eh-id
+ example.org##.testcase-eh-class
+ example.org##.testcase-container > .testcase-eh-descendant
+ ~foo.example.org,example.org##foo
+ ~othersiteneg.org##testneg
+ ! other site
+ )");
+ auto selectors =
+ subscriptions->GetElemhideData(GURL("http://example.org"), false);
+ EXPECT_EQ(std::set<std::string_view>(
+ {"#testcase-eh-id", "testneg",
+ ".testcase-container > .testcase-eh-descendant",
+ ".testcase-eh-class", "foo"}),
+ FilterSelectors(selectors));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, Elementhide_same_result) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example1.org###testcase-eh-id
+ example2.org###testcase-eh-id
+ )");
+ auto selectors_1 =
+ subscriptions->GetElemhideData(GURL("http://example1.org"), false);
+
+ auto selectors_2 =
+ subscriptions->GetElemhideData(GURL("http://example2.org"), false);
+
+ auto selectors_3 = subscriptions->GetElemhideData(
+ GURL("http://non-existing-domain.com"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors_1), FilterSelectors(selectors_2));
+ EXPECT_EQ(FilterSelectors(selectors_1),
+ std::set<std::string_view>({"#testcase-eh-id"}));
+ EXPECT_EQ(FilterSelectors(selectors_3).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_exception_main_domain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ sub.example.org###testcase-eh-id
+ example.org#@##testcase-eh-id
+ )");
+ auto selectors =
+ subscriptions->GetElemhideData(GURL("http://sub.example.org"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_apply_just_domain) {
+ auto subscriptions = ConvertAndLoadRules("example.org###div");
+
+ auto selectors =
+ subscriptions->GetElemhideData(GURL("http://example.org"), true);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>({"#div"}));
+ auto selectors2 =
+ subscriptions->GetElemhideData(GURL("http://example2.org"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors2).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, Elementhideemu_generic) {
+ auto subscriptions = ConvertAndLoadRules("example.org#?#foo");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("http://example.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>({"foo"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_allow_list) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example.org#?#foo
+ example.org#@#foo
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("http://example.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_allow_list_2) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example.org#?#foo
+ example.org#?#another
+ example.org#@#foo
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("http://example.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors),
+ std::set<std::string_view>({"another"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_allow_list_3) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example.org#?#foo
+ example.org#?#another
+ example2.org#?#foo
+ example.org#@#foo
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("http://example2.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>({"foo"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_domain_n_subdomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ !other type of filters
+ /testcasefiles/blocking/addresspart/abptestcasepath/
+ example.org###testcase-eh-id
+ !element hiding emulation selectors
+ example.org#?#div:-abp-properties(width: 213px)
+ example.org#?#div:-abp-has(>div>img.testcase-es-has)
+ example.org#?#span:-abp-contains(ESContainsTarget)
+ ~foo.example.org,example.org#?#div:-abp-properties(width: 213px)
+ !allowlisted
+ example.org#@#foo
+ !other site
+ othersite.com###testcase-eh-id
+ )");
+ const auto selectors_1 =
+ subscriptions->GetElemhideEmulationData(GURL("http://example.org"));
+
+ // should be 3 unique selectors
+ EXPECT_EQ(FilterSelectors(selectors_1),
+ std::set<std::string_view>({
+ "div:-abp-properties(width: 213px)",
+ "div:-abp-has(>div>img.testcase-es-has)",
+ "span:-abp-contains(ESContainsTarget)",
+ }));
+
+ const auto selectors_2 =
+ subscriptions->GetElemhideEmulationData(GURL("http://foo.example.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors_2),
+ std::set<std::string_view>({
+ "div:-abp-properties(width: 213px)",
+ "div:-abp-has(>div>img.testcase-es-has)",
+ "span:-abp-contains(ESContainsTarget)",
+ }));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_excludes_sub) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example.org#?#general
+ example.org#?#specific
+ foo.example.org#@#specific
+ )");
+ const auto selectors_1 =
+ subscriptions->GetElemhideEmulationData(GURL("http://foo.example.org"));
+
+ const auto selectors_2 =
+ subscriptions->GetElemhideEmulationData(GURL("http://example.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors_1),
+ std::set<std::string_view>{"general"});
+
+ EXPECT_EQ(FilterSelectors(selectors_2), std::set<std::string_view>({
+ "general",
+ "specific",
+ }));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, Elementhideemu_list_diff) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ example1.org#?#div:-abp-properties(width: 213px)
+ example2.org#?#div:-abp-properties(width: 213px)
+ example2.org#@#div:-abp-properties(width: 213px)
+ )");
+ const auto selectors_1 =
+ subscriptions->GetElemhideEmulationData(GURL("http://example1.org"));
+
+ EXPECT_EQ(FilterSelectors(selectors_1),
+ std::set<std::string_view>{"div:-abp-properties(width: 213px)"});
+
+ const auto selectors_2 =
+ subscriptions->GetElemhideEmulationData(GURL("http://example2.org"));
+ EXPECT_EQ(FilterSelectors(selectors_2).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_exception_with_excluded_url) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ###_AD
+ ~imore.com#@##_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.imore.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>{"#_AD"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_with_generic_excluded_url) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ~imore.com###_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.domain.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>{"#_AD"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_top_tier_domain_match) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ com###_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.domain.com/"), true);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>{"#_AD"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_with_excluded_url_specific) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ###_AD
+ ~imore.com#@##_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.domain.com/"), true);
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_with_excluded_url_case_insensitive) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ###_AD
+ ~IMore.com#@##_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.imore.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>{"#_AD"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_with_excluded_url_case_insensitive_2) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ###_AD
+ ~imore.com#@##_AD
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://www.IMORE.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>{"#_AD"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_case_insensitive) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ domain.com#?#.ad_box
+ EXAMPLE.com#?#.ad_box
+ )");
+ EXPECT_EQ(FilterSelectors(subscriptions->GetElemhideEmulationData(
+ GURL("https://DOMAIN.com/"))),
+ std::set<std::string_view>{".ad_box"});
+ EXPECT_EQ(FilterSelectors(subscriptions->GetElemhideEmulationData(
+ GURL("https://example.com/"))),
+ std::set<std::string_view>{".ad_box"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhide_exception_subdomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ##.ad_box
+ domain.com,~www.domain.com#@#.ad_box
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://domain.com/"), false);
+ const auto selectors_2 =
+ subscriptions->GetElemhideData(GURL("https://www.domain.com/"), false);
+ const auto selectors_3 =
+ subscriptions->GetElemhideData(GURL("https://domain2.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+ EXPECT_EQ(FilterSelectors(selectors_2),
+ std::set<std::string_view>{".ad_box"});
+ EXPECT_EQ(FilterSelectors(selectors_3),
+ std::set<std::string_view>{".ad_box"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_exception_subdomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ domain.com#?#.ad_box
+ mail.domain.com,~www.mail.domain.com#@#.ad_box
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("https://mail.domain.com/"));
+ const auto selectors_2 = subscriptions->GetElemhideEmulationData(
+ GURL("https://www.mail.domain.com/"));
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+
+ EXPECT_EQ(FilterSelectors(selectors_2),
+ std::set<std::string_view>{".ad_box"});
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_without_include_domains) {
+ // The Elemhide emu filter has no include domains, only an exclude domain,
+ // which makes it generic. Elemhide emu filters cannot be generic, so we
+ // don't apply this filter.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ~domain.com#?#.ad_box
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("https://test.com/"));
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ Elementhideemu_on_top_level_domain) {
+ // The Elemhide emu filter is defined to apply on all .com domains.
+ // Elemhide emu filters cannot be applied so widely.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ com#?#.ad_box
+ .com#?#.ad_box
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideEmulationData(GURL("https://test.com/"));
+
+ EXPECT_EQ(FilterSelectors(selectors).size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, EscapeSelectors) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com###foo[data-bar='{{foo: 1}}']
+ )");
+ const auto selectors =
+ subscriptions->GetElemhideData(GURL("https://test.com/"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors),
+ std::set<std::string_view>(
+ {"#foo[data-bar='\\7b \\7b foo: 1\\7d \\7d ']"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ RemoveAcceptedFromPriviledgedList) {
+ auto subscription = ConvertAndLoadRules(R"(
+ example.com##foo {remove: true;}
+ example.com#?#bar {remove: true;}
+ )",
+ {}, true);
+ const auto eh_data =
+ subscription->GetElemhideData(GURL("http://example.com"), false);
+ const auto ehe_data =
+ subscription->GetElemhideEmulationData(GURL("http://example.com"));
+ EXPECT_EQ(0u, eh_data.remove_selectors.size());
+ EXPECT_EQ(2u, ehe_data.remove_selectors.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ RemoveAcceptedFromNonPriviledgedList) {
+ auto subscription = ConvertAndLoadRules(R"(
+ example.com##foo {remove: true;}
+ example.com#?#bar {remove: true;}
+ )",
+ {}, false);
+ const auto eh_data =
+ subscription->GetElemhideData(GURL("http://example.com"), false);
+ const auto ehe_data =
+ subscription->GetElemhideEmulationData(GURL("http://example.com"));
+ EXPECT_EQ(0u, eh_data.remove_selectors.size());
+ EXPECT_EQ(2u, ehe_data.remove_selectors.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ InlineCssAcceptedFromPriviledgedList) {
+ auto subscription = ConvertAndLoadRules(R"(
+ example.com##foo {color: #000;}
+ example.com#?#bar {color: #000;}
+ )",
+ {}, true);
+ const auto eh_data =
+ subscription->GetElemhideData(GURL("http://example.com"), false);
+ const auto ehe_data =
+ subscription->GetElemhideEmulationData(GURL("http://example.com"));
+ EXPECT_EQ(0u, eh_data.selectors_to_inline_css.size());
+ EXPECT_EQ(2u, ehe_data.selectors_to_inline_css.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest,
+ InlineCssIgnoredFromNonPriviledgedList) {
+ auto subscription = ConvertAndLoadRules(R"(
+ example.com##foo {color: #000;}
+ example.com#?#bar {color: #000;}
+ )",
+ {}, false);
+ const auto eh_data =
+ subscription->GetElemhideData(GURL("http://example.com"), false);
+ const auto ehe_data =
+ subscription->GetElemhideEmulationData(GURL("http://example.com"));
+ EXPECT_EQ(0u, eh_data.selectors_to_inline_css.size());
+ EXPECT_EQ(0u, ehe_data.selectors_to_inline_css.size());
+}
+
+// See DPD-2893
+TEST_F(AdblockInstalledSubscriptionImplElemhideTest, EmptyDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ~example.*###testcase-eh-id
+ )");
+ auto selectors = subscriptions->GetElemhideData(GURL(""), false);
+
+ EXPECT_EQ(FilterSelectors(selectors),
+ std::set<std::string_view>({"#testcase-eh-id"}));
+}
+
+enum class ContentFilterType {
+ kElemhide,
+ kElemhideEmulation,
+};
+
+class AdblockInstalledSubscriptionImplElemhideParametrizedTest
+ : public ::testing::WithParamInterface<ContentFilterType>,
+ public AdblockInstalledSubscriptionImplElemhideTest {
+ protected:
+ std::string separator() const {
+ return GetParam() == ContentFilterType::kElemhide ? "##" : "#?#";
+ }
+
+ void ExpectSelectorsMatch(scoped_refptr<InstalledSubscription> subscription,
+ const std::string& url,
+ const std::set<std::string_view>& expected) {
+ const auto selectors =
+ GetParam() == ContentFilterType::kElemhide
+ ? subscription->GetElemhideData(GURL(url), true)
+ : subscription->GetElemhideEmulationData(GURL(url));
+ EXPECT_EQ(FilterSelectors(selectors), expected);
+ }
+};
+
+TEST_P(AdblockInstalledSubscriptionImplElemhideParametrizedTest,
+ ElementhideMatchWildcardTld) {
+ auto subscription = ConvertAndLoadRules("example.*" + separator() + "#div");
+ const auto expected_selectors = std::set<std::string_view>({"#div"});
+ // example.* matches any valid TLD.
+ ExpectSelectorsMatch(subscription, "http://example.org", expected_selectors);
+ ExpectSelectorsMatch(subscription, "http://example.com", expected_selectors);
+ // Two-component TLD:
+ ExpectSelectorsMatch(subscription, "http://example.com.br",
+ expected_selectors);
+ // Subdomains:
+ ExpectSelectorsMatch(subscription, "http://sub.example.com",
+ expected_selectors);
+ ExpectSelectorsMatch(subscription, "http://sub.example.com.br",
+ expected_selectors);
+}
+
+TEST_P(AdblockInstalledSubscriptionImplElemhideParametrizedTest,
+ ElementhideDoesNotMatchWildcardTld) {
+ auto subscription = ConvertAndLoadRules("example.*" + separator() + "#div");
+ const auto no_selectors = std::set<std::string_view>();
+ // .evil is not a known TLD.
+ ExpectSelectorsMatch(subscription, "http://example.evil", no_selectors);
+ // .blogspot.com is a valid TLD but it's a private registrar.
+ ExpectSelectorsMatch(subscription, "http://example.blogspot.com",
+ no_selectors);
+ // notexample.com is a different domain than example.com, should not be
+ // matched by example.*.
+ ExpectSelectorsMatch(subscription, "http://notexample.com", no_selectors);
+ // .abc.com is not a TLD at all.
+ ExpectSelectorsMatch(subscription, "http://example.abc.com", no_selectors);
+}
+
+TEST_P(AdblockInstalledSubscriptionImplElemhideParametrizedTest,
+ ElementhideMatchExcludedWildcardTld) {
+ auto subscription = ConvertAndLoadRules("example.*,~subdomain.example.*" +
+ separator() + "#div");
+ const auto expected_selectors = std::set<std::string_view>({"#div"});
+ const auto no_selectors = std::set<std::string_view>();
+ // The filter should match example.org and example.com but not
+ // subdomain.example.org or subdomain.example.com.
+ ExpectSelectorsMatch(subscription, "http://example.com", expected_selectors);
+ ExpectSelectorsMatch(subscription, "http://example.org", expected_selectors);
+ ExpectSelectorsMatch(subscription, "http://subdomain.example.com",
+ no_selectors);
+ ExpectSelectorsMatch(subscription, "http://subdomain.example.org",
+ no_selectors);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ AdblockInstalledSubscriptionImplElemhideParametrizedTest,
+ testing::Values(ContentFilterType::kElemhide,
+ ContentFilterType::kElemhideEmulation));
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_header_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_header_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_header_test.cc
@@ -0,0 +1,297 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplHeaderTest
+ : public AdblockInstalledSubscriptionImplTestBase {};
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ HeaderFilterIgnoredForNonPriviledged) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$header=X-Frame-Options=sameorigin
+ )",
+ {}, false);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ BlockingHeaderFilterWithoutPayloadIgnored) {
+ // It's impossible to say if request should be blocked if the filter
+ // doesn't specify disallowed header.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$header
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest, HeaderFilterForUrl) {
+ GURL subscription_url{"url.com"};
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$header=X-Frame-Options=sameorigin
+ )",
+ {}, true);
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(
+ blocking_filters.count({"X-Frame-Options=sameorigin", subscription_url}));
+
+ blocking_filters.clear();
+ // Different URL, not found.
+ subscriptions->FindHeaderFilters(GURL("https://test.org/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ NoDomainSpecificHeaderFilter) {
+ // This filter is not domain-specific.
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$header=X-Frame-Options=sameorigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ // It's not found when domain_specific_only = true.
+ subscriptions->FindHeaderFilters(
+ GURL("https://test.com/resource.jpg"), ContentType::Image, "test.com",
+ FilterCategory::DomainSpecificBlocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest, DomainSpecificHeaderFilter) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ $header=X-Frame-Options=sameorigin,domain=dom-a.com|dom-b.com
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(
+ GURL("https://test.com/resource.jpg"), ContentType::Image, "dom-b.com",
+ FilterCategory::DomainSpecificBlocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(
+ GURL("https://test.com/resource.jpg"), ContentType::Image, "dom-a.com",
+ FilterCategory::DomainSpecificBlocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(
+ GURL("https://dom-a.com/resource.jpg"), ContentType::Image, "test.com",
+ FilterCategory::DomainSpecificBlocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ HeaderFilterForSpecificResource) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$script,header=X-Frame-Options=sameorigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.js"),
+ ContentType::Script, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://dom-a.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ HeaderFilterForMultipleSpecificResources) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$script,header=X-Frame-Options=sameorigin
+ test.com^$xmlhttprequest,header=X-Frame-Options=sameorigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.js"),
+ ContentType::Script, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.xml"),
+ ContentType::Xmlhttprequest, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://dom-a.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest, HeaderFilterWithComma) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$script,header=X-Frame-Options=same\x2corigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.js"),
+ ContentType::Script, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"X-Frame-Options=same,origin", GURL()}));
+ EXPECT_TRUE(
+ blocking_filters.count({"X-Frame-Options=same\x2corigin", GURL()}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ HeaderFilterWithx2cAsPartOfFilter) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com^$script,header=X-Frame-Options=same\\x2corigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.js"),
+ ContentType::Script, "test.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ // This count "X-Frame-Options=same\x2corigin" occurrences, extra \ is
+ // omitted during string construction
+ EXPECT_TRUE(
+ blocking_filters.count({"X-Frame-Options=same\\x2corigin", GURL()}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ AllowingHeaderFilterNoPayload) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ @@test.com^$header
+ )",
+ {}, true);
+
+ // Allowing filter is found, with an empty payload.
+ std::set<HeaderFilterData> allowing_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Allowing, allowing_filters);
+ ASSERT_EQ(1u, allowing_filters.size());
+ EXPECT_TRUE(allowing_filters.count({"", GURL()}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ AllowingHeaderFilterWithPayload) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ @@test.com^$header=X-Frame-Options=value1
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> allowing_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Allowing, allowing_filters);
+ ASSERT_EQ(1u, allowing_filters.size());
+ EXPECT_TRUE(allowing_filters.count({"X-Frame-Options=value1", GURL()}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest,
+ AllowingHeaderFilterForSpecificResource) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ @@test.com^$script,header=X-Frame-Options=sameorigin
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> allowing_filters{};
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.js"),
+ ContentType::Script, "test.com",
+ FilterCategory::Allowing, allowing_filters);
+ ASSERT_EQ(1u, allowing_filters.size());
+ EXPECT_TRUE(allowing_filters.count({"X-Frame-Options=sameorigin", GURL()}));
+
+ allowing_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://test.com/resource.jpg"),
+ ContentType::Image, "test.com",
+ FilterCategory::Allowing, allowing_filters);
+ ASSERT_EQ(0u, allowing_filters.size());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplHeaderTest, ThirdPartyHeaderFilters) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ only-third.com^$header=only-third,third-party
+ never-third.com^$header=never-third,~third-party
+ )",
+ {}, true);
+
+ std::set<HeaderFilterData> blocking_filters{};
+ // only-third is only found when the URL is from a different domain.
+ subscriptions->FindHeaderFilters(GURL("https://only-third.com/resource.jpg"),
+ ContentType::Image, "different.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"only-third", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://only-third.com/resource.jpg"),
+ ContentType::Image, "only-third.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+
+ // never-third is only found when the URL is from the same domain.
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://never-third.com/resource.jpg"),
+ ContentType::Image, "never-third.com",
+ FilterCategory::Blocking, blocking_filters);
+ ASSERT_EQ(1u, blocking_filters.size());
+ EXPECT_TRUE(blocking_filters.count({"never-third", GURL()}));
+
+ blocking_filters.clear();
+ subscriptions->FindHeaderFilters(GURL("https://never-third.com/resource.jpg"),
+ ContentType::Image, "different.com",
+ FilterCategory::Blocking, blocking_filters);
+ EXPECT_EQ(0u, blocking_filters.size());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_list_converter_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_list_converter_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_list_converter_test.cc
@@ -0,0 +1,107 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/rand_util.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplListConverterTest
+ : public AdblockInstalledSubscriptionImplTestBase {
+ public:
+ scoped_refptr<InstalledSubscription> ConvertAndLoadVectorOfRules(
+ std::vector<std::string>& rules,
+ GURL url = GURL(),
+ bool allow_privileged = false) {
+ auto raw_data = converter_->Convert(rules, url, allow_privileged);
+
+ auto subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(raw_data), Subscription::InstallationState::Installed,
+ base::Time());
+
+ EXPECT_EQ(subscription->GetSourceUrl(), "");
+ EXPECT_EQ(subscription->GetTitle(), "");
+ EXPECT_EQ(subscription->GetCurrentVersion(), "");
+ EXPECT_EQ(subscription->GetExpirationInterval(), base::Days(5));
+
+ return subscription;
+ }
+};
+
+TEST_F(AdblockInstalledSubscriptionImplListConverterTest,
+ ConvertEmptyListOfRules) {
+ std::vector<std::string> rules = {};
+ auto subscription = ConvertAndLoadVectorOfRules(rules);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplListConverterTest,
+ ConvertListOfRules_SingleRule) {
+ std::vector<std::string> rules = {"###ad"};
+ auto subscription = ConvertAndLoadVectorOfRules(rules);
+
+ auto selectors = subscription->GetElemhideData(
+ GURL("https://pl.ign.com/marvels-avengers/41262/news/"
+ "marvels-avengers-kratos-zagra-czarna-pantere"),
+ false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>({"#ad"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplListConverterTest,
+ ConvertListOfRulesMultipleRules) {
+ std::vector<std::string> rules = {"###ad1", "example.hu###ad2"};
+ auto subscription = ConvertAndLoadVectorOfRules(rules);
+
+ auto selectors_1 = subscription->GetElemhideData(
+ GURL("https://pl.ign.com/marvels-avengers/41262/news/"
+ "marvels-avengers-kratos-zagra-czarna-pantere"),
+ false);
+
+ auto selectors_2 =
+ subscription->GetElemhideData(GURL("http://example.hu"), false);
+
+ EXPECT_EQ(FilterSelectors(selectors_1), std::set<std::string_view>({"#ad1"}));
+ EXPECT_EQ(FilterSelectors(selectors_2),
+ std::set<std::string_view>({"#ad1", "#ad2"}));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplListConverterTest,
+ ConvertListOfRulesWithInvalidRules) {
+ std::vector<std::string> rules = {"###ad", "", "@@"};
+ auto subscription = ConvertAndLoadVectorOfRules(rules);
+
+ auto selectors = subscription->GetElemhideData(
+ GURL("https://pl.ign.com/marvels-avengers/41262/news/"
+ "marvels-avengers-kratos-zagra-czarna-pantere"),
+ false);
+
+ EXPECT_EQ(FilterSelectors(selectors), std::set<std::string_view>({"#ad"}));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_metadata_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_metadata_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_metadata_test.cc
@@ -0,0 +1,129 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+namespace {
+
+class MockBuffer : public FlatbufferData {
+ public:
+ explicit MockBuffer(scoped_refptr<FlatbufferConverter>& converter)
+ : real_data_(converter->Convert({},
+ GURL{"http://data.com/filters.txt"},
+ false)) {}
+ MOCK_METHOD(void, PermanentlyRemoveSourceOnDestruction, (), (override));
+
+ const uint8_t* data() const override { return real_data_->data(); }
+ size_t size() const override { return real_data_->size(); }
+ const base::span<const uint8_t> span() const final { return {}; }
+
+ std::unique_ptr<FlatbufferData> real_data_;
+};
+
+} // namespace
+
+class AdblockInstalledSubscriptionImplMetadataTest
+ : public AdblockInstalledSubscriptionImplTestBase {};
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest, SubscriptionUrlParsed) {
+ const GURL kSubscriptionUrl{
+ "https://testpages.adblockplus.org/en/abp-testcase-subscription.txt"};
+ auto subscription = ConvertAndLoadRules("", kSubscriptionUrl);
+ EXPECT_EQ(subscription->GetSourceUrl(), kSubscriptionUrl);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest, SubscriptionTitleParsed) {
+ const GURL kSubscriptionUrl{
+ "https://testpages.adblockplus.org/en/abp-testcase-subscription.txt"};
+ auto subscription = ConvertAndLoadRules("", kSubscriptionUrl);
+ EXPECT_EQ(subscription->GetTitle(), "TestingList");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest, CurrentVersionParsed) {
+ const GURL kSubscriptionUrl{
+ "https://testpages.adblockplus.org/en/abp-testcase-subscription.txt"};
+ auto subscription = ConvertAndLoadRules(
+ "! Version: 202108191113\n !Expires: 1 d", kSubscriptionUrl);
+ EXPECT_EQ(subscription->GetCurrentVersion(), "202108191113");
+ EXPECT_EQ(subscription->GetExpirationInterval(), base::Days(1));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest, ConverterRedirects) {
+ const GURL kSubscriptionUrl{
+ "https://testpages.adblockplus.org/en/abp-testcase-subscription.txt"};
+ const GURL kRedirectUrl{"https://redirect.url.org/redirect-fl.txt"};
+ std::string rules = "[Adblock Plus 2.0]\n! Title: TestingList\n! Redirect: " +
+ kRedirectUrl.spec() + "\n";
+ std::stringstream input(std::move(rules));
+ auto converter_result = converter_->Convert(input, kSubscriptionUrl, false);
+ ASSERT_TRUE(absl::holds_alternative<GURL>(converter_result));
+ EXPECT_EQ(absl::get<GURL>(converter_result), kRedirectUrl);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest, InvalidMetadataField) {
+ const GURL kSubscriptionUrl{
+ "https://testpages.adblockplus.org/en/abp-testcase-subscription.txt"};
+ std::string rules = "[uBlock Origin 2.0]\n! Title: TestingList\n!";
+ std::stringstream input(std::move(rules));
+ auto converter_result = converter_->Convert(input, kSubscriptionUrl, false);
+ ASSERT_TRUE(absl::holds_alternative<ConversionError>(converter_result));
+ EXPECT_EQ(absl::get<ConversionError>(converter_result),
+ ConversionError("Invalid filter list metadata"));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest,
+ InstallationStateAndDateReported) {
+ const auto installation_time =
+ base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(30));
+ const auto installation_state = Subscription::InstallationState::Preloaded;
+ auto subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::make_unique<MockBuffer>(converter_), installation_state,
+ installation_time);
+ EXPECT_EQ(subscription->GetInstallationState(), installation_state);
+ EXPECT_EQ(subscription->GetInstallationTime(), installation_time);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest,
+ MarkForPermanentRemovalTriggersSourceRemoval) {
+ auto buffer = std::make_unique<MockBuffer>(converter_);
+ EXPECT_CALL(*buffer, PermanentlyRemoveSourceOnDestruction());
+ auto subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(buffer), Subscription::InstallationState::Installed,
+ base::Time());
+ subscription->MarkForPermanentRemoval();
+}
+
+TEST_F(AdblockInstalledSubscriptionImplMetadataTest,
+ NormalDestructionDoesNotTriggerSourceRemoval) {
+ auto buffer = std::make_unique<MockBuffer>(converter_);
+ EXPECT_CALL(*buffer, PermanentlyRemoveSourceOnDestruction()).Times(0);
+ auto subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(buffer), Subscription::InstallationState::Installed,
+ base::Time());
+ subscription.reset();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_rewrite_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_rewrite_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_rewrite_test.cc
@@ -0,0 +1,190 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplRewriteTest
+ : public AdblockInstalledSubscriptionImplTestBase {};
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteEmpty) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.js$domain=adform.net,rewrite=
+ ||adform.net/banners/scripts/adx.js$domain=adform.net,rewrite
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.js"),
+ "adform.net", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.css$domain=delfi.lt,rewrite=abp-resource:blank-css
+ )");
+ EXPECT_EQ(*subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "delfi.lt", FilterCategory::Blocking)
+ .begin(),
+ "data:text/css,");
+ EXPECT_EQ(*subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "delfi.lt", FilterCategory::DomainSpecificBlocking)
+ .begin(),
+ "data:text/css,");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteFirstParty) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.css$rewrite=abp-resource:blank-css,~third-party
+ )");
+ EXPECT_EQ(*subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "adform.net", FilterCategory::Blocking)
+ .begin(),
+ "data:text/css,");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "adform.net", FilterCategory::DomainSpecificBlocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest,
+ RewriteFirstPartyDomainNotMatching) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.css$rewrite=abp-resource:blank-css,~third-party
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "example.com", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest,
+ RewriteFirstPartyAndDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.js$domain=adform.net,rewrite=abp-resource:blank-js,~third-party
+ )");
+ EXPECT_EQ(*subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.js"),
+ "adform.net", FilterCategory::Blocking)
+ .begin(),
+ "data:application/javascript,");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteWrongResource) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.js$domain=delfi.lt,rewrite=abp-resource:blank-xxx
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.js"),
+ "delfi.lt", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteWrongScheme) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||adform.net/banners/scripts/adx.js$domain=delfi.lt,rewrite=about::blank
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.js"),
+ "delfi.lt", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteStar) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ *$domain=delfi.lt,rewrite=abp-resource:blank-html
+ )");
+ EXPECT_EQ(
+ *subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.html"), "delfi.lt",
+ FilterCategory::Blocking)
+ .begin(),
+ "data:text/html,<!DOCTYPE html><html><head></head><body></body></html>");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteWrongStar) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ *test.com$domain=delfi.lt,rewrite=abp-resource:blank-html
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(GURL("https://test.com/ad.html"),
+ "delfi.lt", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteStrict) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ test.com$domain=delfi.lt,rewrite=abp-resource:blank-html
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(GURL("https://test.com/ad.html"),
+ "delfi.lt", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteNoDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||test.com$rewrite=abp-resource:blank-html
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(GURL("https://test.com/ad.html"),
+ "test.com", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteExcludeDomain) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ ||test.com$domain=~delfi.lt,rewrite=abp-resource:blank-html
+ )");
+ EXPECT_TRUE(subscriptions
+ ->FindRewriteFilters(GURL("https://test.com/ad.html"),
+ "test.com", FilterCategory::Blocking)
+ .empty());
+}
+
+TEST_F(AdblockInstalledSubscriptionImplRewriteTest, RewriteAllowedFilter) {
+ auto subscriptions = ConvertAndLoadRules(R"(
+ @@||adform.net/banners/scripts/adx.css$domain=delfi.lt,rewrite=abp-resource:blank-css
+ )");
+ EXPECT_EQ(*subscriptions
+ ->FindRewriteFilters(
+ GURL("https://adform.net/banners/scripts/adx.css"),
+ "delfi.lt", FilterCategory::Allowing)
+ .begin(),
+ "data:text/css,");
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_snippets_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_snippets_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_snippets_test.cc
@@ -0,0 +1,196 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+struct FlatIndex {
+ explicit FlatIndex(std::unique_ptr<FlatbufferData> data)
+ : buffer_(std::move(data)),
+ index_(flat::GetSubscription(buffer_->data())) {}
+ const std::unique_ptr<FlatbufferData> buffer_;
+ const raw_ptr<const flat::Subscription> index_;
+};
+
+class AdblockInstalledSubscriptionImplSnippetsTest
+ : public AdblockInstalledSubscriptionImplTestBase {
+ public:
+ FlatIndex ConvertAndLoadRulesToIndex(std::string rules,
+ GURL url = GURL(),
+ bool allow_privileged = false) {
+ // Without [Adblock Plus 2.0], the file is not a valid filter list.
+ rules = "[Adblock Plus 2.0]\n! Title: TestingList\n" + rules;
+ std::stringstream input(std::move(rules));
+ auto converter_result = converter_->Convert(input, url, allow_privileged);
+ if (!absl::holds_alternative<std::unique_ptr<FlatbufferData>>(
+ converter_result)) {
+ return FlatIndex(nullptr);
+ }
+ return FlatIndex(std::move(
+ absl::get<std::unique_ptr<FlatbufferData>>(converter_result)));
+ }
+};
+
+/* --------------------- Snippet tests --------------------- */
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetEmpty) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ test.com#$#
+ )",
+ {}, true);
+ EXPECT_EQ(index.index_->snippet()->size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetBasic) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ test.com,~other.test.com#$#log Test
+ )",
+ {}, true);
+ ASSERT_EQ(index.index_->snippet()->size(), 1u);
+ auto* entry = index.index_->snippet()->Get(0);
+ ASSERT_EQ(entry->filter()->size(), 1u);
+ EXPECT_EQ(entry->domain()->str(), "test.com");
+
+ auto* snippet = entry->filter()->Get(0);
+ ASSERT_EQ(snippet->exclude_domains()->size(), 1u);
+ EXPECT_EQ(snippet->exclude_domains()->Get(0)->str(), "other.test.com");
+ EXPECT_EQ(snippet->script()->size(), 1u);
+
+ auto* call = snippet->script()->Get(0);
+ EXPECT_EQ(call->command()->str(), "log");
+ ASSERT_EQ(call->arguments()->size(), 1u);
+ EXPECT_EQ(call->arguments()->Get(0)->str(), "Test");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetSpace) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ test.com#$#log ' Test\t arg'
+ )",
+ {}, true);
+ ASSERT_EQ(index.index_->snippet()->size(), 1u);
+ auto* entry = index.index_->snippet()->Get(0);
+ ASSERT_EQ(entry->filter()->size(), 1u);
+ auto* snippet = entry->filter()->Get(0);
+
+ auto* call = snippet->script()->Get(0);
+ EXPECT_EQ(call->command()->str(), "log");
+ ASSERT_EQ(call->arguments()->size(), 1u);
+ EXPECT_EQ(call->arguments()->Get(0)->str(), " Test\t arg");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetArgumentPack) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ test.com#$#log ab 'a b' '' ccc
+ )",
+ {}, true);
+ ASSERT_EQ(index.index_->snippet()->size(), 1u);
+ auto* entry = index.index_->snippet()->Get(0);
+ ASSERT_EQ(entry->filter()->size(), 1u);
+ auto* snippet = entry->filter()->Get(0);
+ ASSERT_EQ(snippet->script()->size(), 1u);
+
+ auto* call = snippet->script()->Get(0);
+ EXPECT_EQ(call->command()->str(), "log");
+ ASSERT_EQ(call->arguments()->size(), 4u);
+ EXPECT_EQ(call->arguments()->Get(0)->str(), "ab");
+ EXPECT_EQ(call->arguments()->Get(1)->str(), "a b");
+ EXPECT_EQ(call->arguments()->Get(2)->str(), "");
+ EXPECT_EQ(call->arguments()->Get(3)->str(), "ccc");
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetMultipleCalls) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ test.com#$#log test; log test; log
+ )",
+ {}, true);
+ ASSERT_EQ(index.index_->snippet()->size(), 1u);
+ auto* entry = index.index_->snippet()->Get(0);
+ ASSERT_EQ(entry->filter()->size(), 1u);
+ auto* snippet = entry->filter()->Get(0);
+ ASSERT_EQ(snippet->script()->size(), 3u);
+
+ auto* call = snippet->script()->Get(0);
+ EXPECT_EQ(call->command()->str(), "log");
+ ASSERT_EQ(call->arguments()->size(), 1u);
+ EXPECT_EQ(call->arguments()->Get(0)->str(), "test");
+
+ call = snippet->script()->Get(1);
+ EXPECT_EQ(call->command()->str(), "log");
+ ASSERT_EQ(call->arguments()->size(), 1u);
+ EXPECT_EQ(call->arguments()->Get(0)->str(), "test");
+
+ call = snippet->script()->Get(2);
+ EXPECT_EQ(call->command()->str(), "log");
+ EXPECT_EQ(call->arguments()->size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, NoSnippetTest) {
+ auto sub = ConvertAndLoadRules(R"(
+ test.com##selector
+ )",
+ {}, true);
+ EXPECT_EQ(sub->MatchSnippets("random.org").size(), 0u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, NoSnippetForDomainTest) {
+ auto sub = ConvertAndLoadRules(R"(
+ test.com#$#log test; log test; log
+ )",
+ {}, true);
+ EXPECT_EQ(sub->MatchSnippets("domain.com").size(), 0u);
+ EXPECT_EQ(sub->MatchSnippets("test.com").size(), 3u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest,
+ SnippetFiltersTopLevelDomain) {
+ auto sub = ConvertAndLoadRules(R"(
+ test.com,gov.ua,com,localhost#$#abort-on-property-write 1 2 3
+ )",
+ {}, true);
+ // has to have at least one subdomain
+ // but it might be a Top Level domain like gov.ua
+ EXPECT_EQ(sub->MatchSnippets("test.com").size(), 1u);
+ EXPECT_EQ(sub->MatchSnippets("gov.ua").size(), 1u);
+ EXPECT_EQ(sub->MatchSnippets("com").size(), 0u);
+ EXPECT_EQ(sub->MatchSnippets("localhost").size(), 1u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest, SnippetFiltersSubdomain) {
+ auto sub = ConvertAndLoadRules(R"(
+ example.com,gov.ua#$#abort-on-property-write 1 2 3
+ )",
+ {}, true);
+ EXPECT_EQ(sub->MatchSnippets("www.example.com").size(), 1u);
+}
+
+TEST_F(AdblockInstalledSubscriptionImplSnippetsTest,
+ SnippetsIgnoredForNonPriviledged) {
+ auto index = ConvertAndLoadRulesToIndex(R"(
+ example.com,gov.ua#$#abort-on-property-write 1 2 3
+ )",
+ {}, false);
+ ASSERT_EQ(index.index_->snippet()->size(), 0u);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_test_base.cc b/components/adblock/core/subscription/test/installed_subscription_impl_test_base.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_test_base.cc
@@ -0,0 +1,78 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+AdblockInstalledSubscriptionImplTestBase::
+ AdblockInstalledSubscriptionImplTestBase()
+ : converter_(base::MakeRefCounted<FlatbufferConverter>()) {}
+
+AdblockInstalledSubscriptionImplTestBase::
+ ~AdblockInstalledSubscriptionImplTestBase() = default;
+
+std::set<std::string_view>
+AdblockInstalledSubscriptionImplTestBase::FilterSelectors(
+ InstalledSubscription::ContentFiltersData selectors) {
+ // Remove exceptions.
+ selectors.elemhide_selectors.erase(
+ std::remove_if(selectors.elemhide_selectors.begin(),
+ selectors.elemhide_selectors.end(),
+ [&](const auto& selector) {
+ return std::find(selectors.elemhide_exceptions.begin(),
+ selectors.elemhide_exceptions.end(),
+ selector) !=
+ selectors.elemhide_exceptions.end();
+ }),
+ selectors.elemhide_selectors.end());
+ std::set<std::string_view> filtered_selectors(
+ selectors.elemhide_selectors.begin(), selectors.elemhide_selectors.end());
+ return filtered_selectors;
+}
+
+scoped_refptr<InstalledSubscription>
+AdblockInstalledSubscriptionImplTestBase::ConvertAndLoadRules(
+ std::string rules,
+ GURL url,
+ bool allow_privileged) {
+ // Without [Adblock Plus 2.0], the file is not a valid filter list.
+ rules = "[Adblock Plus 2.0]\n! Title: TestingList\n" + rules;
+ std::istringstream input(std::move(rules));
+ auto converter_result = converter_->Convert(input, url, allow_privileged);
+ if (!absl::holds_alternative<std::unique_ptr<FlatbufferData>>(
+ converter_result)) {
+ return {};
+ }
+ return base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(absl::get<std::unique_ptr<FlatbufferData>>(converter_result)),
+ Subscription::InstallationState::Installed, base::Time());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_test_base.h b/components/adblock/core/subscription/test/installed_subscription_impl_test_base.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_test_base.h
@@ -0,0 +1,54 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_INSTALLED_SUBSCRIPTION_IMPL_TEST_BASE_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_INSTALLED_SUBSCRIPTION_IMPL_TEST_BASE_H_
+
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "components/adblock/core/subscription/installed_subscription_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class AdblockInstalledSubscriptionImplTestBase : public testing::Test {
+ public:
+ AdblockInstalledSubscriptionImplTestBase();
+ ~AdblockInstalledSubscriptionImplTestBase() override;
+
+ std::set<std::string_view> FilterSelectors(
+ InstalledSubscription::ContentFiltersData selectors);
+
+ scoped_refptr<InstalledSubscription> ConvertAndLoadRules(
+ std::string rules,
+ GURL url = GURL(),
+ bool allow_privileged = false);
+
+ public:
+ scoped_refptr<FlatbufferConverter> converter_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_INSTALLED_SUBSCRIPTION_IMPL_TEST_BASE_H_
diff --git a/components/adblock/core/subscription/test/installed_subscription_impl_url_test.cc b/components/adblock/core/subscription/test/installed_subscription_impl_url_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/installed_subscription_impl_url_test.cc
@@ -0,0 +1,1307 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h"
+
+#include "absl/types/variant.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "components/adblock/core/converter/flatbuffer_converter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace adblock {
+namespace {
+
+std::string RandomAsciiString(size_t length) {
+ std::string result(length, ' ');
+ for (auto& c : result) {
+ c = base::RandInt('a', 'z');
+ }
+ return result;
+}
+} // namespace
+
+class AdblockInstalledSubscriptionImplUrlTest
+ : public AdblockInstalledSubscriptionImplTestBase {};
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_NoRules) {
+ auto subscription = ConvertAndLoadRules("");
+ EXPECT_FALSE(subscription->HasUrlFilter(GURL("https://untracked.com/file"),
+ "domain.com", ContentType::Stylesheet,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Stylesheet) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abptestpages.org/testfiles/stylesheet/$stylesheet
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/stylesheet/file.css"),
+ "domain.com", ContentType::Stylesheet, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Image) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/image/static/$image
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/image/static/image.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_HandlesLookaroundUrlBlockingFiltersNegativeLookahead) {
+ // The filter below employs negative lookahead
+ // it should match any url except the ones that are within
+ // the (?!) group.
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?://(?![^\s]+\.streamvid\.club|api\.kinogram\.best\/embed\/|cdn\.jsdelivr\.net\/npm\/venom-player)/$third-party,xmlhttprequest,domain=kindkino.ru
+ )");
+
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://something.streamvid.club"), "kindkino.ru",
+ ContentType::Xmlhttprequest, SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https://foo.org"), "kindkino.ru",
+ ContentType::Xmlhttprequest, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_HandlesLookaroundUrlBlockingFiltersCaseSensetive) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?://(?![^\s]+\.streamvid\.club/case)/$third-party,match-case,xmlhttprequest,domain=kindkino.ru
+ )");
+
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://something.streamvid.club/case"), "kindkino.ru",
+ ContentType::Xmlhttprequest, SiteKey(), FilterCategory::Blocking));
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://something.streamvid.club/CASE"), "kindkino.ru",
+ ContentType::Xmlhttprequest, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_NotAffectedByRegexLookaroundFilter) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/image/static/$image
+ /^https?://(?![^\s]+\.streamvid\.club)/$third-party,match-case,xmlhttprequest,domain=kindkino.ru
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/image/static/image.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_NotAffectedByRegexLookaroundFilter_2) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?:\/\/.*\.(onlineee|online|)\/.*/$domain=hclips.com
+ /^https?://(?![^\s]+\.streamvid\.club)/$third-party,match-case,xmlhttprequest,domain=kindkino.ru
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://moneypunchstep.online/saber/ball/nomad/"), "hclips.com",
+ ContentType::Subdocument, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_NotAffectedByInvalidRegexFilter) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/image/static/$image
+ /[/
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/image/static/image.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_TrailingSeparator) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ^pid=Ads^
+ )");
+
+ const std::string url = R"(https://c.contentsquare.net/pageview?pid=905&)";
+
+ EXPECT_FALSE(subscription->HasUrlFilter(GURL(url), "domain.com",
+ ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_NotAffectedByInvalidRegexFilter_3) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?:\/\/.*\.(onlineee|online|)\/.*/$domain=hclips.com
+ /[/
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://moneypunchstep.online/saber/ball/nomad/"), "hclips.com",
+ ContentType::Subdocument, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_LookaroundRegexEnginesMaxUrlSizeStressTest) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?://(?![^\s]+\.streamvid\.club|api\.kinogram\.best\/embed\/|cdn\.jsdelivr\.net\/npm\/venom-player)/$third-party,xmlhttprequest,domain=kindkino.ru
+ )");
+
+ std::string url = "https://something.streamvid.club/";
+ url.append(RandomAsciiString(url::kMaxURLChars - url.size()));
+ const GURL big_url(url);
+
+ EXPECT_EQ(big_url.spec().size(), url::kMaxURLChars);
+ EXPECT_FALSE(subscription->HasUrlFilter(big_url, "kindkino.ru",
+ ContentType::Xmlhttprequest,
+ SiteKey(), FilterCategory::Blocking));
+
+ url.append(RandomAsciiString(url::kMaxURLChars));
+ const GURL bigger_url(url);
+
+ EXPECT_EQ(bigger_url.spec().size(), url::kMaxURLChars * 2);
+ EXPECT_FALSE(subscription->HasUrlFilter(bigger_url, "kindkino.ru",
+ ContentType::Xmlhttprequest,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_AcceptsNonNormalizedUrl) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/ad
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https:abptestpages.org/ad:80"),
+ "domain.com", ContentType::Stylesheet,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Script) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abptestpages.org/testfiles/script/$script
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"), "domain.com",
+ ContentType::Script, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Subdocument) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abptestpages.org/testfiles/subdocument/$subdocument
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/subdocument/index.html"),
+ "domain.com", ContentType::Subdocument, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_WebRTC) {
+ auto subscription = ConvertAndLoadRules(R"(
+ $webrtc,domain=abptestpages.org
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/webrtc"), "abptestpages.org",
+ ContentType::Webrtc, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Wildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/blocking/wildcard/*/wildcard.png
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://domain.com/testfiles/blocking/wildcard/path/component/"
+ "wildcard.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_FullPath) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/blocking/full-path.png
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/blocking/full-path.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_PartialPath) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/blocking/partial-path/
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/blocking/partial-path/content.png"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_EndWithCaret) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ads.example.com^
+ )");
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://ads.example.com.ua"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainWildcardMiddle1) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||a.*.b.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.c.b.com"), "https://abptestpages.org", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainWildcardMiddle2) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||a*.b.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.c.b.com"), "https://abptestpages.org", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainWildcardMiddle3) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||a.*b.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.b.com"), "https://abptestpages.org", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_WildcardDomainAndPath1) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||d*in.com/*/blocking
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://domain.com/testfiles/blocking/wildcard.png"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_WildcardDomainAndPath2) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||a*.b.com/*.png
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.b.com/a.png"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://ac.b.com/image.png"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_DomainWildcardStart) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||*a.b.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.b.com"), "https://abptestpages.org", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://domena.b.com/path"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainWildcardStartEndCaret) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||*a.b.com^
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://a.b.com"), "https://abptestpages.org", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://domena.b.com/path"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainCaretWildcardEnd) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com^*/path.js
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://connect.example.com/en_US/path.js"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_CaretEnd) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com^
+ @@||example.com/ddm^
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://ad.example.com/ddm/ad/infytghiuf/nmys/;ord=1596077603231?"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://ad.example.com/ddm/ad/infytghiuf/nmys/;ord=1596077603231?"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainInParamsNoMatch) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||domain.com^
+ )");
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://example.com/path?domain=https://www.domain.com"),
+ "https://abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_SchemeDomainDot) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ://ads.
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://ads.example.com/u?dp=1"), "https://abptestpages.org",
+ ContentType::Script, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_PathWildcards) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com/a/*/c/script.*.js
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com/a/b/c/script.file.js"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_MultipleCaretAndWildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@^path1/path2/*/path4/file*.js^
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com/path1/path2/path3/path4/file1.2.3.js?v=1"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_PartdomainNoMatch) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||art-domain.com^
+ )");
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://sub.part-domain.com/path"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_DoubleSlash) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com*/script.
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com//script.js"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_Regex) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@/^https:\/\/www\.domain(?:\.[a-z]{2,3}){1,2}\/afs\//
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://www.domain.com/afs/iframe.html"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainMatchFilterWithoutDomain1) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@||*file_name.gif
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com/path/file_name.gif"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainMatchFilterWithoutDomain2) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@||*/file_name.gif
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com/path/file_name.gif"),
+ "https://abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_DomainStart) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.co
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://example.com"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_StartMatchCompleteUrl) {
+ auto subscription = ConvertAndLoadRules(R"(
+ |https://domain.com/path/file.gif
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://domain.com/path/file.gif"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainMatchDotWildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||*.
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com/path/file.gif"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_DomainWithPort) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com:8888/js
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://wwww.example.com:8888/js"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_IPWithPortAndWildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||1.2.3.4:8060/*/
+ )");
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://1.2.3.4:8060/path"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://1.2.3.4:8060/path/file.js"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_DomainWithPortAndCaret) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com:8862^
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com:8862"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://example.com:8862/path"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://example.com:886"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_SinglePipeCaret) {
+ auto subscription = ConvertAndLoadRules(R"(
+ |http://example.com^
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://example.com:8000/"), "https://abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlAllowListDocument) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@^upapi=true$document,domain=thedirect.com|domain2.com
+ )");
+ EXPECT_FALSE(subscription->HasSpecialFilter(
+ SpecialFilterType::Document,
+ GURL("https://backend.upapi.net/pv?upapi=true"), "domain.com",
+ SiteKey()));
+
+ EXPECT_TRUE(subscription->HasSpecialFilter(
+ SpecialFilterType::Document,
+ GURL("https://backend.upapi.net/pv?upapi=true"), "thedirect.com",
+ SiteKey()));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_DomainSpecific) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/domain/dynamic/*$domain=abptestpages.org
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/domain/dynamic/content.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+ // Does not match the same url embedded in a different domain - the rule is
+ // domain-specific.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/domain/dynamic/content.png"),
+ "different.adblockplus.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_InvalidDomainOption) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/domain/dynamic/*$domain=https://example.com
+ )");
+
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/domain/dynamic/content.png"),
+ "example.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_DomainSpecificIgnoresWWW) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/domain/dynamic/*$domain=abptestpages.org
+ )");
+
+ // The filter's domain is abptestpages.org and the request's domain
+ // is www.abptestpages.org. The "www" prefix is ignored, the domains
+ // are considered to match.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/domain/dynamic/content.png"),
+ "www.abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_DomainSpecificIsCaseInsensitive) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/domain/dynamic/*$domain=abptestpages.org
+ )");
+
+ // The filter's domain is abptestpages.org and the request's domain
+ // is abptestpages.org. The domains are considered to match.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/domain/dynamic/content.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Other) {
+ auto subscription = ConvertAndLoadRules(R"(
+ $other,domain=abptestpages.org
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https://whatever.com/script.js"),
+ "abptestpages.org", ContentType::Other,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Ping) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abptestpages.org/*^$ping
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https://abptestpages.org/ping"),
+ "abptestpages.org", ContentType::Ping,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_XmlHttpRequest) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abptestpages.org/testfiles/xmlhttprequest/$xmlhttprequest
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/xmlhttprequest/"
+ "request.xml"),
+ "abptestpages.org", ContentType::Xmlhttprequest, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_ThirdParty) {
+ auto subscription = ConvertAndLoadRules(R"(
+ adblockplus-icon-colour-web.svg$third-party
+ )");
+
+ // $third-party means the rule applies if the domain is different than the
+ // domain of the URL (actually, a bit more complicated than that)
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/adblockplus-icon-colour-web.svg"),
+ "google.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+
+ // Does not apply on same domain.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/adblockplus-icon-colour-web.svg"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_ThirdParty_2) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||adgrx.com^$third-party
+ @@||fls.doubleclick.net^$subdocument,image
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://rtb.adgrx.com/segments/"
+ "EEXrM7_yY0aoq_OtZxYHORtz55uneYk8VAiLioGMm14=/47510.gif"),
+ "8879538.fls.doubleclick.net", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+
+ EXPECT_FALSE(subscription->HasSpecialFilter(
+ SpecialFilterType::Document,
+ GURL("https://rtb.adgrx.com/segments/"
+ "EEXrM7_yY0aoq_OtZxYHORtz55uneYk8VAiLioGMm14=/47510.gif"),
+ "8879538.fls.doubleclick.net", SiteKey()));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_RegexEnd) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||popin.cc/popin_discovery
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://api.popin.cc/popin_discovery5-min.js"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_NotThirdParty) {
+ auto subscription = ConvertAndLoadRules(R"(
+ abb-logo.png$~third-party
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/abb-logo.png"), "abptestpages.org",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ // Does not apply on different domain.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/abb-logo.png"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterBlocking_Popup) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/popup/link.html^$popup
+ )");
+
+ EXPECT_TRUE(subscription->HasPopupFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", SiteKey(), FilterCategory::Blocking));
+
+ // No allowing filter:
+ EXPECT_FALSE(subscription->HasPopupFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", SiteKey(), FilterCategory::Allowing));
+
+ // Does not match if the content type is different.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", ContentType::Subdocument, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_MultipleTypes) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||abptestpages.org/testfiles/popup/link.html^$popup,image,script
+ )");
+
+ EXPECT_TRUE(subscription->HasPopupFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", SiteKey(), FilterCategory::Blocking));
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/popup/link.html"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_UppercaseFilterAndURL) {
+ // A filter with uppercase component should be matched by a uppercase URL,
+ // this requires keywords and urls to be converted to lowercase during filter
+ // parsing.
+ auto subscription = ConvertAndLoadRules(R"(
+ ||yahoo.com/bidRequest?
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://c2shb.ssp.yahoo.com/"
+ "bidRequest?dcn=8a9691510171713aaede3c85d0ab0026&pos=desktop_sky_"
+ "right_bottom&cmd=bid&secure=1&gdpr=1&euconsent="
+ "BO3TikKO3TikKAbABBENC6AAAAAtmAAA"),
+ "www.dailymail.co.uk", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_UppercaseDefinition) {
+ // A filter with uppercase component should be matched by a lowercase URL,
+ // this requires keywords to be converted to lowercase during filter parsing.
+ auto subscription = ConvertAndLoadRules(R"(
+ ||bttrack.com/Pixel/$image,third-party
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://bttrack.com/pixel/"
+ "cookiesync?source=14b8c562-d12b-418b-b680-ad517d5839ec"),
+ "super.abril.com.br", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_UppercaseURL) {
+ // A filter with lowercase component should be matched by an uppercase URL,
+ // this requires keywords to be converted to lowercase during URL matching.
+ auto subscription = ConvertAndLoadRules(R"(
+ ||bttrack.com/pixel/$image,third-party
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://bttrack.com/Pixel/"
+ "cookiesync?source=14b8c562-d12b-418b-b680-ad517d5839ec"),
+ "super.abril.com.br", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilter_CaseSensitiveMatch) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /testfiles/match-case/static/*/abc.png$match-case
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/match-case/static/path/abc.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+ // Does not match if the case is different
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://whatever.com/testfiles/match-case/static/path/ABC.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_AllowlistThirdPartyFilter) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||amazon-adsystem.com^$third-party
+ @@||amazon-adsystem.com//ecm
+ @@||amazon-adsystem.com/ecm
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://s.amazon-adsystem.com//"
+ "ecm3?ex=visualiq&id=a78f7b3f-a525-45eb-a1ed-5eb5eab339af"),
+ "www.wwe.com", ContentType::Image, SiteKey(), FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilter_IgnoresInvalidFilterOption) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@||google.com/recaptcha/$csp,subdocument
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL(" https://www.google.com/recaptcha/api2/a"), "vidoza.net",
+ ContentType::Subdocument, SiteKey(), FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Sitekey) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@$document,sitekey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANGtTstne7e8MbmDHDiMFkGbcuBgXmiVesGOG3gtYeM1EkrzVhBjGUvKXYE4GLFwqty3v5MuWWbvItUWBTYoVVsCAwEAAQ
+ )");
+
+ EXPECT_TRUE(subscription->HasSpecialFilter(
+ SpecialFilterType::Document, GURL("https://whatever.com"), "whatever.com",
+ SiteKey("MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANGtTstne7e8MbmDHDiMFkGbcuBgXmiV"
+ "esGOG3gt"
+ "YeM1EkrzVhBjGUvKXYE4GLFwqty3v5MuWWbvItUWBTYoVVsCAwEAAQ")));
+ // No allow rule without sitekey
+ EXPECT_FALSE(subscription->HasSpecialFilter(SpecialFilterType::Document,
+ GURL("https://whatever.com"),
+ "whatever.com", SiteKey()));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Popup) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/popup
+ ||abptestpages.org/testfiles/popup_exception/link.html^$popup
+ @@||abptestpages.org/testfiles/popup_exception/link.html^$popup
+ )");
+
+ // Finds the blocking filter:
+ EXPECT_TRUE(subscription->HasPopupFilter(
+ GURL("https://abptestpages.org/testfiles/popup_exception/"
+ "link.html"),
+ "abptestpages.org", SiteKey(), FilterCategory::Blocking));
+
+ // But also finds the allowing filter:
+ EXPECT_TRUE(subscription->HasPopupFilter(
+ GURL("https://abptestpages.org/testfiles/popup_exception/"
+ "link.html"),
+ "abptestpages.org", SiteKey(), FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterException_XmlHttpRequest) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/xmlhttprequest
+ ||abptestpages.org/testfiles/xmlhttprequest_exception/*
+ @@abptestpages.org/testfiles/xmlhttprequest_exception/$xmlhttprequest
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/"
+ "xmlhttprequest_exception/link.html"),
+ "https://abptestpages.org", ContentType::Xmlhttprequest, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Ping) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/ping
+ abptestpages.org/*^$ping
+ @@abptestpages.org/en/exceptions/ping*^$ping
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/en/exceptions/ping/link.html"),
+ "https://abptestpages.org", ContentType::Ping, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterException_Subdocument) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/subdocument
+ ||abptestpages.org/testfiles/subdocument_exception/*
+ @@abptestpages.org/testfiles/subdocument_exception/$subdocument
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/subdocument_exception/"
+ "link.html"),
+ "abptestpages.org", ContentType::Subdocument, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Script) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/script
+ ||abptestpages.org/testfiles/script_exception/*
+ @@abptestpages.org/testfiles/script_exception/$script
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script_exception/"
+ "link.html"),
+ "abptestpages.org", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Image) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/image
+ ||abptestpages.org/testfiles/image_exception/*
+ @@abptestpages.org/testfiles/image_exception/$image
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/image_exception/"
+ "image.jpg"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_Stylesheet) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/stylesheet
+ ||abptestpages.org/testfiles/stylesheet_exception/*
+ @@abptestpages.org/testfiles/stylesheet_exception/$stylesheet
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https://abptestpages.org/"
+ "testfiles/stylesheet_exception/"
+ "style.css"),
+ "abptestpages.org",
+ ContentType::Stylesheet, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterException_WebSocket) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/websocket
+ $websocket,domain=abptestpages.org
+ @@$websocket,domain=abptestpages.org
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://whatever.org/socket.wss"), "abptestpages.org",
+ ContentType::Websocket, SiteKey(), FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlRegexAnythingEndingOnline) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?:\/\/.*\.(onlineee|online|)\/.*/$domain=hclips.com
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://moneypunchstep.online/saber/ball/nomad/"), "hclips.com",
+ ContentType::Subdocument, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterRegexContains$WithFilterOptions) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?:\/\/cdn\.[0-9a-z]{3,6}\.xyz\/[a-z0-9]{8,}\.js$/$script,third-party
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://cdn.what.xyz/adsertscript.js"), "domain.com",
+ ContentType::Script, SiteKey(), FilterCategory::Blocking));
+
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://cdn.what.xyz/adsertscript.js"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterRegexContains$WithoutFilterOption) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /^https?:\/\/cdn\.[0-9a-z]{3,6}\.xyz\/[a-z0-9]{8,}\.js$/
+ )");
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://cdn.what.xyz/adsertscript.js"), "domain.com",
+ ContentType::Script, SiteKey(), FilterCategory::Blocking));
+
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://cdn.what.xyz/adsertscript.js"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+// TODO generic block filters may soon be removed from the ContentType enum and
+// handled internally.
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterException_GenericBlock) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ! exceptions/genericblock
+ /testfiles/genericblock/generic.png
+ /testfiles/genericblock/specific.png$domain=abptestpages.org
+ @@||abptestpages.org/en/exceptions/genericblock$genericblock
+ )");
+
+ EXPECT_TRUE(subscription->HasSpecialFilter(
+ SpecialFilterType::Genericblock,
+ GURL("https://abptestpages.org/en/exceptions/genericblock"),
+ "abptestpages.org", SiteKey()));
+ // Since there is a genericblock rule for this parent, we would search for
+ // specific-only rules
+ // The rule /testfiles/genericblock/generic.png does not apply as it is not
+ // domain-specific:
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/genericblock/"
+ "generic.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::DomainSpecificBlocking));
+ // The rule
+ // /testfiles/genericblock/specific.png$domain=abptestpages.org
+ // applies:
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/genericblock/"
+ "specific.png"),
+ "abptestpages.org", ContentType::Image, SiteKey(),
+ FilterCategory::DomainSpecificBlocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterException_DomainSpecificExclusion) {
+ auto subscription = ConvertAndLoadRules(R"(
+||media.net^$third-party
+@@||media.net^$document
+@@||media.net^$third-party,domain=~fandom.com
+@@||contextual.media.net/bidexchange.js
+ )");
+
+ // This reflects a bug squished IRL:
+ // the URL is blocked by ||media.net^$third-party and should NOT be allowed
+ // by any of the allow rules because:
+ // 1. It's not a DOCUMENT type
+ // 2. The third-party allow rule does not apply on roblox.fandom.com
+ // 3. bidexchange.js is a path allowed only for a different page
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://hbx.media.net/bidexchange.js?cid=8CUDYP2MO&version=6.1"),
+ "roblox.fandom.com", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://hbx.media.net/bidexchange.js?cid=8CUDYP2MO&version=6.1"),
+ "roblox.fandom.com", ContentType::Script, SiteKey(),
+ FilterCategory::Allowing));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, UrlFilterWithHashSign) {
+ auto subscription = ConvertAndLoadRules(R"(
+ @@||search.twcc.com/#web/$elemhide
+ )");
+
+ EXPECT_TRUE(subscription->HasSpecialFilter(
+ SpecialFilterType::Elemhide, GURL("https://search.twcc.com/#web/"), "",
+ SiteKey()));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterWildcardUrlShortKeywords) {
+ auto subscription = ConvertAndLoadRules(R"(
+ /test*iles/a/b.png
+ /test*iles/a/b.js
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://domain.com/testfiles/a/b.png"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://domain.com/testfiles/a/b.js"), "domain.com",
+ ContentType::Script, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, RegexFilterNotLowercased) {
+ // \D+ matches "not digits", while \d+ matches "digits":
+ auto subscription = ConvertAndLoadRules(R"(
+ /test\D+.png/
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("https://domain.com/testabc.png"),
+ "domain.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://domain.com/test123.png"), "domain.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, PipeInURL) {
+ // See: https://github.com/gatling/gatling/issues/1272
+ // These | characters in the middle of the filter should match the literal
+ // | characters in the URL, not be treated as anchors.
+ auto subscription = ConvertAndLoadRules(R"(
+ /addyn|*|adtech;
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://adserver.adtech.de/"
+ "addyn|3.0|296|3872016|0|16|ADTECH;loc=100;target=_blank;misc="
+ "1043156433;rdclick=http://ba.ccm2.net/RealMedia/ads/click_lx.ads/"
+ "fr_ccm_hightech/news/L20/1043156433/Position3/OasDefault/"
+ "autopromo_keljob_ccm/autopromo_kelformation_1_ccm.html/"
+ "574b7276735648616451774141686f70?"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, MultiplePipeCharacters) {
+ // This filter combines | characters used as:
+ // - host anchor
+ // - normal text character
+ // - right anchor
+ auto subscription = ConvertAndLoadRules(R"(
+ ||example.com/abc|def*.jpg|
+ )");
+
+ // Correct match.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("http://subdomain.example.com/abc|def/content.jpg"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+
+ // Incorrect, the URL does not end with .jpg, although .jpg occurs in the URL.
+ // Right anchor constraint not met.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://subdomain.example.com/abc|def/file.jpg/content"),
+ "domain.com", ContentType::Image, SiteKey(), FilterCategory::Blocking));
+
+ // Incorrect, the URL does not start with a subdomain of example.com.
+ // Host anchor constraint not met.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://notexample.com/abc|def/content.jpg"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+
+ // Incorrect, the URL does not contain the | in the text.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://subdomain.example.com/abc/def/content.jpg"), "domain.com",
+ ContentType::Image, SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, PipeEndsFilter) {
+ auto subscription = ConvertAndLoadRules(R"(
+ example*|
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("http://example.com/"),
+ "domain.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+// See DPD-1913
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ InvalidFilterDoesNotCrashParser) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||
+ |
+ )");
+ // Filters are too short and were rejected by parser.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://example.com/content.jpg"), "example.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+// See DPD-1978
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, DomainSpecificSinglePipe) {
+ auto subscription = ConvertAndLoadRules(R"(
+ |$domain=example.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("http://example.com/content.jpg"),
+ "example.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://example.com/content.jpg"), "example.net", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+// See DPD-1978
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ DomainSpecificSinglePipeWithWildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ |*$domain=example.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("http://example.com/content.jpg"),
+ "example.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://example.com/content.jpg"), "example.net", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+// See DPD-1978
+TEST_F(AdblockInstalledSubscriptionImplUrlTest, DomainSpecificDoublePipe) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||$domain=example.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("http://example.com/content.jpg"),
+ "example.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://example.com/content.jpg"), "example.net", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+// See DPD-1978
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ DomainSpecificDoublePipeWithWildcard) {
+ auto subscription = ConvertAndLoadRules(R"(
+ ||*$domain=example.com
+ )");
+ EXPECT_TRUE(subscription->HasUrlFilter(GURL("http://example.com/content.jpg"),
+ "example.com", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("http://example.com/content.jpg"), "example.net", ContentType::Image,
+ SiteKey(), FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ ConvertMoreRegexFiltersThanCacheCapacity) {
+ std::vector<std::string> filters;
+ // Create a lot of regex filters
+ for (int i = 0; i < RegexMatcher::kMaxPrebuiltPatterns; i++) {
+ // Match any word followed by the numerical value of i, then another word.
+ filters.push_back(base::StringPrintf("/.*word%dword.*/", i));
+ }
+ // Add one more, this one will not get prebuilt
+ filters.push_back(base::StringPrintf("/.*word%dword.*/", 1000));
+
+ auto buffer =
+ converter_->Convert(filters, GURL{"http://data.com/filters.txt"}, false);
+ ASSERT_TRUE(buffer);
+ auto subscription = base::MakeRefCounted<InstalledSubscriptionImpl>(
+ std::move(buffer), Subscription::InstallationState::Installed,
+ base::Time());
+ // Ensure a URL that matches our "extra" regex filter is matched.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://word1000word.com/ad.jpg"), "example.com",
+ ContentType::Image, {}, FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_WildcardDomainMatch) {
+ // Using a wildcard specifier in the domain list.
+ auto subscription = ConvertAndLoadRules(R"(
+ script/$script,domain=example.*
+ )");
+
+ // example.com is allowed.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "example.com", ContentType::Script, SiteKey(), FilterCategory::Blocking));
+ // example.co.uk is allowed, it's a two-component TLD and the wildcard
+ // specifier matches those as well.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "example.co.uk", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ // a subdomain is matched too.
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "sub.example.com", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ EXPECT_TRUE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "sub.example.co.uk", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+TEST_F(AdblockInstalledSubscriptionImplUrlTest,
+ UrlFilterBlocking_WildcardDomainMismatch) {
+ // Using a wildcard specifier in the domain list.
+ auto subscription = ConvertAndLoadRules(R"(
+ script/$script,domain=example.*
+ )");
+
+ // .evil is not a known TLD
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "example.evil", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ // .blogspot.com is a valid TLD but it's a private registrar.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "example.blogspot.com", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ // notexample.com is a different domain than example.com, should not be
+ // matched by example.*.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "notexample.com", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+ // .abc.com is not a TLD at all.
+ EXPECT_FALSE(subscription->HasUrlFilter(
+ GURL("https://abptestpages.org/testfiles/script/script.js"),
+ "example.abc.com", ContentType::Script, SiteKey(),
+ FilterCategory::Blocking));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/load_gzipped_test_file.cc b/components/adblock/core/subscription/test/load_gzipped_test_file.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/load_gzipped_test_file.cc
@@ -0,0 +1,43 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/load_gzipped_test_file.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace adblock {
+
+std::string LoadGzippedTestFile(std::string_view filename) {
+ base::FilePath path;
+ CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path));
+ path = path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("adblock")
+ .AppendASCII(filename);
+ CHECK(base::PathExists(path));
+ std::string content;
+ CHECK(base::ReadFileToString(path, &content));
+ CHECK(compression::GzipUncompress(content, &content));
+ return content;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/load_gzipped_test_file.h b/components/adblock/core/subscription/test/load_gzipped_test_file.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/load_gzipped_test_file.h
@@ -0,0 +1,34 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_LOAD_GZIPPED_TEST_FILE_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_LOAD_GZIPPED_TEST_FILE_H_
+
+#include <string>
+#include <string_view>
+
+namespace adblock {
+
+// Loads and extracts a file from components/test/data/adblock/|filename|
+// The file is assumed to exist and be gzipped. The function CHECKs and will
+// crash otherwise.
+std::string LoadGzippedTestFile(std::string_view filename);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_LOAD_GZIPPED_TEST_FILE_H_
diff --git a/components/adblock/core/subscription/test/mock_conversion_executors.cc b/components/adblock/core/subscription/test/mock_conversion_executors.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_conversion_executors.cc
@@ -0,0 +1,25 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_conversion_executors.h"
+
+namespace adblock {
+
+MockConversionExecutors::MockConversionExecutors() = default;
+MockConversionExecutors::~MockConversionExecutors() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_conversion_executors.h b/components/adblock/core/subscription/test/mock_conversion_executors.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_conversion_executors.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_CONVERSION_EXECUTORS_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_CONVERSION_EXECUTORS_H_
+
+#include "components/adblock/core/subscription/conversion_executors.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockConversionExecutors : public NiceMock<ConversionExecutors> {
+ public:
+ MockConversionExecutors();
+ ~MockConversionExecutors() override;
+ MOCK_METHOD(scoped_refptr<InstalledSubscription>,
+ ConvertCustomFilters,
+ (const std::vector<std::string>& filters),
+ (override, const));
+ MOCK_METHOD(void,
+ ConvertFilterListFile,
+ (const GURL& subscription_url,
+ const base::FilePath& path,
+ base::OnceCallback<void(ConversionResult)>),
+ (override, const));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_CONVERSION_EXECUTORS_H_
diff --git a/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.cc b/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.cc
@@ -0,0 +1,28 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h"
+
+namespace adblock {
+
+MockFilteringConfigurationMaintainer::MockFilteringConfigurationMaintainer() =
+ default;
+MockFilteringConfigurationMaintainer::~MockFilteringConfigurationMaintainer() {
+ Destructor();
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h b/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_FILTERING_CONFIGURATION_MAINTAINER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_FILTERING_CONFIGURATION_MAINTAINER_H_
+
+#include "components/adblock/core/subscription/filtering_configuration_maintainer.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockFilteringConfigurationMaintainer
+ : public NiceMock<FilteringConfigurationMaintainer> {
+ public:
+ MockFilteringConfigurationMaintainer();
+ ~MockFilteringConfigurationMaintainer() override;
+ MOCK_METHOD(std::vector<scoped_refptr<Subscription>>,
+ GetCurrentSubscriptions,
+ (),
+ (override, const));
+ MOCK_METHOD(std::unique_ptr<SubscriptionCollection>,
+ GetSubscriptionCollection,
+ (),
+ (override, const));
+ MOCK_METHOD(void, RemoveAutoInstalledSubscriptions, (), (override));
+ MOCK_METHOD(void, Destructor, (), ());
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_FILTERING_CONFIGURATION_MAINTAINER_H_
diff --git a/components/adblock/core/subscription/test/mock_installed_subscription.cc b/components/adblock/core/subscription/test/mock_installed_subscription.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_installed_subscription.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_installed_subscription.h"
+
+namespace adblock {
+
+MockInstalledSubscription::MockInstalledSubscription() = default;
+
+MockInstalledSubscription::~MockInstalledSubscription() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_installed_subscription.h b/components/adblock/core/subscription/test/mock_installed_subscription.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_installed_subscription.h
@@ -0,0 +1,102 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_INSTALLED_SUBSCRIPTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_INSTALLED_SUBSCRIPTION_H_
+
+#include "components/adblock/core/subscription/installed_subscription.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockInstalledSubscription : public NiceMock<InstalledSubscription> {
+ public:
+ MockInstalledSubscription();
+ MOCK_METHOD(GURL, GetSourceUrl, (), (override, const));
+ MOCK_METHOD(std::string, GetTitle, (), (override, const));
+ MOCK_METHOD(std::string, GetCurrentVersion, (), (override, const));
+ MOCK_METHOD(InstallationState, GetInstallationState, (), (override, const));
+ MOCK_METHOD(base::Time, GetInstallationTime, (), (override, const));
+ MOCK_METHOD(base::TimeDelta, GetExpirationInterval, (), (override, const));
+ MOCK_METHOD(bool,
+ HasUrlFilter,
+ (const GURL& url,
+ const std::string& document_domain,
+ ContentType type,
+ const SiteKey& sitekey,
+ FilterCategory category),
+ (override, const));
+ MOCK_METHOD(bool,
+ HasPopupFilter,
+ (const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey,
+ FilterCategory category),
+ (override, const));
+ MOCK_METHOD(bool,
+ HasSpecialFilter,
+ (SpecialFilterType type,
+ const GURL& url,
+ const std::string& document_domain,
+ const SiteKey& sitekey),
+ (override, const));
+ MOCK_METHOD(void,
+ FindCspFilters,
+ (const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<std::string_view>& results),
+ (override, const));
+ MOCK_METHOD(std::set<std::string_view>,
+ FindRewriteFilters,
+ (const GURL& url,
+ const std::string& document_domain,
+ FilterCategory category),
+ (override, const));
+ MOCK_METHOD(void,
+ FindHeaderFilters,
+ (const GURL& url,
+ ContentType type,
+ const std::string& document_domain,
+ FilterCategory category,
+ std::set<HeaderFilterData>& results),
+ (override, const));
+ MOCK_METHOD(ContentFiltersData,
+ GetElemhideData,
+ (const GURL& url, bool domain_specific),
+ (override, const));
+ MOCK_METHOD(ContentFiltersData,
+ GetElemhideEmulationData,
+ (const GURL& url),
+ (override, const));
+ MOCK_METHOD(std::vector<Snippet>,
+ MatchSnippets,
+ (const std::string& document_domain),
+ (override, const));
+ MOCK_METHOD(void, MarkForPermanentRemoval, (), (override));
+
+ protected:
+ ~MockInstalledSubscription() override;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_INSTALLED_SUBSCRIPTION_H_
diff --git a/components/adblock/core/subscription/test/mock_subscription.cc b/components/adblock/core/subscription/test/mock_subscription.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription.cc
@@ -0,0 +1,36 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_subscription.h"
+
+namespace adblock {
+
+MockSubscription::MockSubscription() = default;
+
+MockSubscription::~MockSubscription() = default;
+
+scoped_refptr<MockSubscription> MakeMockSubscription(
+ GURL url,
+ Subscription::InstallationState state) {
+ auto subscription = base::MakeRefCounted<MockSubscription>();
+ ON_CALL(*subscription, GetSourceUrl()).WillByDefault(testing::Return(url));
+ ON_CALL(*subscription, GetInstallationState())
+ .WillByDefault(testing::Return(state));
+ return subscription;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_subscription.h b/components/adblock/core/subscription/test/mock_subscription.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription.h
@@ -0,0 +1,51 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+#include "components/adblock/core/subscription/subscription.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockSubscription : public NiceMock<Subscription> {
+ public:
+ MockSubscription();
+ MOCK_METHOD(GURL, GetSourceUrl, (), (override, const));
+ MOCK_METHOD(std::string, GetTitle, (), (override, const));
+ MOCK_METHOD(std::string, GetCurrentVersion, (), (override, const));
+ MOCK_METHOD(InstallationState, GetInstallationState, (), (override, const));
+ MOCK_METHOD(base::Time, GetInstallationTime, (), (override, const));
+ MOCK_METHOD(base::TimeDelta, GetExpirationInterval, (), (override, const));
+
+ protected:
+ ~MockSubscription() override;
+};
+
+scoped_refptr<MockSubscription> MakeMockSubscription(
+ GURL url,
+ Subscription::InstallationState state =
+ Subscription::InstallationState::Installed);
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_H_
diff --git a/components/adblock/core/subscription/test/mock_subscription_collection.cc b/components/adblock/core/subscription/test/mock_subscription_collection.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_collection.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+
+namespace adblock {
+
+MockSubscriptionCollection::MockSubscriptionCollection() = default;
+
+MockSubscriptionCollection::~MockSubscriptionCollection() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_subscription_collection.h b/components/adblock/core/subscription/test/mock_subscription_collection.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_collection.h
@@ -0,0 +1,102 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_COLLECTION_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_COLLECTION_H_
+
+#include "components/adblock/core/subscription/subscription_collection.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockSubscriptionCollection : public NiceMock<SubscriptionCollection> {
+ public:
+ MockSubscriptionCollection();
+ ~MockSubscriptionCollection() override;
+ MOCK_METHOD(const std::string&,
+ GetFilteringConfigurationName,
+ (),
+ (const, override));
+ MOCK_METHOD(absl::optional<GURL>,
+ FindBySubresourceFilter,
+ (const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey,
+ FilterCategory category),
+ (const, override));
+ MOCK_METHOD(absl::optional<GURL>,
+ FindByPopupFilter,
+ (const GURL& popup_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey,
+ FilterCategory category),
+ (const, override));
+ MOCK_METHOD(absl::optional<GURL>,
+ FindByAllowFilter,
+ (const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ const SiteKey& sitekey),
+ (const, override));
+ MOCK_METHOD(absl::optional<GURL>,
+ FindBySpecialFilter,
+ (SpecialFilterType filter_type,
+ const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey),
+ (const, override));
+ MOCK_METHOD(InstalledSubscription::ContentFiltersData,
+ GetElementHideData,
+ (const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ const SiteKey& sitekey),
+ (const, override));
+ MOCK_METHOD(InstalledSubscription::ContentFiltersData,
+ GetElementHideEmulationData,
+ (const GURL& frame_url),
+ (const, override));
+ MOCK_METHOD(base::Value::List,
+ GenerateSnippets,
+ (const GURL& frame_url, const std::vector<GURL>& frame_hierarchy),
+ (const, override));
+ MOCK_METHOD(std::set<std::string_view>,
+ GetCspInjections,
+ (const GURL& frame_url, const std::vector<GURL>& frame_hierarchy),
+ (const, override));
+ MOCK_METHOD(std::set<std::string_view>,
+ GetRewriteFilters,
+ (const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ FilterCategory blocking_filter_category),
+ (const, override));
+ MOCK_METHOD(std::set<HeaderFilterData>,
+ GetHeaderFilters,
+ (const GURL& frame_url,
+ const std::vector<GURL>& frame_hierarchy,
+ ContentType content_type,
+ FilterCategory category),
+ (const, override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_COLLECTION_H_
diff --git a/components/adblock/core/subscription/test/mock_subscription_downloader.cc b/components/adblock/core/subscription/test/mock_subscription_downloader.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_downloader.cc
@@ -0,0 +1,25 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_subscription_downloader.h"
+
+namespace adblock {
+
+MockSubscriptionDownloader::MockSubscriptionDownloader() = default;
+MockSubscriptionDownloader::~MockSubscriptionDownloader() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_subscription_downloader.h b/components/adblock/core/subscription/test/mock_subscription_downloader.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_downloader.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_DOWNLOADER_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_DOWNLOADER_H_
+
+#include "base/functional/callback.h"
+#include "components/adblock/core/subscription/subscription_downloader.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockSubscriptionDownloader : public NiceMock<SubscriptionDownloader> {
+ public:
+ MockSubscriptionDownloader();
+ ~MockSubscriptionDownloader() override;
+ MOCK_METHOD(void,
+ StartDownload,
+ (const GURL& subscription_url,
+ AdblockResourceRequest::RetryPolicy retry_policy,
+ DownloadCompletedCallback on_finished),
+ (override));
+ MOCK_METHOD(void,
+ DoHeadRequest,
+ (const GURL& subscription_url, HeadRequestCallback on_finished),
+ (override));
+ MOCK_METHOD(void, CancelDownload, (const GURL& subscription_url), (override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_DOWNLOADER_H_
diff --git a/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.cc b/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.cc
@@ -0,0 +1,27 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+
+namespace adblock {
+
+MockSubscriptionPersistentMetadata::MockSubscriptionPersistentMetadata() =
+ default;
+MockSubscriptionPersistentMetadata::~MockSubscriptionPersistentMetadata() =
+ default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h b/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_PERSISTENT_METADATA_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_PERSISTENT_METADATA_H_
+
+#include "components/adblock/core/subscription/subscription_persistent_metadata.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockSubscriptionPersistentMetadata
+ : public NiceMock<SubscriptionPersistentMetadata> {
+ public:
+ MockSubscriptionPersistentMetadata();
+ ~MockSubscriptionPersistentMetadata() override;
+ MOCK_METHOD(void,
+ SetExpirationInterval,
+ (const GURL& subscription_url, base::TimeDelta expires_in),
+ (override));
+ MOCK_METHOD(void,
+ SetLastInstallationTime,
+ (const GURL& subscription_url),
+ (override));
+ MOCK_METHOD(void,
+ SetVersion,
+ (const GURL& subscription_url, std::string version),
+ (override));
+ MOCK_METHOD(void,
+ IncrementDownloadSuccessCount,
+ (const GURL& subscription_url),
+ (override));
+ MOCK_METHOD(void,
+ IncrementDownloadErrorCount,
+ (const GURL& subscription_url),
+ (override));
+ MOCK_METHOD(bool,
+ IsExpired,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(base::Time,
+ GetLastInstallationTime,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(std::string,
+ GetVersion,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(int,
+ GetDownloadSuccessCount,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(int,
+ GetDownloadErrorCount,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(void,
+ SetAutoInstalledExpirationInterval,
+ (const GURL& subscription_url, base::TimeDelta expires_in),
+ (override));
+ MOCK_METHOD(bool,
+ IsAutoInstalled,
+ (const GURL& subscription_url),
+ (override, const));
+ MOCK_METHOD(bool,
+ IsAutoInstalledExpired,
+ (const GURL& subscription_url),
+ (override, const));
+
+ MOCK_METHOD(void, RemoveMetadata, (const GURL& subscription_url), (override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_PERSISTENT_METADATA_H_
diff --git a/components/adblock/core/subscription/test/mock_subscription_service.cc b/components/adblock/core/subscription/test/mock_subscription_service.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_service.cc
@@ -0,0 +1,36 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "gtest/gtest.h"
+
+namespace adblock {
+
+MockSubscriptionService::MockSubscriptionService() = default;
+MockSubscriptionService::~MockSubscriptionService() = default;
+
+void MockSubscriptionService::AddObserver(SubscriptionObserver* observer) {
+ ASSERT_FALSE(observer_) << "Adding observer twice";
+ observer_ = observer;
+}
+
+void MockSubscriptionService::RemoveObserver(SubscriptionObserver* observer) {
+ ASSERT_EQ(observer_, observer) << "Removing unknown observer";
+ observer_ = nullptr;
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/mock_subscription_service.h b/components/adblock/core/subscription/test/mock_subscription_service.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/mock_subscription_service.h
@@ -0,0 +1,77 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_SERVICE_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_SERVICE_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/configuration/test/fake_filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::NiceMock;
+
+namespace adblock {
+
+class MockSubscriptionService : public NiceMock<SubscriptionService> {
+ public:
+ MockSubscriptionService();
+ ~MockSubscriptionService() override;
+ MOCK_METHOD(std::vector<scoped_refptr<Subscription>>,
+ GetCurrentSubscriptions,
+ (FilteringConfiguration*),
+ (override, const));
+ MOCK_METHOD(Snapshot, GetCurrentSnapshot, (), (override, const));
+ MOCK_METHOD(void,
+ InstallFilteringConfiguration,
+ (std::unique_ptr<FilteringConfiguration> configuration),
+ (override));
+ MOCK_METHOD(void,
+ UninstallFilteringConfiguration,
+ (std::string_view configuration_name),
+ (override));
+ MOCK_METHOD(std::vector<FilteringConfiguration*>,
+ GetInstalledFilteringConfigurations,
+ (),
+ (override));
+ MOCK_METHOD(FilteringConfiguration*,
+ GetFilteringConfiguration,
+ (std::string_view configuration_name),
+ (override, const));
+ MOCK_METHOD(void, SetAutoInstallEnabled, (bool), (override));
+ MOCK_METHOD(bool, IsAutoInstallEnabled, (), (override, const));
+ void AddObserver(SubscriptionObserver* observer) final;
+ void RemoveObserver(SubscriptionObserver* observer) final;
+
+ void WillRequireFiltering(bool filtering_required) {
+ filtering_configuration_.is_enabled = filtering_required;
+ EXPECT_CALL(*this, GetInstalledFilteringConfigurations())
+ .WillRepeatedly(testing::Return(
+ std::vector<FilteringConfiguration*>{&filtering_configuration_}));
+ }
+
+ raw_ptr<SubscriptionObserver> observer_ = nullptr;
+ FakeFilteringConfiguration filtering_configuration_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_SERVICE_H_
diff --git a/components/adblock/core/subscription/test/pattern_matcher_perftest.cc b/components/adblock/core/subscription/test/pattern_matcher_perftest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/pattern_matcher_perftest.cc
@@ -0,0 +1,73 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "components/adblock/core/subscription/pattern_matcher.h"
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+#include <vector>
+
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_split.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/adblock/core/subscription/test/load_gzipped_test_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "url/gurl.h"
+
+namespace adblock {
+namespace {
+constexpr char kMetricRuntime[] = ".runtime";
+
+void MatchPatterns(const std::vector<std::string_view>& patterns,
+ const GURL& url) {
+ for (const auto p : patterns) {
+ DoesPatternMatchUrl(p, url);
+ }
+}
+
+std::string GetTestName() {
+ auto* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ return std::string(test_info->test_suite_name()) + "." + test_info->name();
+}
+} // namespace
+
+TEST(AdblockPatternMatcherPerfTest, FilterMatchingSpeed) {
+ const auto url_file_content = LoadGzippedTestFile("5000_urls.txt.gz");
+ std::vector<GURL> urls;
+ base::ranges::transform(
+ base::SplitStringPiece(url_file_content, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY),
+ std::back_inserter(urls),
+ [](const auto string_piece) { return GURL(string_piece); });
+ const auto pattern_file_content = LoadGzippedTestFile("5000_patterns.txt.gz");
+ const auto patterns =
+ base::SplitStringPiece(pattern_file_content, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ perf_test::PerfResultReporter reporter(GetTestName(),
+ "5000 patterns, 5000 urls");
+ reporter.RegisterImportantMetric(kMetricRuntime, "ms");
+ base::ElapsedTimer timer;
+ for (const auto& url : urls) {
+ MatchPatterns(patterns, url);
+ }
+ reporter.AddResult(kMetricRuntime,
+ static_cast<size_t>(timer.Elapsed().InMilliseconds()));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/pattern_matcher_test.cc b/components/adblock/core/subscription/test/pattern_matcher_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/pattern_matcher_test.cc
@@ -0,0 +1,376 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/pattern_matcher.h"
+
+#include <string>
+#include <string_view>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockPatternMatcherTest, EmptyPatternMatchesInputs) {
+ // Filters that have no pattern will match any URL. An example of such filter:
+ // $third_party,domain=abc.com
+ // (Nothing before the '$' character that denotes start of options)
+ // This means "block *all* third party URLs while on abc.com".
+ EXPECT_TRUE(DoesPatternMatchUrl("", GURL("https://ads.com")));
+}
+
+TEST(AdblockPatternMatcherTest, NonAnchoredPatternMatchesAnywhere) {
+ // Filter patterns that have no anchoring characters will match anywhere URL.
+ const auto pattern = std::string_view("ad-970");
+
+ // Inside the host:
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://x-ad-970-x.com")));
+ // Start of host:
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://ad-970-x.com/")));
+ // End of host:
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://x-ad-970.com/")));
+ // Somewhere in path:
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://ad.com/path/ad-970/x")));
+ // The very end:
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://ad.com/path/ad-970")));
+
+ // Does not mach partial appearance.
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("https://ad.com/ad/970")));
+}
+
+TEST(AdblockPatternMatcherTest,
+ HostAnchoredPatternMatchesDomainsAndSubdomains) {
+ // Filter pattern that starts from || will match if the prefix is a subdomain
+ // or is empty.
+
+ const auto pattern = std::string_view("||example.com/banner.gif");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/banner.gif")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.com/banner.gif")));
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://subdomain.example.com/banner.gif")));
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://deep.subdomain.example.com/banner.gif")));
+
+ // Partial match is not a match:
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("https://example.com/")));
+ // nonexample.com is not a subdomain of example.com
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("http://nonexample.com/banner.gif")));
+}
+
+TEST(AdblockPatternMatcherTest, HostAnchoredPatternWithImmediateWildcard) {
+ // This is a weird kind of filter but it appears in the wild. It is equivalent
+ // to one that has no host anchor, since the wildcard matches anything anyway.
+ const auto pattern = std::string_view("||*/banner.gif");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/banner.gif")));
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("http://example.com/foobar/banner.gif")));
+}
+
+TEST(AdblockPatternMatcherTest, StartAnchoredPatternMatchesOnlyStartOfUrl) {
+ const auto pattern = std::string_view("|https");
+
+ // Pattern appears at the start of the URL, this is matched:
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/banner.gif")));
+
+ // Partial match = not a match
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.com/banner.gif")));
+ // Pattern appears in the middle of the URL, not a match.
+ EXPECT_FALSE(DoesPatternMatchUrl(
+ pattern, GURL("http://example.com/https/banner.gif")));
+}
+
+TEST(AdblockPatternMatcherTest, EndAnchoredPatternMatchesOnlyEndOfUrl) {
+ const auto pattern = std::string_view("/popup/log|");
+
+ // Pattern appears at the end of the URL, this is matched:
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/popup/log")));
+
+ // Partial match = not a match
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("http://example.com/log")));
+ // Pattern appears in the middle of the URL, not a match.
+ EXPECT_FALSE(DoesPatternMatchUrl(
+ pattern, GURL("http://example.com/popup/log/banner.gif")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterBetweenHostAndPath) {
+ // This is a *very* common filter pattern:
+ const auto pattern = std::string_view("||example.com^");
+
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://example.com/ad.gif")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com:8000/ad.gif")));
+
+ // '.' is not a character matched by ^. This treats example.com.ar as a
+ // different domain
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com.ar/ad.gif")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterAtTheEndOfUrl) {
+ const auto pattern = std::string_view("||example.com^");
+
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://example.com")));
+}
+
+TEST(AdblockPatternMatcherTest,
+ SeparatorCharacterContinuesMatchingAfterEndOfUrl) {
+ // The ^ separator matches the end of URL, but in this filter we still expect
+ // more tokens.
+ const auto pattern = std::string_view("file^more");
+
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("https://start.com/file")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterInsideUrl) {
+ const auto pattern = std::string_view("foo^bar");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.com/foo/bar?a=12")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.com/foo?bar=12")));
+}
+
+TEST(AdblockPatternMatcherTest, MultipleSeparatorCharacters) {
+ const auto pattern = std::string_view("^foo.bar^");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.com/foo.bar?a=12")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharactersNotMatchedTooEagerly) {
+ const auto pattern = std::string_view("^foo^");
+
+ // The first "foo" is not a valid match for the filter, since it's surrounded
+ // by dots which are not considered separators. But the filter should match
+ // the second "foo".
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("http://example.foo.com/foo?a=12")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterDoesNotMatchWords) {
+ const auto pattern = std::string_view("^foo.bar^");
+
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("https://nonfoo.bar.com/")));
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("https://foo.barbara.com/")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterExceptions) {
+ // The separator character can be anything but a letter, a digit, or one of
+ // the following: _, -, ., %.
+ const auto pattern = std::string_view("foo^bar");
+
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/foo-bar")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/foo_bar")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/foo.bar")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/foo%bar")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/fooXbar")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/foo5bar")));
+}
+
+TEST(AdblockPatternMatcherTest, SeparatorCharacterMatchesOnlySingleCharacter) {
+ const auto pattern = std::string_view("http^value");
+
+ // More than one character between "http" and "value"
+ EXPECT_FALSE(DoesPatternMatchUrl(pattern, GURL("http://value.com/")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/http/?value")));
+}
+
+TEST(AdblockPatternMatcherTest, WildcardInPattern) {
+ const auto pattern = std::string_view("&gerf=*&guro=");
+
+ // Wildcard matches zero characters
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://example.com/data?x&gerf=&guro=")));
+
+ // Matches one digit
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://example.com/data?x&gerf=1&guro=")));
+
+ // Matches a long string with non-alphanumerical characters
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://example.com/data?x&gerf=abcd&yyy=zzzz&guro=asd")));
+}
+
+TEST(AdblockPatternMatcherTest, HostWildcard) {
+ const auto pattern = std::string_view("||ad.*.example.net^");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://ad.host.example.net/data")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://ad.server.example.net/data")));
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://subdomain.ad.server.example.net/data")));
+
+ // Does not match because there need to be at least two dots between "ad" and
+ // "example", and possibly something between the dots.
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://ad.example.net/data")));
+ // Does not match the host anchor, even though it matches the wildcard.
+ EXPECT_FALSE(DoesPatternMatchUrl(
+ pattern, GURL("https://domain.com/ad.x.example.net/")));
+}
+
+TEST(AdblockPatternMatcherTest, SeveralWildcardsInPattern) {
+ const auto pattern = std::string_view("||example.com^*_*.php");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/data_file.php")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/_file.php")));
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/data_.php")));
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://example.com/_.php")));
+
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://example.com/datafile.php")));
+}
+
+TEST(AdblockPatternMatcherTest, NonGreedyWildcardMatch) {
+ const auto pattern = std::string_view("start*foobar^");
+
+ // The first match of "foobar" isn't followed by a separator (but instead by
+ // "bad"). But the algorithm doesn't stop searching and finds the next match
+ // that is followed by a separator.
+ EXPECT_TRUE(DoesPatternMatchUrl(
+ pattern, GURL("https://start.com/foobarbad/foobar/file.png")));
+}
+
+TEST(AdblockPatternMatcherTest, MultipleConsecutiveWildcards) {
+ const auto pattern = std::string_view("start***foobar");
+
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://start.com/foobar/file.png")));
+}
+
+TEST(AdblockPatternMatcherTest, UrlEndsWithSeparator) {
+ const auto pattern = std::string_view("file^|");
+
+ // URL ends with "file" followed by a separator character, good match.
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://start.com/foobar/file/")));
+
+ // Ends without a separator, but ^ matches EOF too, good match.
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://start.com/file")));
+
+ // Has file/ but doesn't end with it.
+ EXPECT_FALSE(
+ DoesPatternMatchUrl(pattern, GURL("https://start.com/file/foobar")));
+}
+
+TEST(AdblockPatternMatcherTest, MatchAfterPartialMatch) {
+ const auto pattern = std::string_view("barbar^");
+
+ // This checks that we don't skip too far forward when the first match
+ // position fails. The first position of "barbar" is not followed by a
+ // separator, the second position is but it also overlaps with the first
+ // match.
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://start.com/barbarbar/")));
+}
+
+TEST(AdblockPatternMatcherTest, UrlEndsWithWildcardAndSeparator) {
+ const auto pattern = std::string_view("file*^|");
+
+ // URL ends with "file" followed by a separator character, good match.
+ EXPECT_TRUE(
+ DoesPatternMatchUrl(pattern, GURL("https://start.com/foobar/file/")));
+
+ // Ends without a separator, but ^ matches EOF too, good match.
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://start.com/file")));
+}
+
+TEST(AdblockPatternMatcherTest, FirstTokenIsWildcard) {
+ // This is redundant and equivalent to not having the starting wildcard, but
+ // some filters do this.
+ const auto pattern = std::string_view("*file");
+
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://start.com/file")));
+}
+
+TEST(AdblockPatternMatcherTest, DeepRecursion) {
+ auto recursive_input_maker = [](int depth) -> std::pair<std::string, GURL> {
+ std::string pattern;
+ GURL url("https://example.com");
+ for (int i = 0; i < depth; i++) {
+ pattern += "a^";
+ url = url.Resolve("a/");
+ }
+ return std::make_pair(pattern, url);
+ };
+ // For patterns that are simple enough, match normally.
+ const auto [shallow_pattern, short_url] = recursive_input_maker(5);
+ EXPECT_TRUE(DoesPatternMatchUrl(shallow_pattern, short_url));
+
+ // Malicious, long filters are never matched.
+ const auto [deep_pattern, long_url] = recursive_input_maker(100);
+ EXPECT_FALSE(DoesPatternMatchUrl(deep_pattern, long_url));
+}
+
+TEST(AdblockPatternMatcherTest, EmptyDoublePipe) {
+ const auto pattern = std::string_view("||");
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://url.com")));
+}
+
+TEST(AdblockPatternMatcherTest, EmptySinglePipe) {
+ const auto pattern = std::string_view("|");
+ EXPECT_TRUE(DoesPatternMatchUrl(pattern, GURL("https://url.com")));
+}
+
+TEST(AdblockPatternMatcherTest, InvalidHostPatterns) {
+ EXPECT_FALSE(DoesPatternMatchUrl("||https://test.com",
+ GURL("https://test.com/ad.png")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl("||ps://test.com", GURL("https://test.com/ad.png")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl("||://test.com", GURL("https://test.com/ad.png")));
+ EXPECT_FALSE(
+ DoesPatternMatchUrl("||/test.com", GURL("https://test.com/ad.png")));
+}
+
+TEST(AdblockPatternMatcherTest, HostPatternsMatchingNonStandardUrls) {
+ // These are atypical URLs, but the matching algorithm should still work.
+
+ // Host may come with a port:
+ EXPECT_TRUE(DoesPatternMatchUrl("||example.com^",
+ GURL("https://example.com:8000/ad.png")));
+
+ // A file:// path that looks like it contains the domain name:
+ EXPECT_FALSE(DoesPatternMatchUrl("||example.com^",
+ GURL("file:///example.com/ad.png")));
+
+ // A data: blob that contains a fragment that matches the domain:
+ EXPECT_FALSE(DoesPatternMatchUrl("||example.com^",
+ GURL("data:text/html,example.com#ad")));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/preloaded_subscription_provider_impl_test.cc b/components/adblock/core/subscription/test/preloaded_subscription_provider_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/preloaded_subscription_provider_impl_test.cc
@@ -0,0 +1,149 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/preloaded_subscription_provider_impl.h"
+
+#include "base/ranges/algorithm.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class PreloadedSubscriptionProviderImplTest : public testing::Test {
+ public:
+ base::test::TaskEnvironment task_environment_;
+ PreloadedSubscriptionProviderImpl provider_;
+};
+
+TEST_F(PreloadedSubscriptionProviderImplTest,
+ NoSubscriptionsYieldsNoPreloadedSubscriptions) {
+ EXPECT_TRUE(provider_.GetCurrentPreloadedSubscriptions().empty());
+ provider_.UpdateSubscriptions({}, {});
+ EXPECT_TRUE(provider_.GetCurrentPreloadedSubscriptions().empty());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest,
+ CustomSubscriptionYieldsNoPreloadedSubscriptions) {
+ const GURL kCustomSubscription{"https://subs.com/1.txt"};
+ provider_.UpdateSubscriptions({}, {kCustomSubscription});
+ EXPECT_TRUE(provider_.GetCurrentPreloadedSubscriptions().empty());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest, EasyListRequired) {
+ const GURL kInstalledSubscription{"https://subs.com/1.txt"};
+ const GURL kPendingSubscription{
+ "https://easylist-downloads.adblockplus.org/easylist.txt"};
+ provider_.UpdateSubscriptions({kInstalledSubscription},
+ {kPendingSubscription});
+ const auto preloaded_subscriptions =
+ provider_.GetCurrentPreloadedSubscriptions();
+ ASSERT_EQ(preloaded_subscriptions.size(), 1u);
+ EXPECT_EQ(preloaded_subscriptions[0]->GetSourceUrl(),
+ DefaultSubscriptionUrl());
+ EXPECT_EQ(preloaded_subscriptions[0]->GetInstallationState(),
+ Subscription::InstallationState::Preloaded);
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest, ExceptionrulesRequired) {
+ const GURL kInstalledSubscription{"https://subs.com/1.txt"};
+ const GURL kPendingSubscription{
+ "https://easylist-downloads.adblockplus.org/exceptionrules.txt"};
+ provider_.UpdateSubscriptions({kInstalledSubscription},
+ {kPendingSubscription});
+ const auto preloaded_subscriptions =
+ provider_.GetCurrentPreloadedSubscriptions();
+ ASSERT_EQ(preloaded_subscriptions.size(), 1u);
+ EXPECT_EQ(preloaded_subscriptions[0]->GetSourceUrl(), AcceptableAdsUrl());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest, AnticvRequired) {
+ const GURL kInstalledSubscription{"https://subs.com/1.txt"};
+ const GURL kPendingSubscription{
+ "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt"};
+ provider_.UpdateSubscriptions({kInstalledSubscription},
+ {kPendingSubscription});
+ const auto preloaded_subscriptions =
+ provider_.GetCurrentPreloadedSubscriptions();
+ ASSERT_EQ(preloaded_subscriptions.size(), 1u);
+ EXPECT_EQ(preloaded_subscriptions[0]->GetSourceUrl(), AntiCVUrl());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest,
+ LanguageSpecificEasyListRequired) {
+ const GURL kInstalledSubscription{"https://subs.com/1.txt"};
+ // Even though the required subscription is not exactly easylist.txt, it is
+ // based on easylist.txt and so we provide the preloaded subscription.
+ const GURL kPendingSubscription{
+ "https://easylist-downloads.adblockplus.org/easylistpolish+easylist.txt"};
+ provider_.UpdateSubscriptions({kInstalledSubscription},
+ {kPendingSubscription});
+ const auto preloaded_subscriptions =
+ provider_.GetCurrentPreloadedSubscriptions();
+ ASSERT_EQ(preloaded_subscriptions.size(), 1u);
+ EXPECT_EQ(preloaded_subscriptions[0]->GetSourceUrl(),
+ DefaultSubscriptionUrl());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest,
+ SubscriptionInstalledAndUpdating) {
+ const GURL kInstalledSubscription{
+ "https://easylist-downloads.adblockplus.org/exceptionrules.txt"};
+ const GURL kPendingSubscription{
+ "https://easylist-downloads.adblockplus.org/exceptionrules.txt"};
+ provider_.UpdateSubscriptions({kInstalledSubscription},
+ {kPendingSubscription});
+ const auto preloaded_subscriptions =
+ provider_.GetCurrentPreloadedSubscriptions();
+ // No need to provide a preloaded subscription because the required
+ // subscription is already installed, an update is under way.
+ EXPECT_TRUE(provider_.GetCurrentPreloadedSubscriptions().empty());
+}
+
+TEST_F(PreloadedSubscriptionProviderImplTest, MultipleRequiredSubscriptions) {
+ const GURL kAcceptableAdsSubscription{
+ "https://easylist-downloads.adblockplus.org/exceptionrules.txt"};
+ const GURL kEasyListPolishSubscription{
+ "https://easylist-downloads.adblockplus.org/easylistpolish+easylist.txt"};
+ const GURL kAntiCVSubscription{
+ "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt"};
+ const GURL kOtherInstalledSubscription1{"https://subs.com/1.txt"};
+ const GURL kOtherInstalledSubscription2{"https://subs.com/2.txt"};
+ const GURL kOtherNonInstalledSubscription1{"https://subs.com/3.txt"};
+ provider_.UpdateSubscriptions(
+ {kOtherInstalledSubscription1, kOtherInstalledSubscription2},
+ {kOtherInstalledSubscription2, kOtherNonInstalledSubscription1,
+ kAcceptableAdsSubscription, kEasyListPolishSubscription,
+ kAntiCVSubscription});
+ auto preloaded_subscriptions = provider_.GetCurrentPreloadedSubscriptions();
+ ASSERT_EQ(preloaded_subscriptions.size(), 3u);
+ base::ranges::sort(preloaded_subscriptions, {}, &Subscription::GetSourceUrl);
+ EXPECT_EQ(preloaded_subscriptions[0]->GetSourceUrl(), AntiCVUrl());
+ EXPECT_EQ(preloaded_subscriptions[1]->GetSourceUrl(),
+ DefaultSubscriptionUrl());
+ EXPECT_EQ(preloaded_subscriptions[2]->GetSourceUrl(), AcceptableAdsUrl());
+ EXPECT_EQ(preloaded_subscriptions[0]->GetInstallationState(),
+ Subscription::InstallationState::Preloaded);
+ EXPECT_EQ(preloaded_subscriptions[1]->GetInstallationState(),
+ Subscription::InstallationState::Preloaded);
+ EXPECT_EQ(preloaded_subscriptions[2]->GetInstallationState(),
+ Subscription::InstallationState::Preloaded);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/recommended_subscription_installer_impl_test.cc b/components/adblock/core/subscription/test/recommended_subscription_installer_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/recommended_subscription_installer_impl_test.cc
@@ -0,0 +1,325 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/recommended_subscription_installer_impl.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/configuration/test/mock_filtering_configuration.h"
+#include "components/adblock/core/net/test/mock_adblock_resource_request.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+#include "components/prefs/testing_pref_service.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockRecommendedSubscriptionInstallerImplTest : public testing::Test {
+ public:
+ void SetUp() override {
+ common::prefs::RegisterProfilePrefs(prefs_.registry());
+
+ // Only adblock filtering configurations can have recommended subscriptions
+ // installed
+ const std::string adblock = "adblock";
+ EXPECT_CALL(filtering_configuration_, GetName())
+ .WillRepeatedly(testing::ReturnRef(adblock));
+
+ // Create tmp dir to store mock downloaded recommendation list.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ recommended_subscription_installer_ =
+ std::make_unique<RecommendedSubscriptionInstallerImpl>(
+ &prefs_, &filtering_configuration_, &persistent_metadata_,
+ request_maker_.Get());
+ }
+
+ void MockDownloadedFile(const std::string& content) {
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.GetPath(),
+ &recommendation_json_file_));
+ ASSERT_TRUE(base::WriteFile(recommendation_json_file_, content));
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath recommendation_json_file_;
+ TestingPrefServiceSimple prefs_;
+ MockFilteringConfiguration filtering_configuration_;
+ MockSubscriptionPersistentMetadata persistent_metadata_;
+ base::MockCallback<RecommendedSubscriptionInstallerImpl::ResourceRequestMaker>
+ request_maker_;
+ std::unique_ptr<RecommendedSubscriptionInstallerImpl>
+ recommended_subscription_installer_;
+};
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ UpdateIsDueOnFirstStart) {
+ // Next time recommended subscriptions should be updated is set to
+ // base::Time::Now() on the first start.
+ ASSERT_TRUE(prefs_.GetTime(
+ common::prefs::kAutoInstalledSubscriptionsNextUpdateTime) <=
+ base::Time::Now());
+
+ // ResourceRequestMaker is asked to create a request ...
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ // ... and recommended subscription list download starts.
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(RecommendedSubscriptionListUrl(),
+ AdblockResourceRequest::Method::GET, testing::_,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, ""));
+ return mock_ongoing_request;
+ });
+
+ recommended_subscription_installer_->RunUpdateCheck();
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ NoRequestMadeIfUpdateIsNotDue) {
+ // Set the next time recommended subscriptions should be updated to a time
+ // later then now.
+ prefs_.SetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime,
+ base::Time::Now() + base::Days(1));
+
+ // Since the update is not due no request is made.
+ EXPECT_CALL(request_maker_, Run()).Times(0);
+
+ recommended_subscription_installer_->RunUpdateCheck();
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ NoRecommendedSubscriptionsReceivedMeansNoInstalls) {
+ // ResourceRequestMaker is asked to create a request ...
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ // ... and recommended subscription list download starts.
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(RecommendedSubscriptionListUrl(),
+ AdblockResourceRequest::Method::GET, testing::_,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, ""))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ recommended_subscription_installer_->RunUpdateCheck();
+
+ // Recommended subscriptions is an empty file.
+ MockDownloadedFile("");
+
+ // Store next update time before running the update so we can check if it got
+ // updated.
+ auto kAutoInstalledSubscriptionsNextUpdateTimeBefore =
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime);
+
+ // OnRecommendationListDownloaded called with empty file.
+ response_callback.Run(RecommendedSubscriptionListUrl(),
+ recommendation_json_file_, nullptr);
+
+ // No subscriptions should installed.
+ EXPECT_CALL(filtering_configuration_, AddFilterList(testing::_)).Times(0);
+ // No expiration times should updated.
+ EXPECT_CALL(persistent_metadata_,
+ SetAutoInstalledExpirationInterval(testing::_, testing::_))
+ .Times(0);
+
+ task_environment_.RunUntilIdle();
+
+ // kAutoInstalledSubscriptionsNextUpdateTime got updated.
+ EXPECT_TRUE(
+ kAutoInstalledSubscriptionsNextUpdateTimeBefore <
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime));
+ EXPECT_FALSE(base::PathExists(recommendation_json_file_));
+}
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ RecommendedSubscriptionsGetInstalledOrUpdated) {
+ // ResourceRequestMaker is asked to create a request ...
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ // ... and recommended subscription list download starts.
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(RecommendedSubscriptionListUrl(),
+ AdblockResourceRequest::Method::GET, testing::_,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, ""))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ recommended_subscription_installer_->RunUpdateCheck();
+
+ // Expect that recommended subscriptions get installed.
+ EXPECT_CALL(filtering_configuration_,
+ AddFilterList(GURL("https://recommended-fl/list1.txt")));
+ EXPECT_CALL(filtering_configuration_,
+ AddFilterList(GURL("https://recommended-fl/list2.txt")));
+
+ // Expect that recommended subscriptions expiration times get updated.
+ EXPECT_CALL(persistent_metadata_,
+ SetAutoInstalledExpirationInterval(
+ GURL("https://recommended-fl/list1.txt"), base::Days(14)));
+ EXPECT_CALL(persistent_metadata_,
+ SetAutoInstalledExpirationInterval(
+ GURL("https://recommended-fl/list2.txt"), base::Days(14)));
+
+ // list1 was already auto installed.
+ EXPECT_CALL(filtering_configuration_,
+ IsFilterListPresent(GURL("https://recommended-fl/list1.txt")))
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(persistent_metadata_,
+ IsAutoInstalled(GURL("https://recommended-fl/list1.txt")))
+ .WillOnce(testing::Return(true));
+
+ // list2 was not yet installed.
+ EXPECT_CALL(filtering_configuration_,
+ IsFilterListPresent(GURL("https://recommended-fl/list2.txt")))
+ .WillOnce(testing::Return(false));
+
+ MockDownloadedFile(
+ "[{\"url\": \"https://recommended-fl/list1.txt\"}, {\"url\": "
+ "\"https://recommended-fl/list2.txt\"}]");
+
+ // Store next update time before running the update so we can check if it got
+ // updated.
+ auto kAutoInstalledSubscriptionsNextUpdateTimeBefore =
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime);
+
+ // OnRecommendationListDownloaded called with file containing a list of
+ // recommended subscription urls.
+ response_callback.Run(RecommendedSubscriptionListUrl(),
+ recommendation_json_file_, nullptr);
+
+ task_environment_.RunUntilIdle();
+
+ // kAutoInstalledSubscriptionsNextUpdateTime got updated.
+ EXPECT_TRUE(
+ kAutoInstalledSubscriptionsNextUpdateTimeBefore <
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime));
+ EXPECT_FALSE(base::PathExists(recommendation_json_file_));
+}
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ SkipAlreadyAddedNotAutoInstalledRecommendedSubscription) {
+ // ResourceRequestMaker is asked to create a request ...
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ // ... and recommended subscription list download starts.
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(RecommendedSubscriptionListUrl(),
+ AdblockResourceRequest::Method::GET, testing::_,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, ""))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ recommended_subscription_installer_->RunUpdateCheck();
+
+ // No subscriptions should installed.
+ EXPECT_CALL(filtering_configuration_, AddFilterList(testing::_)).Times(0);
+ // No expiration times should updated.
+ EXPECT_CALL(persistent_metadata_,
+ SetAutoInstalledExpirationInterval(testing::_, testing::_))
+ .Times(0);
+
+ // list1 was already installed.
+ EXPECT_CALL(filtering_configuration_,
+ IsFilterListPresent(GURL("https://recommended-fl/list1.txt")))
+ .WillOnce(testing::Return(true));
+ // list1 was not auto installed but added by user / device locale.
+ EXPECT_CALL(persistent_metadata_,
+ IsAutoInstalled(GURL("https://recommended-fl/list1.txt")))
+ .WillOnce(testing::Return(false));
+
+ MockDownloadedFile("[{\"url\": \"https://recommended-fl/list1.txt\"}]");
+
+ // Store next update time before running the update so we can check if it got
+ // updated.
+ auto kAutoInstalledSubscriptionsNextUpdateTimeBefore =
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime);
+
+ // OnRecommendationListDownloaded called with file containing a list of
+ // recommended subscription urls
+ response_callback.Run(RecommendedSubscriptionListUrl(),
+ recommendation_json_file_, nullptr);
+
+ task_environment_.RunUntilIdle();
+
+ // kAutoInstalledSubscriptionsNextUpdateTime got updated.
+ EXPECT_TRUE(
+ kAutoInstalledSubscriptionsNextUpdateTimeBefore <
+ prefs_.GetTime(common::prefs::kAutoInstalledSubscriptionsNextUpdateTime));
+ EXPECT_FALSE(base::PathExists(recommendation_json_file_));
+}
+
+TEST_F(AdblockRecommendedSubscriptionInstallerImplTest,
+ ExpiredAutoInstalledSubscriptionGetsRemoved) {
+ // ResourceRequestMaker is asked to create a request ...
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ // ... and recommended subscription list download starts.
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(RecommendedSubscriptionListUrl(),
+ AdblockResourceRequest::Method::GET, testing::_,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded, ""))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ const GURL list1("https://recommended-fl/list1.txt");
+ const GURL list2("https://recommended-fl/list2.txt");
+ const std::vector<GURL> installed_subcriptions = {list1, list2};
+
+ EXPECT_CALL(filtering_configuration_, GetFilterLists())
+ .WillOnce(testing::Return(installed_subcriptions));
+
+ // list1.txt is not yet expired.
+ EXPECT_CALL(persistent_metadata_, IsAutoInstalledExpired(list1))
+ .WillOnce(testing::Return(false));
+ // list2.txt was not recommended in a while and will be removed.
+ EXPECT_CALL(persistent_metadata_, IsAutoInstalledExpired(list2))
+ .WillOnce(testing::Return(true));
+
+ EXPECT_CALL(filtering_configuration_, RemoveFilterList(list2));
+
+ recommended_subscription_installer_->RunUpdateCheck();
+
+ // Expired auto installed lists get removed even when parsing fails.
+ MockDownloadedFile("[]");
+
+ response_callback.Run(RecommendedSubscriptionListUrl(),
+ recommendation_json_file_, nullptr);
+
+ task_environment_.RunUntilIdle();
+ EXPECT_FALSE(base::PathExists(recommendation_json_file_));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/recommended_subscription_parser_test.cc b/components/adblock/core/subscription/test/recommended_subscription_parser_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/recommended_subscription_parser_test.cc
@@ -0,0 +1,117 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/recommended_subscription_parser.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockRecommendedSubscriptionParserTest : public testing::Test {
+ public:
+ void Test(const std::string& downloaded_file_content,
+ const std::vector<GURL>& expected_result) {
+ // Mock downloaded file
+ base::FilePath recommendations_tmp_file;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(),
+ &recommendations_tmp_file));
+ ASSERT_TRUE(
+ base::WriteFile(recommendations_tmp_file, downloaded_file_content));
+
+ EXPECT_THAT(expected_result,
+ testing::ContainerEq(RecommendedSubscriptionParser::FromFile(
+ recommendations_tmp_file)));
+
+ task_environment_.RunUntilIdle();
+ EXPECT_FALSE(base::PathExists(recommendations_tmp_file));
+ }
+
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(AdblockRecommendedSubscriptionParserTest, EmptyFile) {
+ std::string downloaded_file_content = "";
+ std::vector<GURL> expected_result = {};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest, ValidListReturned) {
+ std::string downloaded_file_content =
+ "[{\"url\": \"https://recommended-fl/list1.txt\"}, {\"url\": "
+ "\"https://recommended-fl/list2.txt\"}]";
+ std::vector<GURL> expected_result = {
+ GURL("https://recommended-fl/list1.txt"),
+ GURL("https://recommended-fl/list2.txt")};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest,
+ InvalidRecommendationData_JsonSyntaxError) {
+ std::string downloaded_file_content = "not : valid : json";
+ std::vector<GURL> expected_result = {};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest,
+ InvalidRecommendationData_NotAList) {
+ std::string downloaded_file_content =
+ "{\"url\": \"https://recommended-fl/list1.txt\"}";
+ std::vector<GURL> expected_result = {};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest,
+ InvalidRecommendationData_NotADict) {
+ std::string downloaded_file_content =
+ "[[{\"url\": \"https://recommended-fl/list1.txt\"}]]";
+ std::vector<GURL> expected_result = {};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest,
+ InvalidRecommendationData_NoUrlKey) {
+ std::string downloaded_file_content =
+ "[{\"not_url\": \"https://recommended-fl/list1.txt\"}]";
+ std::vector<GURL> expected_result = {};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+TEST_F(AdblockRecommendedSubscriptionParserTest,
+ ValidAndInvalidRecommendationsMixed) {
+ std::string downloaded_file_content =
+ "[{\"not_url\": \"https://recommended-fl/list1.txt\"}, {\"url\": "
+ "\"https://recommended-fl/list2.txt\"}]";
+ std::vector<GURL> expected_result = {
+ GURL("https://recommended-fl/list2.txt")};
+
+ Test(downloaded_file_content, expected_result);
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/regex_matcher_perftest.cc b/components/adblock/core/subscription/test/regex_matcher_perftest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/regex_matcher_perftest.cc
@@ -0,0 +1,86 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "components/adblock/core/subscription/regex_matcher.h"
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+#include <vector>
+
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_split.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/adblock/core/subscription/test/load_gzipped_test_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+#include "url/gurl.h"
+
+namespace adblock {
+namespace {
+
+constexpr char kMetricInitialization[] = ".initialization";
+constexpr char kMetricRuntime[] = ".runtime";
+
+void MatchPatterns(const std::vector<std::string_view>& patterns,
+ const GURL& url,
+ const RegexMatcher& matcher) {
+ for (const auto p : patterns) {
+ matcher.MatchesRegex(p, url, false);
+ }
+}
+
+std::string GetTestName() {
+ auto* test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ return std::string(test_info->test_suite_name()) + "." + test_info->name();
+}
+} // namespace
+
+TEST(AdblockRegexMatcherPerfTest, FilterMatchingSpeed) {
+ const auto url_file_content = LoadGzippedTestFile("5000_urls.txt.gz");
+ std::vector<GURL> urls;
+ base::ranges::transform(
+ base::SplitStringPiece(url_file_content, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY),
+ std::back_inserter(urls),
+ [](const auto string_piece) { return GURL(string_piece); });
+ const auto pattern_file_content =
+ LoadGzippedTestFile("40_regex_patterns.txt.gz");
+ const auto patterns =
+ base::SplitStringPiece(pattern_file_content, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+
+ perf_test::PerfResultReporter reporter(GetTestName(),
+ "40 patterns, 5000 urls");
+ reporter.RegisterImportantMetric(kMetricInitialization, "us");
+ reporter.RegisterImportantMetric(kMetricRuntime, "us");
+
+ RegexMatcher matcher;
+ base::ElapsedTimer init_timer;
+ for (const auto pattern : patterns) {
+ matcher.PreBuildRegexPattern(pattern, false);
+ }
+ reporter.AddResult(kMetricInitialization, init_timer.Elapsed());
+
+ base::ElapsedTimer runtime_timer;
+ for (const auto& url : urls) {
+ MatchPatterns(patterns, url, matcher);
+ }
+ reporter.AddResult(kMetricRuntime, runtime_timer.Elapsed());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_collection_impl_test.cc b/components/adblock/core/subscription/test/subscription_collection_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_collection_impl_test.cc
@@ -0,0 +1,1021 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_collection_impl.h"
+
+#include <string_view>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/adblock/core/subscription/subscription.h"
+#include "components/adblock/core/subscription/test/mock_installed_subscription.h"
+#include "gmock/gmock-actions.h"
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+using testing::_;
+using testing::Return;
+
+class AdblockSubscriptionCollectionImplTest : public ::testing::Test {
+ public:
+ const GURL kImageAddress{"https://address.com/image.png"};
+ const GURL kParentAddress{"https://parent.com/"};
+ const SiteKey kSitekey{"abc"};
+ const GURL kSourceUrl{"https://subscription.com/easylist.txt"};
+};
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ AllowingFilterFoundForFrameHierarchy) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ const std::vector<GURL> frame_hierarchy{
+ GURL("https://frame.com/frame1.html"),
+ GURL("https://frame.com/frame2.html"),
+ GURL("https://frame.com/"),
+ };
+
+ // Subscription has a blocking filter.
+ EXPECT_CALL(*sub1, HasUrlFilter(kImageAddress, frame_hierarchy[0].host(),
+ ContentType::Image, kSitekey,
+ FilterCategory::Blocking))
+ .WillOnce(Return(true));
+
+ // The entire |frame_hierarchy| is queried to see if there's an allowing
+ // filter present for any step in the chain. The final one reports a match.
+ EXPECT_CALL(*sub1, HasUrlFilter(kImageAddress, frame_hierarchy[0].host(),
+ ContentType::Image, kSitekey,
+ FilterCategory::Allowing))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, frame_hierarchy[0],
+ frame_hierarchy[1].host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, frame_hierarchy[1],
+ frame_hierarchy[2].host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, frame_hierarchy[2],
+ frame_hierarchy[2].host(), kSitekey))
+ .WillOnce(Return(true));
+
+ // The resource is allowlisted
+ EXPECT_CALL(*sub1, GetSourceUrl()).WillRepeatedly(Return(kSourceUrl));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+ EXPECT_TRUE(!!collection.FindBySubresourceFilter(
+ kImageAddress, frame_hierarchy, ContentType::Image, kSitekey,
+ FilterCategory::Blocking));
+ EXPECT_TRUE(!!collection.FindByAllowFilter(kImageAddress, frame_hierarchy,
+ ContentType::Image, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ ElemhideSelectorsCombinedFromSubscriptions) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First, we establish whether to search for domain-specific selectors only by
+ // querying for Generichide filters. One subscription does have a Generichide
+ // filter.
+
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Generichide, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Generichide, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(true));
+ const bool domain_specific = true;
+
+ // Now all subscriptions are queried for selectors.
+ InstalledSubscription::ContentFiltersData sub1_selectors;
+ sub1_selectors.elemhide_selectors = {"div", "ad_frame", "billboard"};
+ sub1_selectors.elemhide_exceptions = {"billboard"};
+ EXPECT_CALL(*sub1, GetElemhideData(kParentAddress, domain_specific))
+ .WillOnce(Return(sub1_selectors));
+
+ InstalledSubscription::ContentFiltersData sub2_selectors;
+ sub2_selectors.elemhide_selectors = {"header", "ad_content", "billboard"};
+ sub2_selectors.elemhide_exceptions = {"ad_frame"};
+ EXPECT_CALL(*sub2, GetElemhideData(kParentAddress, domain_specific))
+ .WillOnce(Return(sub2_selectors));
+
+ // sub1's "billboard" exception cancels out the "billboard" selectors from
+ // both subscriptions. sub2's "ad_frame" exception cancels out the "ad_frame"
+ // selector from sub1.
+ std::vector<std::string_view> expected_selectors{"div", "header",
+ "ad_content"};
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ auto actual_selectors =
+ collection.GetElementHideData(kParentAddress, {}, kSitekey)
+ .elemhide_selectors;
+ std::sort(actual_selectors.begin(), actual_selectors.end());
+ std::sort(expected_selectors.begin(), expected_selectors.end());
+ EXPECT_EQ(actual_selectors, expected_selectors);
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ ElemhideEmulationSelectorsCombinedFromSubscriptions) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // This works very similarly to Elemhide selectors, only all Elemhide
+ // Emulation selectors are by design domain-specific, so we don't search for
+ // Generichide allow filters.
+
+ // Now all subscriptions are queried for selectors.
+ InstalledSubscription::ContentFiltersData sub1_selectors;
+ sub1_selectors.elemhide_selectors = {"a", "b", "c"};
+ sub1_selectors.elemhide_exceptions = {"c"};
+ EXPECT_CALL(*sub1, GetElemhideEmulationData(kParentAddress))
+ .WillOnce(Return(sub1_selectors));
+
+ InstalledSubscription::ContentFiltersData sub2_selectors;
+ sub2_selectors.elemhide_selectors = {"d", "b", "e"};
+ sub2_selectors.elemhide_exceptions = {"b"};
+ EXPECT_CALL(*sub2, GetElemhideEmulationData(kParentAddress))
+ .WillOnce(Return(sub2_selectors));
+
+ std::vector<std::string_view> expected_selectors{"a", "d", "e"};
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ auto actual_selectors =
+ collection.GetElementHideEmulationData(kParentAddress).elemhide_selectors;
+ std::sort(actual_selectors.begin(), actual_selectors.end());
+ std::sort(expected_selectors.begin(), expected_selectors.end());
+ EXPECT_EQ(actual_selectors, expected_selectors);
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, GenerateSnippetsJson) {
+ auto subscription = base::MakeRefCounted<MockInstalledSubscription>();
+
+ InstalledSubscription::Snippet snippet;
+ snippet.command = "say";
+ snippet.arguments = {"Hello"};
+ EXPECT_CALL(*subscription, MatchSnippets("parent.com"))
+ .WillOnce(Return(std::vector<InstalledSubscription::Snippet>{snippet}));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{subscription}, {});
+ auto snippets = collection.GenerateSnippets(kParentAddress, {});
+ EXPECT_EQ("say", snippets.front().GetList().front().GetString());
+ EXPECT_EQ("Hello", snippets.front().GetList().back().GetString());
+ EXPECT_EQ("[ [ \"say\", \"Hello\" ] ]\n", snippets.DebugString());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, OneHasAllowingDocumentFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Document, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, HasSpecialFilter(SpecialFilterType::Document, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(true));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_TRUE(!!collection.FindBySpecialFilter(
+ SpecialFilterType::Document, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, NoneHasAllowingDocumentFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Document, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, HasSpecialFilter(SpecialFilterType::Document, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_FALSE(!!collection.FindBySpecialFilter(
+ SpecialFilterType::Document, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, OneHasAllowingElementHideFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Elemhide, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Elemhide, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, HasSpecialFilter(SpecialFilterType::Elemhide, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(true));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_TRUE(!!collection.FindBySpecialFilter(
+ SpecialFilterType::Elemhide, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ NoneHasAllowingElementHideFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Elemhide, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Elemhide, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, HasSpecialFilter(SpecialFilterType::Elemhide, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Elemhide, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_FALSE(!!collection.FindBySpecialFilter(
+ SpecialFilterType::Elemhide, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, OneHasGenericblockFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(true));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_TRUE(collection.FindBySpecialFilter(
+ SpecialFilterType::Genericblock, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, NoneHasGenericblockFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kSourceUrl,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), kSitekey))
+ .WillOnce(Return(false));
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_FALSE(collection.FindBySpecialFilter(
+ SpecialFilterType::Genericblock, kSourceUrl, {kParentAddress}, kSitekey));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, CspBlockingFilterNotFound) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // There are no blocking filters found.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _));
+
+ // Since there are no blocking CSP filters, no need to check allow filters.
+ EXPECT_CALL(*sub1, HasSpecialFilter(_, _, _, _)).Times(0);
+ EXPECT_CALL(*sub2, HasSpecialFilter(_, _, _, _)).Times(0);
+ EXPECT_CALL(*sub1, FindCspFilters(_, _, FilterCategory::Allowing, _))
+ .Times(0);
+ EXPECT_CALL(*sub2, FindCspFilters(_, _, FilterCategory::Allowing, _))
+ .Times(0);
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+
+ // Empty result means no CSP injection necessary.
+ EXPECT_TRUE(
+ collection.GetCspInjections(kImageAddress, {kParentAddress}).empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, CspBlockingFilterFound) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("block"); })));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ // Check for Genericblock filter.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // In presence of a blocking CSP filters and absence of any allowing
+ // filters, the string returned by first subscription becomes the CSP
+ // injection.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+
+ std::set<std::string_view> filters =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(filters, testing::UnorderedElementsAre("block"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, MultipleCspBlockingFilterFound) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("first"); })));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("second"); })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ // Check for Genericblock filter.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // In presence of a blocking CSP filters and absence of any allowing
+ // filters, the string returned by first subscription becomes the CSP
+ // injection.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+
+ std::set<std::string_view> filters =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(filters, testing::UnorderedElementsAre("first", "second"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ SameCspBlockingFilterFoundInMultipleSubs) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("block"); })));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("block"); })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ // Check for Genericblock filter.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // In presence of a blocking CSP filters and absence of any allowing
+ // filters, the string returned by first subscription becomes the CSP
+ // injection.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+
+ std::set<std::string_view> filters =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(filters, testing::UnorderedElementsAre("block"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ CspBlockingFilterOverruledViaDocumentAllowingRule) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src 'none'");
+ })));
+
+ // A document-wide allowing rule exists.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(true));
+
+ // No need to query GenericBlock rules since the blocking CSP filter was
+ // overruled already.
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .Times(0);
+
+ // The allowing Document filter overrules the blocking CSP filter.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ CspBlockingFilterOverruledViaMatchingAllowingCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src 'none'");
+ })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+
+ // Document-wide allowing filters may or may not be consulted, but if they
+ // are, there are no matches.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // An allowing CSP rule, with specific payload, is found for parent.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src 'none'");
+ })));
+
+ // No need to query GenericBlock rules since the blocking CSP filter was
+ // overruled already.
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .Times(0);
+
+ // The allowing CSP filter overrules the blocking CSP filter.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ TwoCspBlockingFilterWithOneOverruledViaMatchingAllowingCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("first");
+ res.insert("second");
+ })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+
+ // Document-wide allowing filters may or may not be consulted, but if they
+ // are, there are no matches.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // An allowing CSP rule, with specific payload, is found for parent.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("second"); })));
+
+ // Check for Genericblock filter.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // The allowing CSP filter overrules the blocking CSP filter.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(results, testing::UnorderedElementsAre("first"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ TwoCspBlockingFilterOverruledViaMatchingAllowingCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("first"); })));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("second"); })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("second"); })));
+
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("first"); })));
+
+ // No need to query GenericBlock rules since the blocking CSP filter was
+ // overruled already.
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*sub2, HasSpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .Times(0);
+
+ // The allowing CSP filter overrules the blocking CSP filter.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ CspBlockingFilterOverruledViaGenericAllowingCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src 'none'");
+ })));
+
+ // Since a blocking filter is found, implementation will try to find allowing
+ // rules.
+
+ // Document-wide allowing filters may or may not be consulted, but if they
+ // are, there are no matches.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // An allowing CSP rule, with no payload, is found for request. A CSP allowing
+ // filter with no payload overrules any blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert(""); })));
+
+ // No need to query GenericBlock rules since the blocking CSP filter was
+ // overruled already.
+ EXPECT_CALL(*sub1, HasSpecialFilter(SpecialFilterType::Genericblock, _, _, _))
+ .Times(0);
+
+ // The allowing CSP filter overrules the blocking CSP filter.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ CspBlockingFilterNotOverruledViaMismatchedAllowingCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // Subscription contains a blocking CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src 'none'");
+ })));
+
+ // No document-wide allowing filters present.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // Allowing CSP rules with different payloads do NOT match the blocking
+ // CSP filter.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("script-src *");
+ })));
+
+ // The blocking filter is not overruled yet, will now check generic block
+ // rules.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+
+ // The blocking CSP filter was NOT overruled by an allowing CSP filter because
+ // the payloads did not match.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(results, testing::UnorderedElementsAre("script-src 'none'"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, GenericBlockCspFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First subscription contains a blocking CSP filter. Second does not.
+ // Both are consulted, because the first subscription's blocking CSP filter
+ // gets overruled as not domain-specific.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("block"); })));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _));
+
+ // Neither subscription contains allowing rule.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _));
+
+ // Second subscription contains a genericblock rule.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub2,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(true));
+
+ // Search is retried but now for domain-specific CSP filters only. No matches.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::DomainSpecificBlocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("domain-block");
+ })));
+
+ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::DomainSpecificBlocking, _));
+
+ // Finally, no CSP filter found.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_THAT(results, testing::UnorderedElementsAre("domain-block"));
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest,
+ GenericBlockCspFilterWithDomainAllowingFilter) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // First subscription contains a blocking CSP filter. Second does not.
+ // Both are consulted, because the first subscription's blocking CSP filter
+ // gets overruled as not domain-specific.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking, _))
+ .WillOnce(testing::WithArgs<3>(testing::Invoke(
+ [](std::set<std::string_view>& res) { res.insert("block"); })));
+
+ // Neither subscription contains allowing rule.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Document, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Allowing, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("domain-block");
+ })));
+
+ // Second subscription contains a genericblock rule.
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kImageAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(false));
+ EXPECT_CALL(*sub1,
+ HasSpecialFilter(SpecialFilterType::Genericblock, kParentAddress,
+ kParentAddress.host(), SiteKey()))
+ .WillOnce(Return(true));
+
+ // Search is retried but now for domain-specific CSP filters only. No matches.
+ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::DomainSpecificBlocking, _))
+ .WillOnce(testing::WithArgs<3>(
+ testing::Invoke([](std::set<std::string_view>& res) {
+ res.insert("domain-block");
+ })));
+
+ // Finally, no CSP filter found.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1}, {});
+ std::set<std::string_view> results =
+ collection.GetCspInjections(kImageAddress, {kParentAddress});
+ EXPECT_TRUE(results.empty());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, RewriteBlockingFilterNotFound) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+
+ // There are no blocking filters found.
+ EXPECT_CALL(*sub1, FindRewriteFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking))
+ .WillOnce(Return(std::set<std::string_view>{}));
+ EXPECT_CALL(*sub2, FindRewriteFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking))
+ .WillOnce(Return(std::set<std::string_view>{}));
+
+ // Since there are no blocking filters, no need to check allow filters.
+ EXPECT_CALL(*sub1, HasSpecialFilter(_, _, _, _)).Times(0);
+ EXPECT_CALL(*sub2, HasSpecialFilter(_, _, _, _)).Times(0);
+
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+
+ // Empty result means no redirect necessary.
+ EXPECT_THAT(collection.GetRewriteFilters(kImageAddress, {kParentAddress},
+ FilterCategory::Blocking),
+ testing::UnorderedElementsAre());
+}
+
+TEST_F(AdblockSubscriptionCollectionImplTest, RewriteBlockingFiltersFound) {
+ auto sub1 = base::MakeRefCounted<MockInstalledSubscription>();
+ auto sub2 = base::MakeRefCounted<MockInstalledSubscription>();
+ constexpr const char* redirect1 = "about::blank/1";
+ constexpr const char* redirect2 = "about::blank/2";
+
+ EXPECT_CALL(*sub1, FindRewriteFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking))
+ .WillOnce(Return(std::set<std::string_view>{redirect1}));
+ EXPECT_CALL(*sub2, FindRewriteFilters(kImageAddress, kParentAddress.host(),
+ FilterCategory::Blocking))
+ .WillOnce(Return(std::set<std::string_view>{redirect2}));
+
+ // In presence of a blocking filters and absence of any allowing filters,
+ // the string returned by first subscription becomes the redirect.
+ SubscriptionCollectionImpl collection(
+ std::vector<scoped_refptr<InstalledSubscription>>{sub1, sub2}, {});
+ EXPECT_THAT(collection.GetRewriteFilters(kImageAddress, {kParentAddress},
+ FilterCategory::Blocking),
+ testing::UnorderedElementsAre(GURL(redirect1), GURL(redirect2)));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_downloader_impl_test.cc b/components/adblock/core/subscription/test/subscription_downloader_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_downloader_impl_test.cc
@@ -0,0 +1,445 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_downloader_impl.h"
+
+#include <memory>
+#include <string_view>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/strings/string_split.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/net/test/mock_adblock_resource_request.h"
+#include "components/adblock/core/subscription/subscription_downloader.h"
+#include "components/adblock/core/subscription/test/mock_conversion_executors.h"
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+#include "components/prefs/pref_service.h"
+#include "gmock/gmock-actions.h"
+#include "gmock/gmock-matchers.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class FakeBuffer final : public FlatbufferData {
+ public:
+ const uint8_t* data() const final { return nullptr; }
+ size_t size() const final { return 0u; }
+ const base::span<const uint8_t> span() const final { return {}; }
+};
+
+class AdblockSubscriptionDownloaderImplTest : public testing::Test {
+ public:
+ AdblockSubscriptionDownloaderImplTest() {
+ downloader_ = std::make_unique<SubscriptionDownloaderImpl>(
+ request_maker_.Get(), &conversion_executor_, &persistent_metadata_);
+ }
+
+ void TestDateHeaderParsing(network::mojom::URLResponseHeadPtr header_response,
+ std::string_view expected_parsed_string) {
+ base::MockCallback<SubscriptionDownloader::HeadRequestCallback>
+ head_request_callback;
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request =
+ std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::HEAD,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ downloader_->DoHeadRequest(kSubscriptionUrlHttps,
+ head_request_callback.Get());
+
+ // The HeadRequestCallback is called with parsed date, or empty string if
+ // parsing failed.
+ EXPECT_CALL(head_request_callback,
+ Run(testing::StrEq(expected_parsed_string)));
+ response_callback.Run(GURL(), base::FilePath(), header_response->headers);
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ base::MockCallback<SubscriptionDownloaderImpl::SubscriptionRequestMaker>
+ request_maker_;
+ MockConversionExecutors conversion_executor_;
+ MockSubscriptionPersistentMetadata persistent_metadata_;
+ std::unique_ptr<SubscriptionDownloaderImpl> downloader_;
+
+ const GURL kSubscriptionUrlHttps{"https://subscription.com/filterlist.txt"};
+};
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ NoDownloadFromNotAllowedUrlScheme) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ callback;
+ // Callback is immediately run with Null since the download cannot start.
+ EXPECT_CALL(callback, Run(testing::IsNull()));
+ // No requests are made.
+ EXPECT_CALL(request_maker_, Run()).Times(0);
+
+ const GURL kSubscriptionUrlHttp{"http://subscription.com/filterlist.txt"};
+ downloader_->StartDownload(kSubscriptionUrlHttp,
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ callback.Get());
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ DownloadStarted_QueryParamsPresent) {
+ EXPECT_CALL(persistent_metadata_, GetVersion(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return("555"));
+ EXPECT_CALL(persistent_metadata_,
+ GetDownloadSuccessCount(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return(6));
+
+ const std::string expected_extra_query_params =
+ "lastVersion=555&disabled=false&downloadCount=4+&safe=true";
+ // SubscriptionRequestMaker is asked to create a request,
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ // Expect that the request gets started, record the requested url.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(kSubscriptionUrlHttps, AdblockResourceRequest::Method::GET,
+ testing::_, AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ expected_extra_query_params));
+ return mock_ongoing_request;
+ });
+
+ downloader_->StartDownload(kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ base::DoNothing());
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ ErrorCountIncreasedWhenDownloadResponseIsEmpty) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ download_completed_callback;
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ testing::InSequence sequence;
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::GET,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ EXPECT_CALL(persistent_metadata_,
+ IncrementDownloadErrorCount(kSubscriptionUrlHttps));
+
+ downloader_->StartDownload(
+ kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded,
+ download_completed_callback.Get());
+
+ // DownloadCompletedCallback will be called with nullptr.
+ EXPECT_CALL(download_completed_callback, Run(testing::IsNull()));
+ // AdblockResourceRequest calls ResponseCallback with empty path,
+ // indicating no content. This will trigger IncrementDownloadErrorCount
+ response_callback.Run(kSubscriptionUrlHttps, base::FilePath(), nullptr);
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ ConvertWhenDownloadResponseIsValid) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ download_completed_callback;
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ testing::InSequence sequence;
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::GET,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ downloader_->StartDownload(
+ kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded,
+ download_completed_callback.Get());
+
+ const base::FilePath downloaded_flatbuffer_path(FILE_PATH_LITERAL("file.fb"));
+ // DownloadCompletedCallback will be called with a correctly converted
+ // FlatbufferData.
+ EXPECT_CALL(download_completed_callback, Run(testing::NotNull()));
+ EXPECT_CALL(conversion_executor_,
+ ConvertFilterListFile(kSubscriptionUrlHttps,
+ downloaded_flatbuffer_path, testing::_))
+ .WillOnce(testing::WithArgs<2>(
+ testing::Invoke([](base::OnceCallback<void(ConversionResult)> cb) {
+ std::move(cb).Run(std::make_unique<FakeBuffer>());
+ })));
+ // AdblockResourceRequest calls ResponseCallback with a path to file with
+ // valid flatbuffer content:
+ response_callback.Run(kSubscriptionUrlHttps, downloaded_flatbuffer_path,
+ nullptr);
+ // The conversion will happen in a thread pool, we will need to run the entire
+ // task environment to have all the callbacks execute.
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ RedirectWhenConverterResultIsRedirect) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ download_completed_callback;
+ const GURL redirect_url =
+ GURL("https://redirect_subscription.com/filterlist.txt");
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ testing::InSequence sequence;
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::GET,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ // The request gets redirected to the expected URL
+ EXPECT_CALL(*mock_ongoing_request, Redirect(testing::_, testing::_));
+ return mock_ongoing_request;
+ });
+
+ downloader_->StartDownload(
+ kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded,
+ download_completed_callback.Get());
+
+ const base::FilePath downloaded_flatbuffer_path(FILE_PATH_LITERAL("file.fb"));
+ // DownloadCompletedCallback will not be called
+ EXPECT_CALL(download_completed_callback, Run(testing::_)).Times(0);
+ EXPECT_CALL(conversion_executor_,
+ ConvertFilterListFile(kSubscriptionUrlHttps,
+ downloaded_flatbuffer_path, testing::_))
+ .WillOnce(testing::WithArgs<2>(testing::Invoke(
+ [&redirect_url](base::OnceCallback<void(ConversionResult)> cb) {
+ std::move(cb).Run(redirect_url);
+ })));
+ // AdblockResourceRequest calls ResponseCallback with a path to file with
+ // valid flatbuffer content:
+ response_callback.Run(kSubscriptionUrlHttps, downloaded_flatbuffer_path,
+ nullptr);
+ // The conversion will happen in a thread pool, we will need to run the entire
+ // task environment to have all the callbacks execute.
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ AbortWhenExceedingMaxNumberOfRedirects) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ download_completed_callback;
+ const GURL redirect_url =
+ GURL("https://redirect_subscription.com/filterlist.txt");
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ testing::InSequence sequence;
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::GET,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ // Redirect counter check gets called and returns kMaxNumberOfRedirects
+ EXPECT_CALL(*mock_ongoing_request, GetNumberOfRedirects()).WillOnce([&]() {
+ return SubscriptionDownloaderImpl::kMaxNumberOfRedirects;
+ });
+ return mock_ongoing_request;
+ });
+
+ downloader_->StartDownload(
+ kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded,
+ download_completed_callback.Get());
+
+ const base::FilePath downloaded_flatbuffer_path(FILE_PATH_LITERAL("file.fb"));
+ // DownloadCompletedCallback will be called with null due to exceeding max
+ // number of redirects
+ EXPECT_CALL(download_completed_callback, Run(testing::IsNull())).Times(1);
+ EXPECT_CALL(conversion_executor_,
+ ConvertFilterListFile(kSubscriptionUrlHttps,
+ downloaded_flatbuffer_path, testing::_))
+ .WillOnce(testing::WithArgs<2>(testing::Invoke(
+ [&redirect_url](base::OnceCallback<void(ConversionResult)> cb) {
+ std::move(cb).Run(redirect_url);
+ })));
+ // AdblockResourceRequest calls ResponseCallback with a path to file with
+ // valid flatbuffer content:
+ response_callback.Run(kSubscriptionUrlHttps, downloaded_flatbuffer_path,
+ nullptr);
+ // The conversion will happen in a thread pool, we will need to run the entire
+ // task environment to have all the callbacks execute.
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest, ConversionFailsTest) {
+ base::MockCallback<SubscriptionDownloader::DownloadCompletedCallback>
+ download_completed_callback;
+
+ // SubscriptionRequestMaker is asked to create a request,
+ AdblockResourceRequest::ResponseCallback response_callback;
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ testing::InSequence sequence;
+ // Expect that the request gets started, record the response callback.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::GET,
+ testing::_, testing::_, testing::_))
+ .WillOnce(testing::SaveArg<2>(&response_callback));
+ return mock_ongoing_request;
+ });
+
+ EXPECT_CALL(persistent_metadata_,
+ IncrementDownloadErrorCount(kSubscriptionUrlHttps));
+
+ downloader_->StartDownload(
+ kSubscriptionUrlHttps,
+ AdblockResourceRequest::RetryPolicy::RetryUntilSucceeded,
+ download_completed_callback.Get());
+
+ const base::FilePath downloaded_flatbuffer_path(FILE_PATH_LITERAL("file.fb"));
+
+ // DownloadCompletedCallback gets called with nullptr, due to conversion
+ // error.
+ EXPECT_CALL(download_completed_callback, Run(testing::IsNull()));
+ EXPECT_CALL(conversion_executor_,
+ ConvertFilterListFile(kSubscriptionUrlHttps,
+ downloaded_flatbuffer_path, testing::_))
+ .WillOnce(testing::WithArgs<2>(
+ testing::Invoke([](base::OnceCallback<void(ConversionResult)> cb) {
+ std::move(cb).Run(ConversionError("Error"));
+ })));
+ // AdblockResourceRequest calls ResponseCallback with a path to file with
+ // invalid flatbuffer content:
+ response_callback.Run(kSubscriptionUrlHttps, downloaded_flatbuffer_path,
+ nullptr);
+ // The conversion will happen in a thread pool, we will need to run the entire
+ // task environment to have all the callbacks execute.
+ task_environment_.RunUntilIdle();
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ HeadRequestReturnsConverterHeaderDate) {
+ auto header_response = network::CreateURLResponseHead(net::HTTP_OK);
+ header_response->headers->AddHeader("Date", "Mon, 27 Sep 2021 13:53:01 GMT");
+ TestDateHeaderParsing(std::move(header_response), "202109271353");
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ HeadRequestReturnsConverterHeaderDateLowerCaseDate) {
+ auto header_response = network::CreateURLResponseHead(net::HTTP_OK);
+ header_response->headers->AddHeader("date", "Mon, 27 Sep 2021 13:53:01 GMT");
+ TestDateHeaderParsing(std::move(header_response), "202109271353");
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ HeadRequestReturnsConverterHeaderDateMalformed) {
+ auto header_response = network::CreateURLResponseHead(net::HTTP_OK);
+ header_response->headers->AddHeader("Date", "Invalid format");
+ TestDateHeaderParsing(std::move(header_response), "");
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ HeadRequestReturnsConverterHeaderDateMissing) {
+ auto header_response = network::CreateURLResponseHead(net::HTTP_OK);
+ TestDateHeaderParsing(std::move(header_response), "");
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest,
+ HeadRequestSetsSubscriptionAsDisabled) {
+ EXPECT_CALL(persistent_metadata_, GetVersion(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return("222"));
+ EXPECT_CALL(persistent_metadata_,
+ GetDownloadSuccessCount(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return(3));
+
+ const std::string expected_extra_query_params =
+ "lastVersion=222&disabled=true&downloadCount=3&safe=true";
+ // SubscriptionRequestMaker is asked to create a request,
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ // Expect that the request gets started, record the requested url.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(
+ *mock_ongoing_request,
+ Start(kSubscriptionUrlHttps, AdblockResourceRequest::Method::HEAD,
+ testing::_, AdblockResourceRequest::RetryPolicy::DoNotRetry,
+ expected_extra_query_params));
+ return mock_ongoing_request;
+ });
+
+ downloader_->DoHeadRequest(kSubscriptionUrlHttps, base::DoNothing());
+}
+
+TEST_F(AdblockSubscriptionDownloaderImplTest, TwoConcurrentDoHeadRequests) {
+ EXPECT_CALL(persistent_metadata_, GetVersion(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return("222"));
+ EXPECT_CALL(persistent_metadata_,
+ GetDownloadSuccessCount(kSubscriptionUrlHttps))
+ .WillRepeatedly(testing::Return(3));
+
+ testing::StrictMock<
+ base::MockCallback<SubscriptionDownloader::HeadRequestCallback>>
+ head_request_callback;
+ // SubscriptionRequestMaker is asked to create a request,
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ // Expect that the request gets started, record the requested url.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::HEAD,
+ testing::_, testing::_, testing::_));
+ return mock_ongoing_request;
+ });
+ downloader_->DoHeadRequest(kSubscriptionUrlHttps,
+ head_request_callback.Get());
+
+ // The HeadRequestCallback is called with empty version string before
+ // concurrent ping overrides ongoing one.
+ EXPECT_CALL(head_request_callback, Run(testing::StrEq("")));
+ // SubscriptionRequestMaker is asked to create a request,
+ EXPECT_CALL(request_maker_, Run()).WillOnce([&]() {
+ // Expect that the request gets started, record the requested url.
+ auto mock_ongoing_request = std::make_unique<MockAdblockResourceRequest>();
+ EXPECT_CALL(*mock_ongoing_request,
+ Start(testing::_, AdblockResourceRequest::Method::HEAD,
+ testing::_, testing::_, testing::_));
+ return mock_ongoing_request;
+ });
+ downloader_->DoHeadRequest(kSubscriptionUrlHttps, base::DoNothing());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_persistent_metadata_impl_test.cc b/components/adblock/core/subscription/test/subscription_persistent_metadata_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_persistent_metadata_impl_test.cc
@@ -0,0 +1,268 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h"
+
+#include <memory>
+
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+// We check two scenarios - whether testee retains relevant values as it is
+// used, and whether it persists them into Prefs.
+enum class PersistenceType { ValuesPersistInMemory, ValuesPersistInPrefs };
+
+class AdblockSubscriptionPersistentMetadataImplTest
+ : public testing::TestWithParam<PersistenceType> {
+ public:
+ AdblockSubscriptionPersistentMetadataImplTest() {
+ common::prefs::RegisterProfilePrefs(prefs_.registry());
+ metadata_ = std::make_unique<SubscriptionPersistentMetadataImpl>(&prefs_);
+ }
+
+ void MaybeRecreateMetadata() {
+ if (GetParam() == PersistenceType::ValuesPersistInPrefs) {
+ // Recreate SubscriptionPersistentMetadataImpl to make sure it re-reads
+ // its state from prefs.
+ metadata_ = std::make_unique<SubscriptionPersistentMetadataImpl>(&prefs_);
+ } else {
+ // Do nothing, verify SubscriptionPersistentMetadataImpl maintains a
+ // consistent state in memory/
+ }
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+ std::unique_ptr<SubscriptionPersistentMetadataImpl> metadata_;
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ const GURL kUrl1{"https://subscription.com/filters1.txt"};
+ const GURL kUrl2{"https://subscription.com/filters2.txt"};
+};
+
+TEST_F(AdblockSubscriptionPersistentMetadataImplTest,
+ UntrackedSubscriptionConsideredExpired) {
+ // Normally, production code should only ask about expiration of installed
+ // subscriptions, and subscriptions should always have an expiration date set
+ // during installation. If there's any corruption though, consider the
+ // subscription expired to trigger an update and, hopefully, force setting a
+ // real expiration time.
+ EXPECT_TRUE(metadata_->IsExpired(kUrl1));
+}
+
+TEST_F(AdblockSubscriptionPersistentMetadataImplTest,
+ UnversionedSubscriptionReturnsZeroVersion) {
+ // It is valid for a subscription to not have a version set. In that case,
+ // we're expected to send "0" in download request's relevant query param.
+ EXPECT_EQ("0", metadata_->GetVersion(kUrl1));
+}
+
+TEST_F(AdblockSubscriptionPersistentMetadataImplTest,
+ UntrackedSubscriptionErrorAndDownloadCountAreZero) {
+ // During first-time installation, it is expected that there's no download
+ // count or error count registered yet.
+ EXPECT_EQ(0, metadata_->GetDownloadSuccessCount(kUrl1));
+ EXPECT_EQ(0, metadata_->GetDownloadErrorCount(kUrl1));
+}
+
+TEST_F(AdblockSubscriptionPersistentMetadataImplTest,
+ UntrackedSubscriptionAutoInstalledExpirationNotExpired) {
+ // Since the auto installed expiration time is not mandatory for a
+ // subscription and it is used for removing auto installed subscriptions false
+ // is returned for not yet installed subscriptions.
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest, ExpirationTimeTracked) {
+ const auto expiration1 = base::Days(1);
+ const auto expiration2 = base::Days(2);
+ metadata_->SetExpirationInterval(kUrl1, expiration1);
+ metadata_->SetExpirationInterval(kUrl2, expiration2);
+
+ // Both expiration dates are in the future.
+ EXPECT_FALSE(metadata_->IsExpired(kUrl1));
+ EXPECT_FALSE(metadata_->IsExpired(kUrl2));
+
+ // Forward clock by 1 day to trigger first expiration.
+ task_environment_.AdvanceClock(base::Days(1));
+
+ // First subscription is now expired.
+ EXPECT_TRUE(metadata_->IsExpired(kUrl1));
+ EXPECT_FALSE(metadata_->IsExpired(kUrl2));
+
+ MaybeRecreateMetadata();
+
+ // Forward clock by another day to trigger second expiration.
+ task_environment_.AdvanceClock(base::Days(1));
+
+ // Both subscriptions are now expired.
+ EXPECT_TRUE(metadata_->IsExpired(kUrl1));
+ EXPECT_TRUE(metadata_->IsExpired(kUrl2));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest,
+ LastInstallationTimeTracked) {
+ // Last installation time is not yet set.
+ EXPECT_EQ(metadata_->GetLastInstallationTime(kUrl1), base::Time());
+ metadata_->SetLastInstallationTime(kUrl1);
+ // Last installation time gets set.
+ EXPECT_EQ(metadata_->GetLastInstallationTime(kUrl1), base::Time::Now());
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest,
+ AutoInstalledExpirationTimeTracked) {
+ const auto auto_installed_expiration1 = base::Days(1);
+ const auto auto_installed_expiration2 = base::Days(2);
+ metadata_->SetAutoInstalledExpirationInterval(kUrl1,
+ auto_installed_expiration1);
+ metadata_->SetAutoInstalledExpirationInterval(kUrl2,
+ auto_installed_expiration2);
+
+ // Both expiration dates are in the future.
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl1));
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl2));
+
+ // Forward clock by 1 day to trigger first expiration.
+ task_environment_.AdvanceClock(base::Days(1));
+
+ // First subscription is now expired.
+ EXPECT_TRUE(metadata_->IsAutoInstalledExpired(kUrl1));
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl2));
+
+ MaybeRecreateMetadata();
+
+ // Forward clock by another day to trigger second expiration.
+ task_environment_.AdvanceClock(base::Days(1));
+
+ // Both subscriptions are now expired.
+ EXPECT_TRUE(metadata_->IsAutoInstalledExpired(kUrl1));
+ EXPECT_TRUE(metadata_->IsAutoInstalledExpired(kUrl2));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest, VersionTracked) {
+ const std::string version1 = "1.0";
+ const std::string version2 = "2.0";
+ metadata_->SetVersion(kUrl1, version1);
+ metadata_->SetVersion(kUrl2, version2);
+
+ MaybeRecreateMetadata();
+
+ EXPECT_EQ(version1, metadata_->GetVersion(kUrl1));
+ EXPECT_EQ(version2, metadata_->GetVersion(kUrl2));
+
+ // Versions can be overwritten later.
+ const std::string new_version = "3.0";
+ metadata_->SetVersion(kUrl1, new_version);
+
+ MaybeRecreateMetadata();
+
+ EXPECT_EQ(new_version, metadata_->GetVersion(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest, DownloadCountTracked) {
+ metadata_->IncrementDownloadSuccessCount(kUrl1);
+ metadata_->IncrementDownloadSuccessCount(kUrl1);
+ metadata_->IncrementDownloadSuccessCount(kUrl1);
+
+ metadata_->IncrementDownloadSuccessCount(kUrl2);
+
+ MaybeRecreateMetadata();
+
+ EXPECT_EQ(3, metadata_->GetDownloadSuccessCount(kUrl1));
+ EXPECT_EQ(1, metadata_->GetDownloadSuccessCount(kUrl2));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest, ErrorCountTracked) {
+ metadata_->IncrementDownloadErrorCount(kUrl1);
+ metadata_->IncrementDownloadErrorCount(kUrl1);
+ metadata_->IncrementDownloadErrorCount(kUrl1);
+
+ metadata_->IncrementDownloadErrorCount(kUrl2);
+
+ MaybeRecreateMetadata();
+
+ EXPECT_EQ(3, metadata_->GetDownloadErrorCount(kUrl1));
+ EXPECT_EQ(1, metadata_->GetDownloadErrorCount(kUrl2));
+
+ // A successful download resets the error count.
+ metadata_->IncrementDownloadSuccessCount(kUrl1);
+
+ MaybeRecreateMetadata();
+
+ EXPECT_EQ(0, metadata_->GetDownloadErrorCount(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest, RemovingMetadata) {
+ // Set some values for kUrl1
+ metadata_->IncrementDownloadSuccessCount(kUrl1);
+ metadata_->IncrementDownloadErrorCount(kUrl1);
+ metadata_->SetVersion(kUrl1, "version");
+ metadata_->SetExpirationInterval(kUrl1, base::Days(1));
+ metadata_->SetAutoInstalledExpirationInterval(kUrl1, base::Days(7));
+
+ // Also set a value for kUrl2
+ metadata_->IncrementDownloadErrorCount(kUrl2);
+ metadata_->SetAutoInstalledExpirationInterval(kUrl2, base::Days(7));
+
+ metadata_->RemoveMetadata(kUrl1);
+
+ MaybeRecreateMetadata();
+
+ // The value set for kUrl2 is left untouched.
+ EXPECT_EQ(1, metadata_->GetDownloadErrorCount(kUrl2));
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl2));
+
+ // The values for kUrl1 are back to defaults.
+ EXPECT_TRUE(metadata_->IsExpired(kUrl1));
+ EXPECT_EQ("0", metadata_->GetVersion(kUrl1));
+ EXPECT_EQ(0, metadata_->GetDownloadSuccessCount(kUrl1));
+ EXPECT_EQ(0, metadata_->GetDownloadErrorCount(kUrl1));
+ EXPECT_FALSE(metadata_->IsAutoInstalledExpired(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest,
+ UntrackedSubscriptionIsNotAutoInstalled) {
+ EXPECT_FALSE(metadata_->IsAutoInstalled(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest,
+ TrackedSubscriptionIsNotAutoInstalled) {
+ const auto expiration1 = base::Days(1);
+ metadata_->SetExpirationInterval(kUrl2, expiration1);
+ EXPECT_FALSE(metadata_->IsAutoInstalled(kUrl1));
+}
+
+TEST_P(AdblockSubscriptionPersistentMetadataImplTest,
+ AutoInstalledSubsriptionIsAutoInstalled) {
+ const auto auto_installed_expiration1 = base::Days(1);
+ metadata_->SetAutoInstalledExpirationInterval(kUrl1,
+ auto_installed_expiration1);
+ EXPECT_TRUE(metadata_->IsAutoInstalled(kUrl1));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All,
+ AdblockSubscriptionPersistentMetadataImplTest,
+ testing::Values(PersistenceType::ValuesPersistInMemory,
+ PersistenceType::ValuesPersistInPrefs));
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_persistent_storage_impl_test.cc b/components/adblock/core/subscription/test/subscription_persistent_storage_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_persistent_storage_impl_test.cc
@@ -0,0 +1,346 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/base_paths.h"
+#include "base/containers/span.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/path_service.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/test/mock_subscription_persistent_metadata.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+namespace {
+
+class MockSubscriptionValidator : public SubscriptionValidator {
+ public:
+ MOCK_METHOD(bool,
+ MockIsSignatureValid,
+ (const FlatbufferData& data, const base::FilePath& path),
+ (const));
+ MOCK_METHOD(void,
+ MockStoreTrustedSignature,
+ (const FlatbufferData& data, const base::FilePath& path),
+ ());
+ MOCK_METHOD(void,
+ MockRemoveStoredSignature,
+ (const base::FilePath& path),
+ ());
+
+ IsSignatureValidThreadSafeCallback IsSignatureValid() const final {
+ return base::BindRepeating(&MockSubscriptionValidator::MockIsSignatureValid,
+ base::Unretained(this));
+ }
+
+ StoreTrustedSignatureThreadSafeCallback StoreTrustedSignature() final {
+ return base::BindRepeating(
+ &MockSubscriptionValidator::MockStoreTrustedSignature,
+ base::Unretained(this));
+ }
+
+ RemoveStoredSignatureThreadSafeCallback RemoveStoredSignature() final {
+ return base::BindRepeating(
+ &MockSubscriptionValidator::MockRemoveStoredSignature,
+ base::Unretained(this));
+ }
+
+ ~MockSubscriptionValidator() override = default;
+};
+
+MATCHER_P(BufferMatches, expected_span, "") {
+ const auto arg_span = arg.span();
+ return std::equal(arg_span.begin(), arg_span.end(), expected_span.begin(),
+ expected_span.end());
+}
+
+} // namespace
+
+class AdblockSubscriptionPersistentStorageImplTest : public ::testing::Test {
+ public:
+ void SetUp() final {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ RecreateStorage();
+ }
+
+ void TearDown() final {
+ // Avoid dangling pointers during destruction.
+ validator_ = nullptr;
+ }
+
+ void RecreateStorage() {
+ auto validator = std::make_unique<MockSubscriptionValidator>();
+ validator_ = validator.get();
+ storage_ = std::make_unique<SubscriptionPersistentStorageImpl>(
+ temp_dir_.GetPath(), std::move(validator), &metadata_);
+ }
+
+ base::FilePath PathRelativeToTemp(std::string_view file_name) const {
+ return temp_dir_.GetPath().AppendASCII(file_name);
+ }
+
+ const std::unique_ptr<FlatbufferData> kEasylistFlatbuffer =
+ utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ const std::unique_ptr<FlatbufferData> kExceptionrulesFlatbuffer =
+ utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES);
+
+ base::test::TaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+ MockSubscriptionPersistentMetadata metadata_;
+ raw_ptr<MockSubscriptionValidator> validator_;
+ std::unique_ptr<SubscriptionPersistentStorageImpl> storage_;
+};
+
+TEST_F(AdblockSubscriptionPersistentStorageImplTest,
+ ReadsValidSubscriptionsAndDeletesInvalid) {
+ // Populate temp_dir_ with two valid flatbuffer files and two invalid ones.
+ ASSERT_TRUE(base::WriteFile(PathRelativeToTemp("easylist.fb"),
+ kEasylistFlatbuffer->span()));
+ ASSERT_TRUE(base::WriteFile(PathRelativeToTemp("exceptionrules.fb"),
+ kExceptionrulesFlatbuffer->span()));
+ ASSERT_TRUE(base::WriteFile(PathRelativeToTemp("invalid1.fb"), "some_data"));
+ ASSERT_TRUE(base::WriteFile(PathRelativeToTemp("invalid2.fb"), "bogus"));
+
+ // Initialize the storage, it should attempt to read contents of temp_dir_.
+ base::MockCallback<SubscriptionPersistentStorage::LoadCallback> callback;
+
+ // Save the argument passed to callback.
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions;
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&loaded_subscriptions));
+
+ // Storage will query metadata for the subscriptions in managed to read from
+ // disk.
+ const auto installation_time_easylist =
+ base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(30));
+ const auto installation_time_exceptionrules =
+ base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(60));
+ EXPECT_CALL(metadata_, GetLastInstallationTime(DefaultSubscriptionUrl()))
+ .WillOnce(testing::Return(installation_time_easylist));
+ EXPECT_CALL(metadata_, GetLastInstallationTime(AcceptableAdsUrl()))
+ .WillOnce(testing::Return(installation_time_exceptionrules));
+
+ // Subscriptions found on disk will be validated.
+ // First two files are OK.
+ EXPECT_CALL(*validator_,
+ MockIsSignatureValid(BufferMatches(kEasylistFlatbuffer->span()),
+ PathRelativeToTemp("easylist.fb")))
+ .WillOnce(testing::Return(true));
+ EXPECT_CALL(*validator_, MockIsSignatureValid(
+ BufferMatches(kExceptionrulesFlatbuffer->span()),
+ PathRelativeToTemp("exceptionrules.fb")))
+ .WillOnce(testing::Return(true));
+ // The other two are invalid.
+ EXPECT_CALL(*validator_,
+ MockIsSignatureValid(BufferMatches(std::string("some_data")),
+ PathRelativeToTemp("invalid1.fb")))
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(*validator_,
+ MockIsSignatureValid(BufferMatches(std::string("bogus")),
+ PathRelativeToTemp("invalid2.fb")))
+ .WillOnce(testing::Return(false));
+
+ storage_->LoadSubscriptions(callback.Get());
+ task_environment_.RunUntilIdle();
+
+ // The two valid subscriptions were loaded.
+ ASSERT_EQ(loaded_subscriptions.size(), 2u);
+ base::ranges::sort(loaded_subscriptions, {}, &Subscription::GetSourceUrl);
+
+ EXPECT_EQ(loaded_subscriptions[0]->GetSourceUrl(), DefaultSubscriptionUrl());
+ EXPECT_EQ(loaded_subscriptions[0]->GetInstallationState(),
+ Subscription::InstallationState::Installed);
+ EXPECT_EQ(loaded_subscriptions[0]->GetInstallationTime(),
+ installation_time_easylist);
+
+ EXPECT_EQ(loaded_subscriptions[1]->GetSourceUrl(), AcceptableAdsUrl());
+ EXPECT_EQ(loaded_subscriptions[1]->GetInstallationState(),
+ Subscription::InstallationState::Installed);
+ EXPECT_EQ(loaded_subscriptions[1]->GetInstallationTime(),
+ installation_time_exceptionrules);
+
+ // The storage directory no longer contains the invalid files.
+ EXPECT_FALSE(base::PathExists(PathRelativeToTemp("invalid1.fb")));
+ EXPECT_FALSE(base::PathExists(PathRelativeToTemp("invalid2.fb")));
+}
+
+TEST_F(AdblockSubscriptionPersistentStorageImplTest, StoreValidSubscription) {
+ storage_->LoadSubscriptions(base::DoNothing());
+ task_environment_.RunUntilIdle();
+
+ // Attempt to store a valid subscription.
+ base::MockCallback<SubscriptionPersistentStorage::StoreCallback> callback;
+ // The callback will be called with parsed Subscription. Save the argument.
+ scoped_refptr<Subscription> loaded_subscription;
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&loaded_subscription));
+ // The subscription will get its signature stored.
+ base::FilePath signature_path;
+ EXPECT_CALL(*validator_,
+ MockStoreTrustedSignature(
+ BufferMatches(kEasylistFlatbuffer->span()), testing::_))
+ .WillOnce(testing::SaveArg<1>(&signature_path));
+
+ storage_->StoreSubscription(utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST),
+ callback.Get());
+ task_environment_.RunUntilIdle();
+
+ ASSERT_TRUE(loaded_subscription);
+ EXPECT_EQ(loaded_subscription->GetSourceUrl(), DefaultSubscriptionUrl());
+
+ // The storage directory is not empty, contains the subscription file with
+ // unspecified filename.
+ EXPECT_FALSE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
+ base::FileEnumerator enumerator(temp_dir_.GetPath(), false,
+ base::FileEnumerator::FILES);
+ base::FilePath subscription_path = enumerator.Next();
+ EXPECT_FALSE(subscription_path.empty());
+ // The base file API operates on chars and flatbuffer data is stored in
+ // unsigned chars. To compare the content of a file with flatbuffer data, we
+ // need to reinterpret cast.
+ std::string file_content;
+ ASSERT_TRUE(base::ReadFileToString(subscription_path, &file_content));
+ auto reinterpreted_content = base::as_byte_span(file_content);
+ EXPECT_TRUE(
+ base::ranges::equal(reinterpreted_content, kEasylistFlatbuffer->span()));
+ // SignatureValidator was given the same path as the one used for storage.
+ EXPECT_EQ(subscription_path, signature_path);
+}
+
+TEST_F(AdblockSubscriptionPersistentStorageImplTest,
+ StoreAndRemoveSubscription) {
+ // Temp directory is empty in the begining
+ EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
+
+ storage_->LoadSubscriptions(base::DoNothing());
+ task_environment_.RunUntilIdle();
+
+ // Store a valid subscription.
+ base::MockCallback<SubscriptionPersistentStorage::StoreCallback> callback;
+ scoped_refptr<InstalledSubscription> loaded_subscription;
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&loaded_subscription));
+
+ // The subscription will get its signature stored.
+ base::FilePath signature_path;
+ EXPECT_CALL(*validator_,
+ MockStoreTrustedSignature(
+ BufferMatches(kEasylistFlatbuffer->span()), testing::_))
+ .WillOnce(testing::SaveArg<1>(&signature_path));
+
+ storage_->StoreSubscription(utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST),
+ callback.Get());
+ task_environment_.RunUntilIdle();
+ ASSERT_TRUE(loaded_subscription);
+
+ // Directory is not empty, contains the subscription.
+ EXPECT_FALSE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
+
+ // The subscription will get its signature removed.
+ EXPECT_CALL(*validator_, MockRemoveStoredSignature(signature_path));
+
+ // Remove the subscription.
+ storage_->RemoveSubscription(loaded_subscription);
+
+ // Reset the pointer to trigger the destructor of loaded_subscription
+ // This is done by FilteringConfigurationMaintainerImpl
+ loaded_subscription.reset();
+
+ task_environment_.RunUntilIdle();
+
+ // Directory is now empty again.
+ EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
+}
+
+TEST_F(AdblockSubscriptionPersistentStorageImplTest, StorageIsPersistent) {
+ // Initially, no loaded subscriptions.
+ base::MockCallback<SubscriptionPersistentStorage::LoadCallback> callback;
+ std::vector<scoped_refptr<InstalledSubscription>> loaded_subscriptions;
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&loaded_subscriptions));
+ storage_->LoadSubscriptions(callback.Get());
+ task_environment_.RunUntilIdle();
+
+ ASSERT_TRUE(loaded_subscriptions.empty());
+
+ // Validator will be asked to store the signature.
+ base::FilePath signature_path;
+ EXPECT_CALL(*validator_,
+ MockStoreTrustedSignature(
+ BufferMatches(kEasylistFlatbuffer->span()), testing::_))
+ .WillOnce(testing::SaveArg<1>(&signature_path));
+
+ // Metadata will be updated.
+ EXPECT_CALL(metadata_,
+ SetExpirationInterval(DefaultSubscriptionUrl(), base::Days(1)));
+ EXPECT_CALL(metadata_, SetLastInstallationTime(DefaultSubscriptionUrl()));
+ EXPECT_CALL(metadata_, SetVersion(DefaultSubscriptionUrl(), testing::_));
+ EXPECT_CALL(metadata_,
+ IncrementDownloadSuccessCount(DefaultSubscriptionUrl()));
+
+ // Store a valid subscription.
+ storage_->StoreSubscription(utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST),
+ base::DoNothing());
+ task_environment_.RunUntilIdle();
+
+ // Destroy and re-create storage.
+ RecreateStorage();
+
+ // Validator will be asked to check the signature of subscription on disk.
+ // Will query the path passed to |StoreTrustedSignature| before.
+ EXPECT_CALL(*validator_,
+ MockIsSignatureValid(BufferMatches(kEasylistFlatbuffer->span()),
+ signature_path))
+ .WillOnce(testing::Return(true));
+
+ // Load subscriptions.
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&loaded_subscriptions));
+ storage_->LoadSubscriptions(callback.Get());
+ task_environment_.RunUntilIdle();
+
+ // This time, we see the subscription added in storage's "previous life".
+ ASSERT_EQ(loaded_subscriptions.size(), 1u);
+ EXPECT_EQ(loaded_subscriptions[0]->GetSourceUrl(), DefaultSubscriptionUrl());
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_service_impl_test.cc b/components/adblock/core/subscription/test/subscription_service_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_service_impl_test.cc
@@ -0,0 +1,378 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_service_impl.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/ranges/algorithm.h"
+#include "components/adblock/core/configuration/filtering_configuration.h"
+#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
+#include "components/adblock/core/configuration/test/fake_filtering_configuration.h"
+#include "components/adblock/core/subscription/filtering_configuration_maintainer.h"
+#include "components/adblock/core/subscription/subscription_collection.h"
+#include "components/adblock/core/subscription/subscription_service.h"
+#include "components/adblock/core/subscription/test/mock_filtering_configuration_maintainer.h"
+#include "components/adblock/core/subscription/test/mock_subscription.h"
+#include "components/adblock/core/subscription/test/mock_subscription_collection.h"
+#include "components/prefs/testing_pref_service.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using testing::NiceMock;
+using testing::Return;
+
+namespace adblock {
+namespace {
+class MockSubscriptionObserver
+ : public NiceMock<SubscriptionService::SubscriptionObserver> {
+ public:
+ MOCK_METHOD(void,
+ OnSubscriptionInstalled,
+ (const GURL& subscription_url),
+ (override));
+};
+} // namespace
+
+class AdblockSubscriptionServiceImplTest : public testing::Test {
+ public:
+ struct MaintainerFactoryCall {
+ raw_ptr<FilteringConfiguration> input_configuration;
+ SubscriptionServiceImpl::SubscriptionUpdatedCallback input_update_callback;
+ raw_ptr<MockFilteringConfigurationMaintainer> output_maintainer;
+ };
+
+ AdblockSubscriptionServiceImplTest()
+ : testee_(
+ &prefs_,
+ base::BindRepeating(
+ &AdblockSubscriptionServiceImplTest::MockMakeMaintainer,
+ base::Unretained(this)),
+ base::BindRepeating(
+ &AdblockSubscriptionServiceImplTest::MockCleanupConfiguration,
+ base::Unretained(this))) {}
+
+ void TearDown() override {
+ // Avoid dangling pointers during destruction.
+ maintainer_factory_calls_.clear();
+ }
+
+ void DisableConfigurationAndEnsureMaintainerDestroyed(
+ MaintainerFactoryCall* entry) {
+ bool maintainer_destroyed = false;
+ EXPECT_CALL(*entry->output_maintainer, Destructor()).WillOnce([&]() {
+ maintainer_destroyed = true;
+ });
+ entry->output_maintainer = nullptr;
+ entry->input_configuration->SetEnabled(false);
+ // Explicitly verifying the maintainer was destroyed now, because the
+ // destructor will always be called *eventually*. We want to make sure it
+ // was called in response to SetEnabled(false).
+ EXPECT_TRUE(maintainer_destroyed);
+ }
+
+ std::unique_ptr<FilteringConfigurationMaintainer> MockMakeMaintainer(
+ FilteringConfiguration* configuration,
+ SubscriptionServiceImpl::SubscriptionUpdatedCallback update_callback) {
+ auto maintainer = std::make_unique<MockFilteringConfigurationMaintainer>();
+ maintainer_factory_calls_.push_back(
+ {configuration, update_callback, maintainer.get()});
+ return maintainer;
+ }
+
+ void MockCleanupConfiguration(FilteringConfiguration* configuration) {
+ cleaned_configs_.push_back(configuration->GetName());
+ }
+
+ bool IsAnEmptySubscription(
+ const scoped_refptr<adblock::Subscription>& subscription) {
+ return (subscription->GetInstallationState() ==
+ InstalledSubscription::InstallationState::Unknown) &&
+ subscription->GetTitle().empty() &&
+ subscription->GetCurrentVersion().empty();
+ }
+
+ std::vector<MaintainerFactoryCall> maintainer_factory_calls_;
+ std::vector<std::string> cleaned_configs_;
+ MockSubscriptionObserver observer_;
+ TestingPrefServiceSimple prefs_;
+ SubscriptionServiceImpl testee_;
+};
+
+TEST_F(AdblockSubscriptionServiceImplTest, EmptySnapshotWithoutConfigurations) {
+ EXPECT_TRUE(testee_.GetCurrentSnapshot().empty());
+ EXPECT_TRUE(testee_.GetInstalledFilteringConfigurations().empty());
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ InstallingDisabledConfigurationDoesNotCreateMaintainer) {
+ auto config = std::make_unique<FakeFilteringConfiguration>();
+ config->is_enabled = false;
+ auto* config_bare_ptr = config.get();
+ testee_.InstallFilteringConfiguration(std::move(config));
+
+ EXPECT_TRUE(maintainer_factory_calls_.empty());
+ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(),
+ std::vector<FilteringConfiguration*>{config_bare_ptr});
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ InstallingEnabledConfigurationCreatesMaintainer) {
+ auto config = std::make_unique<FakeFilteringConfiguration>();
+ auto* config_bare_ptr = config.get();
+ config->is_enabled = true;
+ testee_.InstallFilteringConfiguration(std::move(config));
+
+ ASSERT_EQ(maintainer_factory_calls_.size(), 1u);
+ EXPECT_EQ(maintainer_factory_calls_[0].input_configuration, config_bare_ptr);
+ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(),
+ std::vector<FilteringConfiguration*>{config_bare_ptr});
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ UninstallingConfigurationRemovesData) {
+ auto config = std::make_unique<FakeFilteringConfiguration>();
+ auto config_name = config->GetName();
+ auto* config_bare_ptr = config.get();
+ // Install and confirm.
+ testee_.InstallFilteringConfiguration(std::move(config));
+ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(),
+ std::vector<FilteringConfiguration*>{config_bare_ptr});
+ ASSERT_EQ(0u, cleaned_configs_.size());
+ // Avoid dangling pointers after UninstallFilteringConfiguration().
+ maintainer_factory_calls_[0].input_configuration = nullptr;
+ maintainer_factory_calls_[0].output_maintainer = nullptr;
+ // Uninstall and confirm.
+ testee_.UninstallFilteringConfiguration(config_name);
+ EXPECT_TRUE(testee_.GetInstalledFilteringConfigurations().empty());
+ ASSERT_EQ(1u, cleaned_configs_.size());
+ EXPECT_EQ(cleaned_configs_[0], config_name);
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ EnablingConfigurationCreatesMaintainer) {
+ auto config = std::make_unique<FakeFilteringConfiguration>();
+ auto* config_bare_ptr = config.get();
+ config->is_enabled = false;
+ testee_.InstallFilteringConfiguration(std::move(config));
+
+ EXPECT_TRUE(maintainer_factory_calls_.empty());
+ config_bare_ptr->SetEnabled(true);
+
+ ASSERT_EQ(maintainer_factory_calls_.size(), 1u);
+ EXPECT_EQ(maintainer_factory_calls_[0].input_configuration, config_bare_ptr);
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ DisablingConfigurationDestroysMaintainer) {
+ auto config = std::make_unique<FakeFilteringConfiguration>();
+ auto* config_bare_ptr = config.get();
+ config->is_enabled = true;
+ testee_.InstallFilteringConfiguration(std::move(config));
+
+ DisableConfigurationAndEnsureMaintainerDestroyed(
+ &maintainer_factory_calls_[0]);
+ // The configuration remains installed, even if it is disabled and there is
+ // no maintainer for it.
+ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(),
+ std::vector<FilteringConfiguration*>{config_bare_ptr});
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ EnabledMaintainersConsultedForSubscriptions) {
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("1"));
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("2"));
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("3"));
+
+ ASSERT_EQ(maintainer_factory_calls_.size(), 3u);
+ EXPECT_THAT(testee_.GetInstalledFilteringConfigurations(),
+ testing::UnorderedElementsAre(
+ maintainer_factory_calls_[0].input_configuration,
+ maintainer_factory_calls_[1].input_configuration,
+ maintainer_factory_calls_[2].input_configuration));
+ auto disabled_entry = std::move(maintainer_factory_calls_[1]);
+ // We disable one configuration. The maintainer of that configuration will
+ // be destroyed, so it won't be consulted for subscriptions.
+ DisableConfigurationAndEnsureMaintainerDestroyed(&disabled_entry);
+ EXPECT_TRUE(
+ testee_.GetCurrentSubscriptions(disabled_entry.input_configuration)
+ .empty());
+ // A maintainer of the wrong configuration is not consulted, even if it's
+ // enabled.
+ auto& entry_of_different_config = maintainer_factory_calls_[2];
+ EXPECT_CALL(*entry_of_different_config.output_maintainer,
+ GetCurrentSubscriptions())
+ .Times(0);
+ // The maintainer of the right configuration is consulted for Subscriptions
+ auto& entry_of_config_in_question = maintainer_factory_calls_[0];
+ // Set subscriptions in prefs.
+ auto url1 = GURL{"https://subscription/1.txt"};
+ auto url2 = GURL{"https://subscription/2.txt"};
+ auto subscription1 = MakeMockSubscription(url1);
+ auto subscription2 = MakeMockSubscription(url2);
+ entry_of_config_in_question.input_configuration->AddFilterList(url1);
+ entry_of_config_in_question.input_configuration->AddFilterList(url2);
+ EXPECT_CALL(*entry_of_config_in_question.output_maintainer,
+ GetCurrentSubscriptions())
+ .WillOnce(Return(std::vector<scoped_refptr<Subscription>>{
+ subscription1, subscription2}));
+
+ EXPECT_THAT(testee_.GetCurrentSubscriptions(
+ entry_of_config_in_question.input_configuration),
+ testing::UnorderedElementsAre(subscription1, subscription2));
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ GetCurrentSubscriptionsWhenConfigurationDisabled) {
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>());
+
+ ASSERT_EQ(maintainer_factory_calls_.size(), 1u);
+ EXPECT_THAT(testee_.GetInstalledFilteringConfigurations(),
+ testing::UnorderedElementsAre(
+ maintainer_factory_calls_[0].input_configuration));
+ auto disabled_entry = std::move(maintainer_factory_calls_[0]);
+ // Set subscriptions in prefs.
+ auto url1 = GURL{"https://subscription/1.txt"};
+ auto url2 = GURL{"https://subscription/2.txt"};
+ disabled_entry.input_configuration->AddFilterList(url1);
+ disabled_entry.input_configuration->AddFilterList(url2);
+ // We disable one configuration. The maintainer of that configuration will
+ // be destroyed, so it won't be consulted for subscriptions.
+ DisableConfigurationAndEnsureMaintainerDestroyed(&disabled_entry);
+ auto test_result =
+ testee_.GetCurrentSubscriptions(disabled_entry.input_configuration);
+ EXPECT_EQ(2u, test_result.size());
+ for (const GURL& url : {url1, url2}) {
+ auto it = base::ranges::find_if(test_result, [&](const auto& entry) {
+ return entry->GetSourceUrl() == url;
+ });
+ ASSERT_TRUE(it != test_result.end());
+ EXPECT_TRUE(IsAnEmptySubscription(*it));
+ }
+}
+
+// Test when maintainer is not yet initialized, meaning storage is
+// not yet initialized. This test takes some shortcut and does not mock
+// storage but because maintainer will not install any filter list until
+// storage is initialized we can mimic that by returning an empty vector
+// from FilteringConfigurationMaintainer::GetCurrentSubscriptions().
+TEST_F(AdblockSubscriptionServiceImplTest,
+ GetCurrentSubscriptionsWhenMaintainerNotYetInitialized) {
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>());
+
+ ASSERT_EQ(maintainer_factory_calls_.size(), 1u);
+ EXPECT_THAT(testee_.GetInstalledFilteringConfigurations(),
+ testing::UnorderedElementsAre(
+ maintainer_factory_calls_[0].input_configuration));
+ auto tested_entry = std::move(maintainer_factory_calls_[0]);
+ // Set subscriptions in prefs.
+ auto url1 = GURL{"https://subscription/1.txt"};
+ auto url2 = GURL{"https://subscription/2.txt"};
+ tested_entry.input_configuration->AddFilterList(url1);
+ tested_entry.input_configuration->AddFilterList(url2);
+ EXPECT_CALL(*tested_entry.output_maintainer, GetCurrentSubscriptions())
+ .WillOnce(Return(std::vector<scoped_refptr<Subscription>>{}));
+ auto test_result =
+ testee_.GetCurrentSubscriptions(tested_entry.input_configuration);
+ EXPECT_EQ(2u, test_result.size());
+ for (const GURL& url : {url1, url2}) {
+ auto it = base::ranges::find_if(test_result, [&](const auto& entry) {
+ return entry->GetSourceUrl() == url;
+ });
+ ASSERT_TRUE(it != test_result.end());
+ EXPECT_TRUE(IsAnEmptySubscription(*it));
+ }
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ EnabledMaintainersConsultedForSnapshot) {
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("1"));
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("2"));
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("3"));
+ ASSERT_EQ(maintainer_factory_calls_.size(), 3u);
+ // We disable one configuration. The maintainer of that configuration will
+ // not take part in populating the Snapshot because it will be destroyed.
+ DisableConfigurationAndEnsureMaintainerDestroyed(
+ &maintainer_factory_calls_[1]);
+ // The maintainers of enabled configurations will be asked to provide
+ // SubscriptionCollections for the Snapshot.
+ auto collection1 = std::make_unique<MockSubscriptionCollection>();
+ auto collection2 = std::make_unique<MockSubscriptionCollection>();
+ const std::vector<MockSubscriptionCollection*> returned_collection_ptrs{
+ collection1.get(), collection2.get()};
+
+ EXPECT_CALL(*maintainer_factory_calls_[0].output_maintainer,
+ GetSubscriptionCollection())
+ .WillOnce(Return(testing::ByMove(std::move(collection1))));
+ EXPECT_CALL(*maintainer_factory_calls_[2].output_maintainer,
+ GetSubscriptionCollection())
+ .WillOnce(Return(testing::ByMove(std::move(collection2))));
+
+ // The SubscriptionCollections that comprise the Snapshot are the ones
+ // returned by maintainers.
+ const auto snapshot = testee_.GetCurrentSnapshot();
+ EXPECT_TRUE(base::ranges::is_permutation(
+ snapshot, returned_collection_ptrs, {},
+ &std::unique_ptr<SubscriptionCollection>::get));
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest,
+ SubscriptionObserverNotifiedByMaintainerCallbacks) {
+ const GURL kUrl("https://test.com");
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("1"));
+ testee_.InstallFilteringConfiguration(
+ std::make_unique<FakeFilteringConfiguration>("2"));
+ ASSERT_EQ(maintainer_factory_calls_.size(), 2u);
+
+ MockSubscriptionObserver observer;
+ testee_.AddObserver(&observer);
+ EXPECT_CALL(observer, OnSubscriptionInstalled(kUrl)).Times(2);
+ maintainer_factory_calls_[0].input_update_callback.Run(kUrl);
+ maintainer_factory_calls_[1].input_update_callback.Run(kUrl);
+ testee_.RemoveObserver(&observer);
+ // Observer no longer notified after being removed.
+ EXPECT_CALL(observer, OnSubscriptionInstalled(kUrl)).Times(0);
+ maintainer_factory_calls_[0].input_update_callback.Run(kUrl);
+ maintainer_factory_calls_[1].input_update_callback.Run(kUrl);
+}
+
+TEST_F(AdblockSubscriptionServiceImplTest, InstallConfigurationWithSameName) {
+ auto config1 = std::make_unique<FakeFilteringConfiguration>("test");
+ auto* config1_bare_ptr = config1.get();
+ testee_.InstallFilteringConfiguration(std::move(config1));
+ auto config2 = std::make_unique<FakeFilteringConfiguration>("test");
+ testee_.InstallFilteringConfiguration(std::move(config2));
+ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(),
+ std::vector<FilteringConfiguration*>{config1_bare_ptr});
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/subscription_validator_impl_test.cc b/components/adblock/core/subscription/test/subscription_validator_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/subscription_validator_impl_test.cc
@@ -0,0 +1,251 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/subscription_validator_impl.h"
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/test/task_environment.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "crypto/sha2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+class AdblockSubscriptionValidatorImplTest : public testing::Test {
+ public:
+ void SetUp() override {
+ common::prefs::RegisterProfilePrefs(pref_service_.registry());
+ }
+
+ std::unique_ptr<SubscriptionValidatorImpl> MakeValidator(
+ const std::string& current_schema_version) {
+ return std::make_unique<SubscriptionValidatorImpl>(&pref_service_,
+ current_schema_version);
+ }
+
+ base::test::TaskEnvironment task_environment_;
+ TestingPrefServiceSimple pref_service_;
+};
+
+TEST_F(AdblockSubscriptionValidatorImplTest, EmptyPrefMeansNoSignature) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ StoredSignatureVisibleOnlyAfterRecreatingCallback) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+ auto initial_callback = validator->IsSignatureValid();
+ // Store a new signature after the initial callback was created.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+ // |initial_callback| uses the initial state of prefs, not the
+ // current state, to avoid race conditions. It will still return the old
+ // result.
+ EXPECT_FALSE(initial_callback.Run(*buffer, path));
+ // Recreate the callback.
+ auto new_callback = validator->IsSignatureValid();
+ // The new state has the signature stored in previous life.
+ EXPECT_TRUE(new_callback.Run(*buffer, path));
+ // Only the file component of the path is the key, this allows moving
+ // to a different storage directory if needed.
+ base::FilePath reparented_path(
+ base::FilePath(FILE_PATH_LITERAL("parent")).AppendASCII("x.fb"));
+ EXPECT_TRUE(new_callback.Run(*buffer, reparented_path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest, StoreAndRemoveSignature) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ validator->RemoveStoredSignature().Run(path);
+ task_environment_.RunUntilIdle();
+ // Recreate the validator.
+ validator = MakeValidator(CurrentSchemaVersion());
+ // The signature was removed so it's no longer returned.
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ SchemaVersionChangeInvalidatesSignatures) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store a valid signature.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ // Simulate a schema version change by storing a kLastUsedSchemaVersion
+ // different than the current one.
+ pref_service_.SetString(common::prefs::kLastUsedSchemaVersion, "000");
+
+ // Recreate the validator.
+ validator = MakeValidator(CurrentSchemaVersion());
+ // The signature is invalid because we're not allowed to read flatbuffers
+ // created with a different schema version.
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest, SignatureStoredViaKey) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store a valid signature.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ // When storing dictionary keys with dots in them, there's a difference if
+ // you use "SetIntKey" vs "SetIntPath".
+ // "Path" interprets dict["x.fb"]="hash" as {"x":{"fb":"hash"}} - the dot
+ // indicates a deeper level of dict
+ // "Key" interprets dict["x.fb"]="hash" as {"x.fb":"hash"} - the dot is part
+ // of the key name
+ const base::Value::Dict& pref =
+ pref_service_.GetDict(common::prefs::kSubscriptionSignatures);
+ ASSERT_TRUE(pref.FindString("x.fb"));
+ EXPECT_EQ(*pref.FindString("x.fb"),
+ base::Base64Encode(crypto::SHA256Hash((buffer->span()))));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ DifferentBufferFailsSignatureValidation) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store signature for IDR_ADBLOCK_FLATBUFFER_EASYLIST subscription.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+ // Recreate the validator.
+ validator = MakeValidator(CurrentSchemaVersion());
+ // If a different buffer resides on the same path, the signature does not
+ // match.
+ auto different_buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES);
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*different_buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ NewSchemaVersionInvalidatesSubscriptions) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store signature for IDR_ADBLOCK_FLATBUFFER_EASYLIST subscription.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ // Recreate the validator with new schema version.
+ validator = MakeValidator(CurrentSchemaVersion() + std::string("new"));
+ // Same buffer, same path, but it's no longer valid because schema has
+ // changed.
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ IsSignatureValidCallbackAfterValidatorDestroyed) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store a valid signature.
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ const base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ auto is_signature_valid = validator->IsSignatureValid();
+
+ // Validator dies now:
+ validator.reset();
+
+ // |is_signature_valid| still works, using the remembered initial state.
+ const auto bad_path = base::FilePath().AppendASCII("bad_path.fb");
+ EXPECT_TRUE(is_signature_valid.Run(*buffer, path));
+ EXPECT_FALSE(is_signature_valid.Run(*buffer, bad_path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ StoreSignatureCallbackAfterValidatorDestroyed) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Retrieve callback.
+ auto store_signature = validator->StoreTrustedSignature();
+
+ // Validator dies now:
+ validator.reset();
+
+ // Store a valid signature. This does nothing, since validator is dead.
+ const auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ const base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ std::move(store_signature).Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ // Verify that storing a signature did not have effects on future validators.
+ validator = MakeValidator(CurrentSchemaVersion());
+ // The signature wasn't stored and it is not recognized.
+ EXPECT_FALSE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+TEST_F(AdblockSubscriptionValidatorImplTest,
+ RemoveSignatureCallbackAfterValidatorDestroyed) {
+ auto validator = MakeValidator(CurrentSchemaVersion());
+
+ // Store a valid signature. This does nothing, since validator is dead.
+ const auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ const base::FilePath path(FILE_PATH_LITERAL("x.fb"));
+ validator->StoreTrustedSignature().Run(*buffer, path);
+ task_environment_.RunUntilIdle();
+
+ auto remove_signature = validator->RemoveStoredSignature();
+
+ // Validator dies now:
+ validator.reset();
+
+ // Try removing signature after the validator has died, this does nothing.
+ std::move(remove_signature).Run(path);
+
+ // Verify that removing a signature did not have effects on future validators.
+ validator = MakeValidator(CurrentSchemaVersion());
+ // The signature wasn't removed and it is still recognized.
+ EXPECT_TRUE(validator->IsSignatureValid().Run(*buffer, path));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/test/url_keyword_extractor_test.cc b/components/adblock/core/subscription/test/url_keyword_extractor_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/test/url_keyword_extractor_test.cc
@@ -0,0 +1,74 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/url_keyword_extractor.h"
+
+#include "absl/types/optional.h"
+#include "gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockUrlKeywordExtractor, NoKeywordExtractedFromEmptyInput_Request) {
+ UrlKeywordExtractor extractor("");
+ EXPECT_EQ(extractor.GetNextKeyword(), absl::nullopt);
+}
+
+TEST(AdblockUrlKeywordExtractor, DoesNotExtractCommonKeywords) {
+ // Common keywords include "http", "https", "com" and "js". These should be
+ // skipped.
+ UrlKeywordExtractor extractor("http://www.base.com/path?query.js");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords,
+ testing::ElementsAre("www", "base", "path", "query"));
+}
+
+TEST(AdblockUrlKeywordExtractor, DoesExtractLastKeywordsForRequest) {
+ UrlKeywordExtractor extractor("http://domain.cc/in_discovery5");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords,
+ testing::ElementsAre("domain", "cc", "in", "discovery5"));
+}
+
+TEST(AdblockUrlKeywordExtractor, SingleLetterKeywordsSkipped) {
+ UrlKeywordExtractor extractor("http://a.b/cc");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords, testing::ElementsAre("cc"));
+}
+
+TEST(AdblockUrlKeywordExtractor, KeywordSymbolVsSeparatorSymbols) {
+ // Keyword symbols are alphanumeric characters plus the % symbol. Everything
+ // else is a separator.
+ UrlKeywordExtractor extractor("http://alpha.beta/data123-data2%4?");
+ std::vector<std::string> extracted_keywords;
+ while (auto keyword = extractor.GetNextKeyword()) {
+ extracted_keywords.push_back(keyword->data());
+ }
+ EXPECT_THAT(extracted_keywords,
+ testing::ElementsAre("alpha", "beta", "data123", "data2%4"));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/url_keyword_extractor.cc b/components/adblock/core/subscription/url_keyword_extractor.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/url_keyword_extractor.cc
@@ -0,0 +1,66 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/subscription/url_keyword_extractor.h"
+
+#include <algorithm>
+#include <cctype>
+
+#include "base/strings/string_util.h"
+#include "components/adblock/core/common/keyword_extractor_utils.h"
+
+namespace adblock {
+namespace {
+
+bool IsSeparatorCharacter(char c) {
+ return !(std::isalnum(c) || c == '%');
+}
+
+} // namespace
+
+absl::optional<std::string_view> UrlKeywordExtractor::GetNextKeyword() {
+ std::string_view current_keyword;
+ do {
+ const auto start_of_next_keyword = input_.find_first_not_of('\0');
+ if (start_of_next_keyword == std::string_view::npos) {
+ return absl::nullopt;
+ }
+ input_.remove_prefix(start_of_next_keyword);
+ const auto end_of_keyword = input_.find_first_of('\0');
+ current_keyword = input_.substr(0, end_of_keyword);
+ input_.remove_prefix(current_keyword.size());
+ } while (utils::IsBadKeyword(current_keyword));
+ return current_keyword;
+}
+
+UrlKeywordExtractor::UrlKeywordExtractor(std::string_view url)
+ : url_with_nulls_(url.data(), url.size()) {
+ // The keywords returned by GetNextKeyword() will be passed to
+ // flatbuffers::Vector::LookupByKey(const char* key) which assumes |key| is
+ // null-terminated. In order to avoid allocating a null-terminated
+ // std::string for every extracted keyword, we instead replace separator
+ // characters by nulls, so that a StringPiece referring to a keyword is also
+ // null-terminated.
+ // This isn't elegant, but it's a valid workaround for the limitations of
+ // the flatbuffers generated API.
+ std::ranges::replace_if(url_with_nulls_, &IsSeparatorCharacter, '\0');
+ input_ = url_with_nulls_;
+}
+
+UrlKeywordExtractor::~UrlKeywordExtractor() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/subscription/url_keyword_extractor.h b/components/adblock/core/subscription/url_keyword_extractor.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/subscription/url_keyword_extractor.h
@@ -0,0 +1,59 @@
+
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_
+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_
+
+#include <string>
+#include <string_view>
+
+#include "absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+// Keywords allow selecting filters that could potentially match a URL faster
+// than an exhaustive search.
+// This is how it works:
+// 1. Each URL is split into keywords using
+// GetNextKeyword().
+// "https://content.adblockplus.com/advertisment" becomes:
+// "content", "adblockplus", "advertisment"
+// - "https" and "com" are skipped because they're common
+//
+// 2. The keyword index in the flatbuffer is queried only for filters that match
+// these keywords. A keyword may index multiple filters, a filter is only
+// indexed by one (or none) keywords.
+//
+// 3. If we fail to extract keywords from a filter, we index it under an empty
+// keyword. All filters without a keyword are checked for all URLs, as they
+// could match anything.
+class UrlKeywordExtractor {
+ public:
+ explicit UrlKeywordExtractor(std::string_view url);
+ ~UrlKeywordExtractor();
+ absl::optional<std::string_view> GetNextKeyword();
+
+ private:
+ std::string url_with_nulls_;
+ std::string_view input_;
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_
diff --git a/components/adblock/core/test/activeping_telemetry_topic_provider_test.cc b/components/adblock/core/test/activeping_telemetry_topic_provider_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/activeping_telemetry_topic_provider_test.cc
@@ -0,0 +1,325 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/activeping_telemetry_topic_provider.h"
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/system/sys_info.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/uuid.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/adblock/core/common/app_info.h"
+#include "components/adblock/core/configuration/test/mock_filtering_configuration.h"
+#include "components/adblock/core/subscription/subscription_config.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "components/prefs/pref_value_store.h"
+#include "components/prefs/testing_pref_service.h"
+#include "gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+enum class AcceptableAds { Enabled, Disabled };
+
+class AdblockActivepingTelemetryTopicProviderTest
+ : public testing::TestWithParam<AcceptableAds> {
+ public:
+ void SetUp() override {
+ common::prefs::RegisterProfilePrefs(pref_service_.registry());
+ EXPECT_CALL(subscription_service_,
+ GetFilteringConfiguration(kAdblockFilteringConfigurationName))
+ .WillRepeatedly(testing::Return(&adblock_configuration_));
+ EXPECT_CALL(adblock_configuration_, GetFilterLists())
+ .WillRepeatedly(testing::Return(
+ AcceptableAdsEnabled() ? std::vector<GURL>{AcceptableAdsUrl()}
+ : std::vector<GURL>{}));
+ RecreateProvider();
+ }
+
+ bool AcceptableAdsEnabled() const {
+ return GetParam() == AcceptableAds::Enabled;
+ }
+
+ void RecreateProvider() {
+ provider_ = std::make_unique<ActivepingTelemetryTopicProvider>(
+ &pref_service_, &subscription_service_, kBaseUrl, kAuthToken, nullptr);
+ }
+
+ void ExpectPayloadAndTimeConsistentAfterRestart() {
+ // Current state of provider_ is stored persistently and remains consistent
+ // after recreating the provider_.
+ const auto time_of_next_request = provider_->GetTimeOfNextRequest();
+ const auto payload = GetPayload();
+ RecreateProvider();
+ EXPECT_EQ(time_of_next_request, provider_->GetTimeOfNextRequest());
+ EXPECT_EQ(payload, GetPayload());
+ }
+
+ std::string GetPayload() {
+ base::MockCallback<ActivepingTelemetryTopicProvider::PayloadCallback>
+ callback;
+ std::string payload;
+ EXPECT_CALL(callback, Run(testing::_))
+ .WillOnce(testing::SaveArg<0>(&payload));
+ provider_->GetPayload(callback.Get());
+ return payload;
+ }
+
+ void ExpectPayloadContainsValue(const std::string& json,
+ std::string key,
+ base::Value expected_value) {
+ auto root = base::JSONReader::ReadDict(json);
+ ASSERT_TRUE(root) << "JSON is invalid";
+ auto* value = root->FindByDottedPath("payload." + key);
+ ASSERT_TRUE(value);
+ EXPECT_EQ(*value, expected_value);
+ }
+
+ void ExpectPayloadDoesNotContainValue(const std::string& json,
+ std::string key) {
+ auto value = base::JSONReader::ReadDict(json);
+ ASSERT_TRUE(value) << "JSON is invalid";
+ EXPECT_FALSE(value->FindByDottedPath("payload." + key));
+ }
+
+ void ExpectPayloadContainsRequiredStaticValues(const std::string& json) {
+ ExpectPayloadContainsValue(json, "aa_active",
+ base::Value(AcceptableAdsEnabled()));
+ ExpectPayloadContainsValue(
+ json, "platform", base::Value(base::SysInfo::OperatingSystemName()));
+ ExpectPayloadContainsValue(
+ json, "platform_version",
+ base::Value(base::SysInfo::OperatingSystemVersion()));
+ ExpectPayloadContainsValue(json, "application",
+ base::Value(AppInfo::Get().name));
+ ExpectPayloadContainsValue(json, "application_version",
+ base::Value(AppInfo::Get().version));
+ ExpectPayloadContainsValue(json, "addon_name",
+ base::Value("eyeo-chromium-sdk"));
+ ExpectPayloadContainsValue(json, "addon_version", base::Value("2.0.0"));
+ }
+
+ void ExpectLastPingTagValid(const std::string& json,
+ base::Uuid* parsed_tag = nullptr) {
+ auto root = base::JSONReader::ReadDict(json);
+ ASSERT_TRUE(root);
+ const std::string* tag =
+ root->FindStringByDottedPath("payload.last_ping_tag");
+ ASSERT_TRUE(tag);
+ const auto uuid = base::Uuid::ParseLowercase(*tag);
+ EXPECT_TRUE(uuid.is_valid());
+ if (parsed_tag) {
+ *parsed_tag = uuid;
+ }
+ }
+
+ void ExpectFailureAndRetryForResponse(
+ std::unique_ptr<std::string> bad_response_contents) {
+ const std::string first_attempt_payload = GetPayload();
+ provider_->ParseResponse(std::move(bad_response_contents));
+
+ // Next ping after shorter delay, since the previous one failed:
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(),
+ base::Time::Now() + kRetryPingInterval);
+
+ // Payload is the same as after first ping.
+ const std::string retry_payload = GetPayload();
+ EXPECT_EQ(first_attempt_payload, retry_payload);
+ ExpectPayloadAndTimeConsistentAfterRestart();
+ }
+
+ static constexpr base::TimeDelta kNormalPingInterval = base::Hours(12);
+ static constexpr base::TimeDelta kRetryPingInterval = base::Hours(1);
+ const GURL kBaseUrl{"https://telemetry.com/"};
+ const std::string kAuthToken{"AUTH_TOKEN"};
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ TestingPrefServiceSimple pref_service_;
+ MockSubscriptionService subscription_service_;
+ MockFilteringConfiguration adblock_configuration_;
+ std::unique_ptr<ActivepingTelemetryTopicProvider> provider_;
+};
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, StaticFields) {
+ EXPECT_EQ(provider_->GetEndpointURL(),
+ "https://telemetry.com/topic/eyeochromium_activeping/version/2");
+ EXPECT_EQ(provider_->GetAuthToken(), kAuthToken);
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, DefaultBaseUrl) {
+ EXPECT_EQ(ActivepingTelemetryTopicProvider::DefaultBaseUrl(),
+ GURL(EYEO_TELEMETRY_SERVER_URL));
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, DefaultAuthToken) {
+#if defined(EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN)
+ EXPECT_EQ(ActivepingTelemetryTopicProvider::DefaultAuthToken(),
+ EYEO_TELEMETRY_ACTIVEPING_AUTH_TOKEN);
+#else
+ EXPECT_EQ(ActivepingTelemetryTopicProvider::DefaultAuthToken(), "");
+#endif
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, FirstRun) {
+ // On first run, next ping should happen ASAP.
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(), base::Time::Now());
+
+ // There are no values for "last_ping", "previous_last_ping", "last_ping_tag"
+ // and "first_ping". But there are values for other required fields:
+ const std::string payload = GetPayload();
+ ExpectPayloadContainsRequiredStaticValues(payload);
+ ExpectPayloadDoesNotContainValue(payload, "last_ping");
+ ExpectPayloadDoesNotContainValue(payload, "previous_last_ping");
+ ExpectPayloadDoesNotContainValue(payload, "last_ping_tag");
+ ExpectPayloadDoesNotContainValue(payload, "first_ping");
+ ExpectPayloadAndTimeConsistentAfterRestart();
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, FirstRunSucceeded) {
+ // Successful server response:
+ auto response = std::make_unique<std::string>(R"({"token":"Monday"})");
+ provider_->ParseResponse(std::move(response));
+
+ // Next ping after normal delay, since the previous one succeeded:
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(),
+ base::Time::Now() + kNormalPingInterval);
+
+ // Payload now contains "first_ping" and "last_ping" set to "Monday", as this
+ // was the "token" received from server:
+ const std::string payload = GetPayload();
+ ExpectPayloadContainsRequiredStaticValues(payload);
+ ExpectPayloadContainsValue(payload, "last_ping", base::Value("Monday"));
+ ExpectPayloadContainsValue(payload, "first_ping", base::Value("Monday"));
+ // There's no "previous_last_ping" because we need at least two successful
+ // pings for that.
+ ExpectPayloadDoesNotContainValue(payload, "previous_last_ping");
+
+ // "last_ping_tag" is some valid UUID4.
+ ExpectLastPingTagValid(payload);
+ ExpectPayloadAndTimeConsistentAfterRestart();
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest,
+ FirstRunFailedDueToErrorInResponse) {
+ // "error" in server response:
+ ExpectFailureAndRetryForResponse(
+ std::make_unique<std::string>(R"({"error":"1111","token":"Monday"})"));
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest,
+ FirstRunFailedDueToNoResponse) {
+ // No server response:
+ ExpectFailureAndRetryForResponse(nullptr);
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest,
+ FirstRunFailedDueToNoTokenInResponse) {
+ // No "token" in server response:
+ ExpectFailureAndRetryForResponse(
+ std::make_unique<std::string>(R"({"data":"1111"})"));
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest,
+ FirstRunFailedDueToInvalidJsonResponse) {
+ // Not a valid JSON:
+ ExpectFailureAndRetryForResponse(
+ std::make_unique<std::string>(R"(rubbish data)"));
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, SecondRunSucceeded) {
+ // Successful first server response:
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Monday"})"));
+
+ // Store first last_ping_tag to compare against the next one:
+ base::Uuid first_response_uuid;
+ ExpectLastPingTagValid(GetPayload(), &first_response_uuid);
+
+ // Next ping after normal delay, since the previous one succeeded:
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(),
+ base::Time::Now() + kNormalPingInterval);
+
+ task_environment_.FastForwardBy(kNormalPingInterval);
+
+ // Successful second server response:
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Tuesday"})"));
+ ExpectPayloadAndTimeConsistentAfterRestart();
+
+ // Payload now contains all ping dates set.
+ const std::string payload = GetPayload();
+ ExpectPayloadContainsRequiredStaticValues(payload);
+ ExpectPayloadContainsValue(payload, "last_ping", base::Value("Tuesday"));
+ ExpectPayloadContainsValue(payload, "previous_last_ping",
+ base::Value("Monday"));
+ ExpectPayloadContainsValue(payload, "first_ping", base::Value("Monday"));
+
+ base::Uuid second_response_uuid;
+ ExpectLastPingTagValid(payload, &second_response_uuid);
+
+ // Different tag was generated:
+ EXPECT_NE(second_response_uuid, first_response_uuid);
+ ExpectPayloadAndTimeConsistentAfterRestart();
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest,
+ PreviousLastPingTracksCorrectly) {
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Monday"})"));
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Tuesday"})"));
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Wednesday"})"));
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Thursday"})"));
+
+ const std::string payload = GetPayload();
+ ExpectPayloadContainsRequiredStaticValues(payload);
+ // "last_ping" is the one received most recently.
+ ExpectPayloadContainsValue(payload, "last_ping", base::Value("Thursday"));
+ // "previous_last_ping" is the one received before "last_ping".
+ ExpectPayloadContainsValue(payload, "previous_last_ping",
+ base::Value("Wednesday"));
+ // "first_ping" never changes, after the initial successful response.
+ ExpectPayloadContainsValue(payload, "first_ping", base::Value("Monday"));
+ // Note: "Wednesday" does not appear in results, it would be a
+ // "previous_previous_last_ping" which we don't track.
+ ExpectPayloadAndTimeConsistentAfterRestart();
+}
+
+TEST_P(AdblockActivepingTelemetryTopicProviderTest, TimeToNextPingAfterDelay) {
+ // At first, require next request to happen ASAP.
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(), base::Time::Now());
+ task_environment_.FastForwardBy(base::Seconds(30));
+ provider_->ParseResponse(
+ std::make_unique<std::string>(R"({"token":"Monday"})"));
+ // After a success, next ping should happen after a normal delay.
+ EXPECT_EQ(provider_->GetTimeOfNextRequest(),
+ base::Time::Now() + kNormalPingInterval);
+
+ ExpectPayloadAndTimeConsistentAfterRestart();
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AdblockActivepingTelemetryTopicProviderTest,
+ testing::Values(AcceptableAds::Enabled,
+ AcceptableAds::Disabled));
+} // namespace adblock
diff --git a/components/adblock/core/test/adblock_telemetry_service_unittest.cc b/components/adblock/core/test/adblock_telemetry_service_unittest.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/adblock_telemetry_service_unittest.cc
@@ -0,0 +1,460 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/adblock_telemetry_service.h"
+
+#include <memory>
+
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/adblock/core/common/adblock_constants.h"
+#include "components/adblock/core/configuration/test/mock_filtering_configuration.h"
+#include "components/adblock/core/net/test/mock_adblock_request_throttle.h"
+#include "components/adblock/core/subscription/test/mock_subscription_service.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnRef;
+
+namespace adblock {
+
+namespace {
+
+class MockTopicProvider
+ : public NiceMock<AdblockTelemetryService::TopicProvider> {
+ public:
+ MOCK_METHOD(GURL, GetEndpointURL, (), (const, override));
+ MOCK_METHOD(std::string, GetAuthToken, (), (const, override));
+ MOCK_METHOD(void, GetPayload, (PayloadCallback), (const, override));
+ MOCK_METHOD(base::Time, GetTimeOfNextRequest, (), (const, override));
+ MOCK_METHOD(void, ParseResponse, (std::unique_ptr<std::string>), (override));
+ MOCK_METHOD(void, FetchDebugInfo, (DebugInfoCallback), (const, override));
+};
+
+const auto kCheckInterval = base::Minutes(5);
+} // namespace
+
+class AdblockTelemetryServiceTest : public testing::Test {
+ public:
+ void SetUp() override {
+ static const std::string adblock_name = kAdblockFilteringConfigurationName;
+ EXPECT_CALL(filtering_configuration_, GetName())
+ .WillRepeatedly(testing::ReturnRef(adblock_name));
+ EXPECT_CALL(subscription_service_,
+ GetFilteringConfiguration(kAdblockFilteringConfigurationName))
+ .WillRepeatedly(testing::Return(&filtering_configuration_));
+ telemetry_service_ = std::make_unique<AdblockTelemetryService>(
+ &subscription_service_, test_shared_url_loader_factory_,
+ &request_throttle_, kCheckInterval);
+ }
+
+ MockTopicProvider* RegisterFooMock() {
+ auto* mock_provider = RegisterNewMockTopicProvider();
+ ON_CALL(*mock_provider, GetEndpointURL()).WillByDefault(Return(kFooUrl));
+ ON_CALL(*mock_provider, GetAuthToken()).WillByDefault(Return("foo_token"));
+ ON_CALL(*mock_provider, GetPayload(testing::_))
+ .WillByDefault([](MockTopicProvider::PayloadCallback callback) {
+ std::move(callback).Run("foo_data");
+ });
+ ON_CALL(*mock_provider, GetTimeOfNextRequest())
+ .WillByDefault(Return(base::Time::Now() + kFooDelay));
+ return mock_provider;
+ }
+
+ void ExpectFooMadeRequest(int pending_request_idx) {
+ const auto& resource_request =
+ test_url_loader_factory_.GetPendingRequest(pending_request_idx)
+ ->request;
+ EXPECT_EQ(resource_request.url, kFooUrl);
+ AssertRequestContainsRequiredData(resource_request, "foo_data",
+ "foo_token");
+ }
+
+ MockTopicProvider* RegisterBarMock() {
+ auto* mock_provider = RegisterNewMockTopicProvider();
+ ON_CALL(*mock_provider, GetEndpointURL()).WillByDefault(Return(kBarUrl));
+ ON_CALL(*mock_provider, GetAuthToken()).WillByDefault(Return("bar_token"));
+ ON_CALL(*mock_provider, GetPayload(testing::_))
+ .WillByDefault([](MockTopicProvider::PayloadCallback callback) {
+ std::move(callback).Run("bar_data");
+ });
+ ON_CALL(*mock_provider, GetTimeOfNextRequest())
+ .WillByDefault(Return(base::Time::Now() + kBarDelay));
+ return mock_provider;
+ }
+
+ void ExpectBarMadeRequest(int pending_request_idx) {
+ const auto& resource_request =
+ test_url_loader_factory_.GetPendingRequest(pending_request_idx)
+ ->request;
+ EXPECT_EQ(resource_request.url, kBarUrl);
+ AssertRequestContainsRequiredData(resource_request, "bar_data",
+ "bar_token");
+ }
+
+ protected:
+ MockTopicProvider* RegisterNewMockTopicProvider() {
+ auto provider = std::make_unique<MockTopicProvider>();
+ auto* provider_bare_ptr = provider.get();
+ telemetry_service_->AddTopicProvider(std::move(provider));
+ return provider_bare_ptr;
+ }
+
+ void AssertRequestContainsRequiredData(
+ const network::ResourceRequest& resource_request,
+ const std::string& expected_upload_data,
+ const std::string& expected_auth_token) {
+ // Cookies are not sent nor stored:
+ EXPECT_FALSE(resource_request.SavesCookies());
+ EXPECT_FALSE(resource_request.SendsCookies());
+
+ // Authorization token is being sent:
+ auto auth_header = resource_request.headers.GetHeader(
+ net::HttpRequestHeaders::kAuthorization);
+ ASSERT_TRUE(auth_header);
+ EXPECT_EQ(auth_header.value(), "Bearer " + expected_auth_token);
+
+ // "Accept: application/json" is sent.
+ auto accept_header =
+ resource_request.headers.GetHeader(net::HttpRequestHeaders::kAccept);
+ ASSERT_TRUE(accept_header);
+ EXPECT_EQ(accept_header.value(), "application/json");
+
+ // Cache is being bypassed:
+ EXPECT_EQ(resource_request.load_flags,
+ net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE);
+
+ // Payload is being sent:
+ std::string upload_data;
+ for (const auto& elem : *(resource_request.request_body->elements())) {
+ auto piece = elem.As<network::DataElementBytes>().AsStringPiece();
+ upload_data.append(piece.data(), piece.size());
+ }
+ EXPECT_EQ(upload_data, expected_upload_data);
+ }
+
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ MockFilteringConfiguration filtering_configuration_;
+ MockSubscriptionService subscription_service_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_{
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)};
+ MockAdblockRequestThrottle request_throttle_;
+ constexpr static base::TimeDelta kFooDelay{base::Minutes(4)};
+ constexpr static base::TimeDelta kBarDelay{base::Minutes(6)};
+ const GURL kFooUrl{"https://telemetry.com/topic/eyeo_foo/version/3"};
+ const GURL kBarUrl{"https://telemetry.com/topic/eyeo_bar/version/2"};
+
+ std::unique_ptr<AdblockTelemetryService> telemetry_service_;
+};
+
+TEST_F(AdblockTelemetryServiceTest,
+ RequestNotMadeBeforeRequestsAllowedByThrottle) {
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ auto* mock = RegisterFooMock();
+ EXPECT_CALL(*mock, GetTimeOfNextRequest())
+ .WillOnce(Return(base::Time::Now()));
+
+ telemetry_service_->Start();
+
+ // Despite the Foo topic provider declaring a next request time of Now(),
+ // we wait until AdblockRequestThrottle allows requests, no matter how much
+ // time passes.
+ task_environment_.RunUntilIdle();
+ task_environment_.FastForwardBy(kFooDelay);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // Only once AdblockRequestThrottle allows requests, there's a network request
+ // made.
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+}
+
+TEST_F(AdblockTelemetryServiceTest,
+ ScheduleStartsImmediatelyWhenAdblockEnabled) {
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ RegisterFooMock();
+
+ telemetry_service_->Start();
+
+ // There are no TopicProviders that are due already.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // The topic should have scheduled a request according to its
+ // GetTimeOfNextRequest(). It will be checked after kCheckInterval.
+ task_environment_.FastForwardBy(kCheckInterval);
+
+ // A request was sent to a URL built according to topic provider's data.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+}
+
+TEST_F(AdblockTelemetryServiceTest, ScheduleStartupDelayedWhenAdblockDisabled) {
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(false));
+
+ RegisterFooMock();
+
+ telemetry_service_->Start();
+
+ // No requests initially.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // A lot of time passes.
+ task_environment_.FastForwardBy(kCheckInterval * 5);
+ // There was no network request made, because IsAdblockEnabled() is false.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // IsAdblockEnabled() becomes true:
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+ for (auto& o : filtering_configuration_.observers_) {
+ o.OnEnabledStateChanged(&filtering_configuration_);
+ }
+
+ // Schedule is started, first request made immediately because
+ // AdblockRequestThrottle allows requests.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+}
+
+TEST_F(AdblockTelemetryServiceTest, ScheduleAbortedWhenAdblockDisabled) {
+ // Schedule starts normally, without delay:
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+ RegisterFooMock();
+ telemetry_service_->Start();
+
+ // User disables Adblocking.
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(false));
+ for (auto& o : filtering_configuration_.observers_) {
+ o.OnEnabledStateChanged(&filtering_configuration_);
+ }
+
+ // A lot of time passes with no requests, schedule is stopped.
+ task_environment_.FastForwardBy(kCheckInterval * 5);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+}
+
+TEST_F(AdblockTelemetryServiceTest,
+ ScheduleAbortedWhenAdblockRemovedThenStartsWhenInstalled) {
+ // Schedule starts normally, without delay:
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+ RegisterFooMock();
+ telemetry_service_->Start();
+
+ // "adblock" configuration is removed.
+ subscription_service_.observer_->OnFilteringConfigurationUninstalled(
+ kAdblockFilteringConfigurationName);
+
+ // A lot of time passes with no requests, schedule is stopped.
+ task_environment_.FastForwardBy(kCheckInterval * 5);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // "custom" configuration is added again => still no pings.
+ const std::string custom_name = "custom";
+ MockFilteringConfiguration custom_filtering_configuration;
+ EXPECT_CALL(custom_filtering_configuration, IsEnabled())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(custom_filtering_configuration, GetName())
+ .WillRepeatedly(testing::ReturnRef(custom_name));
+ subscription_service_.observer_->OnFilteringConfigurationInstalled(
+ &custom_filtering_configuration);
+ task_environment_.FastForwardBy(kCheckInterval * 5);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // "adblock" configuration is added again.
+ subscription_service_.observer_->OnFilteringConfigurationInstalled(
+ &filtering_configuration_);
+
+ // Schedule is started, first request made immediately because
+ // AdblockRequestThrottle allows requests.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+}
+
+TEST_F(AdblockTelemetryServiceTest, MultipleProvidersMakeRequests) {
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ RegisterFooMock();
+ RegisterBarMock();
+
+ telemetry_service_->Start();
+
+ // No requests initially.
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+
+ // Time for Foo topic provider to make a request.
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ task_environment_.FastForwardBy(kCheckInterval);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+
+ // Time for Bar topic provider to make a request.
+ task_environment_.FastForwardBy(kCheckInterval);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 2);
+ ExpectBarMadeRequest(1);
+}
+
+TEST_F(AdblockTelemetryServiceTest, SuccessfulResponseReceived) {
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ auto* mock = RegisterFooMock();
+
+ telemetry_service_->Start();
+
+ task_environment_.FastForwardBy(kCheckInterval);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+
+ const std::string response = "response_content";
+
+ // Response will trigger TopicProvider's ParseResponse().
+ EXPECT_CALL(*mock, ParseResponse(testing::Pointee(response)));
+ test_url_loader_factory_.SimulateResponseForPendingRequest(kFooUrl.spec(),
+ response);
+}
+
+TEST_F(AdblockTelemetryServiceTest, Non200ResponseStillParsed) {
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ auto* mock = RegisterFooMock();
+
+ telemetry_service_->Start();
+
+ task_environment_.FastForwardBy(kCheckInterval);
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+
+ const std::string response = "error_content";
+
+ // Even with a non-200 response code, TopicProvider is still shown the body
+ // of the response. The response may contain an error description which the
+ // TopicProvider may want to parse.
+ EXPECT_CALL(*mock, ParseResponse(testing::Pointee(response)));
+ test_url_loader_factory_.SimulateResponseForPendingRequest(
+ kFooUrl.spec(), response, net::HTTP_FORBIDDEN);
+}
+
+TEST_F(AdblockTelemetryServiceTest, RequestAbortedWhenAdblockDisabled) {
+ // Start schedule normally:
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+ auto* mock = RegisterFooMock();
+ telemetry_service_->Start();
+ task_environment_.FastForwardBy(kCheckInterval);
+ // Request is made:
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+
+ // Adblocking is disabled before response arrives:
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(false));
+ for (auto& o : filtering_configuration_.observers_) {
+ o.OnEnabledStateChanged(&filtering_configuration_);
+ }
+
+ // Now, TopicProvider is not triggered even when response arrives.
+ EXPECT_CALL(*mock, ParseResponse(testing::_)).Times(0);
+
+ test_url_loader_factory_.SimulateResponseForPendingRequest(kFooUrl.spec(),
+ "response");
+}
+
+TEST_F(AdblockTelemetryServiceTest, NegativeTimeToNextRequest) {
+ request_throttle_.OverrideDelayImmediatelyForTesting();
+ EXPECT_CALL(filtering_configuration_, IsEnabled())
+ .WillRepeatedly(Return(true));
+
+ auto* mock = RegisterFooMock();
+ // TopicProvider returns a negative time to next request, as if the request
+ // was supposed to happen in the past. This is a normal scenario, ex. when
+ // the browser was shut down for longer than the duration of request interval.
+ EXPECT_CALL(*mock, GetTimeOfNextRequest())
+ .WillOnce(testing::Return(base::Time::Now() - base::Seconds(30)));
+
+ telemetry_service_->Start();
+ // Request is made:
+ ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+ ExpectFooMadeRequest(0);
+}
+
+TEST_F(AdblockTelemetryServiceTest, CollectDebugInfoFromProviders) {
+ auto* foo_mock = RegisterFooMock();
+ auto* bar_mock = RegisterBarMock();
+ base::MockCallback<AdblockTelemetryService::TopicProvidersDebugInfoCallback>
+ service_callback;
+
+ // foo_mock provides the debug info synchronously.
+ EXPECT_CALL(*foo_mock, FetchDebugInfo(testing::_))
+ .WillOnce([](MockTopicProvider::DebugInfoCallback callback) {
+ std::move(callback).Run("foo_debug_info");
+ });
+
+ // bar_mock provides the debug info asynchronously, after foo_mock.
+ AdblockTelemetryService::TopicProvider::DebugInfoCallback bar_callback;
+ EXPECT_CALL(*bar_mock, FetchDebugInfo(testing::_))
+ .WillOnce([&bar_callback](MockTopicProvider::DebugInfoCallback callback) {
+ bar_callback = std::move(callback);
+ });
+
+ // The service callback is called only after both providers have provided
+ // their debug info.
+ // Not yet:
+ EXPECT_CALL(service_callback, Run(testing::_)).Times(0);
+ telemetry_service_->GetTopicProvidersDebugInfo(service_callback.Get());
+
+ // Now bar_mock provides its debug info.
+ EXPECT_CALL(service_callback,
+ Run(testing::ElementsAre("foo_debug_info", "bar_debug_info")));
+ std::move(bar_callback).Run("bar_debug_info");
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/test/bundled_subscription_test.cc b/components/adblock/core/test/bundled_subscription_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/bundled_subscription_test.cc
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <memory>
+
+#include "components/adblock/core/common/adblock_utils.h"
+#include "components/adblock/core/common/flatbuffer_data.h"
+#include "components/adblock/core/resources/grit/adblock_resources.h"
+#include "components/adblock/core/schema/filter_list_schema_generated.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace adblock {
+
+TEST(AdblockBundledSubscriptionTest, EasyListIsValid) {
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EASYLIST);
+ flatbuffers::Verifier verifier(buffer->data(), buffer->size());
+ EXPECT_TRUE(flat::VerifySubscriptionBuffer(verifier));
+}
+
+TEST(AdblockBundledSubscriptionTest, ExceptionrulesIsValid) {
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES);
+ flatbuffers::Verifier verifier(buffer->data(), buffer->size());
+ EXPECT_TRUE(flat::VerifySubscriptionBuffer(verifier));
+}
+
+TEST(AdblockBundledSubscriptionTest, AnticvIsValid) {
+ auto buffer = utils::MakeFlatbufferDataFromResourceBundle(
+ IDR_ADBLOCK_FLATBUFFER_ANTICV);
+ flatbuffers::Verifier verifier(buffer->data(), buffer->size());
+ EXPECT_TRUE(flat::VerifySubscriptionBuffer(verifier));
+}
+
+} // namespace adblock
diff --git a/components/adblock/core/test/mock_sitekey_storage.cc b/components/adblock/core/test/mock_sitekey_storage.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/mock_sitekey_storage.cc
@@ -0,0 +1,26 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/test/mock_sitekey_storage.h"
+
+namespace adblock {
+
+MockSitekeyStorage::MockSitekeyStorage() = default;
+
+MockSitekeyStorage::~MockSitekeyStorage() = default;
+
+} // namespace adblock
diff --git a/components/adblock/core/test/mock_sitekey_storage.h b/components/adblock/core/test/mock_sitekey_storage.h
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/mock_sitekey_storage.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPONENTS_ADBLOCK_CORE_TEST_MOCK_SITEKEY_STORAGE_H_
+#define COMPONENTS_ADBLOCK_CORE_TEST_MOCK_SITEKEY_STORAGE_H_
+
+#include <string>
+#include <vector>
+
+#include "components/adblock/core/sitekey_storage.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace adblock {
+
+class MockSitekeyStorage : public SitekeyStorage {
+ public:
+ MockSitekeyStorage();
+ ~MockSitekeyStorage() override;
+ MOCK_METHOD(void,
+ ProcessResponseHeaders,
+ (const GURL& request_url,
+ const scoped_refptr<net::HttpResponseHeaders>& headers,
+ const std::string& user_agent),
+ (override));
+
+ MOCK_METHOD((absl::optional<std::pair<GURL, SiteKey>>),
+ FindSiteKeyForAnyUrl,
+ (const std::vector<GURL>& urls),
+ (const, override));
+};
+
+} // namespace adblock
+
+#endif // COMPONENTS_ADBLOCK_CORE_TEST_MOCK_SITEKEY_STORAGE_H_
diff --git a/components/adblock/core/test/sitekey_storage_impl_test.cc b/components/adblock/core/test/sitekey_storage_impl_test.cc
new file mode 100644
--- /dev/null
+++ b/components/adblock/core/test/sitekey_storage_impl_test.cc
@@ -0,0 +1,155 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "components/adblock/core/sitekey_storage_impl.h"
+
+#include "components/adblock/core/common/adblock_constants.h"
+#include "gtest/gtest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+static constexpr char test_uri[] =
+ "/info/"
+ "Liquidit%C3%A4t.html?ses="
+ "Y3JlPTEzNTUyNDE2OTImdGNpZD13d3cuYWZmaWxpbmV0LXZlcnplaWNobmlzLmRlNTB"
+ "jNjAwNzIyNTlkNjQuNDA2MjE2MTImZmtpPTcyOTU2NiZ0YXNrPXNlYXJjaCZkb21haW49Y"
+ "WZmaWxpbmV0LXZlcnplaWNobmlzL"
+ "mRlJnM9ZGZmM2U5MTEzZGNhMWYyMWEwNDcmbGFuZ3VhZ2U9ZGUmYV9pZD0yJmtleXdvcmQ"
+ "9TGlxdWlkaXQlQzMlQTR0JnBvcz0"
+ "yJmt3cz03Jmt3c2k9OA==&token=AG06ipCV1LptGtY_"
+ "9gFnr0vBTPy4O0YTvwoTCObJ3N3ckrQCFYIA3wod2TwAjxgAIABQv5"
+ "WiAlCH8qgOUJGr9g9QmuuEG1CDnK0pUPbRrk5QhqDgkQNxP4Qqhz9xZe4";
+static constexpr char test_host[] = "http://www.affilinet-verzeichnis.de";
+static constexpr char test_user_agent[] =
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.21 (KHTML, like "
+ "Gecko) Chrome/25.0.1349.2 Safari/537.21";
+static constexpr char test_public_key[] =
+ "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANnylWw2vLY4hUn9w06zQKbhKBfvjFUC"
+ "sdFlb6TdQhxb9RXWXuI4t31c+o8fYOv/s8q1LGP"
+ "ga3DE1L/tHU4LENMCAwEAAQ==";
+static constexpr char not_the_right_public_key[] =
+ "GSwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANnylWw2vLY4hUn9w06zQKbhKBfvjFUC"
+ "sdFos6TdQhxb9RXWXuI4t31c+o8fYOv/s8q1LGP"
+ "ga3DE1L/tHU4LENMCAwEAAQ==";
+static constexpr char test_signature[] =
+ "nLH8Vbc1rzmy0Q+Xg+bvm43IEO42h8rq5D9C0WCn/Y3ykgAoV4npzm7eMlqBSwZBLA/"
+ "0DuuVsfTJT9MOVaurcA==";
+} // namespace
+
+namespace adblock {
+
+class AdblockSitekeyStorageFlatbufferTest : public testing::Test {
+ public:
+ AdblockSitekeyStorageFlatbufferTest() = default;
+
+ void SetUp() override { storage_ = std::make_unique<SitekeyStorageImpl>(); }
+
+ void ProcessResponseHeader(const std::string& public_key,
+ const std::string& signature,
+ const std::string& host,
+ const std::string& uri,
+ const std::string& user_agent) {
+ std::string sitekey = public_key + "_" + signature;
+ auto headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
+ headers->SetHeader(adblock::kSiteKeyHeaderKey, sitekey);
+
+ GURL url = GURL{host + uri};
+ storage_->ProcessResponseHeaders(url, headers, user_agent);
+ }
+
+ GURL test_url{std::string(test_host) + std::string(test_uri)};
+ std::unique_ptr<SitekeyStorageImpl> storage_;
+};
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, NoUserAgentInfo) {
+ ProcessResponseHeader(test_public_key, test_signature, test_host, test_uri,
+ "");
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, NoSiteKeyHeader) {
+ auto headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
+
+ storage_->ProcessResponseHeaders(test_url, headers, test_user_agent);
+
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, WrongFormatOfSitekeyHeader) {
+ std::string sitekey = std::string(test_public_key) + "NotAnUnderscore" +
+ std::string(test_signature);
+ auto headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
+ headers->SetHeader(adblock::kSiteKeyHeaderKey, sitekey);
+
+ storage_->ProcessResponseHeaders(test_url, headers, test_user_agent);
+
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, InvalidSitekeyPublicKeyNotB64) {
+ ProcessResponseHeader("NotAB64EncodedKey", test_signature, test_host,
+ test_uri, test_user_agent);
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, InvalidSitekeySignatureNotB64) {
+ ProcessResponseHeader(test_public_key, "NotAB64EncodedSignature", test_host,
+ test_uri, test_user_agent);
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, InvalidSitekeyCannotVerify) {
+ ProcessResponseHeader(not_the_right_public_key, test_signature, test_host,
+ test_uri, test_user_agent);
+ EXPECT_FALSE(storage_->FindSiteKeyForAnyUrl({test_url}).has_value());
+}
+
+TEST_F(AdblockSitekeyStorageFlatbufferTest, ValidSignature) {
+ ProcessResponseHeader(test_public_key, test_signature, test_host, test_uri,
+ test_user_agent);
+ std::pair<GURL, SiteKey> expected{
+ GURL{test_url},
+ SiteKey{std::string(test_public_key)
+ .substr(0, std::string(test_public_key).find("=="))}};
+ EXPECT_EQ(expected, storage_->FindSiteKeyForAnyUrl({test_url}).value());
+}
+
+// DPD-1912: This test works with Base64DecodePolicy::kForgiving and fails
+// with Base64DecodePolicy::kStrict. Uses data from a real page.
+TEST_F(AdblockSitekeyStorageFlatbufferTest,
+ SignatureValidWithRelaxedDecodePolicy) {
+ constexpr char public_key[] =
+ "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJRmzcpTevQqkWn6dJuX/N/"
+ "Hxl7YxbOwy8+73ijqYSQEN+WGxrruAKtZtliWC86+ewQ0msW1W8psOFL/b00zWqsCAwEAAQ";
+ constexpr char signature[] =
+ "kJCR9f/Vb/"
+ "NJwTNvLcnLVpM82hW+"
+ "6DiMmiBNLSpCBF0LuCTQ0LQZzUaNf5RK8TPaRZKOpTahrMY0B1fCr82MwA";
+ constexpr char host[] = "http://skillpanda.com";
+ constexpr char uri[] = "/";
+ constexpr char user_agent[] =
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/114.0.0.0 Safari/537.36";
+ auto url = GURL{std::string(host) + std::string(uri)};
+ ProcessResponseHeader(public_key, signature, host, uri, user_agent);
+ std::pair<GURL, SiteKey> expected{
+ GURL{url}, SiteKey{std::string(public_key)
+ .substr(0, std::string(public_key).find("=="))}};
+ EXPECT_EQ(expected, storage_->FindSiteKeyForAnyUrl({url}).value());
+}
+
+} // namespace adblock
diff --git a/components/adblock/features.gni b/components/adblock/features.gni
new file mode 100644
--- /dev/null
+++ b/components/adblock/features.gni
@@ -0,0 +1,51 @@
+#
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+declare_args() {
+ # eyeo Chromium SDK telemetry client id, provided on per-partner basis by eyeo. Used to
+ # attribute usage reports to specific browsers.
+ eyeo_telemetry_client_id = ""
+
+ # eyeo Chromium SDK telemetry server address, by default evaluated to
+ # "https://${eyeo_telemetry_client_id}.telemetry.eyeo.com/".
+ # Override only for testing.
+ eyeo_telemetry_server_url = ""
+
+ # eyeo Chromium SDK telemetry authentication token, provided on per-partner basis by eyeo.
+ eyeo_telemetry_activeping_auth_token = ""
+
+ # eyeo Chromium SDK application name to be used in telemetry and
+ # filter list download requests. If not set the value returned by
+ # version_info::GetProductName() will be used instead.
+ eyeo_application_name = ""
+
+ # eyeo Chromium SDK application version to be used in telemetry and
+ # filter list download requests. If not set the value returned by
+ # version_info::GetVersionNumber() will be used instead.
+ eyeo_application_version = ""
+
+ # If true then requests to "test.data" domain will be intercepted in order to display
+ # or change eyeo Chromium SDK settings. When feature is enabled type "test.data" in the
+ # navigation bar to prompt for help.
+ # This feature is used mainly for automated testing (see DPD-1407).
+ eyeo_intercept_debug_url = false
+
+ # If true then eyeo filtering is disabled by default (applies to 1st run scenario).
+ eyeo_disable_filtering_by_default = false
+
+ # If true then acceptable ads is disabled by default (applies to 1st run scenario).
+ eyeo_disable_aa_by_default = false
+}
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#include "content/browser/loader/navigation_url_loader_impl.h"
#include <map>
@@ -1254,7 +1258,7 @@ void NavigationURLLoaderImpl::OnReceiveRedirect(
LogQueueTimeHistogram("Navigation.QueueTime.OnReceiveRedirect",
resource_request_->is_outermost_main_frame);
net::Error error = net::OK;
- if (!bypass_redirect_checks_ &&
+ if (!bypass_redirect_checks_ && !redirect_info.bypass_redirect_checks &&
!IsSafeRedirectTarget(url_, redirect_info.new_url)) {
error = net::ERR_UNSAFE_REDIRECT;
} else if (--redirect_limit_ == 0) {
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -1,6 +1,10 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -3513,6 +3517,12 @@ void RenderFrameHostImpl::ExecuteJavaScriptMethod(
std::move(callback));
}
+// https://gitlab.com/eyeo/adblockplus/chromium/issues/35
+void RenderFrameHostImpl::InsertAbpElemhideStylesheet(
+ const std::string& stylesheet) {
+ GetAssociatedLocalFrame()->InsertAbpElemhideStylesheet(stylesheet);
+}
+
void RenderFrameHostImpl::ExecuteJavaScript(const std::u16string& javascript,
JavaScriptResultCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1,6 +1,10 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_
#define CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_IMPL_H_
@@ -499,6 +503,10 @@ class CONTENT_EXPORT RenderFrameHostImpl
const std::u16string& method_name,
base::Value::List arguments,
JavaScriptResultCallback callback) override;
+
+ // https://gitlab.com/eyeo/adblockplus/chromium/issues/35
+ void InsertAbpElemhideStylesheet(const std::string& stylesheet) override;
+
void ExecuteJavaScript(const std::u16string& javascript,
JavaScriptResultCallback callback) override;
void ExecuteJavaScriptInIsolatedWorld(const std::u16string& javascript,
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -1,6 +1,10 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_H_
#define CONTENT_PUBLIC_BROWSER_RENDER_FRAME_HOST_H_
@@ -551,6 +555,9 @@ class CONTENT_EXPORT RenderFrameHost : public IPC::Listener,
virtual void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
const std::string& message) = 0;
+ // https://gitlab.com/eyeo/adblockplus/chromium/issues/35
+ virtual void InsertAbpElemhideStylesheet(const std::string& stylesheet) = 0;
+
// Functions to run JavaScript in this frame's context. Pass in a callback to
// receive a result when it is available. If there is no need to receive the
// result, pass in a default-constructed callback. If provided, the callback
diff --git a/content/public/common/isolated_world_ids.h b/content/public/common/isolated_world_ids.h
--- a/content/public/common/isolated_world_ids.h
+++ b/content/public/common/isolated_world_ids.h
@@ -1,6 +1,10 @@
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef CONTENT_PUBLIC_COMMON_ISOLATED_WORLD_IDS_H_
#define CONTENT_PUBLIC_COMMON_ISOLATED_WORLD_IDS_H_
@@ -15,6 +19,10 @@ enum IsolatedWorldIDs : int32_t {
// The main world. Chrome cannot use ID 0 for an isolated world because 0
// represents the main world.
ISOLATED_WORLD_ID_GLOBAL = 0,
+
+ // Isolated world for eyeo ad blocking (element hiding)
+ ISOLATED_WORLD_ID_ADBLOCK,
+
// Custom isolated world ids used by other embedders should start from here.
ISOLATED_WORLD_ID_CONTENT_END,
// If any embedder has more than 10 custom isolated worlds that will be run
diff --git a/content/public/test/fake_local_frame.cc b/content/public/test/fake_local_frame.cc
--- a/content/public/test/fake_local_frame.cc
+++ b/content/public/test/fake_local_frame.cc
@@ -1,6 +1,10 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#include "content/public/test/fake_local_frame.h"
@@ -99,6 +103,9 @@ void FakeLocalFrame::RequestVideoFrameAtWithBoundsHint(
void FakeLocalFrame::PluginActionAt(const gfx::Point& location,
blink::mojom::PluginActionType action) {}
+void FakeLocalFrame::InsertAbpElemhideStylesheet(
+ const std::string& stylesheet) {}
+
void FakeLocalFrame::AdvanceFocusInFrame(
blink::mojom::FocusType focus_type,
const std::optional<blink::RemoteFrameToken>& source_frame_token) {}
diff --git a/content/public/test/fake_local_frame.h b/content/public/test/fake_local_frame.h
--- a/content/public/test/fake_local_frame.h
+++ b/content/public/test/fake_local_frame.h
@@ -1,6 +1,10 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef CONTENT_PUBLIC_TEST_FAKE_LOCAL_FRAME_H_
#define CONTENT_PUBLIC_TEST_FAKE_LOCAL_FRAME_H_
@@ -80,6 +84,7 @@ class FakeLocalFrame : public blink::mojom::LocalFrame {
RequestVideoFrameAtWithBoundsHintCallback callback) override;
void PluginActionAt(const gfx::Point& location,
blink::mojom::PluginActionType action) override;
+ void InsertAbpElemhideStylesheet(const std::string& stylesheet) override;
void AdvanceFocusInFrame(blink::mojom::FocusType focus_type,
const std::optional<blink::RemoteFrameToken>&
source_frame_token) override;
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -1,6 +1,9 @@
# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+#
+# This source code is a part of eyeo Chromium SDK.
+# Use of this source code is governed by the GPLv3 that can be found in the components/adblock/LICENSE file.
import("//build/config/cast.gni")
import("//build/config/chromeos/ui_mode.gni")
@@ -162,6 +165,10 @@ inspector_protocol_generate("protocol_sources") {
static_library("content_shell_lib") {
testonly = true
sources = [
+ "browser/adblock/adblock_shell_browser_context.cc",
+ "browser/adblock/adblock_shell_browser_context.h",
+ "browser/adblock/adblock_shell_content_browser_client.cc",
+ "browser/adblock/adblock_shell_content_browser_client.h",
"browser/protocol/browser_handler.cc",
"browser/protocol/browser_handler.h",
"browser/protocol/domain_handler.h",
@@ -231,6 +238,7 @@ static_library("content_shell_lib") {
"browser/shell_platform_delegate_android.cc",
"browser/shell_web_contents_view_delegate_android.cc",
]
+
}
if (is_mac) {
@@ -312,6 +320,8 @@ static_library("content_shell_lib") {
"//base",
"//base:base_static",
"//cc/base",
+ "//components/adblock/content:browser",
+ "//components/adblock/core/common",
"//components/cdm/renderer",
"//components/custom_handlers",
"//components/custom_handlers:test_support",
@@ -332,6 +342,7 @@ static_library("content_shell_lib") {
"//components/prefs",
"//components/services/storage/test_api",
"//components/url_formatter",
+ "//components/user_prefs:user_prefs",
"//components/variations",
"//components/variations/service",
"//components/web_cache/renderer",
@@ -424,6 +435,7 @@ static_library("content_shell_lib") {
"//mojo/public/java/system:test_support",
"//ui/android",
]
+
}
if (shell_use_toolkit_views) {
@@ -515,6 +527,8 @@ repack("pak") {
sources = [
"$root_gen_dir/base/tracing/protos/tracing_proto_resources.pak",
+ "$root_gen_dir/components/adblock/core/resources/adblock_resources.pak",
+ "$root_gen_dir/components/adblock_internals_resources.pak",
"$root_gen_dir/components/ukm_resources.pak",
"$root_gen_dir/content/attribution_internals_resources.pak",
"$root_gen_dir/content/browser/resources/media/media_internals_resources.pak",
@@ -547,6 +561,8 @@ repack("pak") {
deps = [
":resources",
"//base/tracing/protos:chrome_track_event_resources",
+ "//components/adblock/content/resources/adblock_internals:resources",
+ "//components/adblock/core/resources:adblock_resources",
"//components/ukm/debug:resources",
"//content:content_resources",
"//content/browser/resources:resources",
diff --git a/content/shell/app/shell_main_delegate.cc b/content/shell/app/shell_main_delegate.cc
--- a/content/shell/app/shell_main_delegate.cc
+++ b/content/shell/app/shell_main_delegate.cc
@@ -1,6 +1,10 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#include "content/shell/app/shell_main_delegate.h"
@@ -32,7 +36,7 @@
#include "content/public/common/main_function_params.h"
#include "content/public/common/url_constants.h"
#include "content/shell/app/shell_crash_reporter_client.h"
-#include "content/shell/browser/shell_content_browser_client.h"
+#include "content/shell/browser/adblock/adblock_shell_content_browser_client.h"
#include "content/shell/common/shell_content_client.h"
#include "content/shell/common/shell_paths.h"
#include "content/shell/common/shell_switches.h"
@@ -447,7 +451,7 @@ ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
return browser_client_.get();
}
#endif
- browser_client_ = std::make_unique<ShellContentBrowserClient>();
+ browser_client_ = std::make_unique<AdblockShellContentBrowserClient>();
return browser_client_.get();
}
diff --git a/content/shell/browser/adblock/adblock_shell_browser_context.cc b/content/shell/browser/adblock/adblock_shell_browser_context.cc
new file mode 100644
--- /dev/null
+++ b/content/shell/browser/adblock/adblock_shell_browser_context.cc
@@ -0,0 +1,69 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "content/shell/browser/adblock/adblock_shell_browser_context.h"
+
+#include "components/adblock/core/common/adblock_prefs.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/in_memory_pref_store.h"
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_name_set.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service_factory.h"
+#include "components/prefs/segregated_pref_store.h"
+#include "components/user_prefs/user_prefs.h"
+
+namespace content {
+
+AdblockShellBrowserContext::AdblockShellBrowserContext()
+ : ShellBrowserContext(false) {
+ CreateUserPrefService();
+}
+
+AdblockShellBrowserContext::~AdblockShellBrowserContext() {}
+
+bool AdblockShellBrowserContext::IsOffTheRecord() {
+ // Adblock services should not be created for off-the-record contexts
+ return false;
+}
+
+void AdblockShellBrowserContext::CreateUserPrefService() {
+ auto pref_registry = base::MakeRefCounted<user_prefs::PrefRegistrySyncable>();
+
+ adblock::common::prefs::RegisterProfilePrefs(pref_registry.get());
+
+ PrefServiceFactory pref_service_factory;
+
+ PrefNameSet persistent_prefs;
+
+ // These prefs go in the JsonPrefStore, and will persist across runs.
+ for (auto& pref_name : adblock::common::prefs::GetPrefs()) {
+ persistent_prefs.insert(pref_name.data());
+ }
+
+ pref_service_factory.set_user_prefs(base::MakeRefCounted<SegregatedPrefStore>(
+ base::MakeRefCounted<InMemoryPrefStore>(),
+ base::MakeRefCounted<JsonPrefStore>(
+ GetPath().Append(FILE_PATH_LITERAL("Preferences"))),
+ std::move(persistent_prefs)));
+
+ user_pref_service_ = pref_service_factory.Create(pref_registry);
+
+ user_prefs::UserPrefs::Set(this, user_pref_service_.get());
+}
+
+} // namespace content
diff --git a/content/shell/browser/adblock/adblock_shell_browser_context.h b/content/shell/browser/adblock/adblock_shell_browser_context.h
new file mode 100644
--- /dev/null
+++ b/content/shell/browser/adblock/adblock_shell_browser_context.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_BROWSER_CONTEXT_H_
+#define CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_BROWSER_CONTEXT_H_
+
+#include "content/shell/browser/shell_browser_context.h"
+
+namespace content {
+
+class AdblockShellBrowserContext : public ShellBrowserContext {
+ public:
+ AdblockShellBrowserContext();
+ ~AdblockShellBrowserContext() override;
+
+ bool IsOffTheRecord() override;
+
+ private:
+ void CreateUserPrefService();
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_BROWSER_CONTEXT_H_
diff --git a/content/shell/browser/adblock/adblock_shell_content_browser_client.cc b/content/shell/browser/adblock/adblock_shell_content_browser_client.cc
new file mode 100644
--- /dev/null
+++ b/content/shell/browser/adblock/adblock_shell_content_browser_client.cc
@@ -0,0 +1,30 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "content/shell/browser/adblock/adblock_shell_content_browser_client.h"
+
+#include "content/shell/browser/shell_browser_context.h"
+
+namespace content {
+
+content::BrowserContext*
+AdblockShellContentBrowserClient::GetBrowserContextForEyeoFactories(
+ content::BrowserContext* /*current_browser_context*/) {
+ return browser_context();
+}
+
+} // namespace content
diff --git a/content/shell/browser/adblock/adblock_shell_content_browser_client.h b/content/shell/browser/adblock/adblock_shell_content_browser_client.h
new file mode 100644
--- /dev/null
+++ b/content/shell/browser/adblock/adblock_shell_content_browser_client.h
@@ -0,0 +1,35 @@
+/*
+ * This file is part of eyeo Chromium SDK,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_CONTENT_BROWSER_CLIENT_H_
+#define CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_CONTENT_BROWSER_CLIENT_H_
+
+#include "components/adblock/content/browser/adblock_content_browser_client.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+
+namespace content {
+
+class AdblockShellContentBrowserClient
+ : public adblock::AdblockContentBrowserClient<ShellContentBrowserClient> {
+ private:
+ content::BrowserContext* GetBrowserContextForEyeoFactories(
+ content::BrowserContext* current_browser_context) override;
+};
+
+} // namespace content
+
+#endif // CONTENT_SHELL_BROWSER_ADBLOCK_ADBLOCK_SHELL_CONTENT_BROWSER_CLIENT_H_
diff --git a/content/shell/browser/shell.cc b/content/shell/browser/shell.cc
--- a/content/shell/browser/shell.cc
+++ b/content/shell/browser/shell.cc
@@ -25,6 +25,8 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
+#include "components/adblock/content/browser/adblock_webcontents_observer.h"
+#include "components/adblock/content/browser/factories/embedding_utils.h"
#include "components/custom_handlers/protocol_handler.h"
#include "components/custom_handlers/protocol_handler_registry.h"
#include "components/custom_handlers/simple_protocol_handler_registry_factory.h"
@@ -44,6 +46,7 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/shell/app/resource.h"
+#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/shell/browser/shell_devtools_frontend.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h"
@@ -89,6 +92,12 @@ Shell::Shell(std::unique_ptr<WebContents> web_contents,
windows_.push_back(this);
+ content::BrowserContext* browser_context =
+ ShellContentBrowserClient::Get()->browser_context();
+ adblock::EnsureBackgroundServicesStarted(browser_context);
+ adblock::RegisterAdblockWebContentObserver<
+ adblock::AdblockWebContentObserver>(web_contents_.get(), browser_context);
+
if (shell_created_callback_)
std::move(shell_created_callback_).Run(this);
}
diff --git a/content/shell/browser/shell_browser_context.h b/content/shell/browser/shell_browser_context.h
--- a/content/shell/browser/shell_browser_context.h
+++ b/content/shell/browser/shell_browser_context.h
@@ -1,6 +1,10 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef CONTENT_SHELL_BROWSER_SHELL_BROWSER_CONTEXT_H_
#define CONTENT_SHELL_BROWSER_SHELL_BROWSER_CONTEXT_H_
@@ -9,6 +13,7 @@
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
+#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
class SimpleFactoryKey;
@@ -76,6 +81,8 @@ class ShellBrowserContext : public BrowserContext {
std::unique_ptr<OriginTrialsControllerDelegate>
origin_trials_controller_delegate_;
+ std::unique_ptr<PrefService> user_pref_service_;
+
private:
// Performs initialization of the ShellBrowserContext while IO is still
// allowed on the current thread.
diff --git a/content/shell/browser/shell_browser_main_parts.cc b/content/shell/browser/shell_browser_main_parts.cc
--- a/content/shell/browser/shell_browser_main_parts.cc
+++ b/content/shell/browser/shell_browser_main_parts.cc
@@ -1,6 +1,10 @@
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#include "content/shell/browser/shell_browser_main_parts.h"
@@ -20,6 +24,7 @@
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
+#include "components/adblock/content/browser/adblock_web_ui_controller_factory.h"
#include "components/performance_manager/embedder/graph_features.h"
#include "components/performance_manager/embedder/performance_manager_lifetime.h"
#include "content/public/browser/browser_thread.h"
@@ -30,6 +35,7 @@
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "content/shell/android/shell_descriptors.h"
+#include "content/shell/browser/adblock/adblock_shell_browser_context.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_devtools_manager_delegate.h"
@@ -138,7 +144,7 @@ int ShellBrowserMainParts::PreEarlyInitialization() {
}
void ShellBrowserMainParts::InitializeBrowserContexts() {
- set_browser_context(new ShellBrowserContext(false));
+ set_browser_context(new AdblockShellBrowserContext());
set_off_the_record_browser_context(new ShellBrowserContext(true));
// Persistent Origin Trials needs to be instantiated as soon as possible
// during browser startup, to ensure data is available prior to the first
@@ -186,6 +192,8 @@ int ShellBrowserMainParts::PreMainMessageLoopRun() {
#endif
InitializeBrowserContexts();
+ content::WebUIControllerFactory::RegisterFactory(
+ adblock::AdblockWebUIControllerFactory::GetInstance());
Shell::Initialize(CreateShellPlatformDelegate());
net::NetModule::SetResourceProvider(PlatformResourceProvider);
ShellDevToolsManagerDelegate::StartHttpHandler(browser_context_.get());
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#include "extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h"
#include <memory>
@@ -452,6 +456,7 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect(
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
if (redirect_url_ != redirect_info.new_url &&
+ !redirect_info.bypass_redirect_checks &&
!IsRedirectSafe(request_.url, redirect_info.new_url,
info_->is_navigation_request)) {
OnNetworkError(CreateURLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT));
diff --git a/net/url_request/redirect_info.h b/net/url_request/redirect_info.h
--- a/net/url_request/redirect_info.h
+++ b/net/url_request/redirect_info.h
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#ifndef NET_URL_REQUEST_REDIRECT_INFO_H_
#define NET_URL_REQUEST_REDIRECT_INFO_H_
@@ -83,6 +87,10 @@ struct NET_EXPORT RedirectInfo {
// True if this is a redirect from Signed Exchange to its fallback URL.
bool is_signed_exchange_fallback_redirect = false;
+ // True if this is redirect done by rewrite filter (adblocking). This allows
+ // redirecting ads served over https into empty media through data:// URLs.
+ bool bypass_redirect_checks = false;
+
// The new referrer policy that should be obeyed if there are
// subsequent redirects.
ReferrerPolicy new_referrer_policy =
diff --git a/services/network/public/cpp/net_ipc_param_traits.h b/services/network/public/cpp/net_ipc_param_traits.h
--- a/services/network/public/cpp/net_ipc_param_traits.h
+++ b/services/network/public/cpp/net_ipc_param_traits.h
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#ifndef SERVICES_NETWORK_PUBLIC_CPP_NET_IPC_PARAM_TRAITS_H_
#define SERVICES_NETWORK_PUBLIC_CPP_NET_IPC_PARAM_TRAITS_H_
@@ -275,6 +279,7 @@ IPC_STRUCT_TRAITS_BEGIN(net::RedirectInfo)
IPC_STRUCT_TRAITS_MEMBER(new_referrer)
IPC_STRUCT_TRAITS_MEMBER(insecure_scheme_was_upgraded)
IPC_STRUCT_TRAITS_MEMBER(is_signed_exchange_fallback_redirect)
+ IPC_STRUCT_TRAITS_MEMBER(bypass_redirect_checks)
IPC_STRUCT_TRAITS_MEMBER(new_referrer_policy)
IPC_STRUCT_TRAITS_MEMBER(critical_ch_restart_time)
IPC_STRUCT_TRAITS_END()
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -1,6 +1,10 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the components/adblock/LICENSE file.
+
module blink.mojom;
@@ -979,6 +983,10 @@ interface LocalFrame {
// the given point in the view coordinate space.
PluginActionAt(gfx.mojom.Point location, blink.mojom.PluginActionType action);
+ // https://gitlab.com/eyeo/adblockplus/chromium/issues/35
+ // Request for the renderer to insert user stylesheet.
+ InsertAbpElemhideStylesheet(string stylesheet);
+
// Request to continue running the sequential focus navigation algorithm in
// this frame. |source_frame_token| identifies the frame that issued this
// request. This message is sent when finding the next focusable element would
diff --git a/third_party/blink/public/web/web_document.h b/third_party/blink/public/web/web_document.h
--- a/third_party/blink/public/web/web_document.h
+++ b/third_party/blink/public/web/web_document.h
@@ -28,6 +28,10 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_DOCUMENT_H_
#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_DOCUMENT_H_
@@ -151,6 +155,14 @@ class BLINK_EXPORT WebDocument : public WebNode {
WebCssOrigin = WebCssOrigin::kAuthor,
BackForwardCacheAware = BackForwardCacheAware::kAllow);
+ // Inserts the given CSS source code as a style sheet in the document and
+ // validates it have only expected rules.
+ WebStyleSheetKey InsertAbpElemhideStylesheet(
+ const WebString& source_code,
+ const WebStyleSheetKey* = nullptr,
+ WebCssOrigin = WebCssOrigin::kAuthor,
+ BackForwardCacheAware = BackForwardCacheAware::kAllow);
+
// Removes the CSS which was previously inserted by a call to
// InsertStyleSheet().
void RemoveInsertedStyleSheet(const WebStyleSheetKey&,
diff --git a/third_party/blink/renderer/core/dom/events/event_target.cc b/third_party/blink/renderer/core/dom/events/event_target.cc
--- a/third_party/blink/renderer/core/dom/events/event_target.cc
+++ b/third_party/blink/renderer/core/dom/events/event_target.cc
@@ -29,6 +29,10 @@
*
*/
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include <memory>
@@ -882,7 +886,10 @@ bool EventTarget::dispatchEventForBindings(Event* event,
if (!GetExecutionContext())
return false;
- event->SetTrusted(false);
+ auto* world = GetExecutionContext()->GetCurrentWorld();
+ // content::IsolatedWorldIDs::ISOLATED_WORLD_ID_ADBLOCK == 1
+ bool make_trusted = world && (world->GetWorldId() == 1);
+ event->SetTrusted(make_trusted);
// Return whether the event was cancelled or not to JS not that it
// might have actually been default handled; so check only against
diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_party/blink/renderer/core/exported/web_document.cc
--- a/third_party/blink/renderer/core/exported/web_document.cc
+++ b/third_party/blink/renderer/core/exported/web_document.cc
@@ -276,6 +276,56 @@ WebStyleSheetKey WebDocument::InsertStyleSheet(
return injection_key;
}
+bool IsValidAbpRule(StyleRuleBase* rule) {
+ if (!rule->IsStyleRule()) {
+ return false;
+ }
+ const auto& props = blink::To<StyleRule>(rule)->Properties();
+ if (props.PropertyCount() != 1) {
+ return false;
+ }
+ const auto& ref = props.PropertyAt(0);
+ return ref.PropertyID() == CSSPropertyID::kDisplay && ref.IsImportant();
+}
+
+// Should be same as WebDocument::InsertStyleSheet, excluding content
+// validation.
+WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet(
+ const WebString& source_code,
+ const WebStyleSheetKey* key,
+ WebCssOrigin origin,
+ BackForwardCacheAware back_forward_cache_aware) {
+ Document* document = Unwrap<Document>();
+ DCHECK(document);
+
+ auto* parsed_sheet = MakeGarbageCollected<StyleSheetContents>(
+ MakeGarbageCollected<CSSParserContext>(*document));
+ parsed_sheet->ParseString(source_code);
+ // Rule count is not validated because some selectors can be malformed for
+ // third-party lists. Checking body is valid for all the rules is enough.
+ for (unsigned n = 0; n < parsed_sheet->RuleCount();) {
+ if (IsValidAbpRule(parsed_sheet->RuleAt(n))) {
+ ++n;
+ } else {
+ parsed_sheet->StartMutation();
+ parsed_sheet->WrapperDeleteRule(n);
+ LOG(WARNING) << "[eyeo] Broken rule";
+ }
+ }
+
+ if (back_forward_cache_aware == BackForwardCacheAware::kPossiblyDisallow) {
+ document->GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
+ SchedulingPolicy::Feature::kInjectedStyleSheet,
+ {SchedulingPolicy::DisableBackForwardCache()});
+ }
+
+ const WebStyleSheetKey& injection_key =
+ key && !key->IsNull() ? *key : GenerateStyleSheetKey();
+ DCHECK(!injection_key.IsEmpty());
+ document->GetStyleEngine().InjectSheet(injection_key, parsed_sheet, origin);
+ return injection_key;
+}
+
void WebDocument::RemoveInsertedStyleSheet(const WebStyleSheetKey& key,
WebCssOrigin origin) {
Unwrap<Document>()->GetStyleEngine().RemoveInjectedSheet(key, origin);
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -1,6 +1,10 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#include "third_party/blink/renderer/core/frame/local_frame_mojo_handler.h"
@@ -350,6 +354,14 @@ LocalFrameMojoHandler::LocalFrameMojoHandler(blink::LocalFrame& frame)
WrapWeakPersistent(this)));
}
+void LocalFrameMojoHandler::InsertAbpElemhideStylesheet(
+ const WTF::String& stylesheet) {
+ WebLocalFrameImpl* web_frame = WebLocalFrameImpl::FromFrame(frame_);
+ DCHECK(web_frame);
+ web_frame->GetDocument().InsertAbpElemhideStylesheet(stylesheet, nullptr,
+ WebCssOrigin::kUser);
+}
+
void LocalFrameMojoHandler::Trace(Visitor* visitor) const {
visitor->Trace(frame_);
visitor->Trace(back_forward_cache_controller_host_remote_);
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h
@@ -1,6 +1,10 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+//
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_MOJO_HANDLER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_MOJO_HANDLER_H_
@@ -114,6 +118,7 @@ class LocalFrameMojoHandler
void AddMessageToConsole(mojom::blink::ConsoleMessageLevel level,
const WTF::String& message,
bool discard_duplicates) final;
+ void InsertAbpElemhideStylesheet(const WTF::String& stylesheet) final;
void SwapInImmediately() final;
void CheckCompleted() final;
void StopLoading() final;
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -23,6 +23,10 @@
*
*/
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#include "third_party/blink/renderer/core/html/html_element.h"
#include "base/containers/enum_set.h"
@@ -2494,7 +2498,13 @@ void HTMLElement::setSpellcheck(bool enable) {
}
void HTMLElement::click() {
- DispatchSimulatedClick(nullptr, SimulatedClickCreationScope::kFromScript);
+ auto* world = GetExecutionContext() ? GetExecutionContext()->GetCurrentWorld()
+ : nullptr;
+ // content::IsolatedWorldIDs::ISOLATED_WORLD_ID_ADBLOCK == 1
+ bool make_trusted = world && (world->GetWorldId() == 1);
+ DispatchSimulatedClick(
+ nullptr, make_trusted ? SimulatedClickCreationScope::kFromUserAgent
+ : SimulatedClickCreationScope::kFromScript);
if (IsA<HTMLInputElement>(this)) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLInputElementSimulatedClick);
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.cc
@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// This source code is a part of eyeo Chromium SDK.
+// Use of this source code is governed by the GPLv3 that can be found in the
+// components/adblock/LICENSE file.
+
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/mojo_url_loader_client.h"
#include <iterator>
@@ -389,7 +393,7 @@ void MojoURLLoaderClient::OnReceiveRedirect(
OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
return;
}
- if (!bypass_redirect_checks_ &&
+ if (!bypass_redirect_checks_ && !redirect_info.bypass_redirect_checks &&
!Platform::Current()->IsRedirectSafe(GURL(last_loaded_url_),
redirect_info.new_url)) {
OnComplete(network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT));
diff --git a/tools/eyeo/generate_interdiffs.sh b/tools/eyeo/generate_interdiffs.sh
new file mode 100755
--- /dev/null
+++ b/tools/eyeo/generate_interdiffs.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
+ echo "
+ This script generates interdiffs between eyeo Chromium SDK releases, git diff
+ file will be saved in `eyeo_modules/` directory, created if needed.
+ No upstream changes included in diff file.
+ Module interdiffs supported since version 115.
+
+ Usage:
+ generate_interdiffs.sh <eyeo-release-tag> <eyeo-release-tag> <options>
+
+ Options:
+ --full-sdk Generates one diff includes whole eyeo Chromium SDK implementation (include testing & CI files)
+ --all-modules Generates diff files for each module: base, chrome, webview, android-api, android-settings and extension-api
+ --base Generates one diff for base module changes
+ --chrome Generates one diff for Chorme Integration module changes
+ --webview Generates one diff for Android Webview Integration module changes
+ --android-api Generates one diff for Android API module changes
+ --android-settings Generates one diff for Android UI module changes
+ --extension-api Generates one diff for Extension API module changes
+"
+ exit
+fi
+
+OLD_TAG=$1
+NEW_TAG=$2
+OPTIONS=${@:3}
+OPTIONS="${OPTIONS/--all-modules/ -- base --chrome --webview --android-api --android-settings --extension-api}"
+
+if [ -z "$OLD_TAG" ] || [ -z "$NEW_TAG" ]; then
+ echo "
+ Missing arguments!
+ Usage:
+ generate_interdiffs.sh <eyeo-release-tag> <eyeo-release-tag> <options>
+ or see help:
+ generate_interdiffs.sh --help
+"
+ exit
+fi
+
+# git has no diff support for binary files
+# also remove any internal CI files
+EXCLUDE_FILES=("components/resources/adblocking/easylist*"
+ "components/resources/adblocking/exceptionrules*"
+ "components/resources/adblocking/anticv*"
+ ".ci-scripts/*"
+ ".gitlab-ci.yml"
+)
+
+for module in $OPTIONS
+do
+ if [ "$module" = "--base" ]; then
+ SUFFIX="-base-module-branch"
+ elif [ "$module" = "--chrome" ]; then
+ SUFFIX="-base-chrome-module-branch"
+ elif [ "$module" = "--android-api" ]; then
+ SUFFIX="-base-chrome-android-api-module-branch"
+ elif [ "$module" = "--android-settings" ]; then
+ SUFFIX="-base-chrome-android-settings-module-branch"
+ elif [ "$module" = "--extension-api" ]; then
+ SUFFIX="-base-chrome-desktop-module-branch"
+ elif [ "$module" = "--webview" ]; then
+ SUFFIX="-base-webview-module-branch"
+ elif [ "$module" = "--full-sdk" ]; then
+ SUFFIX=""
+ else
+ echo "
+ Unknow option: $module
+ Avaiable options:
+ --full-sdk
+ --all-modules
+ --base
+ --chrome
+ --webview
+ --android-api
+ --android-settings
+ --extension-api
+"
+ exit
+ fi
+
+ repo_dir=$(git rev-parse --show-toplevel)
+ mkdir -p $repo_dir/eyeo_modules/interdiffs
+
+ OLD_TAG_HEAD=$(git rev-parse --verify origin/${OLD_TAG}${SUFFIX} 2>/dev/null)
+ OLD_TAG_PREV=$(git rev-parse --verify origin/${OLD_TAG}${SUFFIX}~1 2>/dev/null)
+ NEW_TAG_HEAD=$(git rev-parse --verify origin/${NEW_TAG}${SUFFIX} 2>/dev/null)
+ NEW_TAG_PREV=$(git rev-parse --verify origin/${NEW_TAG}${SUFFIX}~1 2>/dev/null)
+
+ if [ "$module" = "--full-sdk" ]; then
+ OLD_TAG_HEAD=$(git ls-remote --tags origin | grep -o -e ${OLD_TAG}$)
+ NEW_TAG_HEAD=$(git ls-remote --tags origin | grep -o -e ${NEW_TAG}$)
+ else
+ OLD_TAG_HEAD=$(git rev-parse --verify origin/${OLD_TAG}${SUFFIX} 2>/dev/null)
+ NEW_TAG_HEAD=$(git rev-parse --verify origin/${NEW_TAG}${SUFFIX} 2>/dev/null)
+ fi
+
+ if [ -z "$OLD_TAG_HEAD" ]; then
+ echo "Interdiff modules are not supported for version ${OLD_TAG}"
+ fi
+ if [ -z "$NEW_TAG_HEAD" ]; then
+ echo "Interdiff modules are not supported for version ${NEW_TAG}"
+ fi
+ if [ -z "$OLD_TAG_HEAD" ] || [ -z "$NEW_TAG_HEAD" ]; then
+ echo "Interdiff are supported since version 115"
+ exit
+ fi
+
+ if [ "$module" = "--full-sdk" ]; then
+ # Find lastest upstream commit for given version.
+ OLD_VERSION=$(echo ${OLD_TAG} | cut -d "-" -f3)
+ NEW_VERSION=$(echo ${NEW_TAG} | cut -d "-" -f3)
+ OLD_TAG_PREV=$(git log --no-decorate --all --grep="Publish DEPS for ${OLD_VERSION}" -n 1 --pretty=format:"%h")
+ NEW_TAG_PREV=$(git log --no-decorate --all --grep="Publish DEPS for ${NEW_VERSION}" -n 1 --pretty=format:"%h")
+ else
+ # Take advantage of modules test branches design,
+ # top commit always contain module changes only.
+ OLD_TAG_PREV=$(git rev-parse --verify origin/${OLD_TAG}${SUFFIX}~1 2>/dev/null)
+ NEW_TAG_PREV=$(git rev-parse --verify origin/${NEW_TAG}${SUFFIX}~1 2>/dev/null)
+ fi
+
+ # Create a list of changed files, it's important to create it for each version,
+ # some of files may renamed.
+ OLD_TAG_FILES=$(git diff --name-only $OLD_TAG_HEAD..$OLD_TAG_PREV)
+ NEW_TAG_FILES=$(git diff --name-only $NEW_TAG_HEAD..$NEW_TAG_PREV)
+ # Combine and uniquify.
+ COMBINED_FILES=(`for R in "${OLD_TAG_FILES[@]}" "${NEW_TAG_FILES[@]}" ; do echo "$R" ; done | sort -du`)
+ # Remove files not supported by git diff
+ for del in ${EXCLUDE_FILES[@]}
+ do
+ COMBINED_FILES=("${COMBINED_FILES[@]/$del}")
+ done
+
+ #Create diff in eyeo_modules/interdiffs/
+ git diff ${OLD_TAG_HEAD}..${NEW_TAG_HEAD} -- ${COMBINED_FILES[@]} > $repo_dir/eyeo_modules/interdiffs/${module:2}-$OLD_TAG-$NEW_TAG.diff
+
+done
diff --git a/tools/eyeo/generate_modules.sh b/tools/eyeo/generate_modules.sh
new file mode 100755
--- /dev/null
+++ b/tools/eyeo/generate_modules.sh
@@ -0,0 +1,259 @@
+#!/bin/bash
+
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+if [ $# -eq 0 ]; then
+ echo "Please provide an eyeo tag argument." \
+ "It is needed to find vanila branch to generate module diffs for."
+ exit 1
+fi
+
+EYEO_RELEASE_TAG=$1
+
+CHROMIUM_TAG=$(echo ${EYEO_RELEASE_TAG} | cut -d "-" -f3)
+TOP_VANILLA_COMMIT=$(git rev-parse --short $(git log --grep='Copy eyeo build requirements from' --pretty=format:"%h")~1)
+
+if [[ $(git cat-file -t ${TOP_VANILLA_COMMIT} 2> /dev/null) != "commit" ]]; then
+ echo "Failed to find vanila branch."
+ exit 1
+fi
+
+INITIAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
+INITIAL_HASH=$(git rev-parse HEAD)
+
+### Remove CI & testing ###
+CI_AND_TESTING_FILES="
+.ci-scripts/
+.gitignore
+.gitlab-ci.yml
+.pre-commit-config.yaml
+.vpython
+android_webview/tools/adblock_shell/
+build/install-build-deps.py
+components/eyeo_testing/
+gclient/
+tools/perf/
+"
+
+for path in $CI_AND_TESTING_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+CI_AND_TESTING_FILES_TO_CROP="
+components/adblock/core/BUILD.gn
+BUILD.gn
+DEPS
+"
+
+for path in $CI_AND_TESTING_FILES_TO_CROP
+do
+ sed -i '/CI & Testing module start/,/CI & Testing module end/d' $path
+ git add $path
+done
+
+git commit -n -m "Remove CI requirements"
+NEGATIVE_CI_TESTING=$(git rev-parse --short HEAD)
+
+### Remove Content Shell
+ANDROID_CONTENT_SHELL_FILES="
+content/shell/android/
+components/adblock/android/
+"
+
+for path in $ANDROID_CONTENT_SHELL_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+ANDROID_CONTENT_SHELL_FILES_TO_CROP="
+content/shell/BUILD.gn
+"
+
+for path in $ANDROID_CONTENT_SHELL_FILES_TO_CROP
+do
+ sed -i '/Android Content Shell module start/,/Android Content Shell module end/d' $path
+ git add $path
+done
+
+git commit -n -m "Negative Android Content Shell module"
+NEGATIVE_ANDROID_CONTENT_SHELL_MODULE_HASH=$(git rev-parse --short HEAD)
+
+### Restore components/adblock/android/ as this is shared part between Webview, Android API and Android Content Shell
+git restore --source=HEAD~1 --staged --worktree -- components/adblock/android/
+git commit -n -m "Restore jni_headers"
+
+### Android UI removal ###
+ANDROID_UI_FILES="
+chrome/android/java/res/xml/main_preferences.xml
+chrome/android/java/res/xml/main_preferences_legacy.xml
+chrome/browser/adblock/android/adblock_strings.grd
+chrome/browser/adblock/android/java/
+chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockFilterFragmentTest.java
+chrome/browser/adblock/android/README.md
+chrome/browser/adblock/android/translations/
+"
+
+ANDROID_UI_SHARED_FILES="
+chrome/browser/adblock/android/BUILD.gn
+chrome/android/BUILD.gn
+"
+
+for path in $ANDROID_UI_SHARED_FILES
+do
+ sed -i '/Android UI module start/,/Android UI module end/d' $path
+ git add $path
+done
+
+for path in $ANDROID_UI_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+git commit -n -m "Negative Android UI module"
+NEGATIVE_ANDROID_UI_MODULE_HASH=$(git rev-parse --short HEAD)
+
+### Android Webview removal ###
+WEBVIEW_FILES="
+android_webview/
+components/adblock/android/
+"
+
+for path in $WEBVIEW_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+git commit -n -m "Remove Android Webview integration"
+NEGATIVE_WEBVIEW_INTEGRATION=$(git rev-parse --short HEAD)
+
+### Restore components/adblock/android/ as this is shared part between Webview and Android API
+git restore --source=HEAD~1 --staged --worktree -- components/adblock/android/
+git commit -n -m "Restore jni_headers"
+
+### Android API removal ###
+ANDROID_API_FILES="
+components/adblock/android/
+chrome/browser/android/adblock/
+chrome/android/
+"
+
+ANDROID_API_SHARED_FILES="
+chrome/browser/BUILD.gn
+chrome/browser/ui/BUILD.gn
+"
+
+for path in $ANDROID_API_SHARED_FILES
+do
+ sed -i '/Android API module start/,/Android API module end/d' $path
+ git add $path
+done
+
+for path in $ANDROID_API_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+git commit -n -m "Negative Android API module"
+NEGATIVE_ANDROID_API_MODULE_HASH=$(git rev-parse --short HEAD)
+
+#### Extension API removal ###
+
+EXTENSION_API_FILES="
+chrome/browser/extensions/api/
+chrome/browser/extensions/extension_function_registration_test.cc
+chrome/common/extensions/api/
+chrome/common/extensions/permissions/
+chrome/test/data/extensions/api_test/
+extensions/browser/extension_event_histogram_value.h
+extensions/browser/extension_function_histogram_value.h
+extensions/common/mojom/api_permission_id.mojom
+tools/metrics/histograms/enums.xml
+tools/typescript/definitions/adblock_private.d.ts
+tools/typescript/definitions/eyeo_filtering_private.d.ts
+"
+
+EXTENSION_API_SHARED_FILES="
+chrome/browser/extensions/BUILD.gn
+chrome/test/BUILD.gn
+"
+
+for path in $EXTENSION_API_SHARED_FILES
+do
+ sed -i '/Extensions API module start/,/Extensions API module end/d' $path
+ git add $path
+done
+
+for path in $EXTENSION_API_FILES
+do
+ git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- $path
+done
+
+git commit -n -m "Negative Extension API module"
+NEGATIVE_EXTENSION_API_MODULE_HASH=$(git rev-parse --short HEAD)
+
+### Chrome removal ###
+git restore --source=$TOP_VANILLA_COMMIT --staged --worktree -- chrome
+
+git commit -n -m "Remove Chrome integration"
+NEGATIVE_CHROME_INTEGRATION=$(git rev-parse --short HEAD)
+
+### Create Base Module
+git reset $TOP_VANILLA_COMMIT --soft
+git commit -n -m "eyeo Browser Ad filtering Solution: Base Module" -m "Based on Chromium ${CHROMIUM_TAG}"
+BASE_MODULE_HASH=$(git rev-parse --short HEAD)
+
+### Generate modules
+repo_dir=$(git rev-parse --show-toplevel)
+mkdir -p $repo_dir/eyeo_modules
+
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/base.patch
+
+git revert -n $NEGATIVE_CHROME_INTEGRATION
+git commit -n -m "eyeo Browser Ad filtering Solution: Chrome Integration Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/chrome_integration.patch
+
+git revert -n $NEGATIVE_WEBVIEW_INTEGRATION
+git commit -n -m "eyeo Browser Ad filtering Solution: Android Weview Integration Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/webview_integration.patch
+
+### Remove webview integration commit to aviod conflicts on shared files from Webview Integration and Android API
+git reset HEAD~1 --hard
+
+git revert -n $NEGATIVE_ANDROID_CONTENT_SHELL_MODULE_HASH
+git commit -n -m "eyeo Browser Ad filtering Solution: Content Shell Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/content_shell.patch
+
+### Remove content shell integration commit to aviod conflicts on shared files from ContentShell Integration and Android API
+git reset HEAD~1 --hard
+
+git revert -n $NEGATIVE_ANDROID_API_MODULE_HASH
+git commit -n -m "eyeo Browser Ad filtering Solution: Android API Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/android_api.patch
+
+git revert -n $NEGATIVE_ANDROID_UI_MODULE_HASH
+git commit -n -m "eyeo Browser Ad filtering Solution: Android Settings UI Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module and Android API Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/android_settings.patch
+
+git revert -n $NEGATIVE_EXTENSION_API_MODULE_HASH
+git commit -n -m "eyeo Browser Ad filtering Solution: Extension API Module" -m "Based on Chromium ${CHROMIUM_TAG}" -m "Pre-requisites: eyeo Browser Ad filtering Solution: Base Module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/extension_api.patch
+
+git revert -n $NEGATIVE_CI_TESTING
+git commit -n -m "eyeo Browser Ad filtering Solution: CI & testing module"
+git format-patch -1 HEAD --stdout > $repo_dir/eyeo_modules/ci_and_testing.patch
+
+### Recover HEAD & branch after script - usefull for local runs
+#git checkout $INITIAL_HASH
+#git branch -D $INITIAL_BRANCH
+#git checkout -b $INITIAL_BRANCH
diff --git a/tools/eyeo/git-grafts.txt b/tools/eyeo/git-grafts.txt
new file mode 100644
--- /dev/null
+++ b/tools/eyeo/git-grafts.txt
@@ -0,0 +1,60 @@
+1993fb58c5732068109ce4178ee09bced4973a8a 83a714deb7aeff970e55ef11174b03aa02970d29
+3cc64cebef5c028d87495de797630d2e3d56d522 a71c84f53e4e2254f6a34ed50e37ecf5d4362eb9
+85b8322d2c1c97e7adfbe2162e7ade16fcd1e019 f2299e697fb99f2a693b405a2043d9622ebfe68b
+075b1b8375709eae8a56081a9d4230ab56484ba4 65212eee6e9e6043ad78fc59619cbba31a692f67
+e5425abe809325552de52772f2c5f379060f41d8 b78c67a90f7e0ed9ee2529da85520ba9d24185c1
+55fb8d16b251b7751aaef1dde724fa1acd3b2512 3d7d288226b954eedd7d4db928133a03bb32d9a4
+a0bdd758f02b0a973a07c5486fef1e5148e985b5 3a18a0a561aa5f19743059f527fde8ac20a329f9
+72c35aa81c0eb92c413ab2f1481e3c81d28fa4b3 e88b1fb508d57308143a0005d7a4368a2e8656d9
+28a859d07ad66e265ece583500d5c2aa513df498 6d0c75854dfddbaf96e45a3e20dca31daf212646
+00a4506ad055e5c7fe50bf7285e7c21ae33a7617 42531c8a229cfdc9d81055d95a93d49281b0cdc7
+71fe1219179ec91612747e760f9ceaf1c55f959c 7183b2bcd824389352a6d4fb114305643b06010a
+0cf8f8cf740f66e797d1a14b98a5af0f2fd7bccc 7f4ecd6ff3c5d59a8ea26c511aed7ea50b34d5f0
+f7ab1d7d5a773d4b05f8219bd8cd8a4e0835955d 030679db5aa258eaeda51e9af0f7f359cd7e5dae
+f9703ad6719b7496dfe936256bd8757935d0cd35 e0c7003923d0111e67783c0feb536bbb937f8f99
+af67c186260b532105c3a9041db0accf0d1d805b 67abc991ac9e6f84c8e9e2cae842c9135757f97a
+b90d21e346c1dab43536225e769ae9a1aed215bf b5f1cd66ae833254af0060d19c2fbb87f2e4e0a2
+3d9140d9dcf8211bf0be5a8dfc3553f06b2ab617 98aff70170930709f1f078746ea2cc218ba2e943
+89e91afc21625e16817fc25dc366bb85d88c33b1 77d2b3fe29f7064fc1cb73208d284c166230736a
+aa494b089524bdfe720bf03f13b7b1e16ab4d89f 4e874c2ea438d5ffe18dec6309209c98d95384a1
+5419042be2706f051ccc08beb294139254bb3dca 10709033d2d2369bd5beee542f894b922afb84ae
+ff255f92522eba0e8df710cb46fcc26e976d44bf 28b4a950a4697779be3e5d2131e72b37164614e7
+b49dfa7f78db0ed4c0dc0624cb7c235e3ab9b279 4d06002be9175c8d69d802a5a8f11092483ab551
+a95021cf432bca5af8cd090f4c1186e21d3414d2 677d965137e0db697e472afabaf8bbcf0d7a9eff
+8ff58a6c01f55cf01982e3b2c6f4a59d06c81d2a bc37057a9c32c6a222c9353005381479aae49b0b
+eed2bc6a3b93da352bc997f0b23bac1b046cc417 1d37d23ed509892c48ef254142417106522a7395
+e2295866881ff6cb879f088cabff07ac27c0b329 75d3007c01b399ad8ae8d81ae06d7e1131d1eea9
+b987521700eee8bc1634a223163870fc636820c4 6c207ed37603d2948f89ecd543809f270f636bfb
+cd323706ed19015f9a0e9c26d871a6bb2904d7b5 2c33603b503621b2288f249502fef3ce1a242515
+129916b33c954bbf320479fb2e6431b86011cbe2 8cbdaaa2b9c76bc1b34ae19b14077b000537c87a
+fb45dea33a8e7becbfc323d166d6ee88af0a4c66 7bc3071f272e65ee1512d3e7d0d00aead9708cfe
+2c93685d830d68e7f9655aefa51353945269c7d1 826ec2a713ba188cc848af0fed9f1fc1a125fbc0
+358aab35b34c525ab06376020a57d22ea33f6e1a 719a6d0c812ce27e4b6a9e88f9906f59cd58b254
+253b575b97b8a629e7fe30a307a75e95d47e9998 70ce4c09c449d3550de862c8c7f747e323dd4ed6
+09286cb9d84611f1423860634a625b8ef5ff3db7 a6a3c99d16f77163499df8f6aa7238e9eec21faf
+f79b75f25bf35cc956418c66f89142420869c433 344d2e5bc555bc68e41c49a276373e2144b6fb3e
+254d4a1bde02c19ba5129f2f43d3a55989c35a05 8e8d9a10a7887cdd4bff19b5b9e8a094322589b8
+51526d2be3cdc5d0c811c269bdd3abac315ebd52 a2eb6c9e1e2bf76d4f1c6da03890d15594d17bdc
+de5ef6a219591f47366ef26eccdeba26cbf4dc03 1bf0ca616a289d6c94eff118cfc303edf4abaffb
+497b28215f9c251b76a82fc231f89e843e6a37ea 2a9c2131c3a1f04d25b534b65dfa025bf93cbf8e
+04b531ba2088e564f777cab502b63b4e7a2cd3e6 9eab1f740b3a8833128d44a7d887766db83693d0
+4c12a11b0f39cd519dbdada53f4dc1549ac67350 1b30d2ef588966afa0fd0bbbf3bc59cc2f80bc11
+1b30d2ef588966afa0fd0bbbf3bc59cc2f80bc11 f5e286d6301ba7554221a75d81b6e7a858f8f073
+41b98cfba00ea986cf9b994dbc040e4ab6182d60 22f11466dced853e7c50a9bbc677ae8052e0f868
+a12933701caa063d2dfe1faef35ab589015e28c9 244e183b19af69ddb18fdb07f953ab3656c84fbe
+1d06e1212f035eed9a82693f8921c22a1fd83ef5 2643a14e760871a401a305d5bfb93a39ed9eebdb
+add946a1ca9ea6914a24e530e849ef300757273e 5f995cfe54f95147b0eaf088b4ab3b300935c8bd
+45c55a85627eefff791d308ec1b319bc756b4b74 37d0711460d777de52dfc2db275993f7a5c4bf59
+8a92cbbf79aeba36022f9add08e32c460445ccb5 bf0c79ebb568cafd788ae7486831ee1f366285a3
+6ffab382a3c3bb2ed452a4d1862d6d7f3e4b089d 61ab65ca5bd94a1c534432450c9e50784b6417c2
+24fd81f78a3ec081cd08520661f55d027a9eeb70 3dcf70cf1550c3e2518f9020fac2f0f4c0a96bf9
+2289b928d38fb24e39955a33b150544d6912ac97 24d69623e939b8087fc8a1d858fb14be1073c6bd
+7f4595e16643888ccf41d450af3cd4b68f22281a 26aa7e401fd389c57634b693748ec920f786c02d
+3c24736e7f01b355a33d91ae8a975c59d14ded31 4e16d7fb75cb732d08d7eec20befced3642fe05d
+a6d29918b59556064dd2bfbac9e5382c3a50850e 6a42736c06aacb5b2224bb534dedaa3ef8eb06f6
+099a7ccbb729d17a381caa59d43577f40b343f1b 5b05662ae7583fd9dea7214a7189e9bf90f38614
+5de71172b93ce67883027b126122a2114ea40e49 0d87ad8ca1e8bce50f5b14c0573ab402480baa64
+780be6bfcfd737aa58aed6d39945b636126c100a 446b5a7cf1d1d6d1ac1b86df6b7157e2c8c3ed29
+3ce435b739edbf0ceca221f5eb1baa0fd277ad57 6b9a72a6c1ecc15bdddc55f4a7c8edc0888c464f
+07b86d6e93f14a6245285570eb869084891b6a33 2edc3ab35ab850f868be548584a97d62cfc160c3
+70a4fda9274518dcda6e4ce9258fdcfaeb46bb83 94a3dddac5f42c677ac839d80fab44c3f07cb149
diff --git a/tools/eyeo/update_git_grafts.py b/tools/eyeo/update_git_grafts.py
new file mode 100755
--- /dev/null
+++ b/tools/eyeo/update_git_grafts.py
@@ -0,0 +1,138 @@
+#!/bin/python3
+
+# This file is part of eyeo Chromium SDK,
+# Copyright (C) 2006-present eyeo GmbH
+#
+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# eyeo Chromium SDK is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with eyeo Chromium SDK. If not, see <http://www.gnu.org/licenses/>.
+
+# This script generates a graft file from git history. A graft file is a list
+# of commits that connect disjoint branches in the git history.
+#
+# Example graft file contents:
+# 1993fb58c5732068109ce4178ee09bced4973a8a 83a714deb7aeff970e55ef11174b03aa02970d29
+# 3cc64cebef5c028d87495de797630d2e3d56d522 a71c84f53e4e2254f6a34ed50e37ecf5d4362eb9
+# 85b8322d2c1c97e7adfbe2162e7ade16fcd1e019 f2299e697fb99f2a693b405a2043d9622ebfe68b
+# ...
+#
+# When git blame traverses history and reaches a commit 1993fb58c5732068109ce4178ee09bced4973a8a
+# it will skip over it and go to 83a714deb7aeff970e55ef11174b03aa02970d29, then resume
+# traversing history from there. If it then reaches 3cc64cebef5c028d87495de797630d2e3d56d522
+# it will skip over to a71c84f53e4e2254f6a34ed50e37ecf5d4362eb9 and so on.
+#
+# In eyeo, we squash our changes when we update to a new Chromium version. This
+# means that we lose the history of our changes after every upgrade. Thankfully,
+# every squashed commit has a "eyeo-parent-commit" line in its commit message
+# which leads us to its pre-squash history. This script traverses
+# the git history and generates a graft file that contains pairs of:
+# hash-of-squash-commit hash-of-its-eyeo-parent-commit
+
+import argparse
+import re
+import subprocess
+
+
+def get_commit_message(commit_hash):
+ try:
+ # Try to get the commit message
+ return subprocess.check_output(
+ ['git', 'show', '-s', '--format=%B', commit_hash]).decode('utf-8')
+ except subprocess.CalledProcessError:
+ # This can happen if the commit is a bad object.
+ # For example, if the branch was deleted after a merge request was merged.
+ return None
+
+
+def get_parent_commits(commit_hash):
+ # Get the parent commits of the current commit
+ return subprocess.check_output(
+ ['git', 'rev-list', '-1', '--parents',
+ commit_hash]).decode('utf-8').strip().split()[1:]
+
+
+def read_existing_grafts(graft_file):
+ # Read the existing grafts from the graft file
+ with open(graft_file, 'r') as f:
+ return [tuple(line.strip().split()) for line in f]
+
+
+def get_grafts_from_history(start_commit, max_commits, existing_grafts):
+ # Recurse into git history to find grafts.
+ grafts = []
+ # Since branches can have multiple parents, we need to recurse through
+ # all possible paths to find the next "eyeo-parent-commit" line (ie. "mainline").
+ # Instead of using recursion, we use a stack onto which we push the next
+ # commit to visit and its depth in the tree.
+ stack = [(start_commit, 0)]
+ while stack:
+ commit_hash, depth = stack.pop()
+ if depth >= max_commits:
+ continue
+ commit_message = get_commit_message(commit_hash)
+ if commit_message is None:
+ continue # Skip this branch, it has a bad object (deleted MR branch?)
+ match = re.search(r'eyeo-parent-commit:([a-f0-9]{40})', commit_message)
+ if match:
+ # We found a graft! Add it to the list.
+ parent_commit = match.group(1)
+ graft = (commit_hash, parent_commit)
+ if graft in existing_grafts:
+ # Since this graft is already in existing_grafts, the rest of history
+ # is already grafted. We can stop traversing. This will happen if
+ # a partially populated graft file already exists and we're only
+ # updating it with the latest grafts.
+ break
+ grafts.append(graft)
+ # Since we found a graft, we know we're on "mainline" and we can
+ # prune unvisited branches. They will not contain any grafts.
+ stack = [(parent_commit, depth + 1)]
+ else:
+ # We didn't find a graft. Keep traversing history, checking all parents.
+ parent_commits = get_parent_commits(commit_hash)
+ for parent_commit in parent_commits:
+ stack.append((parent_commit, depth + 1))
+ return grafts
+
+
+def append_graft_file(grafts, graft_file):
+ # Append the new grafts to the graft file
+ with open(graft_file, 'a') as f:
+ for graft in grafts:
+ f.write(f"{graft[0]} {graft[1]}\n")
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Generate a graft file from git history.')
+ parser.add_argument('--start-commit',
+ default='HEAD',
+ help='The commit to start traversing from.')
+ parser.add_argument('--max-commits',
+ type=int,
+ default=float('inf'),
+ help='The maximum number of commits to traverse.')
+ parser.add_argument('--graft-file',
+ default='git-grafts.txt',
+ help='The file to append grafts to.')
+ args = parser.parse_args()
+
+ # Read the existing grafts
+ existing_grafts = read_existing_grafts(args.graft_file)
+ # Get the new grafts from the git history
+ grafts = get_grafts_from_history(args.start_commit, args.max_commits,
+ existing_grafts)
+ # Append the new grafts to the graft file
+ append_graft_file(grafts, args.graft_file)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -2,6 +2,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
+# This source code is a part of eyeo Chromium SDK.
+# Use of this source code is governed by the GPLv3 that can be found in the
+# components/adblock/LICENSE file.
+#
# This file is used to assign starting resource ids for resources and strings
# used by Chromium. This is done to ensure that resource ids are unique
# across all the grd files. If you are adding a new grd file, please add
@@ -1161,6 +1165,14 @@
"components/user_scripts/browser/resources/browser_resources.grd": {
"includes": [7620],
},
+ "components/adblock/core/resources/adblock_resources.grd": {
+ "META": {"sizes": {"includes": [10],}},
+ "includes": [10020],
+ },
+ "<(SHARED_INTERMEDIATE_DIR)/components/adblock/content/resources/adblock_internals/resources.grd": {
+ "META": {"sizes": {"includes": [10],}},
+ "includes": [10040],
+ },
# END components/ section.
# START ios/ section.
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml
--- a/tools/metrics/histograms/metadata/extensions/enums.xml
+++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -2,6 +2,10 @@
Copyright 2023 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
+
+This source code is a part of eyeo Chromium SDK.
+Use of this source code is governed by the GPLv3 that can be found in the
+components/adblock/LICENSE file.
-->
<!--
@@ -180,7 +184,7 @@ Called by update_extension_histograms.py.-->
<int value="19" label="APP_WINDOW_ON_MAXIMIZED"/>
<int value="20" label="APP_WINDOW_ON_MINIMIZED"/>
<int value="21" label="APP_WINDOW_ON_RESTORED"/>
- <int value="22" label="DELETED_AUDIO_MODEM_ON_RECEIVED"/>
+ <int value="22" label="EYEO_EVENT"/>
<int value="23" label="DELETED_AUDIO_MODEM_ON_TRANSMIT_FAIL"/>
<int value="24" label="DELETED_AUDIO_ON_DEVICE_CHANGED"/>
<int value="25" label="AUDIO_ON_DEVICES_CHANGED"/>
@@ -3209,6 +3213,8 @@ Called by update_extension_permission.py.-->
<int value="260" label="kExperimentalAiData"/>
<int value="261" label="kOmniboxDirectInput"/>
<int value="262" label="kExperimentalActor"/>
+ <int value="999" label="kAdblockPrivate"/>
+ <int value="1000" label="kEyeoFilteringPrivate"/>
</enum>
<enum name="ExtensionPolicyReinstallReason">
--