diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af0e929304872846209b7f9ea1b19a220add8bba..a9c7e6948f4908a93920272bb2fcd9ea323d0b58 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,7 @@ build-docker: stage: prepare rules: - if: $CI_PIPELINE_SOURCE != "schedule" + when: manual tags: - generic_privileged variables: @@ -33,6 +34,7 @@ build-docker: image: $CONTAINER_IMAGE rules: - if: $CI_PIPELINE_SOURCE != "schedule" + when: manual tags: - build-browser artifacts: diff --git a/Dockerfile b/Dockerfile index f4acea2e278d54b7ec878d01b763aa3fc59ca775..604c8906a593ea967be212cd3634feb65d05586b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:20.04 ENV CHROMIUM_DIR "/srv/chromium" -ENV CHROMIUM_VER "134.0.6998.89" +ENV CHROMIUM_VER "135.0.7049.100" RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive \ diff --git a/build.sh b/build.sh index 0357ab0f654b251e4af8ab7e2514a329301c684f..cf2c491a02d506b275c904b0e6c639e1172db4a1 100755 --- a/build.sh +++ b/build.sh @@ -115,13 +115,6 @@ patch() { git am -C0 -3 --ignore-whitespace "${root_dir}/build/e_patches/$file" done - while read dir patch; do - [ -z "$dir" ] && continue # skip empty lines - cd $chromium_dir/src/third_party/$dir - git am -C0 -3 --ignore-whitespace "${root_dir}/build/third_party/$patch" - cd $chromium_dir/src - done < "${root_dir}/build/third_party_patches_list.txt" - # Rename Chrome to Browser rename diff --git a/build/cromite_patches/AImageReader-CFI-crash-mitigations.patch b/build/cromite_patches/AImageReader-CFI-crash-mitigations.patch index d5cd9d4f4cb092756e24359eb3f96cdb7fa4a7e0..438fb6423799bee41ac583086df456786918533d 100644 --- a/build/cromite_patches/AImageReader-CFI-crash-mitigations.patch +++ b/build/cromite_patches/AImageReader-CFI-crash-mitigations.patch @@ -90,7 +90,7 @@ diff --git a/base/android/android_image_reader_compat.h b/base/android/android_i diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json -@@ -3224,6 +3224,10 @@ +@@ -3268,6 +3268,10 @@ "name": "enable-identity-in-auth-error", "owners": [ "jlebel@chromium.org", "chrome-signin-team@google.com" ], "expiry_milestone": 135 @@ -145,7 +145,7 @@ diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_lis diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc --- a/gpu/config/gpu_finch_features.cc +++ b/gpu/config/gpu_finch_features.cc -@@ -59,6 +59,11 @@ BASE_FEATURE(kAggressiveShaderCacheLimits, +@@ -58,6 +58,11 @@ BASE_FEATURE(kAggressiveShaderCacheLimits, base::FEATURE_DISABLED_BY_DEFAULT); #if BUILDFLAG(IS_ANDROID) @@ -181,7 +181,7 @@ diff --git a/gpu/config/gpu_util.cc b/gpu/config/gpu_util.cc if (!gpu_preferences.enable_android_surface_control) return kGpuFeatureStatusDisabled; -@@ -375,6 +378,11 @@ void AdjustGpuFeatureStatusToWorkarounds(GpuFeatureInfo* gpu_feature_info, +@@ -348,6 +351,11 @@ void AdjustGpuFeatureStatusToWorkarounds(GpuFeatureInfo* gpu_feature_info, kGpuFeatureStatusSoftware; } } @@ -215,7 +215,7 @@ diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc #endif #if BUILDFLAG(ENABLE_VULKAN) -@@ -715,6 +716,13 @@ bool GpuInit::InitializeAndStartSandbox(base::CommandLine* command_line, +@@ -692,6 +693,13 @@ bool GpuInit::InitializeAndStartSandbox(base::CommandLine* command_line, } #endif // BUILDFLAG(IS_WIN) diff --git a/build/cromite_patches/Add-AllowUserCertificates-flag.patch b/build/cromite_patches/Add-AllowUserCertificates-flag.patch index 255e9b510da96b1035d8abfb899977e535d20ea3..a985da3797d1e80ccb4bef62d50ddf3a60ce6a7f 100644 --- a/build/cromite_patches/Add-AllowUserCertificates-flag.patch +++ b/build/cromite_patches/Add-AllowUserCertificates-flag.patch @@ -20,7 +20,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -225,6 +225,7 @@ import org.chromium.content_public.browser.LoadUrlParams; +@@ -227,6 +227,7 @@ import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.SelectionPopupController; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.common.ContentSwitches; @@ -28,7 +28,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActiv import org.chromium.printing.PrintManagerDelegateImpl; import org.chromium.printing.PrintingController; import org.chromium.printing.PrintingControllerImpl; -@@ -968,6 +969,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -969,6 +970,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity super.onStartWithNative(); ChromeActivitySessionTracker.getInstance().onStartWithNative(getProfileProviderSupplier()); @@ -40,7 +40,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActiv diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -164,6 +164,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -160,6 +160,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &feed::kFeedPerformanceStudy, &feed::kFeedShowSignInCommand, &feed::kFeedSignedOutViewDemotion, @@ -59,16 +59,16 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_PAGE_SUMMARY = "AdaptiveButtonInTopToolbarPageSummary"; public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2 = -@@ -624,6 +625,8 @@ public abstract class ChromeFeatureList { - public static final CachedFlag sAndroidTabDeclutterDedupeTabIdsKillSwitch = - newCachedFlag(ANDROID_TAB_DECLUTTER_DEDUPE_TAB_IDS_KILL_SWITCH, true); +@@ -643,6 +644,8 @@ public abstract class ChromeFeatureList { + public static final CachedFlag sAndroidMinimalUiLargeScreen = + newCachedFlag(ANDROID_MINIMAL_UI_LARGE_SCREEN, false); public static final CachedFlag sAppSpecificHistory = newCachedFlag(APP_SPECIFIC_HISTORY, true); + public static final CachedFlag sAllowUserCertificates = + newCachedFlag(ALLOW_USER_CERTIFICATES, false); public static final CachedFlag sAsyncNotificationManager = newCachedFlag(ASYNC_NOTIFICATION_MANAGER, false, true); - public static final CachedFlag sBlockIntentsWhileLocked = -@@ -845,6 +848,7 @@ public abstract class ChromeFeatureList { + public static final CachedFlag sAsyncNotificationManagerForDownload = +@@ -878,6 +881,7 @@ public abstract class ChromeFeatureList { public static final List sFlagsCachedFullBrowserChromium = List.of( sAndroidAppIntegration, diff --git a/build/cromite_patches/Add-IsCleartextPermitted-flag.patch b/build/cromite_patches/Add-IsCleartextPermitted-flag.patch index 08929780b5d8c26f8b954c3adab5a64ad62c2371..177c9ea77b069c5b156ac64463586d570c5d1abb 100644 --- a/build/cromite_patches/Add-IsCleartextPermitted-flag.patch +++ b/build/cromite_patches/Add-IsCleartextPermitted-flag.patch @@ -44,7 +44,7 @@ new file mode 100644 diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc -@@ -372,6 +372,11 @@ std::unique_ptr URLRequestHttpJob::Create(URLRequest* request) { +@@ -387,6 +387,11 @@ std::unique_ptr URLRequestHttpJob::Create(URLRequest* request) { } } diff --git a/build/cromite_patches/Add-a-proxy-configuration-page.patch b/build/cromite_patches/Add-a-proxy-configuration-page.patch index b248d175a3658032df6ae22b100faabe6250c901..e4e40be52776995e028e5d60f9038e9cb3b08738 100644 --- a/build/cromite_patches/Add-a-proxy-configuration-page.patch +++ b/build/cromite_patches/Add-a-proxy-configuration-page.patch @@ -29,7 +29,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html chrome/browser/resources/proxy_config.html | 77 ++++ chrome/browser/resources/proxy_config.js | 278 +++++++++++++ chrome/browser/ui/BUILD.gn | 2 + - .../browser/ui/webui/chrome_web_ui_configs.cc | 4 + + .../browser/ui/webui/chrome_web_ui_configs.cc | 5 +- chrome/browser/ui/webui/proxy_config_ui.cc | 389 ++++++++++++++++++ chrome/browser/ui/webui/proxy_config_ui.h | 41 ++ chrome/common/webui_url_constants.cc | 2 + @@ -40,7 +40,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../proxy_config/proxy_policy_handler.cc | 2 +- net/proxy_resolution/proxy_config.cc | 51 ++- net/proxy_resolution/proxy_config.h | 3 + - 25 files changed, 984 insertions(+), 17 deletions(-) + 25 files changed, 984 insertions(+), 18 deletions(-) create mode 100644 chrome/browser/resources/proxy_config.css create mode 100644 chrome/browser/resources/proxy_config.html create mode 100644 chrome/browser/resources/proxy_config.js @@ -106,7 +106,7 @@ diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resou + + + - + diff --git a/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc b/chrome/browser/extensions/api/proxy/proxy_api_helpers.cc @@ -196,16 +196,16 @@ diff --git a/chrome/browser/net/proxy_service_factory.h b/chrome/browser/net/pro diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -200,6 +200,8 @@ +@@ -196,6 +196,8 @@ #include "printing/buildflags/buildflags.h" #include "rlz/buildflags/buildflags.h" +#include "chrome/browser/net/proxy_service_factory.h" + #if BUILDFLAG(ENABLE_BACKGROUND_MODE) - #include "chrome/browser/background/background_mode_manager.h" + #include "chrome/browser/background/extensions/background_mode_manager.h" #endif -@@ -1644,6 +1646,8 @@ void RegisterLocalState(PrefRegistrySimple* registry) { +@@ -1561,6 +1563,8 @@ void RegisterLocalState(PrefRegistrySimple* registry) { component_updater::RegisterPrefs(registry); domain_reliability::RegisterPrefs(registry); embedder_support::OriginTrialPrefs::RegisterPrefs(registry); @@ -217,7 +217,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse diff --git a/chrome/browser/prefs/chrome_command_line_pref_store.cc b/chrome/browser/prefs/chrome_command_line_pref_store.cc --- a/chrome/browser/prefs/chrome_command_line_pref_store.cc +++ b/chrome/browser/prefs/chrome_command_line_pref_store.cc -@@ -157,7 +157,7 @@ void ChromeCommandLinePrefStore::ApplyProxyMode() { +@@ -156,7 +156,7 @@ void ChromeCommandLinePrefStore::ApplyProxyMode() { command_line()->GetSwitchValueASCII(switches::kProxyBypassList); SetValue(proxy_config::prefs::kProxy, base::Value(ProxyConfigDictionary::CreateFixedServers( @@ -660,7 +660,7 @@ new file mode 100644 diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn -@@ -222,6 +222,8 @@ static_library("ui") { +@@ -224,6 +224,8 @@ static_library("ui") { "webui/metrics_internals/metrics_internals_ui.h", "webui/net_export_ui.cc", "webui/net_export_ui.h", @@ -672,7 +672,7 @@ diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc --- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc -@@ -55,6 +55,9 @@ +@@ -54,6 +54,9 @@ #include "extensions/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h" @@ -682,12 +682,15 @@ diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/u #if BUILDFLAG(ENABLE_NACL) #include "chrome/browser/ui/webui/nacl_ui.h" #endif -@@ -388,4 +391,5 @@ void RegisterChromeWebUIConfigs() { - #if BUILDFLAG(ENABLE_GLIC) +@@ -391,7 +394,7 @@ void RegisterChromeWebUIConfigs() { map.AddWebUIConfig(std::make_unique()); + map.AddWebUIConfig(std::make_unique()); #endif +- + map.AddWebUIConfig(std::make_unique()); - } + #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) + map.AddWebUIConfig( + std::make_unique()); diff --git a/chrome/browser/ui/webui/proxy_config_ui.cc b/chrome/browser/ui/webui/proxy_config_ui.cc new file mode 100644 --- /dev/null @@ -1150,7 +1153,7 @@ diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_cons diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h --- a/chrome/common/webui_url_constants.h +++ b/chrome/common/webui_url_constants.h -@@ -160,6 +160,8 @@ inline constexpr char kChromeUINetExportHost[] = "net-export"; +@@ -161,6 +161,8 @@ inline constexpr char kChromeUINetExportHost[] = "net-export"; inline constexpr char kChromeUINetInternalsHost[] = "net-internals"; inline constexpr char kChromeUINetInternalsURL[] = "chrome://net-internals/"; inline constexpr char kChromeUINewTabHost[] = "newtab"; diff --git a/build/cromite_patches/Add-an-always-incognito-mode.patch b/build/cromite_patches/Add-an-always-incognito-mode.patch index 86764e77e22ff6b525dad520140e26165d8c0b1f..3b970c9b4a42476a821b76c2a8828634927470ec 100644 --- a/build/cromite_patches/Add-an-always-incognito-mode.patch +++ b/build/cromite_patches/Add-an-always-incognito-mode.patch @@ -72,6 +72,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../browser/toolbar/LocationBarModel.java | 5 +- chrome/browser/ui/messages/android/BUILD.gn | 1 + .../snackbar/INeedSnackbarManager.java | 28 +++ + .../search_engine_tab_helper.cc | 6 + chrome/common/pref_names.h | 6 + .../browser/content_settings_pref_provider.cc | 6 +- .../browser/content_settings_pref_provider.h | 2 + @@ -85,7 +86,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../add-an-always-incognito-mode.inc | 1 + .../add-an-always-incognito-mode.inc | 3 + .../add-an-always-incognito-mode.inc | 1 + - 66 files changed, 791 insertions(+), 112 deletions(-) + 67 files changed, 797 insertions(+), 112 deletions(-) create mode 100644 chrome/android/java/res/xml/incognito_preferences.xml create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/AlwaysIncognitoLinkInterceptor.java create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/IncognitoSettings.java @@ -98,7 +99,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -636,6 +636,7 @@ chrome_java_resources = [ +@@ -643,6 +643,7 @@ chrome_java_resources = [ "java/res/xml/main_preferences_legacy.xml", "java/res/xml/manage_sync_preferences.xml", "java/res/xml/personalize_google_services_preferences.xml", @@ -117,7 +118,7 @@ diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java "java/src/com/google/android/apps/chrome/appwidget/bookmarks/BookmarkThumbnailWidgetProvider.java", "java/src/org/chromium/chrome/browser/ActivityTabProvider.java", "java/src/org/chromium/chrome/browser/ActivityUtils.java", -@@ -946,6 +947,7 @@ chrome_java_sources = [ +@@ -879,6 +880,7 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/privacy/settings/IncognitoLockSettings.java", "java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java", "java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java", @@ -255,7 +256,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct import org.chromium.chrome.browser.IntentHandler.TabOpenType; import org.chromium.chrome.browser.app.ChromeActivity; import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics; -@@ -715,14 +719,19 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -727,14 +731,19 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn Profile profile = mTabModelSelector.getCurrentModel().getProfile(); // For saving non-incognito tab closures for Recent Tabs. @@ -282,7 +283,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct // Defer creation of this helper so it triggers after TabGroupModelFilter observers. mUndoRefocusHelper = new UndoRefocusHelper( -@@ -2571,8 +2580,9 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -2614,8 +2623,9 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn // We determine the model as soon as possible so every systems get initialized coherently. boolean startIncognito = @@ -297,7 +298,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -92,6 +92,7 @@ import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver; +@@ -94,6 +94,7 @@ import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver; import org.chromium.chrome.browser.compositor.layouts.content.TabContentManagerHandler; import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager; import org.chromium.chrome.browser.device.DeviceClassManager; @@ -305,7 +306,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActiv import org.chromium.chrome.browser.dom_distiller.DomDistillerUiUtils; import org.chromium.chrome.browser.download.DownloadManagerService; import org.chromium.chrome.browser.download.DownloadUtils; -@@ -1952,6 +1953,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -1953,6 +1954,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity throw new IllegalStateException( "Attempting to access TabCreator before initialization"); } @@ -327,7 +328,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App import org.chromium.chrome.browser.bookmarks.BookmarkModel; import org.chromium.chrome.browser.bookmarks.PowerBookmarkUtils; import org.chromium.chrome.browser.commerce.ShoppingServiceFactory; -@@ -101,6 +103,10 @@ import java.util.ArrayList; +@@ -102,6 +104,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -338,7 +339,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App /** * Base implementation of {@link AppMenuPropertiesDelegate} that handles hiding and showing menu * items based on activity state. -@@ -599,6 +605,13 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate +@@ -609,6 +615,13 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate } private void prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito) { @@ -352,7 +353,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App // We have to iterate all menu items since same menu item ID may be associated with more // than one menu items. boolean isOverviewModeMenu = menuGroup == MenuGroup.OVERVIEW_MODE_MENU; -@@ -662,7 +675,15 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate +@@ -672,7 +685,15 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate } if (item.getItemId() == R.id.recent_tabs_menu_id) { @@ -369,7 +370,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App } if (item.getItemId() == R.id.menu_select_tabs) { item.setVisible(isMenuSelectTabsVisible); -@@ -867,8 +888,9 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate +@@ -877,8 +898,9 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate && !isNativePage && !isFileScheme && !isContentScheme @@ -384,15 +385,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java -@@ -30,6 +30,7 @@ import org.chromium.base.metrics.RecordHistogram; +@@ -29,6 +29,7 @@ import org.chromium.base.ContextUtils; import org.chromium.base.shared_preferences.SharedPreferencesManager; import org.chromium.base.supplier.Supplier; import org.chromium.chrome.R; +import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor; + import org.chromium.chrome.browser.bookmarks.BookmarkUtils; import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItem.Item; import org.chromium.chrome.browser.contextmenu.ContextMenuCoordinator.ListItemType; - import org.chromium.chrome.browser.download.DownloadUtils; -@@ -339,6 +340,9 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { +@@ -277,6 +278,9 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { public List> buildContextMenu() { mShowEphemeralTabNewLabel = null; @@ -402,7 +403,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/Chr List> groupedItems = new ArrayList<>(); if (mParams.isAnchor()) { -@@ -349,6 +353,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { +@@ -287,6 +291,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { if (mMode == ContextMenuMode.NORMAL) { linkGroup.add(createListItem(Item.OPEN_IN_NEW_TAB_IN_GROUP)); linkGroup.add(createListItem(Item.OPEN_IN_NEW_TAB)); @@ -410,7 +411,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/Chr if (!mItemDelegate.isIncognito() && mItemDelegate.isIncognitoSupported()) { linkGroup.add(createListItem(Item.OPEN_IN_INCOGNITO_TAB)); } -@@ -379,7 +384,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { +@@ -317,7 +322,7 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator { } } if (FirstRunStatus.getFirstRunFlowComplete()) { @@ -502,7 +503,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/Cust import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -@@ -1180,6 +1183,13 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid +@@ -1183,6 +1186,13 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid return getInitialActivityWidth() > 0; } @@ -538,7 +539,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/Downlo /** A class containing some utility static methods. */ public class DownloadUtils { private static final String TAG = "download"; -@@ -299,7 +304,16 @@ public class DownloadUtils { +@@ -298,7 +303,16 @@ public class DownloadUtils { // Offline pages isn't supported in Incognito. This should be checked before calling // OfflinePageBridge.getForProfile because OfflinePageBridge instance will not be found // for incognito profile. @@ -559,7 +560,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/Downlo diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java -@@ -277,7 +277,9 @@ public class HistoryContentManager implements SignInStateObserver, PrefObserver +@@ -300,7 +300,9 @@ public class HistoryContentManager implements SignInStateObserver, PrefObserver mHistoryAdapter.generateFooterItems(); // Listen to changes in sign in state. @@ -570,7 +571,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/History // Create PrefChangeRegistrar to receive notifications on preference changes. mPrefChangeRegistrar = PrefServiceUtil.createFor(profile); -@@ -353,7 +355,8 @@ public class HistoryContentManager implements SignInStateObserver, PrefObserver +@@ -376,7 +378,8 @@ public class HistoryContentManager implements SignInStateObserver, PrefObserver mHistoryAdapter.onDestroyed(); mLargeIconBridge.destroy(); mLargeIconBridge = null; @@ -578,8 +579,8 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/History + if (IdentityServicesProvider.get().getSigninManager(mProfile) != null) + IdentityServicesProvider.get().getSigninManager(mProfile).removeSignInStateObserver(this); mPrefChangeRegistrar.destroy(); - } - + if (mHistorySyncPromoCoordinator != null) { + mHistorySyncPromoCoordinator.destroy(); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java @@ -684,7 +685,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/Nat import org.chromium.chrome.browser.app.ChromeActivity; import org.chromium.chrome.browser.app.download.home.DownloadPage; import org.chromium.chrome.browser.bookmarks.BookmarkPage; -@@ -319,7 +320,8 @@ public class NativePageFactory { +@@ -326,7 +327,8 @@ public class NativePageFactory { String url, NativePage candidatePage, Tab tab, boolean isIncognito, PdfInfo pdfInfo) { NativePage page; @@ -1043,7 +1044,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/tab_restore diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java -@@ -180,6 +180,8 @@ import org.chromium.ui.InsetObserver; +@@ -191,6 +191,8 @@ import org.chromium.ui.InsetObserver; import org.chromium.ui.UiUtils; import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.DeviceFormFactor; @@ -1052,7 +1053,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/Tab import org.chromium.ui.base.IntentRequestTracker; import org.chromium.ui.base.LocalizationUtils; import org.chromium.ui.dragdrop.DragDropGlobalState; -@@ -1078,7 +1080,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -1148,7 +1150,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { () -> { mTabCreatorManagerSupplier .get() @@ -1075,7 +1076,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/Chrome /** This class creates various kinds of new tabs and adds them to the right {@link TabModel}. */ public class ChromeTabCreator extends TabCreator { private final Activity mActivity; -@@ -500,7 +504,6 @@ public class ChromeTabCreator extends TabCreator { +@@ -502,7 +506,6 @@ public class ChromeTabCreator extends TabCreator { // TODO(crbug.com/40691614): Clean up the launches from SearchActivity/Chrome. public Tab launchUrlFromExternalApp( LoadUrlParams loadUrlParams, String appId, boolean forceNewTab, Intent intent) { @@ -1115,7 +1116,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPer import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; -@@ -821,6 +823,13 @@ public class TabPersistentStore { +@@ -832,6 +834,13 @@ public class TabPersistentStore { } } } @@ -1140,7 +1141,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton; import org.chromium.chrome.browser.back_press.BackPressManager; -@@ -752,7 +753,7 @@ public class ToolbarManager +@@ -748,7 +749,7 @@ public class ToolbarManager TraceEvent.end("isOfflinePage"); return ret; } @@ -1351,10 +1352,10 @@ diff --git a/chrome/browser/content_settings/host_content_settings_map_factory.c #include "components/content_settings/core/browser/content_settings_pref_provider.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/permissions/features.h" -@@ -103,9 +104,25 @@ scoped_refptr - if (profile->IsOffTheRecord() && !profile->IsGuestSession()) +@@ -105,9 +106,25 @@ scoped_refptr GetForProfile(original_profile); + bool should_record_metrics = profiles::IsRegularUserProfile(profile); + bool always_incognito_enabled = false; + bool force_save_site_settings = false; + @@ -1376,9 +1377,9 @@ diff --git a/chrome/browser/content_settings/host_content_settings_map_factory.c + !force_save_site_settings && (profile->IsOffTheRecord() || profile->IsGuestSession()), + force_save_site_settings, /*store_last_modified=*/true, profile->ShouldRestoreOldSessionCookies(), - profiles::IsRegularUserProfile(profile))); + should_record_metrics)); -@@ -114,6 +131,9 @@ scoped_refptr +@@ -116,6 +133,9 @@ scoped_refptr settings_map->RegisterProvider(ProviderType::kWebuiAllowlistProvider, std::move(allowlist_provider)); @@ -1401,7 +1402,7 @@ diff --git a/chrome/browser/history/history_tab_helper.cc b/chrome/browser/histo #else #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" -@@ -492,6 +495,13 @@ void HistoryTabHelper::TitleWasSet(NavigationEntry* entry) { +@@ -511,6 +514,13 @@ void HistoryTabHelper::TitleWasSet(NavigationEntry* entry) { history::HistoryService* HistoryTabHelper::GetHistoryService() { Profile* profile = Profile::FromBrowserContext(web_contents()->GetBrowserContext()); @@ -1415,7 +1416,7 @@ diff --git a/chrome/browser/history/history_tab_helper.cc b/chrome/browser/histo if (profile->IsOffTheRecord()) return nullptr; -@@ -499,6 +509,16 @@ history::HistoryService* HistoryTabHelper::GetHistoryService() { +@@ -518,6 +528,16 @@ history::HistoryService* HistoryTabHelper::GetHistoryService() { profile, ServiceAccessType::IMPLICIT_ACCESS); } @@ -1764,7 +1765,7 @@ diff --git a/chrome/browser/offline_pages/request_coordinator_factory.h b/chrome diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -250,6 +250,7 @@ +@@ -245,6 +245,7 @@ #if BUILDFLAG(IS_ANDROID) #include "chrome/browser/accessibility/accessibility_prefs/android/accessibility_prefs_controller.h" @@ -1772,7 +1773,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse #include "chrome/browser/android/ntp/recent_tabs_page_prefs.h" #include "chrome/browser/android/oom_intervention/oom_intervention_decider.h" #include "chrome/browser/android/preferences/browser_prefs_android.h" -@@ -2105,6 +2106,10 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, +@@ -2010,6 +2011,10 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, usage_stats::UsageStatsBridge::RegisterProfilePrefs(registry); variations::VariationsService::RegisterProfilePrefs(registry); webapps::InstallPromptPrefs::RegisterProfilePrefs(registry); @@ -1933,7 +1934,7 @@ diff --git a/chrome/browser/ui/android/native_page/java/src/org/chromium/chrome/ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -6460,6 +6460,31 @@ To change this setting, BEGIN_LINKdelete the Chrome d +@@ -6528,6 +6528,31 @@ To change this setting, BEGIN_LINKdelete the Chrome d Cookies, cache, and other site data @@ -1990,7 +1991,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/messages/android/BUILD.gn b/chrome/browser/ui/messages/android/BUILD.gn --- a/chrome/browser/ui/messages/android/BUILD.gn +++ b/chrome/browser/ui/messages/android/BUILD.gn -@@ -27,6 +27,7 @@ android_library("java") { +@@ -28,6 +28,7 @@ android_library("java") { srcjar_deps = [ ":jni_headers" ] sources = [ "java/src/org/chromium/chrome/browser/ui/messages/infobar/SimpleConfirmInfoBarBuilder.java", @@ -2031,10 +2032,33 @@ new file mode 100644 +public interface INeedSnackbarManager { + void setSnackbarManagerSupplier(Supplier manager); +} +diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chrome/browser/ui/search_engines/search_engine_tab_helper.cc +--- a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc ++++ b/chrome/browser/ui/search_engines/search_engine_tab_helper.cc +@@ -12,6 +12,7 @@ + #include "chrome/browser/search_engines/template_url_fetcher_factory.h" + #include "chrome/browser/search_engines/template_url_service_factory.h" + #include "chrome/browser/ui/search_engines/edit_search_engine_controller.h" ++#include "chrome/common/pref_names.h" + #include "chrome/common/url_constants.h" + #include "components/search_engines/template_url.h" + #include "components/search_engines/template_url_fetcher.h" +@@ -165,6 +166,11 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( + + // Download the OpenSearch description document. If this is successful, a + // new keyword will be created when done. ++#if BUILDFLAG(IS_ANDROID) ++ if (profile->GetOriginalProfile()->GetPrefs()->GetBoolean(prefs::kIncognitoTabHistoryEnabled)) { ++ is_off_the_record = false; ++ } ++#endif + TemplateURLFetcherFactory::GetForProfile(profile)->ScheduleDownload( + keyword, osdd_url, entry->GetFavicon().url, + frame->GetLastCommittedOrigin(), url_loader_factory.get(), diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h -@@ -4149,6 +4149,12 @@ inline constexpr char kOutOfProcessSystemDnsResolutionEnabled[] = +@@ -4071,6 +4071,12 @@ inline constexpr char kOutOfProcessSystemDnsResolutionEnabled[] = "net.out_of_process_system_dns_resolution_enabled"; #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) @@ -2100,7 +2124,7 @@ diff --git a/components/content_settings/core/browser/content_settings_pref_prov diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc --- a/components/content_settings/core/browser/host_content_settings_map.cc +++ b/components/content_settings/core/browser/host_content_settings_map.cc -@@ -264,6 +264,7 @@ struct ContentSettingEntry { +@@ -247,6 +247,7 @@ struct ContentSettingEntry { HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs, bool is_off_the_record, @@ -2108,7 +2132,7 @@ diff --git a/components/content_settings/core/browser/host_content_settings_map. bool store_last_modified, bool restore_session, bool should_record_metrics) -@@ -273,6 +274,7 @@ HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs, +@@ -256,6 +257,7 @@ HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs, #endif prefs_(prefs), is_off_the_record_(is_off_the_record), @@ -2116,7 +2140,7 @@ diff --git a/components/content_settings/core/browser/host_content_settings_map. store_last_modified_(store_last_modified), allow_invalid_secondary_pattern_for_testing_(false), clock_(base::DefaultClock::GetInstance()) { -@@ -286,7 +288,7 @@ HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs, +@@ -269,7 +271,7 @@ HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs, policy_provider->AddObserver(this); auto pref_provider_ptr = std::make_unique( @@ -2136,7 +2160,7 @@ diff --git a/components/content_settings/core/browser/host_content_settings_map. bool store_last_modified, bool restore_session, bool should_record_metrics); -@@ -502,6 +503,8 @@ class HostContentSettingsMap : public content_settings::Observer, +@@ -505,6 +506,8 @@ class HostContentSettingsMap : public content_settings::Observer, // Whether this settings map is for an incognito or guest session. bool is_off_the_record_; diff --git a/build/cromite_patches/Add-autoplay-site-setting.patch b/build/cromite_patches/Add-autoplay-site-setting.patch index 5893c42f40b97c40c1a968532ea3e13327d76d64..98418797332109c203e391cdd345baac6a12b53b 100644 --- a/build/cromite_patches/Add-autoplay-site-setting.patch +++ b/build/cromite_patches/Add-autoplay-site-setting.patch @@ -292,8 +292,8 @@ diff --git a/third_party/blink/renderer/core/html/media/autoplay_policy.cc b/thi --- a/third_party/blink/renderer/core/html/media/autoplay_policy.cc +++ b/third_party/blink/renderer/core/html/media/autoplay_policy.cc @@ -9,6 +9,7 @@ + #include "third_party/blink/public/mojom/autoplay/autoplay.mojom-blink.h" #include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h" - #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink.h" #include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom-blink.h" +#include "third_party/blink/public/platform/web_content_settings_client.h" #include "third_party/blink/public/platform/web_media_player.h" diff --git a/build/cromite_patches/Add-bookmark-import-export-actions.patch b/build/cromite_patches/Add-bookmark-import-export-actions.patch index 118bc8f4a2e3ed523c3689fe8f8cfea817f0fd23..0313ff4ed633b2b6c202875c3bcdf83d555b8d74 100644 --- a/build/cromite_patches/Add-bookmark-import-export-actions.patch +++ b/build/cromite_patches/Add-bookmark-import-export-actions.patch @@ -14,20 +14,20 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html chrome/android/java/AndroidManifest.xml | 1 - .../menu/bookmark_toolbar_menu_improved.xml | 14 + .../browser/TabbedModeTabDelegateFactory.java | 5 +- - .../app/bookmarks/BookmarkActivity.java | 32 ++ - .../browser/bookmarks/BookmarkBridge.java | 285 ++++++++++++++++++ + .../app/bookmarks/BookmarkActivity.java | 31 ++ + .../native_page/NativePageFactory.java | 9 +- + chrome/browser/BUILD.gn | 11 +- + .../bookmarks/android/bookmark_bridge.cc | 267 +++++++++++++++++ + .../bookmarks/android/bookmark_bridge.h | 30 +- + .../browser/bookmarks/BookmarkBridge.java | 39 +++ .../browser/bookmarks/BookmarkDelegate.java | 10 + .../bookmarks/BookmarkManagerCoordinator.java | 9 + - .../bookmarks/BookmarkManagerMediator.java | 23 ++ - .../browser/bookmarks/BookmarkPage.java | 8 +- - .../browser/bookmarks/BookmarkToolbar.java | 28 ++ + .../bookmarks/BookmarkManagerMediator.java | 283 ++++++++++++++++++ + .../browser/bookmarks/BookmarkPage.java | 9 +- + .../browser/bookmarks/BookmarkToolbar.java | 27 ++ .../bookmarks/BookmarkToolbarMediator.java | 4 + .../bookmarks/BookmarkToolbarProperties.java | 8 +- .../bookmarks/BookmarkToolbarViewBinder.java | 6 + - .../native_page/NativePageFactory.java | 11 +- - chrome/browser/BUILD.gn | 11 +- - .../bookmarks/android/bookmark_bridge.cc | 267 ++++++++++++++++ - .../bookmarks/android/bookmark_bridge.h | 30 +- .../dialogs/DownloadLocationCustomView.java | 8 +- .../DownloadLocationDialogCoordinator.java | 10 +- .../flags/android/chrome_feature_list.cc | 1 + @@ -52,7 +52,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html ui/shell_dialogs/select_file_dialog_linux.cc | 4 + ui/shell_dialogs/select_file_dialog_linux.h | 2 + ui/shell_dialogs/select_file_dialog_win.cc | 5 + - 41 files changed, 888 insertions(+), 30 deletions(-) + 41 files changed, 900 insertions(+), 29 deletions(-) create mode 100644 cromite_flags/chrome/browser/about_flags_cc/add-bookmark-import-export-actions.inc create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/add-bookmark-import-export-actions.inc create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_h/add-bookmark-import-export-actions.inc @@ -103,7 +103,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDe import org.chromium.chrome.browser.app.tab_activity_glue.ActivityTabWebContentsDelegateAndroid; import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; import org.chromium.chrome.browser.compositor.CompositorViewHolder; -@@ -54,7 +55,7 @@ import org.chromium.ui.modaldialog.ModalDialogManager; +@@ -55,7 +56,7 @@ import org.chromium.ui.modaldialog.ModalDialogManager; * {@link ChromeTabbedActivity}. */ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { @@ -112,7 +112,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDe private final BrowserControlsVisibilityDelegate mAppBrowserControlsVisibilityDelegate; private final Supplier mShareDelegateSupplier; private final Supplier mEphemeralTabCoordinatorSupplier; -@@ -83,7 +84,7 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { +@@ -85,7 +86,7 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { private NativePageFactory mNativePageFactory; public TabbedModeTabDelegateFactory( @@ -124,7 +124,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDe diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java -@@ -25,6 +25,9 @@ import org.chromium.components.embedder_support.util.UrlConstants; +@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.app.bookmarks; + import android.content.ComponentName; + import android.content.Intent; + import android.text.TextUtils; ++import android.os.Bundle; + + import org.chromium.base.IntentUtils; + import org.chromium.build.annotations.Nullable; +@@ -28,6 +29,9 @@ import org.chromium.components.embedder_support.util.UrlConstants; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType; @@ -134,17 +142,17 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/B /** * The activity that displays the bookmark UI on the phone. It keeps a {@link * BookmarkManagerCoordinator} inside of it and creates a snackbar manager. This activity should -@@ -36,6 +39,9 @@ public class BookmarkActivity extends SnackbarActivity { - public static final int EDIT_BOOKMARK_REQUEST_CODE = 14; - public static final String INTENT_VISIT_BOOKMARK_ID = "BookmarkEditActivity.VisitBookmarkId"; +@@ -41,6 +45,9 @@ public class BookmarkActivity extends SnackbarActivity { + private @Nullable BookmarkManagerCoordinator mBookmarkManagerCoordinator; + private @Nullable BookmarkOpener mBookmarkOpener; + private ActivityWindowAndroid mWindowAndroid; + private IntentRequestTracker mIntentRequestTracker; + @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); -@@ -62,8 +68,25 @@ public class BookmarkActivity extends SnackbarActivity { + protected void onProfileAvailable(Profile profile) { + super.onProfileAvailable(profile); +@@ -70,6 +77,14 @@ public class BookmarkActivity extends SnackbarActivity { getOnBackPressedDispatcher(), mBookmarkManagerCoordinator, SecondaryActivity.BOOKMARK); @@ -154,23 +162,12 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/B + mWindowAndroid = new ActivityWindowAndroid(this, listenToActivityState, + mIntentRequestTracker, /*InsetObserver*/ null, + /* trackOcclusion= */ true); -+ mWindowAndroid.getIntentRequestTracker().restoreInstanceState(savedInstanceState); + mBookmarkManagerCoordinator.setWindow(mWindowAndroid, -+ new ModalDialogManager( -+ new AppModalPresenter(this), ModalDialogManager.ModalDialogType.APP)); ++ getModalDialogManagerSupplier().get()); } -+ @Override -+ protected void onSaveInstanceState(Bundle outState) { -+ super.onSaveInstanceState(outState); -+ -+ mWindowAndroid.getIntentRequestTracker().saveInstanceState(outState); -+ } -+ @Override - protected void onDestroy() { - super.onDestroy(); -@@ -73,6 +96,7 @@ public class BookmarkActivity extends SnackbarActivity { +@@ -89,6 +104,7 @@ public class BookmarkActivity extends SnackbarActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -178,10 +175,17 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/B if (requestCode == EDIT_BOOKMARK_REQUEST_CODE && resultCode == RESULT_OK) { BookmarkId bookmarkId = BookmarkId.getBookmarkIdFromString( -@@ -86,6 +110,14 @@ public class BookmarkActivity extends SnackbarActivity { - return new ModalDialogManager(new AppModalPresenter(this), ModalDialogType.APP); +@@ -97,6 +113,21 @@ public class BookmarkActivity extends SnackbarActivity { + } } ++ @Override ++ protected void onSaveInstanceState(Bundle outState) { ++ super.onSaveInstanceState(outState); ++ ++ mWindowAndroid.getIntentRequestTracker().saveInstanceState(outState); ++ } ++ + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { @@ -190,1041 +194,1060 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/B + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + - /** - * @return The {@link BookmarkManagerCoordinator} for testing purposes. - */ -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java -@@ -34,6 +34,48 @@ import org.chromium.url.GURL; - import java.util.ArrayList; - import java.util.List; + @Override + protected ModalDialogManager createModalDialogManager() { + return new ModalDialogManager(new AppModalPresenter(this), ModalDialogType.APP); +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java +@@ -18,6 +18,7 @@ import org.chromium.base.supplier.ObservableSupplier; + import org.chromium.base.supplier.OneshotSupplier; + import org.chromium.base.supplier.Supplier; + import org.chromium.chrome.R; ++import org.chromium.chrome.browser.app.ChromeActivity; + import org.chromium.chrome.browser.app.download.home.DownloadPage; + import org.chromium.chrome.browser.bookmarks.BookmarkPage; + import org.chromium.chrome.browser.browser_controls.BrowserControlsMarginSupplier; +@@ -59,7 +60,7 @@ import org.chromium.ui.util.ColorUtils; + * Creates NativePage objects to show chrome-native:// URLs using the native Android view system. + */ + public class NativePageFactory { +- private final Activity mActivity; ++ private final ChromeActivity mActivity; + private final BottomSheetController mBottomSheetController; + private final BrowserControlsManager mBrowserControlsManager; + private final Supplier mCurrentTabSupplier; +@@ -82,7 +83,7 @@ public class NativePageFactory { + private static NativePage sTestPage; -+import android.app.Activity; -+import android.content.Intent; -+import android.content.Context; -+import android.content.pm.PackageManager; -+import android.content.DialogInterface; -+import android.content.res.Resources; -+import android.content.ContentResolver; -+import android.net.Uri; -+import android.provider.Browser; -+import android.provider.DocumentsContract; -+import android.Manifest.permission; -+import android.view.View; -+import android.view.LayoutInflater; -+ -+import androidx.appcompat.app.AlertDialog; -+import android.os.Build; -+ -+import java.io.File; -+ -+import org.chromium.base.ContentUriUtils; -+import org.chromium.base.shared_preferences.SharedPreferencesManager; -+import org.chromium.base.task.AsyncTask; -+import org.chromium.chrome.R; -+import org.chromium.chrome.browser.document.ChromeLauncherActivity; -+import org.chromium.chrome.browser.IntentHandler; -+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; -+import org.chromium.chrome.browser.download.DownloadLocationDialogType; -+import org.chromium.chrome.browser.download.settings.DownloadLocationHelperImpl; -+import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogController; -+import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogCoordinator; -+import org.chromium.chrome.browser.download.dialogs.DownloadLocationCustomView; -+import org.chromium.chrome.browser.download.DirectoryOption; -+import org.chromium.chrome.browser.profiles.ProfileManager; -+import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; -+import org.chromium.chrome.browser.flags.ChromeFeatureList; -+import org.chromium.ui.base.PageTransition; -+import org.chromium.ui.base.WindowAndroid; -+import org.chromium.ui.modelutil.PropertyModel; -+import org.chromium.ui.modaldialog.ModalDialogManager; -+import org.chromium.ui.modaldialog.ModalDialogProperties; -+import org.chromium.ui.modaldialog.DialogDismissalCause; + public NativePageFactory( +- @NonNull Activity activity, ++ @NonNull ChromeActivity activity, + @NonNull BottomSheetController sheetController, + @NonNull BrowserControlsManager browserControlsManager, + @NonNull Supplier currentTabSupplier, +@@ -154,7 +155,7 @@ public class NativePageFactory { + + @VisibleForTesting + static class NativePageBuilder { +- private final Activity mActivity; ++ private final ChromeActivity mActivity; + private final BottomSheetController mBottomSheetController; + private final Supplier mUma; + private final BrowserControlsManager mBrowserControlsManager; +@@ -251,7 +252,7 @@ public class NativePageFactory { + mSnackbarManagerSupplier.get(), + tab.getProfile(), + new TabShim(tab, mBrowserControlsManager, mTabModelSelector), +- mActivity.getComponentName()); ++ mActivity.getComponentName(), mActivity); + } + + protected NativePage buildDownloadsPage(Tab tab) { +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +--- a/chrome/browser/BUILD.gn ++++ b/chrome/browser/BUILD.gn +@@ -221,6 +221,8 @@ static_library("browser") { + "bluetooth/chrome_bluetooth_delegate_impl_client.h", + "bookmarks/bookmark_model_factory.cc", + "bookmarks/bookmark_model_factory.h", ++ "bookmarks/bookmark_html_writer.cc", ++ "bookmarks/bookmark_html_writer.h", + "bookmarks/chrome_bookmark_client.cc", + "bookmarks/chrome_bookmark_client.h", + "bookmarks/managed_bookmark_service_factory.cc", +@@ -1713,6 +1715,13 @@ static_library("browser") { + ] + } + ++ if (is_android) { ++ sources += [ ++ "importer/profile_writer.cc", ++ "importer/profile_writer.h", ++ ] ++ } + - /** - * Provides the communication channel for Android to fetch and manipulate the bookmark model stored - * in native. -@@ -490,6 +532,245 @@ class BookmarkBridge { - .getTotalBookmarkCount(mNativeBookmarkBridge, id.getId(), id.getType()); - } + configs += [ + "//build/config/compiler:wexit_time_destructors", + "//build/config:precompiled_headers", +@@ -3535,8 +3544,6 @@ static_library("browser") { + "bookmarks/bookmark_expanded_state_tracker.h", + "bookmarks/bookmark_expanded_state_tracker_factory.cc", + "bookmarks/bookmark_expanded_state_tracker_factory.h", +- "bookmarks/bookmark_html_writer.cc", +- "bookmarks/bookmark_html_writer.h", + "bookmarks/bookmark_merged_surface_ordering_storage.cc", + "bookmarks/bookmark_merged_surface_ordering_storage.h", + "bookmarks/bookmark_merged_surface_service.cc", +diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc +--- a/chrome/browser/bookmarks/android/bookmark_bridge.cc ++++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc +@@ -66,6 +66,28 @@ + #include "content/public/browser/web_contents.h" + #include "url/gurl.h" -+ @CalledByNative -+ public void bookmarksExported(WindowAndroid window, String bookmarksPath, boolean success) { -+ Uri uri = Uri.parse(bookmarksPath); ++#include "base/android/content_uri_utils.h" ++#include "base/android/path_utils.h" ++#include "base/strings/utf_string_conversions.h" ++#include "chrome/utility/importer/bookmark_html_reader.h" ++#include "chrome/browser/bookmarks/bookmark_html_writer.h" ++#include "chrome/browser/importer/profile_writer.h" ++#include "chrome/browser/platform_util.h" ++#include "chrome/browser/ui/chrome_select_file_policy.h" ++#include "chrome/common/importer/imported_bookmark_entry.h" ++#include "chrome/common/importer/importer_data_types.h" ++#include "chrome/common/url_constants.h" ++#include "components/favicon_base/favicon_usage_data.h" ++#include "components/search_engines/template_url.h" ++#include "components/url_formatter/url_fixer.h" ++#include "ui/android/window_android.h" ++#include "base/task/task_traits.h" ++#include "base/task/thread_pool.h" ++#include "content/public/browser/browser_task_traits.h" ++#include "base/files/file_path.h" ++#include "ui/shell_dialogs/selected_file_info.h" ++#include "ui/shell_dialogs/select_file_dialog.h" + -+ if (success == false) { -+ ((Activity)window.getContext().get()).runOnUiThread(new Runnable() { -+ public void run() { -+ window.showError(R.string.saving_file_error); -+ } -+ }); -+ } else { -+ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance(); -+ sharedPrefs.writeString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, bookmarksPath); + // Must come after all headers that specialize FromJniType() / ToJniType(). + #include "chrome/browser/bookmarks/android/jni_headers/BookmarkBridge_jni.h" + +@@ -84,6 +106,57 @@ using bookmarks::android::JavaBookmarkIdGetType; + using content::BrowserThread; + using power_bookmarks::PowerBookmarkMeta; + ++namespace internal { + -+ Context context = ContextUtils.getApplicationContext(); ++// Returns true if |url| has a valid scheme that we allow to import. We ++// filter out the URL with a unsupported scheme. ++bool CanImportURL(const GURL& url) { ++ // The URL is not valid. ++ if (!url.is_valid()) ++ return false; + -+ Intent intent = new Intent(Intent.ACTION_VIEW, -+ ContentUriUtils.isContentUri(bookmarksPath) ? -+ Uri.parse(bookmarksPath) : Uri.parse("file://" + bookmarksPath)); -+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, -+ context.getPackageName()); -+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); -+ intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, PageTransition.AUTO_BOOKMARK); ++ // Filter out the URLs with unsupported schemes. ++ const char* const kInvalidSchemes[] = {"wyciwyg", "place"}; ++ for (size_t i = 0; i < std::size(kInvalidSchemes); ++i) { ++ if (url.SchemeIs(kInvalidSchemes[i])) ++ return false; ++ } + -+ // If the bookmark manager is shown in a tab on a phone (rather than in a separate -+ // activity) the component name may be null. Send the intent through -+ // ChromeLauncherActivity instead to avoid crashing. See crbug.com/615012. -+ intent.setClass(context, ChromeLauncherActivity.class); ++ // Check if |url| is about:blank. ++ if (url == url::kAboutBlankURL) ++ return true; + -+ IntentHandler.startActivityForTrustedIntent(intent); -+ } -+ } ++ // If |url| starts with chrome:// or about:, check if it's one of the URLs ++ // that we support. ++ if (url.SchemeIs(content::kChromeUIScheme) || ++ url.SchemeIs(url::kAboutScheme)) { ++ if (url.host_piece() == chrome::kChromeUIAboutHost) ++ return true; + -+ /** -+ * Import bookmarks from a selected file. -+ * @param window The current window of the bookmarks activity or page. -+ */ -+ public void importBookmarks(WindowAndroid window) { -+ assert mIsNativeBookmarkModelLoaded; -+ BookmarkBridgeJni.get().importBookmarks(mNativeBookmarkBridge, BookmarkBridge.this, window); ++ GURL fixed_url(url_formatter::FixupURL(url.spec(), std::string())); ++ const base::span hosts = chrome::ChromeURLHosts(); ++ for (const base::cstring_view host : hosts) { ++ if (fixed_url.DomainIs(host)) { ++ return true; ++ } + } + -+ /** -+ * Export bookmarks to a path selected by the user. -+ * @param window The current window of the bookmarks activity or page. -+ */ -+ public void exportBookmarks(WindowAndroid window, ModalDialogManager modalDialogManager) { -+ assert mIsNativeBookmarkModelLoaded; -+ if (ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_EXPORT_USESAF) || -+ Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) -+ exportBookmarksImplUseSaf(window); -+ else -+ exportBookmarksImplUseFile(window, modalDialogManager); ++ if (base::Contains(chrome::ChromeDebugURLs(), fixed_url.spec())) { ++ return true; + } + -+ private void exportBookmarksImplUseSaf(WindowAndroid window) { -+ Context context = window.getContext().get(); -+ -+ // standard name for boorkmark file -+ final String standardBoorkmarkName = "bookmarks.html"; ++ // If url has either chrome:// or about: schemes but wasn't found in the ++ // above lists, it means we don't support it, so we don't allow the user ++ // to import it. ++ return false; ++ } + -+ // use the fileSelector and saf asking user for the file -+ Intent fileSelector = new Intent(Intent.ACTION_CREATE_DOCUMENT); -+ fileSelector.addCategory(Intent.CATEGORY_OPENABLE); -+ fileSelector.setType("text/html"); -+ fileSelector.putExtra(Intent.EXTRA_TITLE, standardBoorkmarkName); -+ fileSelector.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | -+ Intent.FLAG_GRANT_READ_URI_PERMISSION | -+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); ++ // Otherwise, we assume the url has a valid (importable) scheme. ++ return true; ++} + -+ // get last exported uri path, if any -+ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance(); -+ String bookmarksPath = sharedPrefs.readString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, standardBoorkmarkName); -+ Uri lastSelectedUri = Uri.parse(bookmarksPath); ++} // internal + -+ // prepare delegate for file selector -+ DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { -+ @Override -+ public void onClick(DialogInterface dialog, int button) { -+ if (button == AlertDialog.BUTTON_NEGATIVE) { -+ window.showIntent(fileSelector, -+ new WindowAndroid.IntentCallback() { -+ @Override -+ public void onIntentCompleted(int resultCode, Intent data) { -+ if (data == null) return; -+ Uri filePath = data.getData(); -+ doExportBookmarksImpl(window, filePath); -+ } -+ }, -+ null); -+ } else { -+ if (dialog!=null) dialog.dismiss(); -+ doExportBookmarksImpl(window, lastSelectedUri); -+ } -+ } -+ }; + namespace { + // The key used to connect the instance of the bookmark bridge to the bookmark + // model. +@@ -231,6 +304,10 @@ BookmarkBridge::~BookmarkBridge() { + partner_bookmarks_shim_observation_.Reset(); + bookmark_model_observation_.Reset(); + profile_observation_.Reset(); ++ // There may be pending file dialogs, we need to tell them that we've gone ++ // away so they don't try and call back to us. ++ if (select_file_dialog_) ++ select_file_dialog_->ListenerDestroyed(); + } + + void BookmarkBridge::Destroy(JNIEnv* env) { +@@ -765,6 +842,196 @@ jint BookmarkBridge::GetTotalBookmarkCount( + return count; + } + ++void BookmarkBridge::ImportBookmarks(JNIEnv* env, ++ const JavaParamRef& obj, ++ const JavaParamRef& java_window) { ++ DCHECK(IsLoaded()); ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + -+ // as a workaround for https://issuetracker.google.com/issues/37136466 -+ // ask to overwrite if is a valid uri and the file is present -+ if (DocumentsContract.isDocumentUri(context, lastSelectedUri)) { -+ AsyncTask checkUriTask = new AsyncTask() { -+ boolean uriExists = false; -+ String actualFilePath = null; ++ ui::WindowAndroid* window = ++ ui::WindowAndroid::FromJavaWindowAndroid(java_window); ++ CHECK(window); + -+ @Override -+ protected Void doInBackground() { -+ uriExists = ContentUriUtils.contentUriExists(lastSelectedUri.toString()); -+ if (uriExists) { -+ actualFilePath = ContentUriUtils.getFilePathFromContentUri(lastSelectedUri); -+ // get real actual file name on disk -+ if (actualFilePath==null) actualFilePath = lastSelectedUri.toString(); -+ // set file name to last exported file name -+ fileSelector.putExtra(Intent.EXTRA_TITLE, -+ ContentUriUtils.getDisplayName(lastSelectedUri, context, -+ DocumentsContract.Document.COLUMN_DISPLAY_NAME)); -+ } -+ return null; -+ } ++ select_file_dialog_ = ui::SelectFileDialog::Create( ++ this, std::make_unique(nullptr)); + -+ @Override -+ protected void onPostExecute(Void result) { -+ // check for permissions -+ if (uriExists) { -+ AlertDialog.Builder alert = -+ new AlertDialog.Builder(context, R.style.ThemeOverlay_BrowserUI_AlertDialog); -+ AlertDialog alertDialog = -+ alert.setTitle(R.string.export_bookmarks_alert_title) -+ .setMessage(context.getString(R.string.export_bookmarks_alert_message, actualFilePath)) -+ .setPositiveButton( -+ R.string.export_bookmarks_alert_message_yes, onClickListener) -+ .setNegativeButton(R.string.export_bookmarks_alert_message_no, onClickListener) -+ .create(); -+ alertDialog.getDelegate().setHandleNativeActionModesEnabled(false); ++ //NOTE: extension and description are not used on Android, thus not set ++ ui::SelectFileDialog::FileTypeInfo file_type_info; + -+ // show dialog asking for overwrite -+ alertDialog.show(); -+ return; -+ } else { -+ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE); -+ } -+ } -+ }; -+ checkUriTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); -+ return; -+ } ++ const std::vector v_accept_types = { u"text/html" }; ++ select_file_dialog_->SetAcceptTypes(v_accept_types); + -+ // actually open the file selector -+ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE); -+ } ++ select_file_dialog_->SelectFile( ++ ui::SelectFileDialog::SELECT_OPEN_FILE, ++ std::u16string(), ++ export_path_, ++ &file_type_info, ++ 0, ++ base::FilePath::StringType(), ++ window ++ ); ++} + -+ private void doExportBookmarksImpl(WindowAndroid window, Uri filePath) { -+ ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver(); -+ // since we want to persist the uri in settings, ask for persistable permissions -+ resolver.takePersistableUriPermission(filePath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | -+ Intent.FLAG_GRANT_READ_URI_PERMISSION); ++void BookmarkBridge::ExportBookmarks(JNIEnv* env, ++ const JavaParamRef& obj, ++ const JavaParamRef& java_window, ++ const JavaParamRef& j_export_path) { ++ DCHECK(IsLoaded()); ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + -+ BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge, BookmarkBridge.this, -+ window, filePath.toString()); -+ } ++ ui::WindowAndroid* window = ++ ui::WindowAndroid::FromJavaWindowAndroid(java_window); ++ CHECK(window); + -+ private void exportBookmarksImplUseFile(WindowAndroid window, ModalDialogManager modalDialogManager) { -+ Context context = window.getContext().get(); ++ std::u16string export_path = ++ base::android::ConvertJavaStringToUTF16(env, j_export_path); + -+ // standard name for boorkmark file -+ final String standardBoorkmarkName = "bookmarks.html"; ++ export_path_ = base::FilePath::FromUTF16Unsafe(export_path); + -+ // use the download ui and standard file saving -+ DownloadLocationDialogController controller = new DownloadLocationDialogController() { -+ @Override -+ public void onDownloadLocationDialogComplete(String returnedPath) {} ++ if (export_path_.empty()) { ++ if (!base::android::GetDownloadsDirectory(&export_path_)) { ++ LOG(ERROR) << "Could not retrieve downloads directory for bookmarks export"; ++ return; ++ } ++ export_path_ = export_path_.Append(FILE_PATH_LITERAL("bookmarks.html")); ++ } + -+ @Override -+ public void onDownloadLocationDialogCanceled() {} -+ }; ++ bookmark_html_writer::WriteBookmarks(profile_, export_path_, ++ base::BindOnce(&BookmarkBridge::ExportBookmarksEnd, ++ weak_ptr_factory_.GetWeakPtr(), ++ base::android::ScopedJavaGlobalRef(obj), ++ window)); ++} + -+ DownloadLocationDialogCoordinator dialog = new DownloadLocationDialogCoordinator() { -+ @Override -+ protected void onDirectoryOptionsRetrieved(ArrayList dirs) { -+ if (mDialogModel != null) return; ++void BookmarkBridge::ExportBookmarksEnd(const ScopedJavaGlobalRef& obj, ++ ui::WindowAndroid* window, ++ bookmark_html_writer::Result result) const ++{ ++ auto export_path = export_path_.MaybeAsASCII(); ++ if (result == bookmark_html_writer::Result::kSuccess) { ++ LOG(INFO) << "Bookmarks exported successfully to " << export_path; ++ } else if (result == bookmark_html_writer::Result::kCouldNotCreateFile) { ++ LOG(ERROR) << "Bookmarks export: could not create file " << export_path; ++ } else if (result == bookmark_html_writer::Result::kCouldNotWriteHeader) { ++ LOG(ERROR) << "Bookmarks export: could not write header"; ++ } else if (result == bookmark_html_writer::Result::kCouldNotWriteNodes) { ++ LOG(ERROR) << "Bookmarks export: could not write nodes"; ++ } + -+ // Actually show the dialog. -+ mCustomView = (DownloadLocationCustomView) LayoutInflater.from(context).inflate( -+ R.layout.download_location_dialog, null); -+ mCustomView.initialize(DownloadLocationDialogType.DEFAULT, /*totalBytes*/ 0, -+ (isChecked) -> {}, -+ new DownloadLocationHelperImpl(mProfile)); -+ mCustomView.setTitle(context.getString(R.string.export_bookmarks_alert_title)); -+ mCustomView.setFileName(standardBoorkmarkName); -+ mCustomView.mDontShowAgain.setVisibility(View.GONE); ++ JNIEnv* env = AttachCurrentThread(); ++ Java_BookmarkBridge_bookmarksExported(env, obj, window->GetJavaObject(), ++ base::android::ConvertUTF8ToJavaString(env, export_path), ++ result == bookmark_html_writer::Result::kSuccess); ++} + -+ Resources resources = context.getResources(); -+ mDialogModel = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) -+ .with(ModalDialogProperties.CONTROLLER, this) -+ .with(ModalDialogProperties.CUSTOM_VIEW, mCustomView) -+ .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources, -+ R.string.export_bookmarks) -+ .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources, -+ R.string.cancel) -+ .build(); ++// Attempts to create a TemplateURL from the provided data. |title| is optional. ++// If TemplateURL creation fails, returns null. ++std::unique_ptr CreateTemplateURL(const std::u16string& url, ++ const std::u16string& keyword, ++ const std::u16string& title) { ++ if (url.empty() || keyword.empty()) ++ return nullptr; ++ TemplateURLData data; ++ data.SetKeyword(keyword); ++ // We set short name by using the title if it exists. ++ // Otherwise, we use the shortcut. ++ data.SetShortName(title.empty() ? keyword : title); ++ data.SetURL(TemplateURLRef::DisplayURLToURLRef(url)); ++ return std::make_unique(data); ++} + -+ mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.APP); -+ } ++void BookmarkBridge::FileSelected(const ui::SelectedFileInfo& file, int index) { ++ base::FilePath path = file.path(); ++ base::ThreadPool::PostTaskAndReplyWithResult( ++ FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, ++ base::BindOnce(&BookmarkBridge::FileSelectedImpl, ++ base::Unretained(this), ++ path), ++ base::BindOnce(&BookmarkBridge::FileSelectedImplOnUIThread, ++ base::Unretained(this), ++ path)); ++} + -+ @Override -+ public void onDismiss(PropertyModel model, int dismissalCause) { -+ switch (dismissalCause) { -+ case DialogDismissalCause.POSITIVE_BUTTON_CLICKED: -+ { -+ String fileName = mCustomView.getFileName(); -+ String directory = mCustomView.getDirectoryOption().location; -+ if (fileName != null && directory != null) { -+ File file = new File(directory, fileName); ++const std::string BookmarkBridge::FileSelectedImpl(const base::FilePath& path) { ++ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); ++ if (!file.IsValid()) { ++ select_file_dialog_->ShowToast("Cannot open bookmarks file for import"); ++ return ""; ++ } + -+ if (window.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) { -+ BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge, -+ BookmarkBridge.this, window, file.getPath()); -+ } else { -+ String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE}; -+ window.requestPermissions(requestPermissions, (permissions, grantResults) -> { -+ if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { -+ BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge, -+ BookmarkBridge.this, window, file.getPath()); -+ } -+ }); -+ }; -+ } -+ } -+ break; -+ } -+ mDialogModel = null; -+ mCustomView = null; -+ } -+ }; -+ dialog.initialize(controller); -+ dialog.showDialog(context, modalDialogManager, /*totalBytes*/ 0, -+ DownloadLocationDialogType.DEFAULT, /*suggestedPath*/ "", -+ ProfileManager.getLastUsedRegularProfile()); -+ } ++ auto fileLength = file.GetLength(); ++ if (-1 == fileLength) { ++ select_file_dialog_->ShowToast("Cannot read bookmarks file length"); ++ return ""; ++ } + - /** - * Synchronously gets a list of bookmarks that match the specified search query. - * -@@ -1108,6 +1389,10 @@ class BookmarkBridge { - void getChildIds( - long nativeBookmarkBridge, long id, int type, List bookmarksList); - -+ void importBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window); -+ void exportBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window, -+ String export_path); ++ if (fileLength > 10 * 1024 * 1024) { ++ select_file_dialog_->ShowToast("Bookmark file is bigger than 10MB"); ++ return ""; ++ } + - BookmarkId getChildAt(long nativeBookmarkBridge, long id, int type, int index); - - int getTotalBookmarkCount(long nativeBookmarkBridge, long id, int type); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java -@@ -65,6 +65,16 @@ public interface BookmarkDelegate { - /** Shows the search UI. */ - void openSearchUi(); - -+ /** -+ * Imports bookmarks from user-selected file. -+ */ -+ void importBookmarks(); -+ -+ /** -+ * Exports bookmarks to downloads directory. -+ */ -+ void exportBookmarks(); ++ std::vector buffer(fileLength); ++ if (-1 == file.ReadAtCurrentPos(buffer.data(), fileLength)) { ++ select_file_dialog_->ShowToast("Could not read bookmarks file"); ++ return ""; ++ } + - /** Add an observer to bookmark UI changes. */ - void addUiObserver(BookmarkUiObserver observer); - -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java -@@ -54,6 +54,8 @@ import org.chromium.components.image_fetcher.ImageFetcher; - import org.chromium.components.image_fetcher.ImageFetcherConfig; - import org.chromium.components.image_fetcher.ImageFetcherFactory; - import org.chromium.ui.KeyboardVisibilityDelegate; -+import org.chromium.ui.base.ActivityWindowAndroid; -+import org.chromium.ui.modaldialog.ModalDialogManager; - import org.chromium.ui.modaldialog.ModalDialogManager; - import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType; - import org.chromium.ui.modelutil.MVCListAdapter.ModelList; -@@ -326,6 +328,13 @@ public class BookmarkManagerCoordinator - - // Public API implementation. - -+ /** -+ * Sets the Android window that is used by further intents created by the bookmark activity. -+ */ -+ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) { -+ mMediator.setWindow(window, modalDialogManager); -+ } ++ if (buffer.empty()) { ++ select_file_dialog_->ShowToast("Empty bookmarks file"); ++ return ""; ++ } + - /** Destroys and cleans up itself. This must be called after done using this class. */ - public void onDestroyed() { - RecordUserAction.record("MobileBookmarkManagerClose"); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java -@@ -58,6 +58,8 @@ import org.chromium.components.commerce.core.SubscriptionsObserver; - import org.chromium.components.embedder_support.util.UrlConstants; - import org.chromium.components.power_bookmarks.PowerBookmarkMeta; - import org.chromium.components.power_bookmarks.PowerBookmarkType; -+import org.chromium.ui.base.ActivityWindowAndroid; -+import org.chromium.ui.modaldialog.ModalDialogManager; - import org.chromium.ui.accessibility.AccessibilityState; - import org.chromium.ui.listmenu.ListMenu; - import org.chromium.ui.listmenu.ListMenuItemProperties; -@@ -85,6 +87,9 @@ class BookmarkManagerMediator - - private static boolean sPreventLoadingForTesting; - -+ private ActivityWindowAndroid mWindowAndroid; -+ private ModalDialogManager mModalDialogManager; ++ std::string contents(buffer.begin(), buffer.end()); ++ return contents; ++} + - /** Keeps track of whether drag is enabled / active for bookmark lists. */ - private class BookmarkDragStateDelegate implements DragStateDelegate { - private BookmarkDelegate mBookmarkDelegate; -@@ -574,6 +579,14 @@ class BookmarkManagerMediator - mNativePage = nativePage; - } - -+ /** -+ * Sets the Android window that is used by further intents created by the bookmark activity. -+ */ -+ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) { -+ mWindowAndroid = window; -+ mModalDialogManager = modalDialogManager; -+ } ++void BookmarkBridge::FileSelectedImplOnUIThread(const base::FilePath& path, ++ const std::string& contents) { ++ if (contents.empty()) ++ return; + - /** See BookmarkManager(Coordinator)#updateForUrl */ - void updateForUrl(String url) { - // Bookmark model is null if the manager has been destroyed. -@@ -766,6 +779,16 @@ class BookmarkManagerMediator - } - } - -+ @Override -+ public void importBookmarks() { -+ mBookmarkModel.importBookmarks(mWindowAndroid); -+ } ++ // the following import logic comes from BookmarksFileImporter class ++ std::vector bookmarks; ++ std::vector search_engines; ++ favicon_base::FaviconUsageDataList favicons; + -+ @Override -+ public void exportBookmarks() { -+ mBookmarkModel.exportBookmarks(mWindowAndroid, mModalDialogManager); -+ } ++ bookmark_html_reader::ImportBookmarksFile( ++ base::RepeatingCallback(), ++ base::BindRepeating(internal::CanImportURL), ++ contents, ++ &bookmarks, ++ &search_engines, ++ &favicons); + - @Override - public void openSearchUi() { - onSearchTextChangeCallback(""); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java -@@ -13,6 +13,9 @@ import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; - import org.chromium.chrome.browser.ui.native_page.BasicNativePage; - import org.chromium.chrome.browser.ui.native_page.NativePageHost; - import org.chromium.components.embedder_support.util.UrlConstants; -+import org.chromium.chrome.browser.app.ChromeActivity; -+import org.chromium.ui.modaldialog.ModalDialogManager; -+import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; - - /** A native page holding a {@link BookmarkManagerCoordinator} on _tablet_. */ - public class BookmarkPage extends BasicNativePage { -@@ -31,7 +34,7 @@ public class BookmarkPage extends BasicNativePage { - ComponentName componentName, - SnackbarManager snackbarManager, - Profile profile, -- NativePageHost host) { -+ NativePageHost host, ChromeActivity activity) { - super(host); - - mBookmarkManagerCoordinator = -@@ -44,6 +47,9 @@ public class BookmarkPage extends BasicNativePage { - new BookmarkUiPrefs(ChromeSharedPreferences.getInstance()), - /* bookmarkOpenedCallback= */ null); - mBookmarkManagerCoordinator.setBasicNativePage(this); -+ mBookmarkManagerCoordinator.setWindow(activity.getWindowAndroid(), -+ new ModalDialogManager( -+ new AppModalPresenter(activity), ModalDialogManager.ModalDialogType.APP)); - mTitle = host.getContext().getString(R.string.bookmarks); - - initWithView(mBookmarkManagerCoordinator.getView()); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java -@@ -98,6 +98,17 @@ public class BookmarkToolbar extends SelectableListToolbar - setOnMenuItemClickListener(dragEnabled ? null : this); - } - -+ private Runnable mImportBookmarkRunnable; -+ private Runnable mExportBookmarkRunnable; ++ auto *writer = new ProfileWriter(profile_); + -+ void setImportBookmarkRunnable(Runnable runnable) { -+ mImportBookmarkRunnable = runnable; ++ if (!bookmarks.empty()) { ++ // adding bookmarks will begin extensive changes to the model ++ writer->AddBookmarksWithModel(bookmark_model_, bookmarks, u"Imported"); ++ } ++ if (!search_engines.empty()) { ++ TemplateURLService::OwnedTemplateURLVector owned_template_urls; ++ for (const auto& search_engine : search_engines) { ++ std::unique_ptr owned_template_url = CreateTemplateURL( ++ search_engine.url, search_engine.keyword, search_engine.display_name); ++ if (owned_template_url) ++ owned_template_urls.push_back(std::move(owned_template_url)); + } ++ writer->AddKeywords(std::move(owned_template_urls), false); ++ } + -+ void setExportBookmarkRunnable(Runnable runnable) { -+ mExportBookmarkRunnable = runnable; -+ } ++ std::stringstream message; ++ message << "Imported " << bookmarks.size() << " bookmarks and " << ++ search_engines.size() << " search engines from " << path.MaybeAsASCII(); ++ auto result = message.str(); + - void setEditButtonVisible(boolean visible) { - mEditButtonVisible = visible; - getMenu().findItem(R.id.edit_menu_id).setVisible(visible); -@@ -174,6 +185,13 @@ public class BookmarkToolbar extends SelectableListToolbar - - void setCurrentFolder(BookmarkId folder) { - mCurrentFolder = mBookmarkModel.getBookmarkById(folder); -+ enableImportExportMenu(); -+ } ++ select_file_dialog_->ShowToast(result); + -+ void enableImportExportMenu() { -+ getMenu().findItem(R.id.import_menu_id).setVisible(true); -+ getMenu().findItem(R.id.export_menu_id).setVisible(true); -+ } - } - - void setNavigateBackRunnable(Runnable navigateBackRunnable) { -@@ -193,6 +211,13 @@ public class BookmarkToolbar extends SelectableListToolbar - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - hideOverflowMenu(); -+ if (menuItem.getItemId() == R.id.import_menu_id) { -+ mImportBookmarkRunnable.run(); -+ return true; -+ } else if (menuItem.getItemId() == R.id.export_menu_id) { -+ mExportBookmarkRunnable.run(); -+ return true; -+ } - return mMenuIdClickedFunction.apply(menuItem.getItemId()); - } - -@@ -208,6 +233,9 @@ public class BookmarkToolbar extends SelectableListToolbar - protected void showNormalView() { - super.showNormalView(); - -+ getMenu().findItem(R.id.import_menu_id).setVisible(mCurrentFolder != null); -+ getMenu().findItem(R.id.export_menu_id).setVisible(mCurrentFolder != null); ++ LOG(INFO) << result; ++} + - // SelectableListToolbar will show/hide the entire group. - setEditButtonVisible(mEditButtonVisible); - setNewFolderButtonVisible(mNewFolderButtonVisible); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java -@@ -132,6 +132,10 @@ class BookmarkToolbarMediator - mBookmarkDelegate = bookmarkDelegate; - mModel.set( - BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE, this::onNavigateBack); -+ mModel.set( -+ BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::importBookmarks); -+ mModel.set( -+ BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::exportBookmarks); - mBookmarkDelegate.addUiObserver(this); - mBookmarkDelegate.notifyStateChange(this); - }); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java -@@ -78,6 +78,10 @@ class BookmarkToolbarProperties { - - static final WritableObjectPropertyKey NAVIGATE_BACK_RUNNABLE = - new WritableObjectPropertyKey<>(); -+ static final WritableObjectPropertyKey IMPORT_BOOKMARK_RUNNABLE = -+ new WritableObjectPropertyKey<>(); -+ static final WritableObjectPropertyKey EXPORT_BOOKMARK_RUNNABLE = -+ new WritableObjectPropertyKey<>(); - - static final PropertyKey[] ALL_KEYS = { - SELECTION_DELEGATE, -@@ -105,6 +109,8 @@ class BookmarkToolbarProperties { - SELECTION_MODE_SHOW_OPEN_IN_INCOGNITO, - SELECTION_MODE_SHOW_MOVE, - SELECTION_MODE_SHOW_MARK_READ, -- SELECTION_MODE_SHOW_MARK_UNREAD -+ SELECTION_MODE_SHOW_MARK_UNREAD, -+ IMPORT_BOOKMARK_RUNNABLE, -+ EXPORT_BOOKMARK_RUNNABLE - }; - } -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java -@@ -54,6 +54,12 @@ class BookmarkToolbarViewBinder { - model.get(BookmarkToolbarProperties.CHECKED_VIEW_MENU_ID)); - } else if (key == BookmarkToolbarProperties.CURRENT_FOLDER) { - bookmarkToolbar.setCurrentFolder(model.get(BookmarkToolbarProperties.CURRENT_FOLDER)); -+ } else if (key == BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE) { -+ bookmarkToolbar.setImportBookmarkRunnable( -+ model.get(BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE)); -+ } else if (key == BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE) { -+ bookmarkToolbar.setExportBookmarkRunnable( -+ model.get(BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE)); - } else if (key == BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE) { - bookmarkToolbar.setNavigateBackRunnable( - model.get(BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE)); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java -@@ -18,6 +18,7 @@ import org.chromium.base.supplier.ObservableSupplier; - import org.chromium.base.supplier.OneshotSupplier; - import org.chromium.base.supplier.Supplier; - import org.chromium.chrome.R; -+import org.chromium.chrome.browser.app.ChromeActivity; - import org.chromium.chrome.browser.app.download.home.DownloadPage; - import org.chromium.chrome.browser.bookmarks.BookmarkPage; - import org.chromium.chrome.browser.browser_controls.BrowserControlsMarginSupplier; -@@ -58,7 +59,7 @@ import org.chromium.ui.util.ColorUtils; - * Creates NativePage objects to show chrome-native:// URLs using the native Android view system. - */ - public class NativePageFactory { -- private final Activity mActivity; -+ private final ChromeActivity mActivity; - private final BottomSheetController mBottomSheetController; - private final BrowserControlsManager mBrowserControlsManager; - private final Supplier mCurrentTabSupplier; -@@ -80,7 +81,7 @@ public class NativePageFactory { - private static NativePage sTestPage; - - public NativePageFactory( -- @NonNull Activity activity, -+ @NonNull ChromeActivity activity, - @NonNull BottomSheetController sheetController, - @NonNull BrowserControlsManager browserControlsManager, - @NonNull Supplier currentTabSupplier, -@@ -149,7 +150,7 @@ public class NativePageFactory { - - @VisibleForTesting - static class NativePageBuilder { -- private final Activity mActivity; -+ private final ChromeActivity mActivity; - private final BottomSheetController mBottomSheetController; - private final Supplier mUma; - private final BrowserControlsManager mBrowserControlsManager; -@@ -168,7 +169,7 @@ public class NativePageFactory { - private final ObservableSupplier mEdgeToEdgeControllerSupplier; - - public NativePageBuilder( -- Activity activity, -+ ChromeActivity activity, - Supplier uma, - BottomSheetController sheetController, - BrowserControlsManager browserControlsManager, -@@ -242,7 +243,7 @@ public class NativePageFactory { - mActivity.getComponentName(), - mSnackbarManagerSupplier.get(), - tab.getProfile(), -- new TabShim(tab, mBrowserControlsManager, mTabModelSelector)); -+ new TabShim(tab, mBrowserControlsManager, mTabModelSelector), mActivity); - } - - protected NativePage buildDownloadsPage(Tab tab) { -diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn ---- a/chrome/browser/BUILD.gn -+++ b/chrome/browser/BUILD.gn -@@ -218,6 +218,8 @@ static_library("browser") { - "bluetooth/chrome_bluetooth_delegate_impl_client.h", - "bookmarks/bookmark_model_factory.cc", - "bookmarks/bookmark_model_factory.h", -+ "bookmarks/bookmark_html_writer.cc", -+ "bookmarks/bookmark_html_writer.h", - "bookmarks/chrome_bookmark_client.cc", - "bookmarks/chrome_bookmark_client.h", - "bookmarks/managed_bookmark_service_factory.cc", -@@ -1698,6 +1700,13 @@ static_library("browser") { - ] - } - -+ if (is_android) { -+ sources += [ -+ "importer/profile_writer.cc", -+ "importer/profile_writer.h", -+ ] -+ } ++void BookmarkBridge::FileSelectionCanceled() {} + - configs += [ - "//build/config/compiler:wexit_time_destructors", - "//build/config:precompiled_headers", -@@ -3507,8 +3516,6 @@ static_library("browser") { - "bookmarks/bookmark_expanded_state_tracker.h", - "bookmarks/bookmark_expanded_state_tracker_factory.cc", - "bookmarks/bookmark_expanded_state_tracker_factory.h", -- "bookmarks/bookmark_html_writer.cc", -- "bookmarks/bookmark_html_writer.h", - "bookmarks/bookmark_merged_surface_service.cc", - "bookmarks/bookmark_merged_surface_service.h", - "bookmarks/bookmark_merged_surface_service_factory.cc", -diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc ---- a/chrome/browser/bookmarks/android/bookmark_bridge.cc -+++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc -@@ -66,6 +66,28 @@ - #include "content/public/browser/web_contents.h" - #include "url/gurl.h" - -+#include "base/android/content_uri_utils.h" -+#include "base/android/path_utils.h" -+#include "base/strings/utf_string_conversions.h" -+#include "chrome/utility/importer/bookmark_html_reader.h" + void BookmarkBridge::SetBookmarkTitle(JNIEnv* env, + jlong id, + jint type, +diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser/bookmarks/android/bookmark_bridge.h +--- a/chrome/browser/bookmarks/android/bookmark_bridge.h ++++ b/chrome/browser/bookmarks/android/bookmark_bridge.h +@@ -20,6 +20,7 @@ + #include "base/strings/utf_string_conversions.h" + #include "base/supports_user_data.h" + #include "chrome/browser/partnerbookmarks/partner_bookmarks_shim.h" +#include "chrome/browser/bookmarks/bookmark_html_writer.h" -+#include "chrome/browser/importer/profile_writer.h" -+#include "chrome/browser/platform_util.h" -+#include "chrome/browser/ui/chrome_select_file_policy.h" -+#include "chrome/common/importer/imported_bookmark_entry.h" -+#include "chrome/common/importer/importer_data_types.h" -+#include "chrome/common/url_constants.h" -+#include "components/favicon_base/favicon_usage_data.h" -+#include "components/search_engines/template_url.h" -+#include "components/url_formatter/url_fixer.h" -+#include "ui/android/window_android.h" -+#include "base/task/task_traits.h" -+#include "base/task/thread_pool.h" -+#include "content/public/browser/browser_task_traits.h" -+#include "base/files/file_path.h" -+#include "ui/shell_dialogs/selected_file_info.h" + #include "chrome/browser/profiles/profile.h" + #include "chrome/browser/profiles/profile_observer.h" + #include "chrome/browser/reading_list/android/reading_list_manager.h" +@@ -37,6 +38,9 @@ + #include "components/signin/public/identity_manager/identity_manager.h" + #include "url/android/gurl_android.h" + ++#include "components/search_engines/template_url.h" +#include "ui/shell_dialogs/select_file_dialog.h" + - // Must come after all headers that specialize FromJniType() / ToJniType(). - #include "chrome/android/chrome_jni_headers/BookmarkBridge_jni.h" + class BookmarkBridgeTest; -@@ -84,6 +106,57 @@ using bookmarks::android::JavaBookmarkIdGetType; - using content::BrowserThread; - using power_bookmarks::PowerBookmarkMeta; + // The delegate to fetch bookmarks information for the Android native +@@ -50,7 +54,8 @@ class BookmarkBridge : public ProfileObserver, + public ReadingListManager::Observer, + public ReadingListModelObserver, + public signin::IdentityManager::Observer, +- public base::SupportsUserData::Data { ++ public base::SupportsUserData::Data, ++ public ui::SelectFileDialog::Listener { + public: + // All of the injected pointers must be non-null and must outlive `this`. + BookmarkBridge(Profile* profile, +@@ -81,6 +86,11 @@ class BookmarkBridge : public ProfileObserver, -+namespace internal { + bool IsDoingExtensiveChanges(JNIEnv* env); + ++ // SelectFileDialog::Listener implementation. ++ void FileSelected(const ui::SelectedFileInfo& file, ++ int index) override; ++ void FileSelectionCanceled() override; + -+// Returns true if |url| has a valid scheme that we allow to import. We -+// filter out the URL with a unsupported scheme. -+bool CanImportURL(const GURL& url) { -+ // The URL is not valid. -+ if (!url.is_valid()) -+ return false; + jboolean IsEditBookmarksEnabled(JNIEnv* env); + + void LoadEmptyPartnerBookmarkShimForTesting(JNIEnv* env); +@@ -93,6 +103,17 @@ class BookmarkBridge : public ProfileObserver, + jlong id, + jint type); + ++ void ImportBookmarks(JNIEnv* env, ++ const base::android::JavaParamRef& obj, ++ const base::android::JavaParamRef& java_window); + -+ // Filter out the URLs with unsupported schemes. -+ const char* const kInvalidSchemes[] = {"wyciwyg", "place"}; -+ for (size_t i = 0; i < std::size(kInvalidSchemes); ++i) { -+ if (url.SchemeIs(kInvalidSchemes[i])) -+ return false; -+ } ++ void ExportBookmarks(JNIEnv* env, ++ const base::android::JavaParamRef& obj, ++ const base::android::JavaParamRef& java_window, ++ const base::android::JavaParamRef& j_export_path); ++ void ExportBookmarksEnd(const base::android::ScopedJavaGlobalRef& obj, ++ ui::WindowAndroid* window, bookmark_html_writer::Result result) const; + -+ // Check if |url| is about:blank. -+ if (url == url::kAboutBlankURL) -+ return true; + void GetAllFoldersWithDepths( + JNIEnv* env, + const base::android::JavaParamRef& j_folders_obj, +@@ -362,6 +383,8 @@ class BookmarkBridge : public ProfileObserver, + void CreateOrDestroyAccountReadingListManagerIfNeeded(); + + const raw_ptr profile_; // weak ++ base::FilePath export_path_; + -+ // If |url| starts with chrome:// or about:, check if it's one of the URLs -+ // that we support. -+ if (url.SchemeIs(content::kChromeUIScheme) || -+ url.SchemeIs(url::kAboutScheme)) { -+ if (url.host_piece() == chrome::kChromeUIAboutHost) -+ return true; + base::android::ScopedJavaGlobalRef java_bookmark_model_; + const raw_ptr bookmark_model_; // weak + const raw_ptr +@@ -376,6 +399,7 @@ class BookmarkBridge : public ProfileObserver, + std::unique_ptr + grouped_bookmark_actions_; + PrefChangeRegistrar pref_change_registrar_; ++ scoped_refptr select_file_dialog_; + + // Information about the Partner bookmarks (must check for IsLoaded()). + // This is owned by profile. +@@ -407,6 +431,10 @@ class BookmarkBridge : public ProfileObserver, + + bool suppress_observer_notifications_ = false; + ++ const std::string FileSelectedImpl(const base::FilePath& path); ++ void FileSelectedImplOnUIThread(const base::FilePath& path, ++ const std::string& contents); + -+ GURL fixed_url(url_formatter::FixupURL(url.spec(), std::string())); -+ const base::span hosts = chrome::ChromeURLHosts(); -+ for (const base::cstring_view host : hosts) { -+ if (fixed_url.DomainIs(host)) { -+ return true; -+ } + // Weak pointers for creating callbacks that won't call into a destroyed + // object. + base::WeakPtrFactory weak_ptr_factory_; +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java +@@ -36,6 +36,9 @@ import org.chromium.url.GURL; + import java.util.ArrayList; + import java.util.List; + ++import org.chromium.ui.base.WindowAndroid; ++import java.util.function.BiConsumer; ++ + /** + * Provides the communication channel for Android to fetch and manipulate the bookmark model stored + * in native. +@@ -52,6 +55,9 @@ class BookmarkBridge { + private boolean mIsNativeBookmarkModelLoaded; + private boolean mInitializedPartnerBookmarks; + ++ private static final BiConsumer NOOP = (success, bookmarksPath) -> {}; ++ private BiConsumer mOnExportedFunction = NOOP; ++ + // Lazily set pseudo-constants. These should never change at runtime. Used to avoid crossing + // JNI to fetch information. + private @Nullable BookmarkId mRootFolderId; +@@ -498,6 +504,35 @@ class BookmarkBridge { + .getTotalBookmarkCount(mNativeBookmarkBridge, id.getId(), id.getType()); + } + ++ /** ++ * Import bookmarks from a selected file. ++ * @param window The current window of the bookmarks activity or page. ++ */ ++ public void importBookmarks(WindowAndroid window) { ++ assert mIsNativeBookmarkModelLoaded; ++ BookmarkBridgeJni.get().importBookmarks(mNativeBookmarkBridge, ++ BookmarkBridge.this, window); + } + -+ if (base::Contains(chrome::ChromeDebugURLs(), fixed_url.spec())) { -+ return true; ++ /** ++ * Export bookmarks to a path selected by the user. ++ * @param window The current window of the bookmarks activity or page. ++ */ ++ public void exportBookmarks(WindowAndroid window, String exportPath, ++ BiConsumer onExportedFunction) { ++ assert mIsNativeBookmarkModelLoaded; ++ mOnExportedFunction = onExportedFunction; ++ BookmarkBridgeJni.get().exportBookmarks(mNativeBookmarkBridge, ++ BookmarkBridge.this, window, exportPath); + } + -+ // If url has either chrome:// or about: schemes but wasn't found in the -+ // above lists, it means we don't support it, so we don't allow the user -+ // to import it. -+ return false; -+ } ++ @CalledByNative ++ public void bookmarksExported(WindowAndroid window, String bookmarksPath, boolean success) { ++ BiConsumer action = mOnExportedFunction ++ .andThen((x, y) -> mOnExportedFunction = NOOP); ++ action.accept(success, bookmarksPath); ++ } + -+ // Otherwise, we assume the url has a valid (importable) scheme. -+ return true; -+} + /** + * Synchronously gets a list of bookmarks that match the specified search query. + * +@@ -1120,6 +1155,10 @@ class BookmarkBridge { + void getChildIds( + long nativeBookmarkBridge, long id, int type, List bookmarksList); + ++ void importBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window); ++ void exportBookmarks(long nativeBookmarkBridge, BookmarkBridge caller, WindowAndroid window, ++ String export_path); + -+} // internal + BookmarkId getChildAt(long nativeBookmarkBridge, long id, int type, int index); + + int getTotalBookmarkCount(long nativeBookmarkBridge, long id, int type); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkDelegate.java +@@ -65,6 +65,16 @@ public interface BookmarkDelegate { + /** Shows the search UI. */ + void openSearchUi(); + ++ /** ++ * Imports bookmarks from user-selected file. ++ */ ++ void importBookmarks(); + - namespace { - // The key used to connect the instance of the bookmark bridge to the bookmark - // model. -@@ -231,6 +304,10 @@ BookmarkBridge::~BookmarkBridge() { - partner_bookmarks_shim_observation_.Reset(); - bookmark_model_observation_.Reset(); - profile_observation_.Reset(); -+ // There may be pending file dialogs, we need to tell them that we've gone -+ // away so they don't try and call back to us. -+ if (select_file_dialog_) -+ select_file_dialog_->ListenerDestroyed(); - } ++ /** ++ * Exports bookmarks to downloads directory. ++ */ ++ void exportBookmarks(); ++ + /** Add an observer to bookmark UI changes. */ + void addUiObserver(BookmarkUiObserver observer); - void BookmarkBridge::Destroy(JNIEnv* env) { -@@ -765,6 +842,196 @@ jint BookmarkBridge::GetTotalBookmarkCount( - return count; - } +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java +@@ -54,6 +54,8 @@ import org.chromium.components.image_fetcher.ImageFetcher; + import org.chromium.components.image_fetcher.ImageFetcherConfig; + import org.chromium.components.image_fetcher.ImageFetcherFactory; + import org.chromium.ui.KeyboardVisibilityDelegate; ++import org.chromium.ui.base.ActivityWindowAndroid; ++import org.chromium.ui.modaldialog.ModalDialogManager; + import org.chromium.ui.modaldialog.ModalDialogManager; + import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType; + import org.chromium.ui.modelutil.MVCListAdapter.ListItem; +@@ -328,6 +330,13 @@ public class BookmarkManagerCoordinator -+void BookmarkBridge::ImportBookmarks(JNIEnv* env, -+ const JavaParamRef& obj, -+ const JavaParamRef& java_window) { -+ DCHECK(IsLoaded()); -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // Public API implementation. + ++ /** ++ * Sets the Android window that is used by further intents created by the bookmark activity. ++ */ ++ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) { ++ mMediator.setWindow(window, modalDialogManager); ++ } + -+ ui::WindowAndroid* window = -+ ui::WindowAndroid::FromJavaWindowAndroid(java_window); -+ CHECK(window); + /** Destroys and cleans up itself. This must be called after done using this class. */ + public void onDestroyed() { + RecordUserAction.record("MobileBookmarkManagerClose"); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java +@@ -57,6 +57,8 @@ import org.chromium.components.commerce.core.SubscriptionsObserver; + import org.chromium.components.embedder_support.util.UrlConstants; + import org.chromium.components.power_bookmarks.PowerBookmarkMeta; + import org.chromium.components.power_bookmarks.PowerBookmarkType; ++import org.chromium.ui.base.ActivityWindowAndroid; ++import org.chromium.ui.modaldialog.ModalDialogManager; + import org.chromium.ui.accessibility.AccessibilityState; + import org.chromium.ui.listmenu.ListMenu; + import org.chromium.ui.listmenu.ListMenuItemProperties; +@@ -75,6 +77,47 @@ import java.util.function.BooleanSupplier; + import java.util.function.Consumer; + import java.util.function.Predicate; + ++import android.content.Intent; ++import android.content.pm.PackageManager; ++import android.content.DialogInterface; ++import android.content.res.Resources; ++import android.content.ContentResolver; ++import android.net.Uri; ++import android.provider.Browser; ++import android.provider.DocumentsContract; ++import android.Manifest.permission; ++import android.view.View; ++import android.view.LayoutInflater; ++ ++import androidx.appcompat.app.AlertDialog; ++import android.os.Build; ++ ++import java.io.File; ++ ++import org.chromium.base.ContextUtils; ++import org.chromium.base.ContentUriUtils; ++import org.chromium.base.shared_preferences.SharedPreferencesManager; ++import org.chromium.base.task.AsyncTask; ++import org.chromium.chrome.R; ++import org.chromium.chrome.browser.document.ChromeLauncherActivity; ++import org.chromium.chrome.browser.IntentHandler; ++import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; ++import org.chromium.chrome.browser.download.DownloadLocationDialogType; ++import org.chromium.chrome.browser.download.settings.DownloadLocationHelperImpl; ++import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogController; ++import org.chromium.chrome.browser.download.dialogs.DownloadLocationDialogCoordinator; ++import org.chromium.chrome.browser.download.dialogs.DownloadLocationCustomView; ++import org.chromium.chrome.browser.download.DirectoryOption; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; ++import org.chromium.chrome.browser.flags.ChromeFeatureList; ++import org.chromium.ui.base.PageTransition; ++import org.chromium.ui.base.WindowAndroid; ++import org.chromium.ui.modelutil.PropertyModel; ++import org.chromium.ui.modaldialog.ModalDialogManager; ++import org.chromium.ui.modaldialog.ModalDialogProperties; ++import org.chromium.ui.modaldialog.DialogDismissalCause; ++ + /** Responsible for BookmarkManager business logic. */ + // TODO(crbug.com/40256938): Remove BookmarkDelegate if possible. + class BookmarkManagerMediator +@@ -84,6 +127,9 @@ class BookmarkManagerMediator + + private static boolean sPreventLoadingForTesting; + ++ private ActivityWindowAndroid mWindowAndroid; ++ private ModalDialogManager mModalDialogManager; + -+ select_file_dialog_ = ui::SelectFileDialog::Create( -+ this, std::make_unique(nullptr)); + /** Keeps track of whether drag is enabled / active for bookmark lists. */ + private class BookmarkDragStateDelegate implements DragStateDelegate { + private BookmarkDelegate mBookmarkDelegate; +@@ -573,6 +619,14 @@ class BookmarkManagerMediator + mNativePage = nativePage; + } + ++ /** ++ * Sets the Android window that is used by further intents created by the bookmark activity. ++ */ ++ public void setWindow(ActivityWindowAndroid window, ModalDialogManager modalDialogManager) { ++ mWindowAndroid = window; ++ mModalDialogManager = modalDialogManager; ++ } + -+ //NOTE: extension and description are not used on Android, thus not set -+ ui::SelectFileDialog::FileTypeInfo file_type_info; + /** See BookmarkManager(Coordinator)#updateForUrl */ + void updateForUrl(String url) { + // Bookmark model is null if the manager has been destroyed. +@@ -748,6 +802,235 @@ class BookmarkManagerMediator + } + } + ++ @Override ++ public void importBookmarks() { ++ mBookmarkModel.importBookmarks(mWindowAndroid); ++ } + -+ const std::vector v_accept_types = { u"text/html" }; -+ select_file_dialog_->SetAcceptTypes(v_accept_types); ++ @Override ++ public void exportBookmarks() { ++ if (ChromeFeatureList.isEnabled(ChromeFeatureList.BOOKMARKS_EXPORT_USESAF) || ++ Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { ++ exportBookmarksImplUseSaf(); ++ } else { ++ exportBookmarksImplUseFile(); ++ } ++ } + -+ select_file_dialog_->SelectFile( -+ ui::SelectFileDialog::SELECT_OPEN_FILE, -+ std::u16string(), -+ export_path_, -+ &file_type_info, -+ 0, -+ base::FilePath::StringType(), -+ window -+ ); -+} ++ private void exportBookmarksImplUseSaf() { ++ Context context = mWindowAndroid.getContext().get(); + -+void BookmarkBridge::ExportBookmarks(JNIEnv* env, -+ const JavaParamRef& obj, -+ const JavaParamRef& java_window, -+ const JavaParamRef& j_export_path) { -+ DCHECK(IsLoaded()); -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ // standard name for boorkmark file ++ final String standardBoorkmarkName = "bookmarks.html"; + -+ ui::WindowAndroid* window = -+ ui::WindowAndroid::FromJavaWindowAndroid(java_window); -+ CHECK(window); ++ // use the fileSelector and saf asking user for the file ++ Intent fileSelector = new Intent(Intent.ACTION_CREATE_DOCUMENT); ++ fileSelector.addCategory(Intent.CATEGORY_OPENABLE); ++ fileSelector.setType("text/html"); ++ fileSelector.putExtra(Intent.EXTRA_TITLE, standardBoorkmarkName); ++ fileSelector.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | ++ Intent.FLAG_GRANT_READ_URI_PERMISSION | ++ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + -+ std::u16string export_path = -+ base::android::ConvertJavaStringToUTF16(env, j_export_path); ++ // get last exported uri path, if any ++ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance(); ++ String bookmarksPath = sharedPrefs.readString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, standardBoorkmarkName); ++ Uri lastSelectedUri = Uri.parse(bookmarksPath); + -+ export_path_ = base::FilePath::FromUTF16Unsafe(export_path); ++ // prepare delegate for file selector ++ DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { ++ @Override ++ public void onClick(DialogInterface dialog, int button) { ++ if (button == AlertDialog.BUTTON_NEGATIVE) { ++ mWindowAndroid.showIntent(fileSelector, ++ new WindowAndroid.IntentCallback() { ++ @Override ++ public void onIntentCompleted(int resultCode, Intent data) { ++ if (data == null) return; ++ Uri filePath = data.getData(); ++ doExportBookmarksImpl(filePath); ++ } ++ }, ++ null); ++ } else { ++ if (dialog!=null) dialog.dismiss(); ++ doExportBookmarksImpl(lastSelectedUri); ++ } ++ } ++ }; + -+ if (export_path_.empty()) { -+ if (!base::android::GetDownloadsDirectory(&export_path_)) { -+ LOG(ERROR) << "Could not retrieve downloads directory for bookmarks export"; -+ return; -+ } -+ export_path_ = export_path_.Append(FILE_PATH_LITERAL("bookmarks.html")); -+ } ++ // as a workaround for https://issuetracker.google.com/issues/37136466 ++ // ask to overwrite if is a valid uri and the file is present ++ if (DocumentsContract.isDocumentUri(context, lastSelectedUri)) { ++ AsyncTask checkUriTask = new AsyncTask() { ++ boolean uriExists = false; ++ String actualFilePath = null; + -+ bookmark_html_writer::WriteBookmarks(profile_, export_path_, -+ base::BindOnce(&BookmarkBridge::ExportBookmarksEnd, -+ weak_ptr_factory_.GetWeakPtr(), -+ base::android::ScopedJavaGlobalRef(obj), -+ window)); -+} ++ @Override ++ protected Void doInBackground() { ++ uriExists = ContentUriUtils.contentUriExists(lastSelectedUri.toString()); ++ if (uriExists) { ++ actualFilePath = ContentUriUtils.getFilePathFromContentUri(lastSelectedUri); ++ // get real actual file name on disk ++ if (actualFilePath==null) actualFilePath = lastSelectedUri.toString(); ++ // set file name to last exported file name ++ fileSelector.putExtra(Intent.EXTRA_TITLE, ++ ContentUriUtils.getDisplayName(lastSelectedUri, context, ++ DocumentsContract.Document.COLUMN_DISPLAY_NAME)); ++ } ++ return null; ++ } + -+void BookmarkBridge::ExportBookmarksEnd(const ScopedJavaGlobalRef& obj, -+ ui::WindowAndroid* window, -+ bookmark_html_writer::Result result) const -+{ -+ auto export_path = export_path_.MaybeAsASCII(); -+ if (result == bookmark_html_writer::Result::kSuccess) { -+ LOG(INFO) << "Bookmarks exported successfully to " << export_path; -+ } else if (result == bookmark_html_writer::Result::kCouldNotCreateFile) { -+ LOG(ERROR) << "Bookmarks export: could not create file " << export_path; -+ } else if (result == bookmark_html_writer::Result::kCouldNotWriteHeader) { -+ LOG(ERROR) << "Bookmarks export: could not write header"; -+ } else if (result == bookmark_html_writer::Result::kCouldNotWriteNodes) { -+ LOG(ERROR) << "Bookmarks export: could not write nodes"; -+ } ++ @Override ++ protected void onPostExecute(Void result) { ++ // check for permissions ++ if (uriExists) { ++ AlertDialog.Builder alert = ++ new AlertDialog.Builder(context, R.style.ThemeOverlay_BrowserUI_AlertDialog); ++ AlertDialog alertDialog = ++ alert.setTitle(R.string.export_bookmarks_alert_title) ++ .setMessage(context.getString(R.string.export_bookmarks_alert_message, actualFilePath)) ++ .setPositiveButton( ++ R.string.export_bookmarks_alert_message_yes, onClickListener) ++ .setNegativeButton(R.string.export_bookmarks_alert_message_no, onClickListener) ++ .create(); ++ alertDialog.getDelegate().setHandleNativeActionModesEnabled(false); + -+ JNIEnv* env = AttachCurrentThread(); -+ Java_BookmarkBridge_bookmarksExported(env, obj, window->GetJavaObject(), -+ base::android::ConvertUTF8ToJavaString(env, export_path), -+ result == bookmark_html_writer::Result::kSuccess); -+} ++ // show dialog asking for overwrite ++ alertDialog.show(); ++ return; ++ } else { ++ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE); ++ } ++ } ++ }; ++ checkUriTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); ++ return; ++ } + -+// Attempts to create a TemplateURL from the provided data. |title| is optional. -+// If TemplateURL creation fails, returns null. -+std::unique_ptr CreateTemplateURL(const std::u16string& url, -+ const std::u16string& keyword, -+ const std::u16string& title) { -+ if (url.empty() || keyword.empty()) -+ return nullptr; -+ TemplateURLData data; -+ data.SetKeyword(keyword); -+ // We set short name by using the title if it exists. -+ // Otherwise, we use the shortcut. -+ data.SetShortName(title.empty() ? keyword : title); -+ data.SetURL(TemplateURLRef::DisplayURLToURLRef(url)); -+ return std::make_unique(data); -+} ++ // actually open the file selector ++ onClickListener.onClick(null, AlertDialog.BUTTON_NEGATIVE); ++ } + -+void BookmarkBridge::FileSelected(const ui::SelectedFileInfo& file, int index) { -+ base::FilePath path = file.path(); -+ base::ThreadPool::PostTaskAndReplyWithResult( -+ FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, -+ base::BindOnce(&BookmarkBridge::FileSelectedImpl, -+ base::Unretained(this), -+ path), -+ base::BindOnce(&BookmarkBridge::FileSelectedImplOnUIThread, -+ base::Unretained(this), -+ path)); -+} ++ private void exportBookmarksImplUseFile() { ++ Context context = mWindowAndroid.getContext().get(); + -+const std::string BookmarkBridge::FileSelectedImpl(const base::FilePath& path) { -+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); -+ if (!file.IsValid()) { -+ select_file_dialog_->ShowToast("Cannot open bookmarks file for import"); -+ return ""; -+ } ++ // standard name for boorkmark file ++ final String standardBoorkmarkName = "bookmarks.html"; + -+ auto fileLength = file.GetLength(); -+ if (-1 == fileLength) { -+ select_file_dialog_->ShowToast("Cannot read bookmarks file length"); -+ return ""; -+ } ++ // use the download ui and standard file saving ++ DownloadLocationDialogController controller = new DownloadLocationDialogController() { ++ @Override ++ public void onDownloadLocationDialogComplete(String returnedPath) {} + -+ if (fileLength > 10 * 1024 * 1024) { -+ select_file_dialog_->ShowToast("Bookmark file is bigger than 10MB"); -+ return ""; -+ } ++ @Override ++ public void onDownloadLocationDialogCanceled() {} ++ }; + -+ std::vector buffer(fileLength); -+ if (-1 == file.ReadAtCurrentPos(buffer.data(), fileLength)) { -+ select_file_dialog_->ShowToast("Could not read bookmarks file"); -+ return ""; -+ } ++ DownloadLocationDialogCoordinator dialog = new DownloadLocationDialogCoordinator() { ++ @Override ++ protected void onDirectoryOptionsRetrieved(ArrayList dirs) { ++ if (mDialogModel != null) return; + -+ if (buffer.empty()) { -+ select_file_dialog_->ShowToast("Empty bookmarks file"); -+ return ""; -+ } ++ // Actually show the dialog. ++ mCustomView = (DownloadLocationCustomView) LayoutInflater.from(context).inflate( ++ R.layout.download_location_dialog, null); ++ mCustomView.initialize(DownloadLocationDialogType.DEFAULT, /*totalBytes*/ 0, ++ (isChecked) -> {}, ++ new DownloadLocationHelperImpl(mProfile)); ++ mCustomView.setTitle(context.getString(R.string.export_bookmarks_alert_title)); ++ mCustomView.setFileName(standardBoorkmarkName); ++ mCustomView.mDontShowAgain.setVisibility(View.GONE); + -+ std::string contents(buffer.begin(), buffer.end()); -+ return contents; -+} ++ Resources resources = context.getResources(); ++ mDialogModel = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) ++ .with(ModalDialogProperties.CONTROLLER, this) ++ .with(ModalDialogProperties.CUSTOM_VIEW, mCustomView) ++ .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources, ++ R.string.export_bookmarks) ++ .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources, ++ R.string.cancel) ++ .build(); + -+void BookmarkBridge::FileSelectedImplOnUIThread(const base::FilePath& path, -+ const std::string& contents) { -+ if (contents.empty()) -+ return; ++ mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.APP); ++ } + -+ // the following import logic comes from BookmarksFileImporter class -+ std::vector bookmarks; -+ std::vector search_engines; -+ favicon_base::FaviconUsageDataList favicons; ++ @Override ++ public void onDismiss(PropertyModel model, int dismissalCause) { ++ switch (dismissalCause) { ++ case DialogDismissalCause.POSITIVE_BUTTON_CLICKED: ++ { ++ String fileName = mCustomView.getFileName(); ++ String directory = mCustomView.getDirectoryOption().location; ++ if (fileName != null && directory != null) { ++ File file = new File(directory, fileName); + -+ bookmark_html_reader::ImportBookmarksFile( -+ base::RepeatingCallback(), -+ base::BindRepeating(internal::CanImportURL), -+ contents, -+ &bookmarks, -+ &search_engines, -+ &favicons); ++ if (mWindowAndroid.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) { ++ doExportBookmarksImpl(file.getPath()); ++ } else { ++ String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE}; ++ mWindowAndroid.requestPermissions(requestPermissions, (permissions, grantResults) -> { ++ if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { ++ doExportBookmarksImpl(file.getPath()); ++ } ++ }); ++ }; ++ } ++ } ++ break; ++ } ++ mDialogModel = null; ++ mCustomView = null; ++ } ++ }; ++ dialog.initialize(controller); ++ dialog.showDialog(context, mModalDialogManager, /*totalBytes*/ 0, ++ DownloadLocationDialogType.DEFAULT, /*suggestedPath*/ "", ++ ProfileManager.getLastUsedRegularProfile()); ++ } + -+ auto *writer = new ProfileWriter(profile_); ++ private void doExportBookmarksImpl(Uri filePath) { ++ ContentResolver resolver = ContextUtils.getApplicationContext().getContentResolver(); ++ // since we want to persist the uri in settings, ask for persistable permissions ++ resolver.takePersistableUriPermission(filePath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | ++ Intent.FLAG_GRANT_READ_URI_PERMISSION); + -+ if (!bookmarks.empty()) { -+ // adding bookmarks will begin extensive changes to the model -+ writer->AddBookmarksWithModel(bookmark_model_, bookmarks, u"Imported"); -+ } -+ if (!search_engines.empty()) { -+ TemplateURLService::OwnedTemplateURLVector owned_template_urls; -+ for (const auto& search_engine : search_engines) { -+ std::unique_ptr owned_template_url = CreateTemplateURL( -+ search_engine.url, search_engine.keyword, search_engine.display_name); -+ if (owned_template_url) -+ owned_template_urls.push_back(std::move(owned_template_url)); ++ doExportBookmarksImpl(filePath.toString()); + } -+ writer->AddKeywords(std::move(owned_template_urls), false); -+ } + -+ std::stringstream message; -+ message << "Imported " << bookmarks.size() << " bookmarks and " << -+ search_engines.size() << " search engines from " << path.MaybeAsASCII(); -+ auto result = message.str(); ++ private void doExportBookmarksImpl(String filePath) { ++ mBookmarkModel.exportBookmarks(mWindowAndroid, filePath, ++ (success, bookmarksPath) -> { ++ if (!success) { ++ ((Activity)mWindowAndroid.getContext().get()).runOnUiThread(new Runnable() { ++ public void run() { ++ mWindowAndroid.showError(R.string.saving_file_error); ++ } ++ }); ++ } else { ++ SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance(); ++ sharedPrefs.writeString(ChromePreferenceKeys.BOOKMARKS_LAST_EXPORT_URI, bookmarksPath); + -+ select_file_dialog_->ShowToast(result); ++ Context context = ContextUtils.getApplicationContext(); + -+ LOG(INFO) << result; -+} ++ Intent intent = new Intent(Intent.ACTION_VIEW, ++ ContentUriUtils.isContentUri(bookmarksPath) ? ++ Uri.parse(bookmarksPath) : Uri.parse("file://" + bookmarksPath)); ++ intent.putExtra(Browser.EXTRA_APPLICATION_ID, ++ context.getPackageName()); ++ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ++ intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, PageTransition.AUTO_BOOKMARK); + -+void BookmarkBridge::FileSelectionCanceled() {} ++ // If the bookmark manager is shown in a tab on a phone (rather than in a separate ++ // activity) the component name may be null. Send the intent through ++ // ChromeLauncherActivity instead to avoid crashing. See crbug.com/615012. ++ intent.setClass(context, ChromeLauncherActivity.class); + - void BookmarkBridge::SetBookmarkTitle(JNIEnv* env, - jlong id, - jint type, -diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser/bookmarks/android/bookmark_bridge.h ---- a/chrome/browser/bookmarks/android/bookmark_bridge.h -+++ b/chrome/browser/bookmarks/android/bookmark_bridge.h -@@ -20,6 +20,7 @@ - #include "base/strings/utf_string_conversions.h" - #include "base/supports_user_data.h" - #include "chrome/browser/partnerbookmarks/partner_bookmarks_shim.h" -+#include "chrome/browser/bookmarks/bookmark_html_writer.h" - #include "chrome/browser/profiles/profile.h" - #include "chrome/browser/profiles/profile_observer.h" - #include "chrome/browser/reading_list/android/reading_list_manager.h" -@@ -37,6 +38,9 @@ - #include "components/signin/public/identity_manager/identity_manager.h" - #include "url/android/gurl_android.h" - -+#include "components/search_engines/template_url.h" -+#include "ui/shell_dialogs/select_file_dialog.h" ++ IntentHandler.startActivityForTrustedIntent(intent); ++ } ++ }); ++ } + - class BookmarkBridgeTest; - - // The delegate to fetch bookmarks information for the Android native -@@ -50,7 +54,8 @@ class BookmarkBridge : public ProfileObserver, - public ReadingListManager::Observer, - public ReadingListModelObserver, - public signin::IdentityManager::Observer, -- public base::SupportsUserData::Data { -+ public base::SupportsUserData::Data, -+ public ui::SelectFileDialog::Listener { - public: - // All of the injected pointers must be non-null and must outlive `this`. - BookmarkBridge(Profile* profile, -@@ -81,6 +86,11 @@ class BookmarkBridge : public ProfileObserver, + @Override + public void openSearchUi() { + onSearchTextChangeCallback(""); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java +@@ -15,6 +15,9 @@ import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; + import org.chromium.chrome.browser.ui.native_page.BasicNativePage; + import org.chromium.chrome.browser.ui.native_page.NativePageHost; + import org.chromium.components.embedder_support.util.UrlConstants; ++import org.chromium.chrome.browser.app.ChromeActivity; ++import org.chromium.ui.modaldialog.ModalDialogManager; ++import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; - bool IsDoingExtensiveChanges(JNIEnv* env); + /** A native page holding a {@link BookmarkManagerCoordinator} on _tablet_. */ + public class BookmarkPage extends BasicNativePage { +@@ -34,7 +37,8 @@ public class BookmarkPage extends BasicNativePage { + @NonNull SnackbarManager snackbarManager, + @NonNull Profile profile, + @NonNull NativePageHost host, +- @Nullable ComponentName componentName) { ++ @Nullable ComponentName componentName, ++ ChromeActivity activity) { + super(host); + mTitle = host.getContext().getString(R.string.bookmarks); -+ // SelectFileDialog::Listener implementation. -+ void FileSelected(const ui::SelectedFileInfo& file, -+ int index) override; -+ void FileSelectionCanceled() override; -+ - jboolean IsEditBookmarksEnabled(JNIEnv* env); +@@ -53,6 +57,9 @@ public class BookmarkPage extends BasicNativePage { + mBookmarkOpener, + componentName); + mBookmarkManagerCoordinator.setBasicNativePage(this); ++ mBookmarkManagerCoordinator.setWindow(activity.getWindowAndroid(), ++ new ModalDialogManager( ++ new AppModalPresenter(activity), ModalDialogManager.ModalDialogType.APP)); + initWithView(mBookmarkManagerCoordinator.getView()); + } - void LoadEmptyPartnerBookmarkShimForTesting(JNIEnv* env); -@@ -93,6 +103,17 @@ class BookmarkBridge : public ProfileObserver, - jlong id, - jint type); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java +@@ -96,6 +96,17 @@ public class BookmarkToolbar extends SelectableListToolbar + setOnMenuItemClickListener(dragEnabled ? null : this); + } -+ void ImportBookmarks(JNIEnv* env, -+ const base::android::JavaParamRef& obj, -+ const base::android::JavaParamRef& java_window); ++ private Runnable mImportBookmarkRunnable; ++ private Runnable mExportBookmarkRunnable; + -+ void ExportBookmarks(JNIEnv* env, -+ const base::android::JavaParamRef& obj, -+ const base::android::JavaParamRef& java_window, -+ const base::android::JavaParamRef& j_export_path); -+ void ExportBookmarksEnd(const base::android::ScopedJavaGlobalRef& obj, -+ ui::WindowAndroid* window, bookmark_html_writer::Result result) const; ++ void setImportBookmarkRunnable(Runnable runnable) { ++ mImportBookmarkRunnable = runnable; ++ } + - void GetAllFoldersWithDepths( - JNIEnv* env, - const base::android::JavaParamRef& j_folders_obj, -@@ -362,6 +383,8 @@ class BookmarkBridge : public ProfileObserver, - void CreateOrDestroyAccountReadingListManagerIfNeeded(); ++ void setExportBookmarkRunnable(Runnable runnable) { ++ mExportBookmarkRunnable = runnable; ++ } ++ + void setEditButtonVisible(boolean visible) { + mEditButtonVisible = visible; + getMenu().findItem(R.id.edit_menu_id).setVisible(visible); +@@ -172,6 +183,12 @@ public class BookmarkToolbar extends SelectableListToolbar - const raw_ptr profile_; // weak -+ base::FilePath export_path_; + void setCurrentFolder(BookmarkId folder) { + mCurrentFolder = mBookmarkModel.getBookmarkById(folder); ++ enableImportExportMenu(); ++ } + - base::android::ScopedJavaGlobalRef java_bookmark_model_; - const raw_ptr bookmark_model_; // weak - const raw_ptr -@@ -376,6 +399,7 @@ class BookmarkBridge : public ProfileObserver, - std::unique_ptr - grouped_bookmark_actions_; - PrefChangeRegistrar pref_change_registrar_; -+ scoped_refptr select_file_dialog_; ++ void enableImportExportMenu() { ++ getMenu().findItem(R.id.import_menu_id).setVisible(true); ++ getMenu().findItem(R.id.export_menu_id).setVisible(true); + } - // Information about the Partner bookmarks (must check for IsLoaded()). - // This is owned by profile. -@@ -407,6 +431,10 @@ class BookmarkBridge : public ProfileObserver, + void setNavigateBackRunnable(Runnable navigateBackRunnable) { +@@ -191,6 +208,13 @@ public class BookmarkToolbar extends SelectableListToolbar + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + hideOverflowMenu(); ++ if (menuItem.getItemId() == R.id.import_menu_id) { ++ mImportBookmarkRunnable.run(); ++ return true; ++ } else if (menuItem.getItemId() == R.id.export_menu_id) { ++ mExportBookmarkRunnable.run(); ++ return true; ++ } + return mMenuIdClickedFunction.apply(menuItem.getItemId()); + } - bool suppress_observer_notifications_ = false; +@@ -206,6 +230,9 @@ public class BookmarkToolbar extends SelectableListToolbar + protected void showNormalView() { + super.showNormalView(); -+ const std::string FileSelectedImpl(const base::FilePath& path); -+ void FileSelectedImplOnUIThread(const base::FilePath& path, -+ const std::string& contents); ++ getMenu().findItem(R.id.import_menu_id).setVisible(mCurrentFolder != null); ++ getMenu().findItem(R.id.export_menu_id).setVisible(mCurrentFolder != null); + - // Weak pointers for creating callbacks that won't call into a destroyed - // object. - base::WeakPtrFactory weak_ptr_factory_; + // SelectableListToolbar will show/hide the entire group. + setEditButtonVisible(mEditButtonVisible); + setNewFolderButtonVisible(mNewFolderButtonVisible); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java +@@ -135,6 +135,10 @@ class BookmarkToolbarMediator + mBookmarkDelegate = bookmarkDelegate; + mModel.set( + BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE, this::onNavigateBack); ++ mModel.set( ++ BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::importBookmarks); ++ mModel.set( ++ BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE, mBookmarkDelegate::exportBookmarks); + mBookmarkDelegate.addUiObserver(this); + mBookmarkDelegate.notifyStateChange(this); + }); +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java +@@ -79,6 +79,10 @@ class BookmarkToolbarProperties { + + static final WritableObjectPropertyKey NAVIGATE_BACK_RUNNABLE = + new WritableObjectPropertyKey<>(); ++ static final WritableObjectPropertyKey IMPORT_BOOKMARK_RUNNABLE = ++ new WritableObjectPropertyKey<>(); ++ static final WritableObjectPropertyKey EXPORT_BOOKMARK_RUNNABLE = ++ new WritableObjectPropertyKey<>(); + + static final PropertyKey[] ALL_KEYS = { + SELECTION_DELEGATE, +@@ -106,6 +110,8 @@ class BookmarkToolbarProperties { + SELECTION_MODE_SHOW_OPEN_IN_INCOGNITO, + SELECTION_MODE_SHOW_MOVE, + SELECTION_MODE_SHOW_MARK_READ, +- SELECTION_MODE_SHOW_MARK_UNREAD ++ SELECTION_MODE_SHOW_MARK_UNREAD, ++ IMPORT_BOOKMARK_RUNNABLE, ++ EXPORT_BOOKMARK_RUNNABLE + }; + } +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java +@@ -54,6 +54,12 @@ class BookmarkToolbarViewBinder { + model.get(BookmarkToolbarProperties.CHECKED_VIEW_MENU_ID)); + } else if (key == BookmarkToolbarProperties.CURRENT_FOLDER) { + bookmarkToolbar.setCurrentFolder(model.get(BookmarkToolbarProperties.CURRENT_FOLDER)); ++ } else if (key == BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE) { ++ bookmarkToolbar.setImportBookmarkRunnable( ++ model.get(BookmarkToolbarProperties.IMPORT_BOOKMARK_RUNNABLE)); ++ } else if (key == BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE) { ++ bookmarkToolbar.setExportBookmarkRunnable( ++ model.get(BookmarkToolbarProperties.EXPORT_BOOKMARK_RUNNABLE)); + } else if (key == BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE) { + bookmarkToolbar.setNavigateBackRunnable( + model.get(BookmarkToolbarProperties.NAVIGATE_BACK_RUNNABLE)); diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java --- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java +++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DownloadLocationCustomView.java @@ -1302,18 +1325,18 @@ diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browse diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -214,6 +214,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -213,6 +213,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kCCTAuthTab, &kCCTAuthTabDisableAllExternalIntents, &kCCTAuthTabEnableHttpsRedirects, + &kBookmarksExportUseSaf, &kCCTBeforeUnload, + &kCCTBlockTouchesDuringEnterAnimation, &kCCTClientDataHeader, - &kCCTEarlyNav, diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -585,6 +585,7 @@ public abstract class ChromeFeatureList { +@@ -599,6 +599,7 @@ public abstract class ChromeFeatureList { public static final String USE_LIBUNWINDSTACK_NATIVE_UNWINDER_ANDROID = "UseLibunwindstackNativeUnwinderAndroid"; public static final String VISITED_URL_RANKING_SERVICE = "VisitedURLRankingService"; @@ -1367,7 +1390,7 @@ diff --git a/chrome/browser/importer/profile_writer.h b/chrome/browser/importer/ diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java -@@ -121,6 +121,8 @@ public final class ChromePreferenceKeys { +@@ -127,6 +127,8 @@ public final class ChromePreferenceKeys { "enhanced_bookmark_last_used_parent_folder"; public static final String BOOKMARKS_SORT_ORDER = "Chrome.Bookmarks.BookmarkRowSortOrder"; public static final String BOOKMARKS_VISUALS_PREF = "Chrome.Bookmarks.BookmarkRowDisplay"; @@ -1376,8 +1399,8 @@ diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/bro /** Whether Chrome is set as the default browser. Default value is false. */ public static final String CHROME_DEFAULT_BROWSER = "applink.chrome_default_browser"; -@@ -938,6 +940,7 @@ public final class ChromePreferenceKeys { - AUXILIARY_SEARCH_IS_SCHEMA_SET, +@@ -957,6 +959,7 @@ public final class ChromePreferenceKeys { + AUXILIARY_SEARCH_IS_SCHEMA_V2_SET, APP_LAUNCH_LAST_KNOWN_ACTIVE_TAB_STATE, APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO, + BOOKMARKS_LAST_EXPORT_URI, @@ -1415,7 +1438,7 @@ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chro diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn --- a/chrome/common/BUILD.gn +++ b/chrome/common/BUILD.gn -@@ -356,6 +356,9 @@ static_library("common_lib") { +@@ -350,6 +350,9 @@ static_library("common_lib") { sources += [ "media/chrome_media_drm_bridge_client.cc", "media/chrome_media_drm_bridge_client.h", diff --git a/build/cromite_patches/Add-cromite-flags-support.patch b/build/cromite_patches/Add-cromite-flags-support.patch index 6541dfe55e408114193f05af965b1c9b59f556c7..3eecdcb2150e000c323caee8a2ecc43dd55f44dd 100644 --- a/build/cromite_patches/Add-cromite-flags-support.patch +++ b/build/cromite_patches/Add-cromite-flags-support.patch @@ -51,20 +51,20 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html components/components_strings.grd | 1 + .../content_settings/core/common/features.cc | 1 + .../placeholder.txt | 1 + - components/flags_ui/flags_state.cc | 59 ++++++ - components/flags_ui/resources/app.css | 24 +++ - components/flags_ui/resources/app.html.ts | 28 +++ - components/flags_ui/resources/app.ts | 23 +++ - components/flags_ui/resources/experiment.css | 9 +- - .../flags_ui/resources/experiment.html.ts | 7 +- - components/flags_ui/resources/experiment.ts | 14 +- - .../flags_ui/resources/flags_browser_proxy.ts | 5 + .../core/offline_page_feature.cc | 1 + .../offline_pages/core/offline_page_feature.h | 1 + .../browser/features/password_features.cc | 1 + - components/permissions/features.cc | 1 + + components/permissions/features.cc | 2 + ...nthetic_trials_active_group_id_provider.cc | 4 +- ...ynthetic_trials_active_group_id_provider.h | 4 +- + components/webui/flags/flags_state.cc | 59 ++++++ + components/webui/flags/resources/app.css | 24 +++ + components/webui/flags/resources/app.html.ts | 28 +++ + components/webui/flags/resources/app.ts | 24 +++ + .../webui/flags/resources/experiment.css | 9 +- + .../webui/flags/resources/experiment.html.ts | 7 +- + .../webui/flags/resources/experiment.ts | 12 +- + .../flags/resources/flags_browser_proxy.ts | 5 + .../webui/version/version_handler_helper.cc | 4 +- content/common/features.cc | 1 + content/public/common/content_features.cc | 1 + @@ -106,7 +106,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html third_party/blink/public/common/features.h | 1 + ui/base/ui_base_features.cc | 1 + ui/base/ui_base_features.h | 1 + - 93 files changed, 811 insertions(+), 30 deletions(-) + 93 files changed, 812 insertions(+), 29 deletions(-) create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/include_all_directory.java create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/java_template/CromiteCachedFlag.java.tmpl create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Add-cromite-flags-support.grdp @@ -143,7 +143,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html diff --git a/base/BUILD.gn b/base/BUILD.gn --- a/base/BUILD.gn +++ b/base/BUILD.gn -@@ -165,6 +165,8 @@ use_epoll = is_linux || is_chromeos || is_android +@@ -164,6 +164,8 @@ use_epoll = is_linux || is_chromeos || is_android # This does not include test code (test support and anything in the test # directory) which should use source_set as is recommended for GN targets). component("base") { @@ -152,7 +152,7 @@ diff --git a/base/BUILD.gn b/base/BUILD.gn sources = [ "allocator/allocator_check.cc", "allocator/allocator_check.h", -@@ -1016,7 +1018,7 @@ component("base") { +@@ -1019,7 +1021,7 @@ component("base") { "//build/config/compiler:wglobal_constructors", ] @@ -191,7 +191,7 @@ diff --git a/base/android/java/src/org/chromium/base/cached_flags/ValuesReturned diff --git a/base/feature_list.cc b/base/feature_list.cc --- a/base/feature_list.cc +++ b/base/feature_list.cc -@@ -42,6 +42,31 @@ +@@ -41,6 +41,31 @@ namespace base { @@ -223,7 +223,7 @@ diff --git a/base/feature_list.cc b/base/feature_list.cc namespace { // Pointer to the FeatureList instance singleton that was set via -@@ -473,6 +498,46 @@ bool FeatureList::IsEnabled(const Feature& feature) { +@@ -472,6 +497,46 @@ bool FeatureList::IsEnabled(const Feature& feature) { return g_feature_list_instance->IsFeatureEnabled(feature); } @@ -270,7 +270,7 @@ diff --git a/base/feature_list.cc b/base/feature_list.cc // static bool FeatureList::IsValidFeatureOrFieldTrialName(std::string_view name) { return IsStringASCII(name) && name.find_first_of(",<*") == std::string::npos; -@@ -743,6 +808,17 @@ void FeatureList::VisitFeaturesAndParams(FeatureVisitor& visitor, +@@ -742,6 +807,17 @@ void FeatureList::VisitFeaturesAndParams(FeatureVisitor& visitor, void FeatureList::FinalizeInitialization() { DCHECK(!initialized_); @@ -291,7 +291,7 @@ diff --git a/base/feature_list.cc b/base/feature_list.cc diff --git a/base/feature_list.h b/base/feature_list.h --- a/base/feature_list.h +++ b/base/feature_list.h -@@ -170,8 +170,10 @@ enum class FeatureMacroHandshake { kSecret }; +@@ -167,8 +167,10 @@ enum class FeatureMacroHandshake { kSecret }; struct BASE_EXPORT LOGICALLY_CONST Feature { constexpr Feature(const char* name, FeatureState default_state, @@ -304,7 +304,7 @@ diff --git a/base/feature_list.h b/base/feature_list.h #if BUILDFLAG(ENABLE_BANNED_BASE_FEATURE_PREFIX) if (std::string_view(name).find(BUILDFLAG(BANNED_BASE_FEATURE_PREFIX)) == 0) { -@@ -198,6 +200,9 @@ struct BASE_EXPORT LOGICALLY_CONST Feature { +@@ -195,6 +197,9 @@ struct BASE_EXPORT LOGICALLY_CONST Feature { // command line switch. const FeatureState default_state; @@ -314,7 +314,7 @@ diff --git a/base/feature_list.h b/base/feature_list.h private: friend class FeatureList; -@@ -477,6 +482,11 @@ class BASE_EXPORT FeatureList { +@@ -474,6 +479,11 @@ class BASE_EXPORT FeatureList { // instance, which is checked in builds with DCHECKs enabled. static bool IsEnabled(const Feature& feature); @@ -326,7 +326,7 @@ diff --git a/base/feature_list.h b/base/feature_list.h // Some characters are not allowed to appear in feature names or the // associated field trial names, as they are used as special characters for // command-line serialization. This function checks that the strings are ASCII -@@ -746,4 +756,62 @@ class BASE_EXPORT FeatureList { +@@ -743,4 +753,62 @@ class BASE_EXPORT FeatureList { } // namespace base @@ -632,7 +632,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/setting diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc -@@ -394,6 +394,10 @@ using flags_ui::kOsLinux; +@@ -396,6 +396,10 @@ using flags_ui::kOsLinux; using flags_ui::kOsMac; using flags_ui::kOsWin; @@ -643,9 +643,9 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc namespace about_flags { namespace { -@@ -4383,6 +4387,10 @@ const FeatureEntry::FeatureVariation kTabSwitcherColorBlendAnimateVariations[] = - std::size(kTabSwitcherColorBlendAnimateVariation3), nullptr}}; - #endif // BUILDFLAG(IS_ANDROID) +@@ -4428,6 +4432,10 @@ const FeatureEntry::FeatureVariation + std::size(kStandardBoundSessionCredentialsEnabledOriginTrialToken), + nullptr}}; +#define FEATURE_PARAM_SECTION +#include "cromite_flags/chrome_browser_about_flags_cc.inc" @@ -654,7 +654,7 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc // RECORDING USER METRICS FOR FLAGS: // ----------------------------------------------------------------------------- // The first line of the entry is the internal name. -@@ -4409,6 +4417,9 @@ const FeatureEntry::FeatureVariation kTabSwitcherColorBlendAnimateVariations[] = +@@ -4454,6 +4462,9 @@ const FeatureEntry::FeatureVariation const FeatureEntry kFeatureEntries[] = { // Include generated flags for flag unexpiry; see //docs/flag_expiry.md and // //tools/flags/generate_unexpire_flags.py. @@ -667,7 +667,7 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc --- a/chrome/browser/browser_features.cc +++ b/chrome/browser/browser_features.cc -@@ -367,4 +367,5 @@ BASE_FEATURE(kRemovalOfIWAsFromTabCapture, +@@ -357,4 +357,5 @@ BASE_FEATURE(kRemovalOfIWAsFromTabCapture, "RemovalOfIWAsFromTabCapture", base::FEATURE_ENABLED_BY_DEFAULT); @@ -676,7 +676,7 @@ diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_feature diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h --- a/chrome/browser/browser_features.h +++ b/chrome/browser/browser_features.h -@@ -136,6 +136,7 @@ BASE_DECLARE_FEATURE(kRemovalOfIWAsFromTabCapture); +@@ -134,6 +134,7 @@ BASE_DECLARE_FEATURE(kRemovalOfIWAsFromTabCapture); // module, e.g. // //chrome/browser//features.h // @@ -726,7 +726,7 @@ diff --git a/chrome/browser/flags/BUILD.gn b/chrome/browser/flags/BUILD.gn diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -1182,5 +1182,6 @@ BASE_FEATURE(kWebOtpCrossDeviceSimpleString, +@@ -1207,5 +1207,6 @@ BASE_FEATURE(kWebOtpCrossDeviceSimpleString, "WebOtpCrossDeviceSimpleString", base::FEATURE_DISABLED_BY_DEFAULT); @@ -736,7 +736,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h --- a/chrome/browser/flags/android/chrome_feature_list.h +++ b/chrome/browser/flags/android/chrome_feature_list.h -@@ -236,6 +236,7 @@ constexpr base::FeatureParam kQuickDeleteAndroidSurveyTriggerId( +@@ -241,6 +241,7 @@ constexpr base::FeatureParam kQuickDeleteAndroidSurveyTriggerId( "trigger_id", /*default_value=*/""); @@ -758,7 +758,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f import java.util.List; import java.util.Map; -@@ -830,7 +834,7 @@ public abstract class ChromeFeatureList { +@@ -863,7 +867,7 @@ public abstract class ChromeFeatureList { public static final CachedFlag sWebApkMinShellApkVersion = newCachedFlag(WEB_APK_MIN_SHELL_APK_VERSION, true); @@ -767,7 +767,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f List.of( sAndroidAppIntegration, sAndroidAppIntegrationModule, -@@ -933,6 +937,13 @@ public abstract class ChromeFeatureList { +@@ -968,6 +972,13 @@ public abstract class ChromeFeatureList { sUseLibunwindstackNativeUnwinderAndroid, sWebApkMinShellApkVersion); @@ -778,9 +778,9 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f + public static final List sFlagsCachedFullBrowser = + concatenateLists(CromiteCachedFlagImplBase.getList(), sFlagsCachedFullBrowserChromium); + - public static final List sFlagsCachedInMinimalBrowser = List.of(); + public static final List sFlagsCachedInMinimalBrowser = + List.of(sAsyncNotificationManagerForDownload); - public static final List sTestCachedFlags = diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/include_all_directory.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/include_all_directory.java new file mode 100644 --- /dev/null @@ -853,10 +853,10 @@ diff --git a/chrome/browser/settings/BUILD.gn b/chrome/browser/settings/BUILD.gn diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java --- a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java +++ b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/ChromeBaseSettingsFragment.java -@@ -7,12 +7,19 @@ package org.chromium.chrome.browser.settings; - import androidx.annotation.NonNull; - import androidx.preference.PreferenceFragmentCompat; +@@ -8,12 +8,19 @@ import androidx.preference.PreferenceFragmentCompat; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; +import org.chromium.base.supplier.Supplier; import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher; import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherFactory; @@ -873,9 +873,9 @@ diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browse /** * Base class for settings in Chrome. * -@@ -26,6 +33,49 @@ public abstract class ChromeBaseSettingsFragment extends PreferenceFragmentCompa - private Profile mProfile; - private SettingsCustomTabLauncher mCustomTabLauncher; +@@ -28,6 +35,49 @@ public abstract class ChromeBaseSettingsFragment extends PreferenceFragmentCompa + private @Nullable Profile mProfile; + private @Nullable SettingsCustomTabLauncher mCustomTabLauncher; + private Supplier mRequireRestartDelegateSupplier; + @@ -957,16 +957,16 @@ new file mode 100644 diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc --- a/chrome/browser/ui/ui_features.cc +++ b/chrome/browser/ui/ui_features.cc -@@ -416,4 +416,5 @@ BASE_FEATURE(kFedCmContinueWithoutName, - "FedCmContinueWithoutName", - base::FEATURE_ENABLED_BY_DEFAULT); +@@ -371,4 +371,5 @@ BASE_FEATURE(kByDateHistoryInSidePanel, + "ByDateHistoryInSidePanel", + base::FEATURE_DISABLED_BY_DEFAULT); +#include "cromite_flags/chrome_browser_ui_ui_features_cc.inc" } // namespace features diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc -@@ -1569,4 +1569,5 @@ BASE_FEATURE(kDisableShortcutsEnableDiy, +@@ -1591,4 +1591,5 @@ BASE_FEATURE(kDisableShortcutsEnableDiy, "DisableShortcutsEnableDiy", base::FEATURE_ENABLED_BY_DEFAULT); @@ -1129,10 +1129,93 @@ new file mode 100644 +++ b/components/cromite_components_strings_grd/placeholder.txt @@ -0,0 +1 @@ +this file is intentionally empty -diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_state.cc ---- a/components/flags_ui/flags_state.cc -+++ b/components/flags_ui/flags_state.cc -@@ -373,6 +373,21 @@ void FlagsState::GetSwitchesAndFeaturesFromFlags( +diff --git a/components/offline_pages/core/offline_page_feature.cc b/components/offline_pages/core/offline_page_feature.cc +--- a/components/offline_pages/core/offline_page_feature.cc ++++ b/components/offline_pages/core/offline_page_feature.cc +@@ -48,4 +48,5 @@ bool IsOfflinePagesNetworkStateLikelyUnknown() { + return base::FeatureList::IsEnabled(kOfflinePagesNetworkStateLikelyUnknown); + } + ++#include "cromite_flags/components_offline_pages_core_offline_page_feature_cc.inc" + } // namespace offline_pages +diff --git a/components/offline_pages/core/offline_page_feature.h b/components/offline_pages/core/offline_page_feature.h +--- a/components/offline_pages/core/offline_page_feature.h ++++ b/components/offline_pages/core/offline_page_feature.h +@@ -42,6 +42,7 @@ bool IsOnTheFlyMhtmlHashComputationEnabled(); + // offline pages to avoid showing them even when the device is online. + bool IsOfflinePagesNetworkStateLikelyUnknown(); + ++#include "cromite_flags/components_offline_pages_core_offline_page_feature_h.inc" + } // namespace offline_pages + + #endif // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_FEATURE_H_ +diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc +--- a/components/password_manager/core/browser/features/password_features.cc ++++ b/components/password_manager/core/browser/features/password_features.cc +@@ -187,4 +187,5 @@ BASE_FEATURE(kImprovedPasswordChangeService, + "ImprovedPasswordChangeService", + base::FEATURE_DISABLED_BY_DEFAULT); + ++#include "cromite_flags/components_password_manager_core_browser_features_password_features_cc.inc" + } // namespace password_manager::features +diff --git a/components/permissions/features.cc b/components/permissions/features.cc +--- a/components/permissions/features.cc ++++ b/components/permissions/features.cc +@@ -137,6 +137,8 @@ BASE_FEATURE(kOsAdditionalSecurityPermissionKillSwitch, + "OsAdditionalSecurityPermissionKillSwitch", + base::FEATURE_DISABLED_BY_DEFAULT); + #endif ++ ++#include "cromite_flags/components_permissions_features_cc.inc" + } // namespace features + namespace feature_params { + +diff --git a/components/variations/synthetic_trials_active_group_id_provider.cc b/components/variations/synthetic_trials_active_group_id_provider.cc +--- a/components/variations/synthetic_trials_active_group_id_provider.cc ++++ b/components/variations/synthetic_trials_active_group_id_provider.cc +@@ -27,7 +27,7 @@ SyntheticTrialsActiveGroupIdProvider::GetActiveGroupIds() { + return group_ids_; + } + +-#if !defined(NDEBUG) ++#if true + std::vector + SyntheticTrialsActiveGroupIdProvider::GetGroups() { + base::AutoLock scoped_lock(lock_); +@@ -53,7 +53,7 @@ void SyntheticTrialsActiveGroupIdProvider::OnSyntheticTrialsChanged( + for (const auto& group : groups) { + group_ids_.push_back(group.id()); + } +-#if !defined(NDEBUG) ++#if true + groups_ = groups; + #endif // !defined(NDEBUG) + } +diff --git a/components/variations/synthetic_trials_active_group_id_provider.h b/components/variations/synthetic_trials_active_group_id_provider.h +--- a/components/variations/synthetic_trials_active_group_id_provider.h ++++ b/components/variations/synthetic_trials_active_group_id_provider.h +@@ -36,7 +36,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider + // Returns currently active synthetic trial group IDs. + std::vector GetActiveGroupIds(); + +-#if !defined(NDEBUG) ++#if true + // In debug mode, not only the group IDs are tracked but also the full group + // info, to display the names unhashed in chrome://version. + std::vector GetGroups(); +@@ -60,7 +60,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider + + base::Lock lock_; + std::vector group_ids_; // GUARDED_BY(lock_); +-#if !defined(NDEBUG) ++#if true + // In debug builds, keep the full group information to be able to display it + // in chrome://version. + std::vector groups_; // GUARDED_BY(lock_); +diff --git a/components/webui/flags/flags_state.cc b/components/webui/flags/flags_state.cc +--- a/components/webui/flags/flags_state.cc ++++ b/components/webui/flags/flags_state.cc +@@ -377,6 +377,21 @@ void FlagsState::GetSwitchesAndFeaturesFromFlags( for (const std::string& entry_name : enabled_entries) { const auto& entry_it = name_to_switch_map.find(entry_name); @@ -1154,7 +1237,7 @@ diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_stat CHECK(entry_it != name_to_switch_map.end(), base::NotFatalUntil::M130); const SwitchEntry& entry = entry_it->second; -@@ -690,6 +705,27 @@ void FlagsState::GetFlagFeatureEntries( +@@ -705,6 +720,27 @@ void FlagsState::GetFlagFeatureEntries( data.Set("links", std::move(links)); } @@ -1182,7 +1265,7 @@ diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_stat switch (entry.type) { case FeatureEntry::SINGLE_VALUE: case FeatureEntry::SINGLE_DISABLE_VALUE: -@@ -816,6 +852,16 @@ void FlagsState::AddSwitchesToCommandLine( +@@ -832,6 +868,16 @@ void FlagsState::AddSwitchesToCommandLine( for (const std::string& entry_name : enabled_entries) { const auto& entry_it = name_to_switch_map.find(entry_name); if (entry_it == name_to_switch_map.end()) { @@ -1199,7 +1282,7 @@ diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_stat NOTREACHED(); } -@@ -1067,6 +1113,14 @@ const FeatureEntry* FlagsState::FindFeatureEntryByName( +@@ -1088,6 +1134,14 @@ const FeatureEntry* FlagsState::FindFeatureEntryByName( bool FlagsState::IsSupportedFeature(const FlagsStorage* storage, const std::string& name, int platform_mask) const { @@ -1213,8 +1296,8 @@ diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_stat + } for (const auto& entry : feature_entries_) { DCHECK(entry.IsValid()); - if (!(entry.supported_platforms & platform_mask)) -@@ -1098,6 +1152,11 @@ void FlagsState::SetFlags(FlagsStorage* flags_storage, + if (!(entry.supported_platforms & platform_mask)) { +@@ -1122,6 +1176,11 @@ void FlagsState::SetFlags(FlagsStorage* flags_storage, std::string feature_internal_name = flag.substr(0, at_index); const flags_ui::FeatureEntry* entry = FindFeatureEntryByName(feature_internal_name); @@ -1226,9 +1309,9 @@ diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_stat CHECK(entry); if (entry->type == FeatureEntry::FEATURE_VALUE || -diff --git a/components/flags_ui/resources/app.css b/components/flags_ui/resources/app.css ---- a/components/flags_ui/resources/app.css -+++ b/components/flags_ui/resources/app.css +diff --git a/components/webui/flags/resources/app.css b/components/webui/flags/resources/app.css +--- a/components/webui/flags/resources/app.css ++++ b/components/webui/flags/resources/app.css @@ -383,3 +383,27 @@ cr-tabs { padding-top: 1.5rem; } @@ -1257,9 +1340,9 @@ diff --git a/components/flags_ui/resources/app.css b/components/flags_ui/resourc +.cromite .section-header-title { + display: none; +} -diff --git a/components/flags_ui/resources/app.html.ts b/components/flags_ui/resources/app.html.ts ---- a/components/flags_ui/resources/app.html.ts -+++ b/components/flags_ui/resources/app.html.ts +diff --git a/components/webui/flags/resources/app.html.ts b/components/webui/flags/resources/app.html.ts +--- a/components/webui/flags/resources/app.html.ts ++++ b/components/webui/flags/resources/app.html.ts @@ -9,6 +9,7 @@ import type {AppElement} from './app.js'; export function getHtml(this: AppElement) { // clang-format off @@ -1309,9 +1392,9 @@ diff --git a/components/flags_ui/resources/app.html.ts b/components/flags_ui/res `; // clang-format on } -diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resources/app.ts ---- a/components/flags_ui/resources/app.ts -+++ b/components/flags_ui/resources/app.ts +diff --git a/components/webui/flags/resources/app.ts b/components/webui/flags/resources/app.ts +--- a/components/webui/flags/resources/app.ts ++++ b/components/webui/flags/resources/app.ts @@ -132,6 +132,7 @@ export class FlagsAppElement extends CrLitElement { // loadTimeData.getString('unavailable'), @@ -1333,13 +1416,13 @@ diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resource private announceStatusDelayMs: number = 100; private featuresResolver: PromiseResolver = new PromiseResolver(); private flagSearch: FlagSearch|null = null; -@@ -186,10 +190,24 @@ export class FlagsAppElement extends CrLitElement { +@@ -186,10 +190,25 @@ export class FlagsAppElement extends CrLitElement { if (changedPrivateProperties.has('data')) { const defaultFeatures: Feature[] = []; const nonDefaultFeatures: Feature[] = []; + const defaultCromiteFeatures: Feature[] = []; + const nonDefaultCromiteFeatures: Feature[] = []; - ++ + if (this.onlyCromiteFlags) { + this.data.supportedFeatures = + this.data.supportedFeatures.filter(item => item.is_new); @@ -1350,6 +1433,7 @@ diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resource + : undefined)); + this.data.supportedFeatures.sort( + (a,b) => (a.internal_name.localeCompare(b.internal_name))); + this.data.supportedFeatures.forEach( f => (f.is_default ? defaultFeatures : nonDefaultFeatures).push(f)); @@ -1358,7 +1442,7 @@ diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resource this.defaultFeatures = defaultFeatures; this.nonDefaultFeatures = nonDefaultFeatures; -@@ -233,6 +251,11 @@ export class FlagsAppElement extends CrLitElement { +@@ -233,6 +252,11 @@ export class FlagsAppElement extends CrLitElement { override connectedCallback() { super.connectedCallback(); @@ -1370,9 +1454,9 @@ diff --git a/components/flags_ui/resources/app.ts b/components/flags_ui/resource // const pathname = new URL(window.location.href).pathname; this.isFlagsDeprecatedUrl_ = -diff --git a/components/flags_ui/resources/experiment.css b/components/flags_ui/resources/experiment.css ---- a/components/flags_ui/resources/experiment.css -+++ b/components/flags_ui/resources/experiment.css +diff --git a/components/webui/flags/resources/experiment.css b/components/webui/flags/resources/experiment.css +--- a/components/webui/flags/resources/experiment.css ++++ b/components/webui/flags/resources/experiment.css @@ -12,6 +12,7 @@ } @@ -1417,9 +1501,9 @@ diff --git a/components/flags_ui/resources/experiment.css b/components/flags_ui/ width: 100%; } -diff --git a/components/flags_ui/resources/experiment.html.ts b/components/flags_ui/resources/experiment.html.ts ---- a/components/flags_ui/resources/experiment.html.ts -+++ b/components/flags_ui/resources/experiment.html.ts +diff --git a/components/webui/flags/resources/experiment.html.ts b/components/webui/flags/resources/experiment.html.ts +--- a/components/webui/flags/resources/experiment.html.ts ++++ b/components/webui/flags/resources/experiment.html.ts @@ -10,7 +10,8 @@ export function getHtml(this: ExperimentElement) { // clang-format off return html` @@ -1441,9 +1525,9 @@ diff --git a/components/flags_ui/resources/experiment.html.ts b/components/flags `)} -diff --git a/components/flags_ui/resources/experiment.ts b/components/flags_ui/resources/experiment.ts ---- a/components/flags_ui/resources/experiment.ts -+++ b/components/flags_ui/resources/experiment.ts +diff --git a/components/webui/flags/resources/experiment.ts b/components/webui/flags/resources/experiment.ts +--- a/components/webui/flags/resources/experiment.ts ++++ b/components/webui/flags/resources/experiment.ts @@ -81,6 +81,11 @@ export class ExperimentElement extends CrLitElement { enabled: false, is_default: false, @@ -1456,7 +1540,7 @@ diff --git a/components/flags_ui/resources/experiment.ts b/components/flags_ui/r }; // Whether the controls to change the experiment state should be hidden. -@@ -127,12 +132,15 @@ export class ExperimentElement extends CrLitElement { +@@ -127,9 +132,12 @@ export class ExperimentElement extends CrLitElement { } protected getExperimentTitle_(): string { @@ -1470,14 +1554,10 @@ diff --git a/components/flags_ui/resources/experiment.ts b/components/flags_ui/r + loadTimeData.getString('experiment-enabled')) + suffix; } -- return ''; -+ return suffix; - } - - protected getPlatforms_(): string { -diff --git a/components/flags_ui/resources/flags_browser_proxy.ts b/components/flags_ui/resources/flags_browser_proxy.ts ---- a/components/flags_ui/resources/flags_browser_proxy.ts -+++ b/components/flags_ui/resources/flags_browser_proxy.ts + return ''; +diff --git a/components/webui/flags/resources/flags_browser_proxy.ts b/components/webui/flags/resources/flags_browser_proxy.ts +--- a/components/webui/flags/resources/flags_browser_proxy.ts ++++ b/components/webui/flags/resources/flags_browser_proxy.ts @@ -16,6 +16,11 @@ export interface Feature { description: string; enabled: boolean; @@ -1490,88 +1570,6 @@ diff --git a/components/flags_ui/resources/flags_browser_proxy.ts b/components/f supported_platforms: string[]; origin_list_value?: string; string_value?: string; -diff --git a/components/offline_pages/core/offline_page_feature.cc b/components/offline_pages/core/offline_page_feature.cc ---- a/components/offline_pages/core/offline_page_feature.cc -+++ b/components/offline_pages/core/offline_page_feature.cc -@@ -48,4 +48,5 @@ bool IsOfflinePagesNetworkStateLikelyUnknown() { - return base::FeatureList::IsEnabled(kOfflinePagesNetworkStateLikelyUnknown); - } - -+#include "cromite_flags/components_offline_pages_core_offline_page_feature_cc.inc" - } // namespace offline_pages -diff --git a/components/offline_pages/core/offline_page_feature.h b/components/offline_pages/core/offline_page_feature.h ---- a/components/offline_pages/core/offline_page_feature.h -+++ b/components/offline_pages/core/offline_page_feature.h -@@ -42,6 +42,7 @@ bool IsOnTheFlyMhtmlHashComputationEnabled(); - // offline pages to avoid showing them even when the device is online. - bool IsOfflinePagesNetworkStateLikelyUnknown(); - -+#include "cromite_flags/components_offline_pages_core_offline_page_feature_h.inc" - } // namespace offline_pages - - #endif // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_FEATURE_H_ -diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc ---- a/components/password_manager/core/browser/features/password_features.cc -+++ b/components/password_manager/core/browser/features/password_features.cc -@@ -182,4 +182,5 @@ BASE_FEATURE(kImprovedPasswordChangeService, - "ImprovedPasswordChangeService", - base::FEATURE_DISABLED_BY_DEFAULT); - -+#include "cromite_flags/components_password_manager_core_browser_features_password_features_cc.inc" - } // namespace password_manager::features -diff --git a/components/permissions/features.cc b/components/permissions/features.cc ---- a/components/permissions/features.cc -+++ b/components/permissions/features.cc -@@ -120,6 +120,7 @@ BASE_FEATURE(kCpssQuietChipTextUpdate, - BASE_FEATURE(kCpssUseTfliteSignatureRunner, - "CpssUseTfliteSignatureRunner", - base::FEATURE_DISABLED_BY_DEFAULT); -+#include "cromite_flags/components_permissions_features_cc.inc" - } // namespace features - namespace feature_params { - -diff --git a/components/variations/synthetic_trials_active_group_id_provider.cc b/components/variations/synthetic_trials_active_group_id_provider.cc ---- a/components/variations/synthetic_trials_active_group_id_provider.cc -+++ b/components/variations/synthetic_trials_active_group_id_provider.cc -@@ -27,7 +27,7 @@ SyntheticTrialsActiveGroupIdProvider::GetActiveGroupIds() { - return group_ids_; - } - --#if !defined(NDEBUG) -+#if true - std::vector - SyntheticTrialsActiveGroupIdProvider::GetGroups() { - base::AutoLock scoped_lock(lock_); -@@ -53,7 +53,7 @@ void SyntheticTrialsActiveGroupIdProvider::OnSyntheticTrialsChanged( - for (const auto& group : groups) { - group_ids_.push_back(group.id()); - } --#if !defined(NDEBUG) -+#if true - groups_ = groups; - #endif // !defined(NDEBUG) - } -diff --git a/components/variations/synthetic_trials_active_group_id_provider.h b/components/variations/synthetic_trials_active_group_id_provider.h ---- a/components/variations/synthetic_trials_active_group_id_provider.h -+++ b/components/variations/synthetic_trials_active_group_id_provider.h -@@ -36,7 +36,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider - // Returns currently active synthetic trial group IDs. - std::vector GetActiveGroupIds(); - --#if !defined(NDEBUG) -+#if true - // In debug mode, not only the group IDs are tracked but also the full group - // info, to display the names unhashed in chrome://version. - std::vector GetGroups(); -@@ -60,7 +60,7 @@ class COMPONENT_EXPORT(VARIATIONS) SyntheticTrialsActiveGroupIdProvider - - base::Lock lock_; - std::vector group_ids_; // GUARDED_BY(lock_); --#if !defined(NDEBUG) -+#if true - // In debug builds, keep the full group information to be able to display it - // in chrome://version. - std::vector groups_; // GUARDED_BY(lock_); diff --git a/components/webui/version/version_handler_helper.cc b/components/webui/version/version_handler_helper.cc --- a/components/webui/version/version_handler_helper.cc +++ b/components/webui/version/version_handler_helper.cc @@ -1596,7 +1594,7 @@ diff --git a/components/webui/version/version_handler_helper.cc b/components/web diff --git a/content/common/features.cc b/content/common/features.cc --- a/content/common/features.cc +++ b/content/common/features.cc -@@ -490,4 +490,5 @@ BASE_FEATURE(kLimitCrossOriginNonActivatedPaintHolding, +@@ -509,4 +509,5 @@ BASE_FEATURE(kLimitCrossOriginNonActivatedPaintHolding, // Please keep features in alphabetical order. @@ -1605,7 +1603,7 @@ diff --git a/content/common/features.cc b/content/common/features.cc diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc -@@ -1425,4 +1425,5 @@ bool IsVideoCaptureServiceEnabledForBrowserProcess() { +@@ -1485,4 +1485,5 @@ bool IsVideoCaptureServiceEnabledForBrowserProcess() { VideoCaptureServiceConfiguration::kEnabledForBrowserProcess; } @@ -1614,7 +1612,7 @@ diff --git a/content/public/common/content_features.cc b/content/public/common/c diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h --- a/content/public/common/content_features.h +++ b/content/public/common/content_features.h -@@ -339,6 +339,7 @@ CONTENT_EXPORT extern const base::FeatureParam +@@ -350,6 +350,7 @@ CONTENT_EXPORT extern const base::FeatureParam CONTENT_EXPORT bool IsVideoCaptureServiceEnabledForOutOfProcess(); CONTENT_EXPORT bool IsVideoCaptureServiceEnabledForBrowserProcess(); @@ -1960,7 +1958,7 @@ new file mode 100755 diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc --- a/media/base/media_switches.cc +++ b/media/base/media_switches.cc -@@ -1799,5 +1799,5 @@ uint32_t GetPassthroughAudioFormats() { +@@ -1807,5 +1807,5 @@ uint32_t GetPassthroughAudioFormats() { return 0; #endif // BUILDFLAG(ENABLE_PASSTHROUGH_AUDIO_CODECS) } @@ -1970,7 +1968,7 @@ diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc diff --git a/media/base/media_switches.h b/media/base/media_switches.h --- a/media/base/media_switches.h +++ b/media/base/media_switches.h -@@ -568,5 +568,5 @@ MEDIA_EXPORT OOPVDMode GetOutOfProcessVideoDecodingMode(); +@@ -571,5 +571,5 @@ MEDIA_EXPORT OOPVDMode GetOutOfProcessVideoDecodingMode(); MEDIA_EXPORT uint32_t GetPassthroughAudioFormats(); } // namespace media @@ -1980,18 +1978,18 @@ diff --git a/media/base/media_switches.h b/media/base/media_switches.h diff --git a/net/base/features.cc b/net/base/features.cc --- a/net/base/features.cc +++ b/net/base/features.cc -@@ -701,4 +701,5 @@ BASE_FEATURE(kUseCertTransparencyAwareApiForOsCertVerify, - base::FEATURE_ENABLED_BY_DEFAULT); - #endif // BUILDFLAG(IS_ANDROID) +@@ -709,4 +709,5 @@ BASE_FEATURE(kSelfSignedLocalNetworkInterstitial, + "SelfSignedLocalNetworkInterstitial", + base::FEATURE_DISABLED_BY_DEFAULT); +#include "cromite_flags/net_base_features_cc.inc" } // namespace net::features diff --git a/net/base/features.h b/net/base/features.h --- a/net/base/features.h +++ b/net/base/features.h -@@ -719,6 +719,7 @@ NET_EXPORT BASE_DECLARE_FEATURE(kReportingApiCorsOriginHeader); - NET_EXPORT BASE_DECLARE_FEATURE(kUseCertTransparencyAwareApiForOsCertVerify); - #endif // BUILDFLAG(IS_ANDROID) +@@ -721,6 +721,7 @@ NET_EXPORT BASE_DECLARE_FEATURE(kUseCertTransparencyAwareApiForOsCertVerify); + // URLs. + NET_EXPORT BASE_DECLARE_FEATURE(kSelfSignedLocalNetworkInterstitial); +#include "cromite_flags/net_base_features_h.inc" } // namespace net::features @@ -2000,18 +1998,18 @@ diff --git a/net/base/features.h b/net/base/features.h diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc --- a/services/network/public/cpp/features.cc +++ b/services/network/public/cpp/features.cc -@@ -351,4 +351,5 @@ BASE_FEATURE(kAdAuctionEventRegistration, - "AdAuctionEventRegistration", - base::FEATURE_DISABLED_BY_DEFAULT); +@@ -544,4 +544,5 @@ BASE_FEATURE_PARAM(int, + "max_ops_before_maintenance", + 1000); +#include "cromite_flags/services_network_public_cpp_features_cc.inc" } // namespace network::features diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h --- a/services/network/public/cpp/features.h +++ b/services/network/public/cpp/features.h -@@ -150,6 +150,7 @@ COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kCreateURLLoaderPipeAsync); - // requests be processed? - COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kAdAuctionEventRegistration); +@@ -261,6 +261,7 @@ BASE_DECLARE_FEATURE_PARAM(int, kInterestGroupStorageMaxNegativeGroupsPerOwner); + COMPONENT_EXPORT(NETWORK_CPP) + BASE_DECLARE_FEATURE_PARAM(int, kInterestGroupStorageMaxOpsBeforeMaintenance); +#include "cromite_flags/services_network_public_cpp_features_h.inc" } // namespace network::features @@ -2020,7 +2018,7 @@ diff --git a/services/network/public/cpp/features.h b/services/network/public/cp diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc -@@ -2899,4 +2899,5 @@ bool IsLinkPreviewTriggerTypeEnabled(LinkPreviewTriggerType type) { +@@ -2744,4 +2744,5 @@ bool IsLinkPreviewTriggerTypeEnabled(LinkPreviewTriggerType type) { // // DO NOT ADD NEW FEATURES HERE. @@ -2029,7 +2027,7 @@ diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/fea diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h --- a/third_party/blink/public/common/features.h +++ b/third_party/blink/public/common/features.h -@@ -1889,6 +1889,7 @@ BLINK_COMMON_EXPORT bool IsUpdateComplexSafaAreaConstraintsEnabled(); +@@ -1793,6 +1793,7 @@ BLINK_COMMON_EXPORT bool IsUpdateComplexSafaAreaConstraintsEnabled(); // // DO NOT ADD NEW FEATURES HERE. @@ -2040,7 +2038,7 @@ diff --git a/third_party/blink/public/common/features.h b/third_party/blink/publ diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc --- a/ui/base/ui_base_features.cc +++ b/ui/base/ui_base_features.cc -@@ -451,4 +451,5 @@ BASE_FEATURE(kAsyncFullscreenWindowState, +@@ -456,4 +456,5 @@ BASE_FEATURE(kAsyncFullscreenWindowState, "AsyncFullscreenWindowState", base::FEATURE_DISABLED_BY_DEFAULT); diff --git a/build/cromite_patches/Add-custom-tab-intents-privacy-option.patch b/build/cromite_patches/Add-custom-tab-intents-privacy-option.patch index f66bd056e7eaa631adb4084bb0acaddee9a50d0f..f531b22f8e1706437009935250aec57b3054d490 100644 --- a/build/cromite_patches/Add-custom-tab-intents-privacy-option.patch +++ b/build/cromite_patches/Add-custom-tab-intents-privacy-option.patch @@ -108,7 +108,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDis diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java -@@ -1187,7 +1187,9 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid +@@ -1190,7 +1190,9 @@ public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvid public @CustomTabProfileType int getCustomTabMode() { return AlwaysIncognitoLinkInterceptor.isAlwaysIncognito() ? CustomTabProfileType.INCOGNITO @@ -122,7 +122,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/Cust diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java -@@ -980,6 +980,7 @@ public class CustomTabsConnection { +@@ -981,6 +981,7 @@ public class CustomTabsConnection { PostTask.postTask( TaskTraits.UI_DEFAULT, () -> { @@ -266,7 +266,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/setting diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -730,6 +730,8 @@ public abstract class ChromeFeatureList { +@@ -767,6 +767,8 @@ public abstract class ChromeFeatureList { public static final CachedFlag sMagicStackAndroid = newCachedFlag(MAGIC_STACK_ANDROID, true); public static final CachedFlag sMostVisitedTilesCustomization = newCachedFlag(MOST_VISITED_TILES_CUSTOMIZATION, false); @@ -275,7 +275,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f public static final CachedFlag sMostVisitedTilesReselect = newCachedFlag(MOST_VISITED_TILES_RESELECT, false); public static final CachedFlag sMultiInstanceApplicationStatusCleanup = -@@ -897,6 +899,7 @@ public abstract class ChromeFeatureList { +@@ -933,6 +935,7 @@ public abstract class ChromeFeatureList { sNotificationTrampoline, sMagicStackAndroid, sMostVisitedTilesCustomization, diff --git a/build/cromite_patches/Add-exit-menu-item.patch b/build/cromite_patches/Add-exit-menu-item.patch index 784dbf484068da1a1c406fe1dc6cb6d7a9d7303e..0bcee1285d724e9808c7510835b70820bdadef82 100644 --- a/build/cromite_patches/Add-exit-menu-item.patch +++ b/build/cromite_patches/Add-exit-menu-item.patch @@ -16,7 +16,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml --- a/chrome/android/java/res/menu/main_menu.xml +++ b/chrome/android/java/res/menu/main_menu.xml -@@ -166,6 +166,9 @@ found in the LICENSE file. +@@ -175,6 +175,9 @@ found in the LICENSE file. @@ -26,7 +26,7 @@ diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/re -@@ -195,6 +198,9 @@ found in the LICENSE file. +@@ -204,6 +207,9 @@ found in the LICENSE file. @@ -39,7 +39,7 @@ diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/re diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java -@@ -293,6 +293,8 @@ import java.util.Set; +@@ -297,6 +297,8 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.DoubleConsumer; @@ -48,7 +48,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct /** * This is the main activity for ChromeMobile when not running in document mode. All the tabs are * accessible via a chrome specific tab switching UI. -@@ -3021,6 +3023,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -3070,6 +3072,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn .closeTabs( TabClosureParams.closeTab(currentTab).build(), /* allowDialog= */ true); RecordUserAction.record("MobileTabClosed"); @@ -60,15 +60,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -60,6 +60,7 @@ import org.chromium.chrome.R; +@@ -59,6 +59,7 @@ import org.chromium.base.supplier.UnownedUserDataSupplier; + import org.chromium.chrome.R; import org.chromium.chrome.browser.ActivityTabProvider; import org.chromium.chrome.browser.ActivityUtils; - import org.chromium.chrome.browser.AppHooks; +import org.chromium.chrome.browser.lifetime.ApplicationLifetime; import org.chromium.chrome.browser.ChromeActivitySessionTracker; import org.chromium.chrome.browser.ChromeApplicationImpl; import org.chromium.chrome.browser.ChromeKeyboardVisibilityDelegate; -@@ -2374,6 +2375,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -2375,6 +2376,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity return true; } @@ -102,7 +102,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeLife diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -4345,6 +4345,9 @@ To change this setting, BEGIN_LINKdelete the Chrome d +@@ -4382,6 +4382,9 @@ To change this setting, BEGIN_LINKdelete the Chrome d Dark theme diff --git a/build/cromite_patches/Add-flag-for-omnibox-autocomplete-filtering.patch b/build/cromite_patches/Add-flag-for-omnibox-autocomplete-filtering.patch index 9ef86be5574e09728d3061973dfe3faa28c8febe..4dd75b5743615bb056b8eb3432844a9076fcac69 100644 --- a/build/cromite_patches/Add-flag-for-omnibox-autocomplete-filtering.patch +++ b/build/cromite_patches/Add-flag-for-omnibox-autocomplete-filtering.patch @@ -26,7 +26,7 @@ diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/ #include "base/feature_list.h" #include "base/format_macros.h" #include "base/functional/bind.h" -@@ -540,6 +541,15 @@ AutocompleteController::AutocompleteController( +@@ -536,6 +537,15 @@ AutocompleteController::AutocompleteController( provider_client_->GetOmniboxTriggeredFeatureService()), steady_state_omnibox_position_( metrics::OmniboxEventProto::UNKNOWN_POSITION) { diff --git a/build/cromite_patches/Add-flag-to-configure-maximum-connections-per-host.patch b/build/cromite_patches/Add-flag-to-configure-maximum-connections-per-host.patch index f43672e013664e4d3574359ee1e3c66c2c686bdf..346c8e4039fd2a3aef7830357460b73bd67b2ed8 100644 --- a/build/cromite_patches/Add-flag-to-configure-maximum-connections-per-host.patch +++ b/build/cromite_patches/Add-flag-to-configure-maximum-connections-per-host.patch @@ -103,7 +103,7 @@ diff --git a/net/socket/client_socket_pool_manager.cc b/net/socket/client_socket #include "net/ssl/ssl_config.h" #include "url/gurl.h" #include "url/scheme_host_port.h" -@@ -161,6 +165,19 @@ void ClientSocketPoolManager::set_max_sockets_per_pool( +@@ -164,6 +168,19 @@ void ClientSocketPoolManager::set_max_sockets_per_pool( int ClientSocketPoolManager::max_sockets_per_group( HttpNetworkSession::SocketPoolType pool_type) { DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES); diff --git a/build/cromite_patches/Add-flag-to-disable-IPv6-probes.patch b/build/cromite_patches/Add-flag-to-disable-IPv6-probes.patch index 6883cd5d31d785864c1e3c3e66df40bf421050a1..433eccf706c0e5257f97f79570eff41f958a7460 100644 --- a/build/cromite_patches/Add-flag-to-disable-IPv6-probes.patch +++ b/build/cromite_patches/Add-flag-to-disable-IPv6-probes.patch @@ -45,8 +45,8 @@ new file mode 100644 diff --git a/net/BUILD.gn b/net/BUILD.gn --- a/net/BUILD.gn +++ b/net/BUILD.gn -@@ -1118,6 +1118,7 @@ component("net") { - "//build:chromeos_buildflags", +@@ -1119,6 +1119,7 @@ component("net") { + ":net_deps", "//components/miracle_parameter/common", "//components/network_time/time_tracker", + "//components/network_session_configurator/common", @@ -64,7 +64,7 @@ diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc #include "net/log/net_log_with_source.h" #include "net/socket/client_socket_factory.h" #include "net/url_request/url_request_context.h" -@@ -1425,6 +1426,13 @@ int HostResolverManager::StartIPv6ReachabilityCheck( +@@ -1477,6 +1478,13 @@ int HostResolverManager::StartIPv6ReachabilityCheck( return OK; } diff --git a/build/cromite_patches/Add-flag-to-disable-external-intent-requests.patch b/build/cromite_patches/Add-flag-to-disable-external-intent-requests.patch index b8e7af240fd1e4262b53f08bd2211b075f960924..dbe32a8af46397716dba211b38c4d05e7411a61e 100644 --- a/build/cromite_patches/Add-flag-to-disable-external-intent-requests.patch +++ b/build/cromite_patches/Add-flag-to-disable-external-intent-requests.patch @@ -57,7 +57,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/Cust import org.chromium.chrome.browser.fullscreen.BrowserControlsManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.init.ChromeActivityNativeDelegate; -@@ -133,6 +134,7 @@ public class CustomTabDelegateFactory implements TabDelegateFactory { +@@ -127,6 +128,7 @@ public class CustomTabDelegateFactory implements TabDelegateFactory { @Override public boolean shouldDisableAllExternalIntents() { @@ -144,13 +144,13 @@ diff --git a/components/external_intents/android/external_intents_features.cc b/ @@ -27,7 +27,6 @@ namespace { const base::Feature* const kFeaturesExposedToJava[] = { &kExternalNavigationDebugLogs, &kBlockFrameRenavigations, - &kBlockIntentsToSelf, &kTrustedClientGestureBypass}; + &kBlockIntentsToSelf}; - } // namespace // Alphabetical: -@@ -48,7 +47,13 @@ BASE_FEATURE(kTrustedClientGestureBypass, - "TrustedClientGestureBypass", +@@ -44,7 +43,13 @@ BASE_FEATURE(kBlockIntentsToSelf, + "BlockIntentsToSelf", base::FEATURE_ENABLED_BY_DEFAULT); +CROMITE_FEATURE(kIntentBlockExternalFormRedirectsNoGesture, @@ -177,9 +177,9 @@ diff --git a/components/external_intents/android/external_intents_features.h b/c diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java --- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java +++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatures.java -@@ -18,6 +18,12 @@ import org.chromium.base.Features; - */ +@@ -20,6 +20,12 @@ import org.chromium.build.annotations.NullMarked; @JNINamespace("external_intents") + @NullMarked public class ExternalIntentsFeatures extends Features { + public static final String INTENT_BLOCK_EXTERNAL_FORM_REDIRECT_NO_GESTURE_NAME = + "IntentBlockExternalFormRedirectsNoGesture"; diff --git a/build/cromite_patches/Add-flag-to-disable-vibration.patch b/build/cromite_patches/Add-flag-to-disable-vibration.patch index 5d3f0e58c722909cd491b0c4ec6bcb84577857fa..35567bca49596910614f265ff9207fca238dcda0 100644 --- a/build/cromite_patches/Add-flag-to-disable-vibration.patch +++ b/build/cromite_patches/Add-flag-to-disable-vibration.patch @@ -104,9 +104,9 @@ diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.c diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -855,6 +855,10 @@ - status: {"Android": "stable"}, - base_feature: "none", +@@ -869,6 +869,10 @@ + { + name: "CompositingDecisionAtAnimationPhaseBoundaries" }, + { + name: "Vibration", diff --git a/build/cromite_patches/Add-lifetime-options-for-permissions.patch b/build/cromite_patches/Add-lifetime-options-for-permissions.patch index 0baeefa9ec8cb1cb275b0f06ce8f161ef3b10186..d34bde2872956a80daa3db6e520333339264f62c 100644 --- a/build/cromite_patches/Add-lifetime-options-for-permissions.patch +++ b/build/cromite_patches/Add-lifetime-options-for-permissions.patch @@ -232,7 +232,7 @@ diff --git a/chrome/browser/permissions/one_time_permissions_tracker_factory.cc diff --git a/chrome/browser/ui/views/permissions/chip/chip_controller.cc b/chrome/browser/ui/views/permissions/chip/chip_controller.cc --- a/chrome/browser/ui/views/permissions/chip/chip_controller.cc +++ b/chrome/browser/ui/views/permissions/chip/chip_controller.cc -@@ -235,7 +235,7 @@ void ChipController::OnWidgetDestroyed(views::Widget* widget) { +@@ -224,7 +224,7 @@ void ChipController::OnWidgetDestroyed(views::Widget* widget) { active_chip_permission_request_manager_.value()->Accept(); break; case permissions::PermissionAction::GRANTED_ONCE: @@ -279,10 +279,10 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java -@@ -591,6 +591,11 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment - } +@@ -603,6 +603,11 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment } + @RequiresNonNull({"mSite"}) + private boolean isSessionPermission(@ContentSettingsType.EnumType int type) { + return mSite.getPermissionInfo(type) != null && + mSite.getPermissionInfo(type).getSessionModel() == SessionModel.USER_SESSION; @@ -290,8 +290,8 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c + private void setUpClearDataPreference() { ClearWebsiteStorage preference = findPreference(PREF_CLEAR_DATA); - long usage = mSite.getTotalUsage(); -@@ -1156,6 +1161,10 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment + assumeNonNull(preference); +@@ -1195,6 +1200,10 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment AppCompatResources.getColorStateList(getContext(), mHighlightColor) .getDefaultColor()); } @@ -305,7 +305,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc -@@ -200,7 +200,7 @@ void GetOrigins(JNIEnv* env, +@@ -208,7 +208,7 @@ void GetOrigins(JNIEnv* env, seen_origins.push_back(origin); insertionFunc(env, static_cast(content_type), list, ConvertOriginToJavaString(env, origin), jembedder, @@ -532,7 +532,7 @@ diff --git a/components/page_info/android/page_info_controller_android.cc b/comp diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc --- a/components/page_info/page_info.cc +++ b/components/page_info/page_info.cc -@@ -1206,6 +1206,8 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, +@@ -1197,6 +1197,8 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, permission_info.is_one_time = (info.metadata.session_model() == content_settings::mojom::SessionModel::ONE_TIME); @@ -555,15 +555,15 @@ diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java -@@ -13,6 +13,7 @@ import org.jni_zero.JNINamespace; - import org.jni_zero.NativeMethods; - +@@ -15,6 +15,7 @@ import org.jni_zero.NativeMethods; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; import org.chromium.ui.base.WindowAndroid; +import org.chromium.components.content_settings.LifetimeMode; import java.util.ArrayList; import java.util.List; -@@ -63,6 +64,9 @@ public class PermissionDialogDelegate { +@@ -66,6 +67,9 @@ public class PermissionDialogDelegate { // Prompt(screen) variant we want to display on the dialog. private @EmbeddedPromptVariant int mEmbeddedPromptVariant; @@ -573,7 +573,7 @@ diff --git a/components/permissions/android/java/src/org/chromium/components/per /** * Defines a (potentially empty) list of ranges represented as pairs of , * which shall be used by the UI to format the specified ranges as bold text. -@@ -132,6 +136,15 @@ public class PermissionDialogDelegate { +@@ -135,6 +139,15 @@ public class PermissionDialogDelegate { .acceptThisTime(mNativeDelegatePtr, PermissionDialogDelegate.this); } @@ -592,7 +592,7 @@ diff --git a/components/permissions/android/java/src/org/chromium/components/per diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java -@@ -12,6 +12,18 @@ import org.chromium.ui.UiUtils; +@@ -13,6 +13,18 @@ import org.chromium.ui.UiUtils; import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modelutil.PropertyModel; @@ -609,9 +609,9 @@ diff --git a/components/permissions/android/java/src/org/chromium/components/per +import org.chromium.components.content_settings.LifetimeMode; + /** This class creates the model for the permission dialog. */ + @NullMarked class PermissionDialogModelFactory { - public static PropertyModel getModel( -@@ -65,7 +77,75 @@ class PermissionDialogModelFactory { +@@ -67,7 +79,75 @@ class PermissionDialogModelFactory { ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE) .with(ModalDialogProperties.CHANGE_CUSTOM_VIEW_OR_BUTTONS, true); } @@ -691,7 +691,7 @@ diff --git a/components/permissions/android/java/src/org/chromium/components/per diff --git a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc --- a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc +++ b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc -@@ -105,7 +105,8 @@ void EmbeddedPermissionPromptAndroid::Acknowledge() { +@@ -109,7 +109,8 @@ void EmbeddedPermissionPromptAndroid::Acknowledge() { delegate()->FinalizeCurrentRequests(); } @@ -704,7 +704,7 @@ diff --git a/components/permissions/android/permission_prompt/embedded_permissio diff --git a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h --- a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h +++ b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h -@@ -43,7 +43,7 @@ class EmbeddedPermissionPromptAndroid : public PermissionPromptAndroid { +@@ -44,7 +44,7 @@ class EmbeddedPermissionPromptAndroid : public PermissionPromptAndroid { const override; void Closing() override; void Accept() override; @@ -1378,7 +1378,7 @@ diff --git a/components/permissions/permission_request_manager.cc b/components/p request); } -@@ -1558,7 +1569,7 @@ void PermissionRequestManager::DoAutoResponseForTesting() { +@@ -1562,7 +1573,7 @@ void PermissionRequestManager::DoAutoResponseForTesting() { } switch (auto_response_for_test_) { case ACCEPT_ONCE: @@ -1402,7 +1402,7 @@ diff --git a/components/permissions/permission_request_manager.h b/components/pe void Dismiss() override; void Ignore() override; void FinalizeCurrentRequests() override; -@@ -374,9 +376,12 @@ class PermissionRequestManager +@@ -383,9 +385,12 @@ class PermissionRequestManager // Calls PermissionGranted on a request and all its duplicates. void PermissionGrantedIncludingDuplicates(PermissionRequest* request, diff --git a/build/cromite_patches/Add-menu-item-to-bookmark-all-tabs.patch b/build/cromite_patches/Add-menu-item-to-bookmark-all-tabs.patch index 39eb971aa1d5dd91da913bf9605ccdb662ad2113..e39840c00b226c5eebb57e9f2614eca7df3f335b 100644 --- a/build/cromite_patches/Add-menu-item-to-bookmark-all-tabs.patch +++ b/build/cromite_patches/Add-menu-item-to-bookmark-all-tabs.patch @@ -4,32 +4,32 @@ Subject: Add menu item to bookmark all tabs License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- - chrome/android/java/res/menu/main_menu.xml | 7 ++ - .../chrome/browser/ChromeTabbedActivity.java | 24 +++++++ - .../browser/bookmarks/BookmarkBridge.java | 67 +++++++++++++++++++ - .../bookmarks/android/bookmark_bridge.cc | 42 ++++++++++++ - .../bookmarks/android/bookmark_bridge.h | 8 +++ - .../browser/bookmarks/bookmark_html_writer.cc | 14 +++- - .../bookmark_merged_surface_service.cc | 3 + + chrome/android/java/res/menu/main_menu.xml | 7 +++ + .../chrome/browser/ChromeTabbedActivity.java | 43 +++++++++++++++++++ + .../bookmarks/android/bookmark_bridge.cc | 42 ++++++++++++++++++ + .../bookmarks/android/bookmark_bridge.h | 8 ++++ + .../browser/bookmarks/BookmarkBridge.java | 41 ++++++++++++++++++ + .../browser/bookmarks/bookmark_html_writer.cc | 14 +++++- + .../bookmark_merged_surface_service.cc | 3 ++ .../permanent_folder_ordering_tracker.cc | 2 + .../dialogs/DownloadLocationCustomView.java | 4 +- - .../strings/android_chrome_strings.grd | 3 + + .../strings/android_chrome_strings.grd | 3 ++ .../bookmark_ui_operations_helper.cc | 2 + - components/bookmark_bar_strings.grdp | 6 ++ - .../bookmarks/browser/bookmark_codec.cc | 20 +++++- - components/bookmarks/browser/bookmark_codec.h | 7 +- - .../browser/bookmark_load_details.cc | 14 +++- + components/bookmark_bar_strings.grdp | 6 +++ + .../bookmarks/browser/bookmark_codec.cc | 20 +++++++-- + components/bookmarks/browser/bookmark_codec.h | 7 ++- + .../browser/bookmark_load_details.cc | 14 ++++-- .../bookmarks/browser/bookmark_load_details.h | 2 + .../bookmarks/browser/bookmark_model.cc | 3 +- - components/bookmarks/browser/bookmark_model.h | 7 ++ - components/bookmarks/browser/bookmark_node.cc | 11 +++ - components/bookmarks/browser/bookmark_node.h | 3 + + components/bookmarks/browser/bookmark_model.h | 7 +++ + components/bookmarks/browser/bookmark_node.cc | 11 +++++ + components/bookmarks/browser/bookmark_node.h | 3 ++ .../bookmarks/browser/bookmark_storage.cc | 2 + - .../bookmarks/browser/bookmark_uuids.cc | 3 + + .../bookmarks/browser/bookmark_uuids.cc | 3 ++ components/bookmarks/browser/bookmark_uuids.h | 1 + - components/bookmarks/browser/model_loader.cc | 8 ++- + components/bookmarks/browser/model_loader.cc | 8 +++- .../bookmark_specifics_conversions.cc | 1 + - 25 files changed, 250 insertions(+), 14 deletions(-) + 25 files changed, 243 insertions(+), 14 deletions(-) diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml --- a/chrome/android/java/res/menu/main_menu.xml @@ -45,7 +45,7 @@ diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/re -@@ -189,6 +193,9 @@ found in the LICENSE file. +@@ -198,6 +202,9 @@ found in the LICENSE file. @@ -66,7 +66,25 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct import org.chromium.chrome.browser.compositor.layouts.Layout; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone; -@@ -3053,6 +3054,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -253,12 +254,17 @@ import org.chromium.chrome.browser.ui.RootUiCoordinator; + import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate; + import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderUtils; + import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils; ++import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; ++import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; ++import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController; + import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityClient; + import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityExtras.IntentOrigin; + import org.chromium.chrome.browser.undo_tab_close_snackbar.UndoBarController; + import org.chromium.chrome.browser.usage_stats.UsageStatsService; + import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; + import org.chromium.chrome.browser.xr.XrLayoutStateObserver; ++import org.chromium.components.bookmarks.BookmarkId; ++import org.chromium.components.bookmarks.BookmarkItem; + import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; + import org.chromium.components.browser_ui.edge_to_edge.SystemBarColorHelper; + import org.chromium.components.browser_ui.edge_to_edge.TabbedSystemBarColorHelper; +@@ -3103,6 +3109,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn getTabModelSelectorSupplier().get(), closeAllTabsRunnable); RecordUserAction.record("MobileMenuCloseAllTabs"); @@ -75,7 +93,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct } else if (id == R.id.close_all_incognito_tabs_menu_id) { // Close only incognito tabs Runnable closeAllTabsRunnable = -@@ -3138,6 +3141,27 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -3191,6 +3199,41 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn getTabModalLifetimeHandler().onOmniboxFocusChanged(hasFocus); } @@ -94,134 +112,29 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct + if (tab.isNativePage()) { + continue; + } -+ bookmarkModel.addToTabsCollection(this, tab); ++ bookmarkModel.addToTabsCollection(tab); + } -+ bookmarkModel.finishedAddingToTabsCollection(this, getSnackbarManager()); ++ BookmarkId parent = bookmarkModel.getTabsCollectionFolderId(); ++ BookmarkItem bookmarkItem = bookmarkModel.getBookmarkById(parent); ++ String folderName = ""; ++ if (bookmarkItem != null) { ++ folderName = bookmarkItem.getTitle(); ++ } ++ SnackbarController snackbarController = new SnackbarController() { ++ @Override ++ public void onAction(Object actionData) { ++ } ++ }; ++ Snackbar snackbar = Snackbar.make(folderName, snackbarController, Snackbar.TYPE_ACTION, ++ Snackbar.UMA_BOOKMARK_ADDED) ++ .setTemplateText(getString(R.string.bookmark_page_saved_folder)); ++ getSnackbarManager().showSnackbar(snackbar); + }); + } + private void recordLauncherShortcutAction(boolean isIncognito) { if (isIncognito) { RecordUserAction.record("Android.LauncherShortcut.NewIncognitoTab"); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java -@@ -17,6 +17,7 @@ import org.jni_zero.JniType; - import org.jni_zero.NativeMethods; - - import org.chromium.base.ContextUtils; -+import org.chromium.base.Log; - import org.chromium.base.ObserverList; - import org.chromium.base.ThreadUtils; - import org.chromium.base.metrics.RecordUserAction; -@@ -24,6 +25,11 @@ import org.chromium.base.supplier.Supplier; - import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmark; - import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim; - import org.chromium.chrome.browser.profiles.Profile; -+import org.chromium.chrome.browser.tab.Tab; -+import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; -+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; -+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController; -+import org.chromium.chrome.R; - import org.chromium.components.bookmarks.BookmarkId; - import org.chromium.components.bookmarks.BookmarkItem; - import org.chromium.components.bookmarks.BookmarkType; -@@ -83,6 +89,8 @@ import org.chromium.ui.modaldialog.DialogDismissalCause; - class BookmarkBridge { - private final ObserverList mObservers = new ObserverList<>(); - -+ private static final String TAG = "BookmarkBridge"; -+ - private long mNativeBookmarkBridge; - private Profile mProfile; - private boolean mIsDoingExtensiveChanges; -@@ -411,6 +419,16 @@ class BookmarkBridge { - return mMobileFolderId; - } - -+ /** -+ * @return The BookmarkId for the Tabs collecction folder node -+ */ -+ public BookmarkId getTabsCollectionFolderId() { -+ ThreadUtils.assertOnUiThread(); -+ assert mIsNativeBookmarkModelLoaded; -+ return BookmarkBridgeJni.get().getTabsCollectionFolderId( -+ mNativeBookmarkBridge, BookmarkBridge.this); -+ } -+ - /** Returns Id representing the special "other" folder from bookmark model. */ - public BookmarkId getOtherFolderId() { - ThreadUtils.assertOnUiThread(); -@@ -1132,6 +1150,50 @@ class BookmarkBridge { - .addToReadingList(mNativeBookmarkBridge, parentId, title, url); - } - -+ // Used to bookmark all tabs in a specific folder, created if not existing -+ public BookmarkId addToTabsCollection(Context context, Tab tab) { -+ BookmarkId parent = getTabsCollectionFolderId(); -+ BookmarkId existingId = BookmarkBridgeJni.get().getBookmarkIdForTabsCollection( -+ mNativeBookmarkBridge, tab.getOriginalUrl()); -+ if (existingId != null && existingId.getId() != BookmarkId.INVALID_ID) { -+ BookmarkId existingBookmarkId = new BookmarkId(existingId.getId(), BookmarkType.NORMAL); -+ BookmarkItem existingBookmark = getBookmarkById(existingBookmarkId); -+ if (parent.equals(existingBookmark.getParentId())) { -+ // bookmark already exists in the tabs collection folder -+ return existingBookmarkId; -+ } -+ } -+ BookmarkId bookmarkId = -+ addBookmark(parent, getChildCount(parent), tab.getTitle(), tab.getUrl()); -+ -+ if (bookmarkId == null) { -+ Log.e(TAG, -+ "Failed to add bookmarks: parentTypeAndId %s", parent); -+ } -+ return bookmarkId; -+ } -+ -+ public void finishedAddingToTabsCollection(Activity activity, SnackbarManager snackbarManager) { -+ BookmarkId parent = getTabsCollectionFolderId(); -+ -+ BookmarkItem bookmarkItem = getBookmarkById(parent); -+ String folderName; -+ if (bookmarkItem != null) { -+ folderName = bookmarkItem.getTitle(); -+ } else { -+ folderName = ""; -+ } -+ SnackbarController snackbarController = new SnackbarController() { -+ @Override -+ public void onAction(Object actionData) { -+ } -+ }; -+ Snackbar snackbar = Snackbar.make(folderName, snackbarController, Snackbar.TYPE_ACTION, -+ Snackbar.UMA_BOOKMARK_ADDED) -+ .setTemplateText(activity.getString(R.string.bookmark_page_saved_folder)); -+ snackbarManager.showSnackbar(snackbar); -+ } -+ - /** - * Helper method to mark an item as read. - * -@@ -1345,6 +1407,9 @@ class BookmarkBridge { - BookmarkId getMostRecentlyAddedUserBookmarkIdForUrl( - long nativeBookmarkBridge, @JniType("GURL") GURL url); - -+ BookmarkId getBookmarkIdForTabsCollection( -+ long nativeBookmarkBridge, GURL url); -+ - BookmarkItem getBookmarkById(long nativeBookmarkBridge, long id, int type); - - void getTopLevelFolderIds( -@@ -1366,6 +1431,8 @@ class BookmarkBridge { - - BookmarkId getRootFolderId(long nativeBookmarkBridge); - -+ BookmarkId getTabsCollectionFolderId(long nativeBookmarkBridge, BookmarkBridge caller); -+ - BookmarkId getMobileFolderId(long nativeBookmarkBridge); - - BookmarkId getOtherFolderId(long nativeBookmarkBridge); diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.cc b/chrome/browser/bookmarks/android/bookmark_bridge.cc --- a/chrome/browser/bookmarks/android/bookmark_bridge.cc +++ b/chrome/browser/bookmarks/android/bookmark_bridge.cc @@ -320,6 +233,99 @@ diff --git a/chrome/browser/bookmarks/android/bookmark_bridge.h b/chrome/browser base::android::ScopedJavaLocalRef AddFolder( JNIEnv* env, const base::android::JavaParamRef& j_parent_id_obj, +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java +@@ -18,6 +18,7 @@ import org.jni_zero.JniType; + import org.jni_zero.NativeMethods; + + import org.chromium.base.ContextUtils; ++import org.chromium.base.Log; + import org.chromium.base.ObserverList; + import org.chromium.base.ThreadUtils; + import org.chromium.base.metrics.RecordUserAction; +@@ -26,6 +27,7 @@ import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim; + import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; + import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; + import org.chromium.chrome.browser.profiles.Profile; ++import org.chromium.chrome.browser.tab.Tab; + import org.chromium.components.bookmarks.BookmarkId; + import org.chromium.components.bookmarks.BookmarkItem; + import org.chromium.components.bookmarks.BookmarkType; +@@ -46,6 +48,7 @@ import java.util.function.BiConsumer; + class BookmarkBridge { + private static OneshotSupplierImpl + sPartnerBookmarkIteratorSupplier = new OneshotSupplierImpl<>(); ++ private static final String TAG = "BookmarkBridge"; + + private final ObserverList mObservers = new ObserverList<>(); + private final Profile mProfile; +@@ -383,6 +386,16 @@ class BookmarkBridge { + return mMobileFolderId; + } + ++ /** ++ * @return The BookmarkId for the Tabs collecction folder node ++ */ ++ public BookmarkId getTabsCollectionFolderId() { ++ ThreadUtils.assertOnUiThread(); ++ assert mIsNativeBookmarkModelLoaded; ++ return BookmarkBridgeJni.get().getTabsCollectionFolderId( ++ mNativeBookmarkBridge, BookmarkBridge.this); ++ } ++ + /** Returns Id representing the special "other" folder from bookmark model. */ + public BookmarkId getOtherFolderId() { + ThreadUtils.assertOnUiThread(); +@@ -894,6 +907,29 @@ class BookmarkBridge { + .addToReadingList(mNativeBookmarkBridge, parentId, title, url); + } + ++ // Used to bookmark all tabs in a specific folder, created if not existing ++ public BookmarkId addToTabsCollection(Tab tab) { ++ BookmarkId parent = getTabsCollectionFolderId(); ++ BookmarkId existingId = BookmarkBridgeJni.get().getBookmarkIdForTabsCollection( ++ mNativeBookmarkBridge, tab.getOriginalUrl()); ++ if (existingId != null && existingId.getId() != BookmarkId.INVALID_ID) { ++ BookmarkId existingBookmarkId = new BookmarkId(existingId.getId(), BookmarkType.NORMAL); ++ BookmarkItem existingBookmark = getBookmarkById(existingBookmarkId); ++ if (parent.equals(existingBookmark.getParentId())) { ++ // bookmark already exists in the tabs collection folder ++ return existingBookmarkId; ++ } ++ } ++ BookmarkId bookmarkId = ++ addBookmark(parent, getChildCount(parent), tab.getTitle(), tab.getUrl()); ++ ++ if (bookmarkId == null) { ++ Log.e(TAG, ++ "Failed to add bookmarks: parentTypeAndId %s", parent); ++ } ++ return bookmarkId; ++ } ++ + /** + * Helper method to mark an item as read. + * +@@ -1111,6 +1147,9 @@ class BookmarkBridge { + BookmarkId getMostRecentlyAddedUserBookmarkIdForUrl( + long nativeBookmarkBridge, @JniType("GURL") GURL url); + ++ BookmarkId getBookmarkIdForTabsCollection( ++ long nativeBookmarkBridge, GURL url); ++ + BookmarkItem getBookmarkById(long nativeBookmarkBridge, long id, int type); + + void getTopLevelFolderIds( +@@ -1132,6 +1171,8 @@ class BookmarkBridge { + + BookmarkId getRootFolderId(long nativeBookmarkBridge); + ++ BookmarkId getTabsCollectionFolderId(long nativeBookmarkBridge, BookmarkBridge caller); ++ + BookmarkId getMobileFolderId(long nativeBookmarkBridge); + + BookmarkId getOtherFolderId(long nativeBookmarkBridge); diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/bookmarks/bookmark_html_writer.cc --- a/chrome/browser/bookmarks/bookmark_html_writer.cc +++ b/chrome/browser/bookmarks/bookmark_html_writer.cc @@ -381,7 +387,7 @@ diff --git a/chrome/browser/bookmarks/bookmark_html_writer.cc b/chrome/browser/b diff --git a/chrome/browser/bookmarks/bookmark_merged_surface_service.cc b/chrome/browser/bookmarks/bookmark_merged_surface_service.cc --- a/chrome/browser/bookmarks/bookmark_merged_surface_service.cc +++ b/chrome/browser/bookmarks/bookmark_merged_surface_service.cc -@@ -29,6 +29,7 @@ BookmarkParentFolder GetBookmarkParentFolderFromPermanentType( +@@ -32,6 +32,7 @@ BookmarkParentFolder GetBookmarkParentFolderFromPermanentType( BookmarkNode::Type type) { switch (type) { case bookmarks::BookmarkNode::URL: @@ -389,7 +395,7 @@ diff --git a/chrome/browser/bookmarks/bookmark_merged_surface_service.cc b/chrom NOTREACHED(); case bookmarks::BookmarkNode::FOLDER: // TODO(crbug.com/381252292): Consider extending type with a value -@@ -67,6 +68,8 @@ std::optional GetIfPermanentFolderType( +@@ -70,6 +71,8 @@ std::optional GetIfPermanentFolderType( case BookmarkNode::Type::URL: NOTREACHED(); @@ -409,7 +415,7 @@ diff --git a/chrome/browser/bookmarks/permanent_folder_ordering_tracker.cc b/chr NOTREACHED(); case bookmarks::BookmarkNode::BOOKMARK_BAR: -@@ -226,6 +227,7 @@ void PermanentFolderOrderingTracker::SetTrackedPermanentNodes() { +@@ -235,6 +236,7 @@ void PermanentFolderOrderingTracker::SetTrackedPermanentNodes() { switch (tracked_type_) { case bookmarks::BookmarkNode::URL: case bookmarks::BookmarkNode::FOLDER: @@ -441,7 +447,7 @@ diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browse diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -4447,6 +4447,9 @@ To change this setting, BEGIN_LINKdelete the Chrome d +@@ -4487,6 +4487,9 @@ To change this setting, BEGIN_LINKdelete the Chrome d Select tabs @@ -716,7 +722,7 @@ diff --git a/components/bookmarks/browser/bookmark_load_details.h b/components/b diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmarks/browser/bookmark_model.cc --- a/components/bookmarks/browser/bookmark_model.cc +++ b/components/bookmarks/browser/bookmark_model.cc -@@ -809,7 +809,7 @@ bool BookmarkModel::HasBookmarks() const { +@@ -835,7 +835,7 @@ bool BookmarkModel::HasBookmarks() const { bool BookmarkModel::HasNoUserCreatedBookmarksOrFolders() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return bookmark_bar_node_->children().empty() && @@ -725,7 +731,7 @@ diff --git a/components/bookmarks/browser/bookmark_model.cc b/components/bookmar } bool BookmarkModel::IsBookmarked(const GURL& url) const { -@@ -1141,6 +1141,7 @@ void BookmarkModel::DoneLoading(std::unique_ptr details) { +@@ -1167,6 +1167,7 @@ void BookmarkModel::DoneLoading(std::unique_ptr details) { bookmark_bar_node_ = details->bb_node(); other_node_ = details->other_folder_node(); mobile_node_ = details->mobile_folder_node(); @@ -749,7 +755,7 @@ diff --git a/components/bookmarks/browser/bookmark_model.h b/components/bookmark bool is_root_node(const BookmarkNode* node) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return node == root_; -@@ -602,6 +608,7 @@ class BookmarkModel : public BookmarkUndoProvider, +@@ -611,6 +617,7 @@ class BookmarkModel : public BookmarkUndoProvider, raw_ptr account_bookmark_bar_node_ = nullptr; raw_ptr account_other_node_ = nullptr; raw_ptr account_mobile_node_ = nullptr; @@ -796,7 +802,7 @@ diff --git a/components/bookmarks/browser/bookmark_node.h b/components/bookmarks MOBILE }; -@@ -241,6 +242,8 @@ class BookmarkPermanentNode : public BookmarkNode { +@@ -247,6 +248,8 @@ class BookmarkPermanentNode : public BookmarkNode { int64_t id); static std::unique_ptr CreateMobileBookmarks( int64_t id); diff --git a/build/cromite_patches/Add-menu-item-to-view-source.patch b/build/cromite_patches/Add-menu-item-to-view-source.patch index ea0b015fe7cd87132f019de98c56854c92e11b63..d7424b0429b2f8c70e9fa085296f7b46a127a763 100644 --- a/build/cromite_patches/Add-menu-item-to-view-source.patch +++ b/build/cromite_patches/Add-menu-item-to-view-source.patch @@ -29,7 +29,7 @@ diff --git a/chrome/android/java/res/menu/custom_tabs_menu.xml b/chrome/android/ diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml --- a/chrome/android/java/res/menu/main_menu.xml +++ b/chrome/android/java/res/menu/main_menu.xml -@@ -119,6 +119,9 @@ found in the LICENSE file. +@@ -125,6 +125,9 @@ found in the LICENSE file. @@ -42,7 +42,7 @@ diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/re diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java -@@ -3086,6 +3086,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -3135,6 +3135,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_DOWNLOADS_MANAGER); } RecordUserAction.record("MobileMenuDownloadManager"); @@ -54,7 +54,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -2524,6 +2524,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -2531,6 +2531,11 @@ public abstract class ChromeActivity extends AsyncInitializationActivity return doOpenWebApk(currentTab); } @@ -69,7 +69,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActiv diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java -@@ -580,6 +580,7 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate +@@ -587,6 +587,7 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate isNativePage, isFileScheme, isContentScheme, isIncognito, url)); updateRequestDesktopSiteMenuItem(menu, currentTab, true /* can show */, isNativePage); @@ -77,7 +77,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App updateAutoDarkMenuItem(menu, currentTab, isNativePage); -@@ -1293,6 +1294,23 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate +@@ -1328,6 +1329,23 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate findInPageMenuRow.setVisible(itemVisible); } @@ -104,7 +104,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/App diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabAppMenuPropertiesDelegate.java -@@ -303,6 +303,7 @@ public class CustomTabAppMenuPropertiesDelegate extends AppMenuPropertiesDelegat +@@ -304,6 +304,7 @@ public class CustomTabAppMenuPropertiesDelegate extends AppMenuPropertiesDelegat updateRequestDesktopSiteMenuItem( menu, currentTab, requestDesktopSiteVisible, isNativePage); diff --git a/build/cromite_patches/Add-option-to-force-tablet-UI.patch b/build/cromite_patches/Add-option-to-force-tablet-UI.patch index 0ec3592a9988c8fb2d640d2496d9d7b8c7abb370..892dc6109e1da577d453e0e1bcbe3a206053c09e 100644 --- a/build/cromite_patches/Add-option-to-force-tablet-UI.patch +++ b/build/cromite_patches/Add-option-to-force-tablet-UI.patch @@ -169,7 +169,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/s diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -758,7 +758,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -759,7 +759,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity // Inflate the correct toolbar layout for the device. int toolbarLayoutId = getToolbarLayoutId(); if (toolbarLayoutId != ActivityUtils.NO_RESOURCE_ID && controlContainer != null) { @@ -183,15 +183,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActiv diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java -@@ -114,6 +114,7 @@ import org.chromium.components.tab_group_sync.SavedTabGroup; - import org.chromium.components.tab_group_sync.TabGroupSyncService; +@@ -115,6 +115,7 @@ import org.chromium.components.tab_group_sync.TabGroupSyncService; + import org.chromium.components.tab_group_sync.TriggerSource; import org.chromium.components.tab_groups.TabGroupColorId; import org.chromium.ui.MotionEventUtils; +import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.base.LocalizationUtils; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.modaldialog.ModalDialogManager; -@@ -4060,8 +4061,13 @@ public class StripLayoutHelper +@@ -4113,8 +4114,13 @@ public class StripLayoutHelper mTabMenu.setAnchorView(tabView); // 3. Set the vertical offset to align the tab menu with bottom of the tab strip int tabHeight = mManagerHost.getHeight(); @@ -210,7 +210,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java -@@ -94,6 +94,7 @@ import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateMa +@@ -93,6 +93,7 @@ import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateMa import org.chromium.components.browser_ui.styles.SemanticColorUtils; import org.chromium.components.browser_ui.widget.scrim.ScrimProperties; import org.chromium.content_public.browser.LoadUrlParams; @@ -218,7 +218,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over import org.chromium.ui.base.LocalizationUtils; import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; -@@ -453,7 +454,10 @@ public class StripLayoutHelperManager +@@ -450,7 +451,10 @@ public class StripLayoutHelperManager mIsLayoutOptimizationsEnabled = ToolbarFeatures.isTabStripWindowLayoutOptimizationEnabled(true); @@ -252,7 +252,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrow diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java -@@ -620,7 +620,9 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -664,7 +664,9 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { @Override public int getControlContainerHeightResource() { @@ -266,16 +266,16 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/Tab diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java -@@ -109,6 +109,8 @@ import org.chromium.chrome.browser.paint_preview.DemoPaintPreview; +@@ -100,6 +100,8 @@ import org.chromium.chrome.browser.paint_preview.DemoPaintPreview; import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer; import org.chromium.chrome.browser.password_manager.PasswordManagerLauncher; import org.chromium.chrome.browser.pdf.PdfPage; +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; +import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; - import org.chromium.chrome.browser.price_insights.PriceInsightsButtonController; - import org.chromium.chrome.browser.price_tracking.CurrentTabPriceTrackingStateSupplier; - import org.chromium.chrome.browser.price_tracking.PriceTrackingButtonController; -@@ -1777,7 +1779,9 @@ public class RootUiCoordinator + import org.chromium.chrome.browser.profiles.Profile; + import org.chromium.chrome.browser.quick_delete.QuickDeleteController; + import org.chromium.chrome.browser.quick_delete.QuickDeleteDelegateImpl; +@@ -1616,7 +1618,9 @@ public class RootUiCoordinator if (!mSupportsFindInPageSupplier.getAsBoolean()) return; int stubId = R.id.find_toolbar_stub; @@ -300,7 +300,7 @@ diff --git a/chrome/browser/preferences/BUILD.gn b/chrome/browser/preferences/BU diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java -@@ -280,6 +280,7 @@ public final class ChromePreferenceKeys { +@@ -286,6 +286,7 @@ public final class ChromePreferenceKeys { /** Whether the app-specific history info text was already seen by users. */ public static final String HISTORY_APP_SPECIFIC_INFO_SEEN = "Chrome.History.AppSpecificInfoSeen"; @@ -351,7 +351,7 @@ diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/bro diff --git a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java --- a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java +++ b/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/chrome/browser/ui/desktop_windowing/AppHeaderCoordinator.java -@@ -35,6 +35,7 @@ import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderUtils.Windowing +@@ -38,6 +38,7 @@ import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderUtils.Windowing import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState; import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateManager; import org.chromium.components.browser_ui.edge_to_edge.EdgeToEdgeStateProvider; @@ -359,7 +359,7 @@ diff --git a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/c import org.chromium.ui.InsetObserver; import org.chromium.ui.InsetObserver.WindowInsetsConsumer; import org.chromium.ui.InsetsRectProvider; -@@ -260,6 +261,7 @@ public class AppHeaderCoordinator +@@ -266,6 +267,7 @@ public class AppHeaderCoordinator private static @DesktopWindowHeuristicResult int checkIsInDesktopWindow( InsetsRectProvider insetsRectProvider, @DesktopWindowHeuristicResult int currentResult) { @@ -370,7 +370,7 @@ diff --git a/chrome/browser/ui/android/desktop_windowing/java/src/org/chromium/c diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java -@@ -799,7 +799,7 @@ public class LocationBarCoordinator +@@ -790,7 +790,7 @@ public class LocationBarCoordinator } private boolean isTabletWindow() { @@ -382,7 +382,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -1657,6 +1657,13 @@ Your Google account may have other forms of browsing history like searches and a +@@ -1671,6 +1671,13 @@ Your Google account may have other forms of browsing history like searches and a Hide your IP address @@ -499,7 +499,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.Rect; -@@ -143,8 +144,25 @@ public class ToolbarControlContainer extends OptimizedFrameLayout +@@ -144,8 +145,25 @@ public class ToolbarControlContainer extends OptimizedFrameLayout } @Override @@ -529,15 +529,15 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java -@@ -63,6 +63,7 @@ import org.chromium.chrome.browser.util.BrowserUiUtils; - import org.chromium.chrome.browser.util.BrowserUiUtils.ModuleTypeOnStartAndNtp; +@@ -63,6 +63,7 @@ import org.chromium.chrome.browser.util.BrowserUiUtils.ModuleTypeOnStartAndNtp; import org.chromium.components.feature_engagement.Tracker; + import org.chromium.ui.MotionEventUtils; import org.chromium.ui.base.ViewUtils; +import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.util.TokenHolder; import org.chromium.url.GURL; -@@ -590,7 +591,9 @@ public abstract class ToolbarLayout extends FrameLayout +@@ -583,7 +584,9 @@ public abstract class ToolbarLayout extends FrameLayout * not have a tab strip. */ protected int getTabStripHeightFromResource() { @@ -559,7 +559,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow import org.chromium.ui.resources.dynamics.DynamicResourceReadyOnceCallback; import org.chromium.ui.util.TokenHolder; -@@ -559,6 +560,7 @@ class HeightTransitionHandler { +@@ -556,6 +557,7 @@ class HeightTransitionHandler { if (TabStripTransitionCoordinator.sHeightTransitionThresholdForTesting != null) { return TabStripTransitionCoordinator.sHeightTransitionThresholdForTesting; } @@ -578,7 +578,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow /** Class used to manage tab strip visibility and height updates. */ public class TabStripTransitionCoordinator implements ComponentCallbacks, AppHeaderObserver { -@@ -153,7 +154,10 @@ public class TabStripTransitionCoordinator implements ComponentCallbacks, AppHea +@@ -154,7 +155,10 @@ public class TabStripTransitionCoordinator implements ComponentCallbacks, AppHea mTabStripReservedTopPadding = controlContainerView() .getResources() @@ -639,7 +639,7 @@ diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/c Preference captions = findPreference(PREF_CAPTIONS); captions.setOnPreferenceClickListener( preference -> { -@@ -165,6 +175,9 @@ public class AccessibilitySettings extends ChromeBaseSettingsFragment +@@ -163,6 +173,9 @@ public class AccessibilitySettings extends ChromeBaseSettingsFragment public boolean onPreferenceChange(Preference preference, Object newValue) { if (PREF_FORCE_ENABLE_ZOOM.equals(preference.getKey())) { mDelegate.getForceEnableZoomAccessibilityDelegate().setValue((Boolean) newValue); diff --git a/build/cromite_patches/Add-option-to-not-persist-tabs-across-sessions.patch b/build/cromite_patches/Add-option-to-not-persist-tabs-across-sessions.patch index 286ce00557f534923acf12cc04787bf2c0ebaedc..24e3a9677e4fd1797b2e398e56c2fda2ac61cc0f 100644 --- a/build/cromite_patches/Add-option-to-not-persist-tabs-across-sessions.patch +++ b/build/cromite_patches/Add-option-to-not-persist-tabs-across-sessions.patch @@ -36,7 +36,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedAct import org.chromium.base.IntentUtils; import org.chromium.base.Log; import org.chromium.base.MemoryPressureListener; -@@ -1666,8 +1667,10 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn +@@ -1695,8 +1696,10 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn boolean hadCipherData = CipherLazyHolder.sCipherInstance.restoreFromBundle(getSavedInstanceState()); @@ -105,7 +105,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/setting diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -5479,6 +5479,12 @@ To change this setting, BEGIN_LINKdelete the Chrome d +@@ -5541,6 +5541,12 @@ To change this setting, BEGIN_LINKdelete the Chrome d IMAGE diff --git a/build/cromite_patches/Add-option-to-use-home-page-as-NTP.patch b/build/cromite_patches/Add-option-to-use-home-page-as-NTP.patch index 19ba9f2bf7253ce0b8c37cd9788e5d8d21aaf2ab..aae0e8e1deebb9c0cf3a223278f5ff57ee60fcc2 100644 --- a/build/cromite_patches/Add-option-to-use-home-page-as-NTP.patch +++ b/build/cromite_patches/Add-option-to-use-home-page-as-NTP.patch @@ -34,7 +34,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser /** * A mediator for the TabGridDialog component, responsible for communicating with the components' * coordinator as well as managing the business logic for dialog show/hide. -@@ -918,9 +921,15 @@ public class TabGridDialogMediator +@@ -930,9 +933,15 @@ public class TabGridDialogMediator return; } @@ -54,15 +54,15 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java -@@ -25,6 +25,7 @@ import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory; +@@ -34,6 +34,7 @@ import org.chromium.chrome.browser.collaboration.CollaborationServiceFactory; import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory; import org.chromium.chrome.browser.data_sharing.ui.shared_image_tiles.SharedImageTilesCoordinator; - import org.chromium.chrome.browser.flags.ChromeFeatureList; -+import org.chromium.chrome.browser.homepage.HomepageManager; import org.chromium.chrome.browser.layouts.LayoutStateProvider; ++import org.chromium.chrome.browser.homepage.HomepageManager; import org.chromium.chrome.browser.layouts.LayoutStateProvider.LayoutStateObserver; import org.chromium.chrome.browser.layouts.LayoutType; -@@ -366,10 +367,15 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, + import org.chromium.chrome.browser.profiles.Profile; +@@ -401,10 +402,15 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, assert relatedTabs.size() > 0; Tab parentTabToAttach = relatedTabs.get(relatedTabs.size() - 1); @@ -107,7 +107,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over /** * A coordinator for the context menu on the tab strip by long-pressing on the group titles. It is * responsible for creating a list of menu items, setting up the menu and displaying the menu. -@@ -197,9 +200,15 @@ public class TabGroupContextMenuCoordinator extends TabGroupOverflowMenuCoordina +@@ -199,9 +202,15 @@ public class TabGroupContextMenuCoordinator extends TabGroupOverflowMenuCoordina /* didCloseCallback= */ null); recordUserAction("DeleteGroup"); } else if (menuId == org.chromium.chrome.R.id.open_new_tab_in_group) { @@ -208,7 +208,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/Chrome import org.chromium.chrome.browser.prefetch.settings.PreloadPagesSettingsBridge; import org.chromium.chrome.browser.prefetch.settings.PreloadPagesState; import org.chromium.chrome.browser.profiles.Profile; -@@ -485,6 +486,13 @@ public class ChromeTabCreator extends TabCreator { +@@ -487,6 +488,13 @@ public class ChromeTabCreator extends TabCreator { * @return the created tab. */ public Tab launchUrl(String url, @TabLaunchType int type, Intent intent, long intentTimestamp) { @@ -225,7 +225,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/Chrome diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java -@@ -293,6 +293,7 @@ public final class ChromePreferenceKeys { +@@ -299,6 +299,7 @@ public final class ChromePreferenceKeys { public static final String HOMEPAGE_USE_CHROME_NTP = "Chrome.Homepage.UseNTP"; public static final String HOMEPAGE_USE_DEFAULT_URI = "homepage_partner_enabled"; @@ -247,7 +247,7 @@ diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/bro diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -1478,6 +1478,9 @@ Your Google account may have other forms of browsing history like searches and a +@@ -1492,6 +1492,9 @@ Your Google account may have other forms of browsing history like searches and a Last hour @@ -272,9 +272,9 @@ diff --git a/chrome/browser/ui/browser_ui_prefs.cc b/chrome/browser/ui/browser_u diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h -@@ -1326,6 +1326,10 @@ inline constexpr char kShowHomeButton[] = "browser.show_home_button"; - // toolbar. - inline constexpr char kShowForwardButton[] = "browser.show_forward_button"; +@@ -1319,6 +1319,10 @@ inline constexpr char kShowForwardButton[] = "browser.show_forward_button"; + // by enterprise policy. + inline constexpr char kGeminiSettings[] = "browser.gemini_settings"; +// A boolean specifying whether opening a new tab should open the Home page +// instead of the New Tab page. diff --git a/build/cromite_patches/Add-search-engine.patch b/build/cromite_patches/Add-search-engine.patch index 797e7b8ffa9a58feefceb60f97814cdefff00aeb..26d7de69ea474f074da8886c1ec5971805271248 100644 --- a/build/cromite_patches/Add-search-engine.patch +++ b/build/cromite_patches/Add-search-engine.patch @@ -68,7 +68,7 @@ diff --git a/components/resources/search_engine_choice_scaled_resources.grdp b/c diff --git a/components/search_engines/BUILD.gn b/components/search_engines/BUILD.gn --- a/components/search_engines/BUILD.gn +++ b/components/search_engines/BUILD.gn -@@ -70,6 +70,7 @@ static_library("search_engines") { +@@ -72,6 +72,7 @@ static_library("search_engines") { "//third_party/metrics_proto", "//third_party/omnibox_proto", "//third_party/search_engines_data:prepopulated_engines", @@ -76,7 +76,7 @@ diff --git a/components/search_engines/BUILD.gn b/components/search_engines/BUIL ] deps = [ -@@ -155,6 +156,7 @@ source_set("search_engine_utils") { +@@ -157,6 +158,7 @@ source_set("search_engine_utils") { ":search_engine_type", "//components/google/core/common", "//third_party/search_engines_data:prepopulated_engines", @@ -84,7 +84,7 @@ diff --git a/components/search_engines/BUILD.gn b/components/search_engines/BUIL ] deps = [ "//url" ] -@@ -235,6 +237,7 @@ source_set("unit_tests") { +@@ -237,6 +239,7 @@ source_set("unit_tests") { "//testing/gmock", "//testing/gtest", "//third_party/search_engines_data:prepopulated_engines", @@ -169,7 +169,7 @@ diff --git a/components/search_engines/search_engine_choice/search_engine_choice diff --git a/components/search_engines/search_engine_countries-inc.cc b/components/search_engines/search_engine_countries-inc.cc --- a/components/search_engines/search_engine_countries-inc.cc +++ b/components/search_engines/search_engine_countries-inc.cc -@@ -1452,5 +1452,7 @@ const std::vector GetPrepopulationSetFromCountryID( +@@ -1453,5 +1453,7 @@ const std::vector GetPrepopulationSetFromCountryID( for (size_t i = 0; i < num_engines; i++) { t_url.push_back(engines[i]); } @@ -180,12 +180,12 @@ diff --git a/components/search_engines/search_engine_countries-inc.cc b/componen diff --git a/components/search_engines/search_engine_type.h b/components/search_engines/search_engine_type.h --- a/components/search_engines/search_engine_type.h +++ b/components/search_engines/search_engine_type.h -@@ -91,10 +91,15 @@ enum SearchEngineType { - SEARCH_ENGINE_YOU = 72, - SEARCH_ENGINE_STARTER_PACK_GEMINI = 73, - SEARCH_ENGINE_LILO = 74, -+ SEARCH_ENGINE_GOOGLE_EN = 75, -+ SEARCH_ENGINE_DUCKDUCKGOLIGHT = 76, +@@ -94,10 +94,15 @@ enum SearchEngineType { + SEARCH_ENGINE_STARTPAGE = 75, + SEARCH_ENGINE_STARTER_PACK_PAGE = 76, + SEARCH_ENGINE_MCAFEE = 77, ++ SEARCH_ENGINE_GOOGLE_EN = 78, ++ SEARCH_ENGINE_DUCKDUCKGOLIGHT = 79, SEARCH_ENGINE_MAX // Bounding value needed for UMA histogram macro. }; @@ -199,7 +199,7 @@ diff --git a/components/search_engines/search_engine_type.h b/components/search_ diff --git a/components/search_engines/template_url_prepopulate_data.cc b/components/search_engines/template_url_prepopulate_data.cc --- a/components/search_engines/template_url_prepopulate_data.cc +++ b/components/search_engines/template_url_prepopulate_data.cc -@@ -29,6 +29,8 @@ +@@ -28,6 +28,8 @@ #include "components/version_info/version_info.h" #include "third_party/search_engines_data/resources/definitions/prepopulated_engines.h" @@ -208,7 +208,7 @@ diff --git a/components/search_engines/template_url_prepopulate_data.cc b/compon namespace TemplateURLPrepopulateData { // Helpers -------------------------------------------------------------------- -@@ -78,7 +80,6 @@ GetPrepopulatedEnginesForEeaRegionCountries(int country_id, +@@ -67,7 +69,6 @@ GetPrepopulatedEnginesForEeaRegionCountries(CountryID country_id, generator.seed(profile_seed); std::shuffle(t_urls.begin(), t_urls.end(), generator); @@ -219,7 +219,7 @@ diff --git a/components/search_engines/template_url_prepopulate_data.cc b/compon diff --git a/components/search_engines/template_url_prepopulate_data.h b/components/search_engines/template_url_prepopulate_data.h --- a/components/search_engines/template_url_prepopulate_data.h +++ b/components/search_engines/template_url_prepopulate_data.h -@@ -32,13 +32,6 @@ struct PrepopulatedEngine; +@@ -31,13 +31,6 @@ struct PrepopulatedEngine; extern const int kMaxPrepopulatedEngineID; diff --git a/build/cromite_patches/Add-site-engagement-flag.patch b/build/cromite_patches/Add-site-engagement-flag.patch index 52f5e3b161f5af380adada5aab5beb33447c20f8..add02cd563af76a48c5591cc066aab278e906154 100644 --- a/build/cromite_patches/Add-site-engagement-flag.patch +++ b/build/cromite_patches/Add-site-engagement-flag.patch @@ -24,7 +24,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc -@@ -166,6 +166,7 @@ +@@ -161,6 +161,7 @@ #include "components/sensitive_content/features.h" #include "components/services/heap_profiling/public/cpp/switches.h" #include "components/services/storage/public/cpp/buckets/bucket_info.h" diff --git a/build/cromite_patches/Add-support-for-ISupportHelpAndFeedback.patch b/build/cromite_patches/Add-support-for-ISupportHelpAndFeedback.patch index c376258cef3afc8ec003d8df4755cd50faac1326..9aa51e144788df099923800215ff93c007a27103 100644 --- a/build/cromite_patches/Add-support-for-ISupportHelpAndFeedback.patch +++ b/build/cromite_patches/Add-support-for-ISupportHelpAndFeedback.patch @@ -20,7 +20,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/Settin import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; import org.chromium.components.browser_ui.settings.EmbeddableSettingsPage; import org.chromium.components.browser_ui.util.TraceEventVectorDrawableCompat; -@@ -442,8 +443,12 @@ public class SettingsActivity extends ChromeBaseAppCompatActivity +@@ -441,8 +442,12 @@ public class SettingsActivity extends ChromeBaseAppCompatActivity finishCurrentSettings(mainFragment); return true; } else if (item.getItemId() == R.id.menu_id_general_help) { diff --git a/build/cromite_patches/Add-webGL-site-setting.patch b/build/cromite_patches/Add-webGL-site-setting.patch index bc6548a194d80e7e490ec9690391cc76a8477012..ee7d0d32d4133a86257e67c1cf039330ab8d5405 100644 --- a/build/cromite_patches/Add-webGL-site-setting.patch +++ b/build/cromite_patches/Add-webGL-site-setting.patch @@ -206,7 +206,7 @@ diff --git a/third_party/blink/renderer/core/execution_context/execution_context diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h --- a/third_party/blink/renderer/core/execution_context/execution_context.h +++ b/third_party/blink/renderer/core/execution_context/execution_context.h -@@ -116,6 +116,8 @@ enum ReasonForCallingCanExecuteScripts { +@@ -115,6 +115,8 @@ enum ReasonForCallingCanExecuteScripts { enum ReferrerPolicySource { kPolicySourceHttpHeader, kPolicySourceMetaTag }; @@ -218,7 +218,7 @@ diff --git a/third_party/blink/renderer/core/execution_context/execution_context diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc -@@ -297,6 +297,13 @@ void WebGLRenderingContextBase::InitializeWebGLContextLimits( +@@ -299,6 +299,13 @@ void WebGLRenderingContextBase::InitializeWebGLContextLimits( } } @@ -232,7 +232,7 @@ diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_ba unsigned WebGLRenderingContextBase::CurrentMaxGLContexts() { base::AutoLock locker(WebGLContextLimitLock()); DCHECK(webgl_context_limits_initialized_); -@@ -566,25 +573,6 @@ static String ExtractWebGLContextCreationError( +@@ -568,25 +575,6 @@ static String ExtractWebGLContextCreationError( const Platform::GraphicsInfo& info) { StringBuilder builder; builder.Append("Could not create a WebGL context"); @@ -258,7 +258,7 @@ diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_ba FormatWebGLStatusString("ErrorMessage", info.error_message.Utf8().c_str(), builder); builder.Append('.'); -@@ -644,6 +632,12 @@ WebGLRenderingContextBase::CreateWebGraphicsContext3DProvider( +@@ -646,6 +634,12 @@ WebGLRenderingContextBase::CreateWebGraphicsContext3DProvider( const CanvasContextCreationAttributesCore& attributes, Platform::ContextType context_type, Platform::GraphicsInfo* graphics_info) { @@ -274,7 +274,7 @@ diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_ba diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h -@@ -1936,6 +1936,8 @@ class MODULES_EXPORT WebGLRenderingContextBase : public CanvasRenderingContext, +@@ -1931,6 +1931,8 @@ class MODULES_EXPORT WebGLRenderingContextBase : public CanvasRenderingContext, DOMArrayBufferView* pixels, int64_t offset); diff --git a/build/cromite_patches/Allow-building-without-enable_reporting.patch b/build/cromite_patches/Allow-building-without-enable_reporting.patch index b70484c8283195fea0fce6c8d994194c2ad83da4..e55898929b9f879f758bf029ec70ee33a9e27b01 100644 --- a/build/cromite_patches/Allow-building-without-enable_reporting.patch +++ b/build/cromite_patches/Allow-building-without-enable_reporting.patch @@ -22,11 +22,11 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../network/public/mojom/network_context.mojom | 3 +++ .../core/frame/csp/content_security_policy.cc | 3 +++ .../blink/renderer/core/frame/local_frame.cc | 3 +++ - .../blink/renderer/core/frame/local_frame.h | 4 +++- + .../blink/renderer/core/frame/local_frame.h | 3 +++ .../renderer/core/frame/reporting_context.cc | 9 +++++++++ .../renderer/core/frame/reporting_context.h | 7 +++++-- .../renderer/core/frame/reporting_observer.cc | 3 +-- - 22 files changed, 63 insertions(+), 26 deletions(-) + 22 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 cromite_flags/services/network/public/cpp/features_cc/Allow-building-without-enable_reporting.inc diff --git a/chrome/browser/net/chrome_report_sender.cc b/chrome/browser/net/chrome_report_sender.cc @@ -43,7 +43,7 @@ diff --git a/chrome/browser/net/chrome_report_sender.cc b/chrome/browser/net/chr diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc --- a/content/browser/devtools/protocol/network_handler.cc +++ b/content/browser/devtools/protocol/network_handler.cc -@@ -1473,6 +1473,7 @@ String BuildReportStatus(const net::ReportingReport::Status status) { +@@ -1474,6 +1474,7 @@ String BuildReportStatus(const net::ReportingReport::Status status) { } } @@ -51,7 +51,7 @@ diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/brow std::vector ComputeReportingURLs(RenderFrameHostImpl* frame_host) { std::vector urls; frame_host->ForEachRenderFrameHostImplWithAction( -@@ -1486,6 +1487,7 @@ std::vector ComputeReportingURLs(RenderFrameHostImpl* frame_host) { +@@ -1487,6 +1488,7 @@ std::vector ComputeReportingURLs(RenderFrameHostImpl* frame_host) { }); return urls; } @@ -108,7 +108,7 @@ diff --git a/content/browser/network/reporting_service_proxy.cc b/content/browse #include "content/browser/service_worker/service_worker_host.h" #include "content/browser/worker_host/dedicated_worker_host.h" #include "content/browser/worker_host/shared_worker_host.h" -@@ -222,12 +223,14 @@ class ReportingServiceProxyImpl : public blink::mojom::ReportingServiceProxy { +@@ -226,12 +227,14 @@ class ReportingServiceProxyImpl : public blink::mojom::ReportingServiceProxy { const std::string& group, const std::string& type, base::Value::Dict body) { @@ -126,7 +126,7 @@ diff --git a/content/browser/network/reporting_service_proxy.cc b/content/browse 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 -@@ -15702,12 +15702,6 @@ void RenderFrameHostImpl::MaybeGenerateCrashReport( +@@ -15759,12 +15759,6 @@ void RenderFrameHostImpl::MaybeGenerateCrashReport( } } } @@ -138,7 +138,7 @@ diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/b - std::move(body)); } - void RenderFrameHostImpl::SendCommitNavigation( + void RenderFrameHostImpl::UpdateOrDisableCompositorMetricRecorder() const { diff --git a/content/browser/security/coop/cross_origin_opener_policy_reporter.cc b/content/browser/security/coop/cross_origin_opener_policy_reporter.cc --- a/content/browser/security/coop/cross_origin_opener_policy_reporter.cc +++ b/content/browser/security/coop/cross_origin_opener_policy_reporter.cc @@ -300,7 +300,7 @@ diff --git a/net/reporting/reporting_service.cc b/net/reporting/reporting_servic diff --git a/services/network/network_context.h b/services/network/network_context.h --- a/services/network/network_context.h +++ b/services/network/network_context.h -@@ -470,14 +470,14 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext +@@ -475,14 +475,14 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext const GURL& url, const std::optional& reporting_source, const net::NetworkAnonymizationKey& network_anonymization_key, @@ -332,7 +332,7 @@ diff --git a/services/network/public/cpp/parsed_headers.cc b/services/network/pu diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn --- a/services/network/public/mojom/BUILD.gn +++ b/services/network/public/mojom/BUILD.gn -@@ -1590,7 +1590,6 @@ mojom("mojom") { +@@ -1668,7 +1668,6 @@ mojom("mojom") { export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1" export_header_blink = "third_party/blink/public/platform/web_common.h" if (enable_reporting) { @@ -343,7 +343,7 @@ diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mo diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom --- a/services/network/public/mojom/network_context.mojom +++ b/services/network/public/mojom/network_context.mojom -@@ -1205,6 +1205,7 @@ interface NetworkContext { +@@ -1208,6 +1208,7 @@ interface NetworkContext { // provided `network_anonymization_key`. // // Spec: https://w3c.github.io/reporting/#concept-reports @@ -351,7 +351,7 @@ diff --git a/services/network/public/mojom/network_context.mojom b/services/netw QueueReport(string type, string group, url.mojom.Url url, -@@ -1212,6 +1213,7 @@ interface NetworkContext { +@@ -1215,6 +1216,7 @@ interface NetworkContext { NetworkAnonymizationKey network_anonymization_key, mojo_base.mojom.DictionaryValue body); @@ -359,7 +359,7 @@ diff --git a/services/network/public/mojom/network_context.mojom b/services/netw QueueEnterpriseReport(string type, string group, url.mojom.Url url, -@@ -1222,6 +1224,7 @@ interface NetworkContext { +@@ -1225,6 +1227,7 @@ interface NetworkContext { // Note that this queued report will never be delivered if no reporting // endpoint matching is registered for with the provided // `network_anonymization_key`. @@ -378,7 +378,7 @@ diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.c #include "base/containers/contains.h" #include "base/debug/dump_without_crashing.h" #include "services/network/public/cpp/web_sandbox_flags.h" -@@ -1295,6 +1296,7 @@ void ContentSecurityPolicy::ReportViolation( +@@ -1345,6 +1346,7 @@ void ContentSecurityPolicy::ReportViolation( return; } @@ -386,7 +386,7 @@ diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.c PostViolationReport(violation_data, context_frame, report_endpoints, use_reporting_api); -@@ -1302,6 +1304,7 @@ void ContentSecurityPolicy::ReportViolation( +@@ -1352,6 +1354,7 @@ void ContentSecurityPolicy::ReportViolation( // `context_frame` (i.e. we're not processing 'frame-ancestors'). if (delegate_ && !context_frame) delegate_->DispatchViolationEvent(*violation_data, element); @@ -405,7 +405,7 @@ diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/ #include "services/network/public/cpp/features.h" #include "services/network/public/mojom/content_security_policy.mojom-blink.h" #include "services/network/public/mojom/source_location.mojom-blink.h" -@@ -2796,9 +2797,11 @@ DocumentResourceCoordinator* LocalFrame::GetDocumentResourceCoordinator() { +@@ -2752,9 +2753,11 @@ DocumentResourceCoordinator* LocalFrame::GetDocumentResourceCoordinator() { return CHECK_DEREF(GetDocument()).GetResourceCoordinator(); } @@ -428,11 +428,10 @@ diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/b #include "mojo/public/cpp/bindings/pending_associated_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "services/metrics/public/cpp/ukm_source_id.h" -@@ -659,8 +660,9 @@ class CORE_EXPORT LocalFrame final - void FinishedScrollSequence(); +@@ -650,7 +651,9 @@ class CORE_EXPORT LocalFrame final + return client_hints_preferences_; + } - SmoothScrollSequencer* GetSmoothScrollSequencer() const; -- +#if BUILDFLAG(ENABLE_REPORTING) mojom::blink::ReportingServiceProxy* GetReportingService(); +#endif @@ -494,7 +493,7 @@ diff --git a/third_party/blink/renderer/core/frame/reporting_context.cc b/third_ const String& type = report->type(); if (!(type == ReportType::kCSPViolation || type == ReportType::kCSPHash || type == ReportType::kDeprecation || -@@ -254,6 +262,7 @@ void ReportingContext::SendToReportingAPI(Report* report, +@@ -255,6 +263,7 @@ void ReportingContext::SendToReportingAPI(Report* report, url, endpoint, body->featureId(), body->disposition(), body->message(), body->sourceFile(), line_number, column_number); } diff --git a/build/cromite_patches/Allow-building-without-supervised-users.patch b/build/cromite_patches/Allow-building-without-supervised-users.patch index 30af6ffb74ff5568f6d0e10a6533b5075c04193a..2fe30d8e554f4992ae90c8a8dacf1dd676e11b80 100644 --- a/build/cromite_patches/Allow-building-without-supervised-users.patch +++ b/build/cromite_patches/Allow-building-without-supervised-users.patch @@ -23,7 +23,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -784,7 +784,6 @@ if (current_toolchain == default_toolchain) { +@@ -786,7 +786,6 @@ if (current_toolchain == default_toolchain) { "//components/offline_pages/core:offline_page_model_enums_java", "//components/sharing_message:sharing_dialog_type_generated_enum", "//components/sharing_message:sharing_send_message_result_generated_enum", @@ -34,7 +34,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java --- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java -@@ -92,7 +92,6 @@ public class ChromeFeedbackCollector extends FeedbackCollectorGetPrefs()->SetString(prefs::kProfileName, profile_name); } @@ -121,7 +121,7 @@ diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profile #if !BUILDFLAG(IS_ANDROID) if (profile->IsNewProfile()) { profile->GetPrefs()->SetBoolean(prefs::kHasSeenWelcomePage, false); -@@ -1970,9 +1966,6 @@ void ProfileManager::AddProfileToStorage(Profile* profile) { +@@ -1976,9 +1972,6 @@ void ProfileManager::AddProfileToStorage(Profile* profile) { init_params.icon_index = profile->GetPrefs()->GetInteger(prefs::kProfileAvatarIndex); @@ -218,7 +218,7 @@ diff --git a/components/supervised_user/core/browser/supervised_user_service.cc diff --git a/components/supervised_user/core/browser/supervised_user_utils.cc b/components/supervised_user/core/browser/supervised_user_utils.cc --- a/components/supervised_user/core/browser/supervised_user_utils.cc +++ b/components/supervised_user/core/browser/supervised_user_utils.cc -@@ -300,6 +300,7 @@ GURL NormalizeUrl(const GURL& url) { +@@ -308,6 +308,7 @@ GURL NormalizeUrl(const GURL& url) { } bool AreWebFilterPrefsDefault(const PrefService& pref_service) { @@ -229,9 +229,9 @@ diff --git a/components/supervised_user/core/browser/supervised_user_utils.cc b/ diff --git a/components/supervised_user/core/common/features.cc b/components/supervised_user/core/common/features.cc --- a/components/supervised_user/core/common/features.cc +++ b/components/supervised_user/core/common/features.cc -@@ -171,4 +171,10 @@ BASE_FEATURE(kReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS, +@@ -185,4 +185,10 @@ BASE_FEATURE(kReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS, "ReplaceSupervisionSystemCapabilitiesWithAccountCapabilitiesOnIOS", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); #endif + +SET_CROMITE_FEATURE_DISABLED(kLocalWebApprovals); diff --git a/build/cromite_patches/Allow-playing-audio-in-background.patch b/build/cromite_patches/Allow-playing-audio-in-background.patch index d69d5d87a99effa0f419ff7ab0be1594a499872d..1f07a677c41770439b17f7de071bf73b1a717a41 100644 --- a/build/cromite_patches/Allow-playing-audio-in-background.patch +++ b/build/cromite_patches/Allow-playing-audio-in-background.patch @@ -11,7 +11,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc --- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc +++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc -@@ -1311,6 +1311,12 @@ void WebMediaPlayerImpl::OnSelectedVideoTrackChanged( +@@ -1343,6 +1343,12 @@ void WebMediaPlayerImpl::OnSelectedVideoTrackChanged( pipeline_controller_->OnSelectedVideoTrackChanged(selected); } @@ -24,7 +24,7 @@ diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc void WebMediaPlayerImpl::EnabledAudioTracksChanged( const std::vector& enabled_track_ids) { DCHECK(main_task_runner_->BelongsToCurrentThread()); -@@ -3710,7 +3716,10 @@ bool WebMediaPlayerImpl::ShouldPausePlaybackWhenHidden() const { +@@ -3749,7 +3755,10 @@ bool WebMediaPlayerImpl::ShouldPausePlaybackWhenHidden() const { : HasAudio(); // Audio only stream is allowed to play when in background. diff --git a/build/cromite_patches/Always-use-new-tab-page-for-default-home-page.patch b/build/cromite_patches/Always-use-new-tab-page-for-default-home-page.patch index a625a469b3b2360f97b94e8526c060fed987c9d7..6237297fdc81b1bbee322e1bd5c10ccb4b13c65b 100644 --- a/build/cromite_patches/Always-use-new-tab-page-for-default-home-page.patch +++ b/build/cromite_patches/Always-use-new-tab-page-for-default-home-page.patch @@ -32,14 +32,14 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/Homepa diff --git a/chrome/browser/partnerbookmarks/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksProviderIterator.java b/chrome/browser/partnerbookmarks/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksProviderIterator.java --- a/chrome/browser/partnerbookmarks/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksProviderIterator.java +++ b/chrome/browser/partnerbookmarks/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksProviderIterator.java -@@ -61,6 +61,7 @@ public class PartnerBookmarksProviderIterator implements PartnerBookmark.Bookmar - * @return Iterator over bookmarks or null. - */ - public static PartnerBookmarksProviderIterator createIfAvailable() { -+ if ((true)) return null; - try { - Cursor cursor = - ContextUtils.getApplicationContext() +@@ -68,6 +68,7 @@ public class PartnerBookmarksProviderIterator implements PartnerBookmark.Bookmar + new AsyncTask<@Nullable Cursor>() { + @Override + protected @Nullable Cursor doInBackground() { ++ if ((true)) return null; + try { + return ContextUtils.getApplicationContext() + .getContentResolver() diff --git a/chrome/browser/partnercustomizations/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java b/chrome/browser/partnercustomizations/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java --- a/chrome/browser/partnercustomizations/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java +++ b/chrome/browser/partnercustomizations/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java diff --git a/build/cromite_patches/AudioBuffer-AnalyserNode-fp-mitigations.patch b/build/cromite_patches/AudioBuffer-AnalyserNode-fp-mitigations.patch index b494c711b46bae62d66eea43e262776839aa1255..27974571753223a4b622b12af007cf53b6b1c688 100644 --- a/build/cromite_patches/AudioBuffer-AnalyserNode-fp-mitigations.patch +++ b/build/cromite_patches/AudioBuffer-AnalyserNode-fp-mitigations.patch @@ -207,7 +207,7 @@ diff --git a/third_party/blink/renderer/modules/webaudio/realtime_analyser.cc b/ diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -485,9 +485,8 @@ +@@ -479,9 +479,8 @@ { // AudioContext.playoutStats interface. // https://chromestatus.com/feature/5172818344148992 diff --git a/build/cromite_patches/Block-qjz9zk-or-trk-requests.patch b/build/cromite_patches/Block-qjz9zk-or-trk-requests.patch index 5742a106c538505361b60d650d081753cf2510d3..d2ebe964fe93abecb66639fe52fc6053e085c365 100644 --- a/build/cromite_patches/Block-qjz9zk-or-trk-requests.patch +++ b/build/cromite_patches/Block-qjz9zk-or-trk-requests.patch @@ -118,7 +118,7 @@ diff --git a/content/browser/child_process_security_policy_impl.cc b/content/bro diff --git a/net/BUILD.gn b/net/BUILD.gn --- a/net/BUILD.gn +++ b/net/BUILD.gn -@@ -1085,6 +1085,8 @@ component("net") { +@@ -1087,6 +1087,8 @@ component("net") { "url_request/url_request_http_job.cc", "url_request/url_request_http_job.h", "url_request/url_request_interceptor.cc", @@ -203,7 +203,7 @@ diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc #include "base/synchronization/lock.h" #include "base/task/single_thread_task_runner.h" #include "base/types/optional_util.h" -@@ -53,6 +54,7 @@ +@@ -54,6 +55,7 @@ #include "net/url_request/url_request_redirect_job.h" #include "url/gurl.h" #include "url/origin.h" @@ -211,7 +211,7 @@ diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc namespace net { -@@ -635,6 +637,12 @@ URLRequest::URLRequest(base::PassKey pass_key, +@@ -642,6 +644,12 @@ URLRequest::URLRequest(base::PassKey pass_key, // Sanity check out environment. DCHECK(base::SingleThreadTaskRunner::HasCurrentDefault()); @@ -235,7 +235,7 @@ diff --git a/net/url_request/url_request_context_builder.cc b/net/url_request/ur #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_job_factory.h" #include "url/url_constants.h" -@@ -601,6 +602,8 @@ std::unique_ptr URLRequestContextBuilder::Build() { +@@ -594,6 +595,8 @@ std::unique_ptr URLRequestContextBuilder::Build() { job_factory->SetProtocolHandler(scheme_handler.first, std::move(scheme_handler.second)); } diff --git a/build/cromite_patches/Bookmarks-select-all-menu-entry.patch b/build/cromite_patches/Bookmarks-select-all-menu-entry.patch index 71eb9e18b85b29ada853ce87db98738503be4d25..6165bbc28308204354454b6fdf37074474e2c996 100644 --- a/build/cromite_patches/Bookmarks-select-all-menu-entry.patch +++ b/build/cromite_patches/Bookmarks-select-all-menu-entry.patch @@ -28,36 +28,30 @@ diff --git a/chrome/android/java/res/menu/bookmark_toolbar_menu_improved.xml b/c +@@ -184,6 +185,11 @@ public class BookmarkToolbar extends SelectableListToolbar void setCurrentFolder(BookmarkId folder) { mCurrentFolder = mBookmarkModel.getBookmarkById(folder); enableImportExportMenu(); + enableSelectAllMenu(); - } - - void enableImportExportMenu() { - getMenu().findItem(R.id.import_menu_id).setVisible(true); - getMenu().findItem(R.id.export_menu_id).setVisible(true); - } ++ } + + void enableSelectAllMenu() { + getMenu().findItem(R.id.select_all_menu_id).setVisible(true); } - void setNavigateBackRunnable(Runnable navigateBackRunnable) { -@@ -218,6 +224,17 @@ public class BookmarkToolbar extends SelectableListToolbar + void enableImportExportMenu() { +@@ -215,6 +221,17 @@ public class BookmarkToolbar extends SelectableListToolbar mExportBookmarkRunnable.run(); return true; } @@ -75,7 +69,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm return mMenuIdClickedFunction.apply(menuItem.getItemId()); } -@@ -235,6 +252,7 @@ public class BookmarkToolbar extends SelectableListToolbar +@@ -232,6 +249,7 @@ public class BookmarkToolbar extends SelectableListToolbar getMenu().findItem(R.id.import_menu_id).setVisible(mCurrentFolder != null); getMenu().findItem(R.id.export_menu_id).setVisible(mCurrentFolder != null); diff --git a/build/cromite_patches/Bromite-subresource-adblocker.patch b/build/cromite_patches/Bromite-subresource-adblocker.patch index c707f34a905d046e0f6e431b7722ec3f7c834509..afecc126f057187405d0d3f78faf1a2dc10c2dfc 100644 --- a/build/cromite_patches/Bromite-subresource-adblocker.patch +++ b/build/cromite_patches/Bromite-subresource-adblocker.patch @@ -66,7 +66,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -322,6 +322,7 @@ if (current_toolchain == default_toolchain) { +@@ -320,6 +320,7 @@ if (current_toolchain == default_toolchain) { "//chrome/android/features/tab_ui/public:ui_java_resources", "//chrome/android/modules/stack_unwinder/provider:java", "//chrome/android/webapk/libs/client:client_java", @@ -77,7 +77,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -436,6 +436,7 @@ chrome_java_resources = [ +@@ -443,6 +443,7 @@ chrome_java_resources = [ "java/res/layout/account_divider_preference.xml", "java/res/layout/account_management_account_row.xml", "java/res/layout/app_history_filter.xml", @@ -85,10 +85,10 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja "java/res/layout/auto_sign_in_first_run_dialog.xml", "java/res/layout/autofill_billing_address_dropdown.xml", "java/res/layout/autofill_card_name_and_number.xml", -@@ -624,6 +625,7 @@ chrome_java_resources = [ - "java/res/xml/about_chrome_preferences.xml", +@@ -631,6 +632,7 @@ chrome_java_resources = [ "java/res/xml/account_management_preferences.xml", "java/res/xml/ad_services_config.xml", + "java/res/xml/appearance_preferences.xml", + "java/res/xml/adblock_preferences.xml", "java/res/xml/bookmark_widget_info.xml", "java/res/xml/clear_browsing_data_preferences.xml", @@ -96,7 +96,7 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -941,6 +941,8 @@ chrome_java_sources = [ +@@ -874,6 +874,8 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/permissions/PermissionBlockedDialog.java", "java/src/org/chromium/chrome/browser/permissions/PermissionUpdateRequester.java", "java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceImpl.java", @@ -180,7 +180,7 @@ new file mode 100644 diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml --- a/chrome/android/java/res/values/styles.xml +++ b/chrome/android/java/res/values/styles.xml -@@ -256,6 +256,24 @@ found in the LICENSE file. +@@ -251,6 +251,24 @@ found in the LICENSE file. @@ -426,7 +426,7 @@ new file mode 100644 diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java -@@ -923,9 +923,7 @@ public class TabModelImpl extends TabModelJniBridge { +@@ -916,9 +916,7 @@ public class TabModelImpl extends TabModelJniBridge { .createTabWithWebContents( parent, webContents, @@ -440,7 +440,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabMod diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd -@@ -16490,6 +16490,16 @@ Please help our engineers fix this problem. Tell us what happened right before y +@@ -16534,6 +16534,16 @@ Please help our engineers fix this problem. Tell us what happened right before y Never show this again. @@ -460,16 +460,16 @@ diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources diff --git a/chrome/browser/after_startup_task_utils.cc b/chrome/browser/after_startup_task_utils.cc --- a/chrome/browser/after_startup_task_utils.cc +++ b/chrome/browser/after_startup_task_utils.cc -@@ -29,6 +29,8 @@ - #include "chromeos/startup/browser_params_proxy.h" - #endif // BUILDFLAG(IS_CHROMEOS_LACROS) +@@ -24,6 +24,8 @@ + #include "chrome/browser/ui/ash/login/login_display_host.h" + #endif +#include "chrome/browser/browser_process.h" + using content::BrowserThread; namespace { -@@ -134,6 +136,8 @@ void SetBrowserStartupIsComplete() { +@@ -129,6 +131,8 @@ void SetBrowserStartupIsComplete() { } GetAfterStartupTasks().clear(); GetAfterStartupTasks().shrink_to_fit(); @@ -481,7 +481,7 @@ diff --git a/chrome/browser/after_startup_task_utils.cc b/chrome/browser/after_s diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h --- a/chrome/browser/browser_process.h +++ b/chrome/browser/browser_process.h -@@ -32,6 +32,7 @@ +@@ -31,6 +31,7 @@ #include "chrome/common/buildflags.h" #include "components/safe_browsing/buildflags.h" #include "media/media_buildflags.h" @@ -489,7 +489,7 @@ diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h class BackgroundModeManager; class BrowserProcessPlatformPart; -@@ -89,6 +90,10 @@ namespace component_updater { +@@ -88,6 +89,10 @@ namespace component_updater { class ComponentUpdateService; } @@ -500,7 +500,7 @@ diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h namespace gcm { class GCMDriver; } -@@ -269,6 +274,7 @@ class BrowserProcess { +@@ -266,6 +271,7 @@ class BrowserProcess { #endif virtual component_updater::ComponentUpdateService* component_updater() = 0; @@ -511,7 +511,7 @@ diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc --- a/chrome/browser/browser_process_impl.cc +++ b/chrome/browser/browser_process_impl.cc -@@ -1240,6 +1240,34 @@ BrowserProcessImpl::component_updater() { +@@ -1236,6 +1236,34 @@ BrowserProcessImpl::component_updater() { return component_updater_.get(); } @@ -549,7 +549,7 @@ diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_pro diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h --- a/chrome/browser/browser_process_impl.h +++ b/chrome/browser/browser_process_impl.h -@@ -217,6 +217,7 @@ class BrowserProcessImpl : public BrowserProcess, +@@ -214,6 +214,7 @@ class BrowserProcessImpl : public BrowserProcess, #endif component_updater::ComponentUpdateService* component_updater() override; @@ -557,7 +557,7 @@ diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_proc MediaFileSystemRegistry* media_file_system_registry() override; WebRtcLogUploader* webrtc_log_uploader() override; network_time::NetworkTimeTracker* network_time_tracker() override; -@@ -429,6 +430,7 @@ class BrowserProcessImpl : public BrowserProcess, +@@ -423,6 +424,7 @@ class BrowserProcessImpl : public BrowserProcess, // to concerns over integrity of data shared between profiles, // but some users of component updater only install per-user. std::unique_ptr component_updater_; @@ -568,7 +568,7 @@ diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_proc diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc -@@ -468,6 +468,9 @@ StartupProfileInfo CreateInitialProfile( +@@ -386,6 +386,9 @@ StartupProfileInfo CreateInitialProfile( // missing code in the above test. CHECK(profile_info.profile) << "Cannot get default profile."; @@ -576,8 +576,8 @@ diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_brows + g_browser_process->adblock_updater(); + #else - #if BUILDFLAG(IS_CHROMEOS_LACROS) - // Lacros has a special "primary" profile that is tied to the active ChromeOS + profile_info = + GetStartupProfile(/*cur_dir=*/base::FilePath(), parsed_command_line); diff --git a/chrome/browser/flags/BUILD.gn b/chrome/browser/flags/BUILD.gn --- a/chrome/browser/flags/BUILD.gn +++ b/chrome/browser/flags/BUILD.gn @@ -679,7 +679,7 @@ new file mode 100755 diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc --- a/chrome/browser/net/system_network_context_manager.cc +++ b/chrome/browser/net/system_network_context_manager.cc -@@ -640,6 +640,10 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) { +@@ -639,6 +639,10 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) { StubResolverConfigReader::RegisterPrefs(registry); DefaultDnsOverHttpsConfigSource::RegisterPrefs(registry); @@ -734,7 +734,7 @@ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chro diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h -@@ -2868,6 +2868,11 @@ inline constexpr char kAudioCaptureAllowed[] = "hardware.audio_capture_enabled"; +@@ -2851,6 +2851,11 @@ inline constexpr char kAudioCaptureAllowed[] = "hardware.audio_capture_enabled"; inline constexpr char kAudioCaptureAllowedUrls[] = "hardware.audio_capture_allowed_urls"; @@ -749,7 +749,7 @@ diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp --- a/components/browser_ui/strings/android/site_settings.grdp +++ b/components/browser_ui/strings/android/site_settings.grdp -@@ -373,6 +373,9 @@ +@@ -379,6 +379,9 @@ This site shows intrusive or misleading ads @@ -1573,17 +1573,17 @@ new file mode 100644 diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc --- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc +++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc -@@ -18,6 +18,9 @@ - #include "base/strings/stringprintf.h" +@@ -19,6 +19,9 @@ #include "base/trace_event/trace_event.h" #include "base/trace_event/traced_value.h" + #include "base/tracing/protos/chrome_track_event.pbzero.h" +#include "components/content_settings/core/common/content_settings_types.h" +#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h" +#include "components/subresource_filter/content/browser/subresource_filter_profile_context.h" #include "components/subresource_filter/content/browser/ad_tagging_utils.h" #include "components/subresource_filter/content/browser/content_subresource_filter_web_contents_helper.h" #include "components/subresource_filter/content/browser/profile_interaction_manager.h" -@@ -112,6 +115,7 @@ ContentSubresourceFilterThrottleManager:: +@@ -113,6 +116,7 @@ ContentSubresourceFilterThrottleManager:: profile_interaction_manager_( std::make_unique( profile_context)), @@ -1591,7 +1591,7 @@ diff --git a/components/subresource_filter/content/browser/content_subresource_f web_contents_helper_(web_contents_helper) {} ContentSubresourceFilterThrottleManager:: -@@ -679,6 +683,17 @@ ContentSubresourceFilterThrottleManager:: +@@ -624,6 +628,17 @@ ContentSubresourceFilterThrottleManager:: throttle->NotifyPageActivationWithRuleset(EnsureRulesetHandle(), ad_tagging_state); } @@ -1612,7 +1612,7 @@ diff --git a/components/subresource_filter/content/browser/content_subresource_f diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h --- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h +++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h -@@ -414,6 +414,8 @@ class ContentSubresourceFilterThrottleManager +@@ -406,6 +406,8 @@ class ContentSubresourceFilterThrottleManager std::unique_ptr profile_interaction_manager_; diff --git a/build/cromite_patches/Client-hints-overrides.patch b/build/cromite_patches/Client-hints-overrides.patch index a6650a281c44706d30a5c82d7d9ff079ba08316d..5dfefd8b239afafc001eb7a2918e4befbd4eaa76 100644 --- a/build/cromite_patches/Client-hints-overrides.patch +++ b/build/cromite_patches/Client-hints-overrides.patch @@ -30,7 +30,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc --- a/components/embedder_support/user_agent_utils.cc +++ b/components/embedder_support/user_agent_utils.cc -@@ -188,9 +188,7 @@ const blink::UserAgentBrandList GetUserAgentBrandList( +@@ -185,9 +185,7 @@ const blink::UserAgentBrandList GetUserAgentBrandList( bool parse_result = base::StringToInt(major_version, &major_version_number); DCHECK(parse_result); std::optional brand; @@ -44,15 +44,15 @@ diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedd diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/client_hints/client_hints.cc --- a/content/browser/client_hints/client_hints.cc +++ b/content/browser/client_hints/client_hints.cc -@@ -60,6 +60,7 @@ - #include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h" - #include "third_party/blink/public/common/permissions_policy/permissions_policy.h" +@@ -62,6 +62,7 @@ + #include "third_party/blink/public/common/features.h" + #include "third_party/blink/public/common/page/page_zoom.h" #include "third_party/blink/public/common/user_agent/user_agent_metadata.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "url/origin.h" -@@ -704,7 +705,8 @@ void UpdateNavigationRequestClientUaHeadersImpl( +@@ -708,7 +709,8 @@ void UpdateNavigationRequestClientUaHeadersImpl( // value, disable them. This overwrites previous decision from UI. disable_due_to_custom_ua = !ua_metadata.has_value(); } @@ -62,7 +62,7 @@ diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/clie if (!disable_due_to_custom_ua) { if (!ua_metadata.has_value()) ua_metadata = delegate->GetUserAgentMetadata(); -@@ -886,10 +888,12 @@ void AddRequestClientHintsHeaders( +@@ -890,10 +892,12 @@ void AddRequestClientHintsHeaders( AddEctHeader(headers, network_quality_tracker, url); } @@ -79,7 +79,7 @@ diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/clie if (ShouldAddClientHint(data, WebClientHintsType::kPrefersColorScheme)) { AddPrefersColorSchemeHeader(headers, frame_tree_node); -@@ -975,6 +979,7 @@ ParseAndPersistAcceptCHForNavigation( +@@ -974,6 +978,7 @@ ParseAndPersistAcceptCHForNavigation( BrowserContext* context, ClientHintsControllerDelegate* delegate, FrameTreeNode* frame_tree_node) { @@ -90,7 +90,7 @@ diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/clie 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 -@@ -1301,6 +1301,11 @@ void NavigationURLLoaderImpl::OnAcceptCHFrameReceived( +@@ -1305,6 +1305,11 @@ void NavigationURLLoaderImpl::OnAcceptCHFrameReceived( return; } @@ -131,7 +131,7 @@ new file mode 100644 diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc -@@ -109,6 +109,7 @@ const size_t kMaxRestarts = 32; +@@ -105,6 +105,7 @@ const size_t kMaxRestarts = 32; // Returns true when Early Hints are allowed on the given protocol. bool EarlyHintsAreAllowedOn(HttpConnectionInfo connection_info) { @@ -156,12 +156,12 @@ diff --git a/third_party/blink/common/client_hints/client_hints.cc b/third_party @@ -14,6 +14,7 @@ #include "base/strings/string_util.h" #include "services/network/public/cpp/client_hints.h" - #include "third_party/blink/public/common/features.h" + #include "services/network/public/cpp/permissions_policy/client_hints_permissions_policy_mapping.h" +#include "third_party/blink/public/common/features_generated.h" - #include "third_party/blink/public/common/permissions_policy/permissions_policy.h" + #include "services/network/public/cpp/permissions_policy/permissions_policy.h" + #include "third_party/blink/public/common/features.h" #include "url/origin.h" - -@@ -108,11 +109,12 @@ const PolicyFeatureToClientHintMap& GetPolicyFeatureToClientHintMap() { +@@ -22,11 +23,12 @@ namespace blink { bool IsClientHintSentByDefault(network::mojom::WebClientHintsType type) { switch (type) { @@ -234,7 +234,7 @@ diff --git a/third_party/blink/renderer/core/frame/navigator_ua_data.idl b/third diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc --- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc -@@ -217,6 +217,8 @@ bool ShouldSendClientHint(const PermissionsPolicy& policy, +@@ -224,6 +224,8 @@ bool ShouldSendClientHint(const network::PermissionsPolicy& policy, bool is_1p_origin, network::mojom::blink::WebClientHintsType type, const ClientHintsPreferences& hints_preferences) { @@ -243,7 +243,7 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/thi // For subresource requests, sending the hint in the fetch request based on // the permissions policy. if (!policy.IsFeatureEnabledForOrigin( -@@ -610,7 +612,7 @@ void FrameFetchContext::AddClientHintsIfNecessary( +@@ -604,7 +606,7 @@ void FrameFetchContext::AddClientHintsIfNecessary( } // Only send User Agent hints if the info is available @@ -255,8 +255,8 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/thi diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -438,6 +438,11 @@ - name: "AriaNotify", +@@ -439,6 +439,11 @@ + name: "AriaNotifyV2", status: "test", }, + { @@ -275,6 +275,6 @@ diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 - status: "test", + name: "ClientHintUAHighEntropyValuesPermissionPolicy", // disabled by default }, - // This ensures that clipboard event fires on a target node which is - // focused in case no visible selection is present. + { + // Enables the API for getting a unique token of the system clipboard's -- diff --git a/build/cromite_patches/Content-settings-infrastructure.patch b/build/cromite_patches/Content-settings-infrastructure.patch index 380d7c320e738ddc52322df233afe968a3a417a1..a138928ea4dc06bdba1b4420f80ace30bc1ac6be 100644 --- a/build/cromite_patches/Content-settings-infrastructure.patch +++ b/build/cromite_patches/Content-settings-infrastructure.patch @@ -44,9 +44,9 @@ Require: bromite-build-utils.patch .../BromiteCustomContentSettingImpl.java | 412 ++++++++++++++++++ ...tomTriStateSiteSettingsPreferenceImpl.java | 24 + .../ContentSettingsResources.java | 16 +- - .../site_settings/SingleCategorySettings.java | 55 ++- + .../site_settings/SingleCategorySettings.java | 56 ++- .../site_settings/SingleWebsiteSettings.java | 58 ++- - .../site_settings/SiteSettings.java | 31 +- + .../site_settings/SiteSettings.java | 33 +- .../site_settings/SiteSettingsCategory.java | 18 +- .../site_settings/SiteSettingsDelegate.java | 2 + .../TriStateSiteSettingsPreference.java | 13 +- @@ -84,7 +84,7 @@ Require: bromite-build-utils.patch .../platform/web_content_settings_client.h | 13 + .../execution_context/execution_context.cc | 16 + .../execution_context/execution_context.h | 5 + - 73 files changed, 1941 insertions(+), 104 deletions(-) + 73 files changed, 1942 insertions(+), 106 deletions(-) create mode 100644 components/browser_ui/settings/android/java/res/layout/preference_spinner_single_widget.xml create mode 100644 components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/BromiteCustomContentSetting.java create mode 100644 components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/BromiteCustomContentSettingImpl.java @@ -98,7 +98,7 @@ Require: bromite-build-utils.patch diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java -@@ -68,6 +68,10 @@ import org.chromium.url.GURL; +@@ -67,6 +67,10 @@ import org.chromium.url.GURL; import java.util.List; import java.util.Set; @@ -109,7 +109,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/C /** A SiteSettingsDelegate instance that contains Chrome-specific Site Settings logic. */ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { public static final String EMBEDDED_CONTENT_HELP_CENTER_URL = -@@ -242,7 +246,7 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { +@@ -236,7 +240,7 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { @Override public boolean isHelpAndFeedbackEnabled() { @@ -118,7 +118,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/C } @Override -@@ -399,6 +403,17 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { +@@ -393,6 +397,17 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { return mPrivacySandboxBridge.getRelatedWebsiteSetOwner(memberOrigin); } @@ -151,7 +151,7 @@ diff --git a/chrome/browser/content_settings/page_specific_content_settings_dele diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html --- a/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html -@@ -1432,4 +1432,5 @@ +@@ -1526,4 +1526,5 @@ @@ -710,7 +710,7 @@ diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_ diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn -@@ -1791,6 +1791,7 @@ static_library("ui") { +@@ -1789,6 +1789,7 @@ static_library("ui") { "//components/commerce/core/mojom:mojo_bindings", "//components/commerce/core/webui", "//components/endpoint_fetcher:endpoint_fetcher", @@ -718,10 +718,10 @@ diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn "//components/enterprise/common:files_scan_data", "//components/feedback/proto", "//components/headless/policy", -@@ -5006,6 +5007,7 @@ static_library("ui") { +@@ -5001,6 +5002,7 @@ static_library("ui") { + "//components/power_bookmarks/storage", "//components/prefs", "//components/reading_list/features:flags", - "//components/segmentation_platform/embedder/default_model:default_model", + "//components/strings:components_strings_grit", "//components/services/app_service", "//components/soda", @@ -729,7 +729,7 @@ diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn diff --git a/chrome/browser/ui/views/controls/rich_controls_container_view.h b/chrome/browser/ui/views/controls/rich_controls_container_view.h --- a/chrome/browser/ui/views/controls/rich_controls_container_view.h +++ b/chrome/browser/ui/views/controls/rich_controls_container_view.h -@@ -48,6 +48,13 @@ class RichControlsContainerView : public views::FlexLayoutView { +@@ -50,6 +50,13 @@ class RichControlsContainerView : public views::FlexLayoutView { return AddChildView(std::move(control_view)); } @@ -754,7 +754,7 @@ diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/b #include "ui/views/view_class_properties.h" #if BUILDFLAG(FULL_SAFE_BROWSING) -@@ -256,8 +257,20 @@ void PageInfoMainView::SetPermissionInfo( +@@ -258,8 +259,20 @@ void PageInfoMainView::SetPermissionInfo( scroll_view->SetDrawOverflowIndicator(false); auto* content_view = scroll_view->SetContents(std::make_unique()); @@ -777,7 +777,7 @@ diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/b content_view->SetID(PageInfoViewFactory::VIEW_ID_PAGE_INFO_PERMISSION_VIEW); content_view->SetProperty(views::kElementIdentifierKey, kPermissionsElementId); -@@ -272,11 +285,16 @@ void PageInfoMainView::SetPermissionInfo( +@@ -274,11 +287,16 @@ void PageInfoMainView::SetPermissionInfo( } } @@ -794,7 +794,7 @@ diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/b toggle_row->SetProperty(views::kCrossAxisAlignmentKey, views::LayoutAlignment::kStretch); syncable_permission_rows_.emplace(permission.type, toggle_row); -@@ -284,6 +302,8 @@ void PageInfoMainView::SetPermissionInfo( +@@ -286,6 +304,8 @@ void PageInfoMainView::SetPermissionInfo( } for (auto& object : chosen_object_info_list) { @@ -803,7 +803,7 @@ diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/b // The view takes ownership of the object info. auto object_view = std::make_unique( std::move(object), -@@ -294,6 +314,10 @@ void PageInfoMainView::SetPermissionInfo( +@@ -296,6 +316,10 @@ void PageInfoMainView::SetPermissionInfo( content_view->AddChildView(std::move(object_view))); } @@ -862,7 +862,7 @@ diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.cc b/chrom diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.h b/chrome/browser/ui/views/page_info/page_info_view_factory.h --- a/chrome/browser/ui/views/page_info/page_info_view_factory.h +++ b/chrome/browser/ui/views/page_info/page_info_view_factory.h -@@ -94,6 +94,11 @@ class PageInfoViewFactory { +@@ -95,6 +95,11 @@ class PageInfoViewFactory { const PageInfo::PermissionInfo& info, bool blocked_on_system_level = false); @@ -1071,7 +1071,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc b/c + if (choose_button_) { + choose_button_->SetEnabledTextColors(std::nullopt); + if (permission_.setting == CONTENT_SETTING_DEFAULT) { -+ choose_button_->SetTextColorId(views::Button::ButtonState::STATE_NORMAL, ++ choose_button_->SetTextColor(views::Button::ButtonState::STATE_NORMAL, + ui::kColorLabelForeground); + } + } @@ -1110,7 +1110,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc b/c + } + choose_button_->SetText(caption); + if (permission_.setting == CONTENT_SETTING_DEFAULT) { -+ choose_button_->SetTextColorId(views::Button::ButtonState::STATE_NORMAL, ++ choose_button_->SetTextColor(views::Button::ButtonState::STATE_NORMAL, + ui::kColorLabelForeground); + } + } @@ -1120,7 +1120,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc b/c diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.h b/chrome/browser/ui/views/page_info/permission_toggle_row_view.h --- a/chrome/browser/ui/views/page_info/permission_toggle_row_view.h +++ b/chrome/browser/ui/views/page_info/permission_toggle_row_view.h -@@ -16,6 +16,11 @@ +@@ -17,6 +17,11 @@ #include "ui/views/controls/label.h" #include "ui/views/view.h" @@ -1132,7 +1132,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.h b/ch class ChromePageInfoUiDelegate; class PageInfoNavigationHandler; -@@ -31,7 +36,8 @@ class PageInfoBubbleViewTestApi; +@@ -32,7 +37,8 @@ class PageInfoBubbleViewTestApi; // A view that shows a permission that a site is able to access, and // allows the user to control via toggle whether that access is granted. Has a // button that opens a subpage with more controls. @@ -1142,7 +1142,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.h b/ch METADATA_HEADER(PermissionToggleRowView, views::View) public: -@@ -68,6 +74,12 @@ class PermissionToggleRowView : public views::View { +@@ -69,6 +75,12 @@ class PermissionToggleRowView : public views::View { private: friend class test::PageInfoBubbleViewTestApi; @@ -1155,7 +1155,7 @@ diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.h b/ch void OnToggleButtonPressed(); void AddToggleButton(const std::u16string& toggle_accessible_name, int icon_label_spacing); -@@ -86,6 +98,10 @@ class PermissionToggleRowView : public views::View { +@@ -87,6 +99,10 @@ class PermissionToggleRowView : public views::View { raw_ptr toggle_button_ = nullptr; raw_ptr spacer_view_ = nullptr; @@ -1177,7 +1177,7 @@ diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provide #include "build/branding_buildflags.h" #include "build/build_config.h" #include "build/buildflag.h" -@@ -73,6 +74,9 @@ +@@ -74,6 +75,9 @@ #include "components/dom_distiller/core/dom_distiller_features.h" #include "components/google/core/common/google_util.h" #include "components/history/core/common/pref_names.h" @@ -1187,7 +1187,7 @@ diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provide #include "components/password_manager/core/browser/leak_detection_dialog_utils.h" #include "components/password_manager/core/browser/manage_passwords_referrer.h" #include "components/password_manager/core/common/password_manager_features.h" -@@ -3802,6 +3806,56 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source, +@@ -3812,6 +3816,56 @@ void AddLocalizedStrings(content::WebUIDataSource* html_source, policy_indicator::AddLocalizedStrings(html_source); AddSecurityKeysStrings(html_source); @@ -1256,8 +1256,8 @@ diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/b #include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_pattern.h" #include "components/content_settings/core/common/content_settings_types.h" -@@ -249,13 +251,13 @@ constexpr auto kContentSettingsTypeGroupNames = std::to_array< - {ContentSettingsType::POINTER_LOCK, nullptr}, +@@ -250,13 +252,13 @@ constexpr auto kContentSettingsTypeGroupNames = std::to_array< + {ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS, nullptr}, }); -static_assert( @@ -1277,7 +1277,7 @@ diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/b struct SiteSettingSourceStringMapping { SiteSettingSource source; -@@ -503,6 +505,13 @@ bool HasRegisteredGroupName(ContentSettingsType type) { +@@ -504,6 +506,13 @@ bool HasRegisteredGroupName(ContentSettingsType type) { return true; } } @@ -1291,7 +1291,7 @@ diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/b return false; } -@@ -516,11 +525,24 @@ ContentSettingsType ContentSettingsTypeFromGroupName(std::string_view name) { +@@ -517,11 +526,24 @@ ContentSettingsType ContentSettingsTypeFromGroupName(std::string_view name) { return entry.type; } } @@ -1317,7 +1317,7 @@ diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/b for (const auto& entry : kContentSettingsTypeGroupNames) { if (type == entry.type) { // Content setting types that aren't represented in the settings UI -@@ -535,7 +557,6 @@ std::string_view ContentSettingsTypeToGroupName(ContentSettingsType type) { +@@ -536,7 +558,6 @@ std::string_view ContentSettingsTypeToGroupName(ContentSettingsType type) { return entry.name ? entry.name : std::string_view(); } } @@ -1325,7 +1325,7 @@ diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/b NOTREACHED() << static_cast(type) << " is not a recognized content settings type."; } -@@ -644,6 +665,13 @@ std::vector GetVisiblePermissionCategories( +@@ -645,6 +666,13 @@ std::vector GetVisiblePermissionCategories( base_types->push_back(ContentSettingsType::WEB_APP_INSTALLATION); } @@ -1505,7 +1505,7 @@ diff --git a/components/browser_ui/settings/android/widget/java/src/org/chromium diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn --- a/components/browser_ui/site_settings/android/BUILD.gn +++ b/components/browser_ui/site_settings/android/BUILD.gn -@@ -147,6 +147,23 @@ android_library("java") { +@@ -148,6 +148,23 @@ android_library("java") { ":site_settings_jni_headers", "//components/content_settings/android:java_pref_names_srcjar", ] @@ -1529,7 +1529,7 @@ diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/b } android_library("javatests") { -@@ -195,6 +212,7 @@ robolectric_library("junit") { +@@ -196,6 +213,7 @@ robolectric_library("junit") { } android_resources("java_resources") { @@ -1555,7 +1555,7 @@ diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_setti diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/AllSiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/AllSiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/AllSiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/AllSiteSettings.java -@@ -386,7 +386,8 @@ public class AllSiteSettings extends BaseSiteSettingsFragment +@@ -397,7 +397,8 @@ public class AllSiteSettings extends BaseSiteSettingsFragment if (queryHasChanged) getInfoForOrigins(); }); @@ -1565,7 +1565,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c MenuItem help = menu.add( Menu.NONE, -@@ -398,6 +399,8 @@ public class AllSiteSettings extends BaseSiteSettingsFragment +@@ -409,6 +410,8 @@ public class AllSiteSettings extends BaseSiteSettingsFragment getResources(), R.drawable.ic_help_and_feedback, getContext().getTheme())); @@ -2183,8 +2183,8 @@ new file mode 100644 diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java -@@ -37,7 +37,7 @@ import org.chromium.device.DeviceFeatureMap; - @SuppressLint("UseSparseArrays") +@@ -40,7 +40,7 @@ import org.chromium.device.DeviceFeatureMap; + @NullMarked public class ContentSettingsResources { /** An inner class contains all the resources for a ContentSettingsType */ - private static class ResourceItem { @@ -2192,7 +2192,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c private final int mIcon; private final int mTitle; private final @ContentSettingValues @Nullable Integer mDefaultEnabledValue; -@@ -46,7 +46,7 @@ public class ContentSettingsResources { +@@ -49,7 +49,7 @@ public class ContentSettingsResources { private final int mDisabledSummary; private final int mSummaryOverrideForScreenReader; @@ -2201,16 +2201,16 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c int icon, int title, @ContentSettingValues @Nullable Integer defaultEnabledValue, -@@ -445,6 +445,8 @@ public class ContentSettingsResources { +@@ -450,6 +450,8 @@ public class ContentSettingsResources { R.string.website_settings_category_vr_blocked, R.string.website_settings_category_vr_a11y); } + ResourceItem ri = BromiteCustomContentSettingImpl.getResourceItem(contentType); + if (ri != null) return ri; assert false; // NOTREACHED - return null; + return assumeNonNull(null); } -@@ -603,6 +605,14 @@ public class ContentSettingsResources { +@@ -608,6 +610,14 @@ public class ContentSettingsResources { return getResourceItem(contentType).getDefaultDisabledValue(); } @@ -2225,10 +2225,10 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c /** * Returns the string resource id for a given ContentSetting to show with a permission category. * -@@ -776,6 +786,8 @@ public class ContentSettingsResources { +@@ -781,6 +791,8 @@ public class ContentSettingsResources { * Blocked states, in that order. */ - public static int[] getTriStateSettingDescriptionIDs(int contentType) { + public static int @Nullable [] getTriStateSettingDescriptionIDs(int contentType) { + int[] value = BromiteCustomContentSettingImpl.getTriStateSettingDescriptionIDs(contentType); + if (value != null) return value; if (contentType == ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER) { @@ -2237,7 +2237,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java -@@ -324,6 +324,10 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -334,6 +334,10 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment website.site() .getContentSetting( browserContextHandle, mCategory.getContentSettingsType()); @@ -2248,7 +2248,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (contentSetting != null) { return ContentSettingValues.BLOCK == contentSetting; } -@@ -488,6 +492,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -519,6 +523,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment ? new HashSet<>(getArguments().getStringArrayList(EXTRA_SELECTED_DOMAINS)) : null; @@ -2256,7 +2256,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c configureGlobalToggles(); if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE) { RecordUserAction.record("DesktopSiteContentSetting.SettingsPage.Entered"); -@@ -523,7 +528,8 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -554,7 +559,8 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment if (queryHasChanged) getInfoForOrigins(); }); @@ -2266,7 +2266,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c MenuItem help = menu.add( Menu.NONE, -@@ -535,12 +541,20 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -566,12 +572,20 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment getResources(), R.drawable.ic_help_and_feedback, getContext().getTheme())); @@ -2287,7 +2287,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (mCategory.getType() == SiteSettingsCategory.Type.PROTECTED_MEDIA) { getSiteSettingsDelegate() .launchProtectedContentHelpAndFeedbackActivity(getActivity()); -@@ -612,6 +626,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -644,6 +658,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment BrowserContextHandle browserContextHandle = getSiteSettingsDelegate().getBrowserContextHandle(); PrefService prefService = UserPrefs.get(browserContextHandle); @@ -2299,7 +2299,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (BINARY_TOGGLE_KEY.equals(preference.getKey())) { assert !mCategory.isManaged(); boolean toggleValue = (boolean) newValue; -@@ -789,7 +808,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -827,7 +846,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment ? R.string.website_settings_add_site_description_javascript_optimizer_block : R.string.website_settings_add_site_description_javascript_optimizer_allow; } @@ -2308,7 +2308,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } // OnPreferenceClickListener: -@@ -903,10 +922,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -947,10 +966,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment default: break; } @@ -2323,7 +2323,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c getPreferenceScreen() .addPreference( new AddExceptionPreference( -@@ -1091,8 +1111,16 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -1138,9 +1158,16 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment private boolean isBlocked() { switch (mGlobalToggleLayout) { case GlobalToggleLayout.TRI_STATE_TOGGLE: @@ -2336,13 +2336,14 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c + } TriStateSiteSettingsPreference triStateToggle = getPreferenceScreen().findPreference(TRI_STATE_TOGGLE_KEY); +- assumeNonNull(triStateToggle); + if (triStateToggle == null) return true; return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK); case GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE: TriStateCookieSettingsPreference triStateCookieToggle = -@@ -1162,7 +1190,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment - infoText.setSummary( - R.string.website_settings_category_javascript_optimizer_page_description); +@@ -1269,7 +1296,11 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment + } else if (res_id != -1) { + infoText.setSummary(res_id); } else { - screen.removePreference(infoText); + int infoMessage = BromiteCustomContentSettingImpl.getCategoryDescription(mCategory); @@ -2353,7 +2354,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } // Hide the anti-abuse text preferences, as needed. -@@ -1199,6 +1231,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -1314,6 +1345,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment } else { screen.removePreference(mLocationTriStatePref); } @@ -2361,7 +2362,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (permissionBlockedByOs) { maybeShowOsWarning(screen); -@@ -1357,7 +1390,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -1491,7 +1523,7 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment getSiteSettingsDelegate().getBrowserContextHandle(), contentType); int[] descriptionIds = ContentSettingsResources.getTriStateSettingDescriptionIDs(contentType); @@ -2370,7 +2371,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } private void configureBinaryToggle(ChromeSwitchPreference binaryToggle, int contentType) { -@@ -1481,6 +1514,14 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment +@@ -1615,6 +1647,14 @@ public class SingleCategorySettings extends BaseSiteSettingsFragment @ContentSettingValues Integer value = site.getContentSetting(browserContextHandle, contentSettingsType); @@ -2388,15 +2389,15 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java -@@ -51,6 +51,7 @@ import java.util.Collection; +@@ -54,6 +54,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.List; /** Shows the permissions and other settings for a particular website. */ - public class SingleWebsiteSettings extends BaseSiteSettingsFragment -@@ -177,7 +178,7 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment + @NullMarked +@@ -181,7 +182,7 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment case ContentSettingsType.FILE_SYSTEM_WRITE_GUARD: return "file_system_write_guard_permission_list"; default: @@ -2405,10 +2406,10 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } } -@@ -566,8 +567,21 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment - +@@ -577,8 +578,21 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment private void setupContentSettingsPreferences() { - mMaxPermissionOrder = findPreference(PREF_PERMISSIONS_HEADER).getOrder(); + Preference permissionsHeaderPref = assumeNonNull(findPreference(PREF_PERMISSIONS_HEADER)); + mMaxPermissionOrder = permissionsHeaderPref.getOrder(); - for (@ContentSettingsType.EnumType int type : SiteSettingsUtil.SETTINGS_ORDER) { - Preference preference = new ChromeSwitchPreference(getStyledContext()); + List order = BromiteCustomContentSettingImpl.getSettingsOrder(); @@ -2429,7 +2430,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c preference.setKey(getPreferenceKey(type)); if (type == ContentSettingsType.ADS) { -@@ -1144,20 +1158,32 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -1183,20 +1197,32 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment @ContentSettingValues @Nullable Integer value, boolean isEmbargoed, boolean isOneTime) { @@ -2470,7 +2471,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c AppCompatResources.getColorStateList(getContext(), mHighlightColor) .getDefaultColor()); } -@@ -1372,14 +1398,20 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -1413,14 +1439,20 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment if (newValue instanceof Boolean) { permission = (Boolean) newValue ? getEnabledValue(type) : ContentSettingValues.BLOCK; } else { @@ -2484,7 +2485,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c + permission = (Integer) newValue; } - mSite.setContentSetting(browserContextHandle, type, permission); + assumeNonNull(mSite).setContentSetting(browserContextHandle, type, permission); // In Clank, one time grants are only possible via prompt, not via page // info. preference.setSummary( @@ -2496,7 +2497,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java -@@ -53,6 +53,7 @@ public class SiteSettings extends BaseSiteSettingsFragment +@@ -57,6 +57,7 @@ public class SiteSettings extends BaseSiteSettingsFragment SettingsUtils.addPreferencesFromResource(this, R.xml.site_settings_preferences); mPageTitle.set(getContext().getString(R.string.prefs_site_settings)); @@ -2504,7 +2505,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c configurePreferences(); updatePreferenceStates(); } -@@ -79,7 +80,7 @@ public class SiteSettings extends BaseSiteSettingsFragment +@@ -87,7 +88,7 @@ public class SiteSettings extends BaseSiteSettingsFragment // Remove unsupported settings categories. for (@SiteSettingsCategory.Type int type = 0; @@ -2512,8 +2513,8 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c + type < BromiteCustomContentSettingImpl.NUM_ENTRIES(); type++) { if (!getSiteSettingsDelegate().isCategoryVisible(type)) { - getPreferenceScreen().removePreference(findPreference(type)); -@@ -101,7 +102,7 @@ public class SiteSettings extends BaseSiteSettingsFragment + Preference pref = assumeNonNull(findPreference(type)); +@@ -113,7 +114,7 @@ public class SiteSettings extends BaseSiteSettingsFragment @CookieControlsMode int cookieControlsMode = UserPrefs.get(browserContextHandle).getInteger(COOKIE_CONTROLS_MODE); @@ -2522,7 +2523,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c Preference p = findPreference(prefCategory); int contentType = SiteSettingsCategory.contentSettingsType(prefCategory); // p can be null if the Preference was removed in configurePreferences. -@@ -176,19 +177,21 @@ public class SiteSettings extends BaseSiteSettingsFragment +@@ -191,20 +192,22 @@ public class SiteSettings extends BaseSiteSettingsFragment } else if (Type.ZOOM == prefCategory) { // Don't want to set a summary for Zoom because we don't want any message to display // under the Zoom row on site settings. @@ -2532,10 +2533,11 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c - setting, /* isOneTime= */ false)); } else { - @ContentSettingValues -- int defaultForToggle = +- Integer defaultForToggle = - checked - ? ContentSettingsResources.getDefaultEnabledValue(contentType) - : ContentSettingsResources.getDefaultDisabledValue(contentType); +- assumeNonNull(defaultForToggle); - p.setSummary( - ContentSettingsResources.getCategorySummary( - defaultForToggle, /* isOneTime= */ false)); @@ -2545,10 +2547,11 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c + p.setSummary(summary); + } else { + @ContentSettingValues -+ int defaultForToggle = ++ Integer defaultForToggle = + checked + ? ContentSettingsResources.getDefaultEnabledValue(contentType) + : ContentSettingsResources.getDefaultDisabledValue(contentType); ++ assumeNonNull(defaultForToggle); + summary = ContentSettingsResources.getCategorySummary( + contentType, defaultForToggle, /* isOneTime= */ false); + p.setSummary(summary); @@ -2559,7 +2562,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java -@@ -75,7 +75,7 @@ public class SiteSettingsCategory { +@@ -77,7 +77,7 @@ public class SiteSettingsCategory { Type.TRACKING_PROTECTION, Type.FILE_EDITING, Type.JAVASCRIPT_OPTIMIZER, @@ -2568,7 +2571,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c }) @Retention(RetentionPolicy.SOURCE) public @interface Type { -@@ -117,7 +117,7 @@ public class SiteSettingsCategory { +@@ -119,7 +119,7 @@ public class SiteSettingsCategory { int JAVASCRIPT_OPTIMIZER = 33; /** Number of handled categories used for calculating array sizes. */ @@ -2577,7 +2580,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } private final BrowserContextHandle mBrowserContextHandle; -@@ -166,6 +166,9 @@ public class SiteSettingsCategory { +@@ -170,6 +170,9 @@ public class SiteSettingsCategory { } else { permission = ""; } @@ -2587,7 +2590,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c return new SiteSettingsCategory(browserContextHandle, type, permission); } -@@ -174,7 +177,7 @@ public class SiteSettingsCategory { +@@ -178,7 +181,7 @@ public class SiteSettingsCategory { @ContentSettingsType.EnumType int contentSettingsType) { assert contentSettingsType != -1; assert Type.ALL_SITES == 0; @@ -2596,8 +2599,8 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (contentSettingsType(i) == contentSettingsType) { return createFromType(browserContextHandle, i); } -@@ -185,7 +188,7 @@ public class SiteSettingsCategory { - public static SiteSettingsCategory createFromPreferenceKey( +@@ -189,7 +192,7 @@ public class SiteSettingsCategory { + public static @Nullable SiteSettingsCategory createFromPreferenceKey( BrowserContextHandle browserContextHandle, String preferenceKey) { assert Type.ALL_SITES == 0; - for (@Type int i = Type.ALL_SITES; i < Type.NUM_ENTRIES; i++) { @@ -2605,7 +2608,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c if (preferenceKey(i).equals(preferenceKey)) { return createFromType(browserContextHandle, i); } -@@ -262,8 +265,7 @@ public class SiteSettingsCategory { +@@ -266,8 +269,7 @@ public class SiteSettingsCategory { case Type.TRACKING_PROTECTION: return ContentSettingsType.DEFAULT; // Conversion unavailable. } @@ -2615,7 +2618,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } /** -@@ -354,8 +356,12 @@ public class SiteSettingsCategory { +@@ -358,8 +360,12 @@ public class SiteSettingsCategory { case Type.ZOOM: return "zoom"; default: @@ -2631,7 +2634,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java -@@ -217,6 +217,8 @@ public interface SiteSettingsDelegate { +@@ -218,6 +218,8 @@ public interface SiteSettingsDelegate { */ String getRelatedWebsiteSetOwner(String memberOrigin); @@ -2643,15 +2646,15 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TriStateSiteSettingsPreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TriStateSiteSettingsPreference.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TriStateSiteSettingsPreference.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TriStateSiteSettingsPreference.java -@@ -13,6 +13,7 @@ import androidx.preference.PreferenceViewHolder; - +@@ -18,6 +18,7 @@ import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; import org.chromium.components.browser_ui.widget.RadioButtonWithDescription; import org.chromium.components.content_settings.ContentSettingValues; +import org.chromium.components.content_settings.ContentSettingsType; /** A 3-state Allowed/Ask/Blocked radio group Preference used for SiteSettings. */ - public class TriStateSiteSettingsPreference extends Preference -@@ -23,6 +24,7 @@ public class TriStateSiteSettingsPreference extends Preference + @NullMarked +@@ -29,6 +30,7 @@ public class TriStateSiteSettingsPreference extends Preference private RadioButtonWithDescription mAsk; private RadioButtonWithDescription mBlocked; private RadioGroup mRadioGroup; @@ -2659,19 +2662,19 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c public TriStateSiteSettingsPreference(Context context, AttributeSet attrs) { super(context, attrs); -@@ -42,7 +44,9 @@ public class TriStateSiteSettingsPreference extends Preference +@@ -48,7 +50,9 @@ public class TriStateSiteSettingsPreference extends Preference * @param descriptionIds An array of 3 resource IDs for descriptions for * Allowed, Ask and Blocked states, in that order. */ -- public void initialize(@ContentSettingValues int setting, int[] descriptionIds) { +- public void initialize(@ContentSettingValues int setting, int @Nullable [] descriptionIds) { + public void initialize(@ContentSettingsType.EnumType int contentType, -+ @ContentSettingValues int setting, int[] descriptionIds) { ++ @ContentSettingValues int setting, int @Nullable [] descriptionIds) { + mContentType = contentType; mSetting = setting; mDescriptionIds = descriptionIds; } -@@ -75,6 +79,13 @@ public class TriStateSiteSettingsPreference extends Preference - mRadioGroup = (RadioGroup) holder.findViewById(R.id.radio_button_layout); +@@ -86,6 +90,13 @@ public class TriStateSiteSettingsPreference extends Preference + mRadioGroup = assumeNonNull(radioGroup); mRadioGroup.setOnCheckedChangeListener(this); + BromiteCustomContentSetting cs = @@ -2687,7 +2690,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java -@@ -327,7 +327,11 @@ public final class Website implements WebsiteEntry { +@@ -331,7 +331,11 @@ public final class Website implements WebsiteEntry { RecordUserAction.record("SoundContentSetting.UnmuteBy.SiteSettings"); } } @@ -2703,7 +2706,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java -@@ -110,7 +110,7 @@ public class WebsitePermissionsFetcher { +@@ -111,7 +111,7 @@ public class WebsitePermissionsFetcher { case ContentSettingsType.USB_GUARD: return WebsitePermissionsType.CHOSEN_OBJECT_INFO; default: @@ -2715,7 +2718,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java -@@ -291,7 +291,7 @@ public class WebsitePreferenceBridge { +@@ -321,7 +321,7 @@ public class WebsitePreferenceBridge { case ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER: return true; default: @@ -2724,7 +2727,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } } -@@ -307,7 +307,6 @@ public class WebsitePreferenceBridge { +@@ -337,7 +337,6 @@ public class WebsitePreferenceBridge { public static boolean isCategoryEnabled( BrowserContextHandle browserContextHandle, @ContentSettingsType.EnumType int contentSettingsType) { @@ -2811,7 +2814,7 @@ diff --git a/components/content_settings/android/BUILD.gn b/components/content_s diff --git a/components/content_settings/core/browser/BUILD.gn b/components/content_settings/core/browser/BUILD.gn --- a/components/content_settings/core/browser/BUILD.gn +++ b/components/content_settings/core/browser/BUILD.gn -@@ -84,6 +84,13 @@ static_library("browser") { +@@ -86,6 +86,13 @@ static_library("browser") { } configs += [ "//build/config/compiler:wexit_time_destructors" ] @@ -2842,7 +2845,7 @@ diff --git a/components/content_settings/core/browser/content_settings_registry. namespace content_settings { -@@ -858,6 +859,7 @@ void ContentSettingsRegistry::Init() { +@@ -859,6 +860,7 @@ void ContentSettingsRegistry::Init() { WebsiteSettingsRegistry::DESKTOP, ContentSettingsInfo::INHERIT_IN_INCOGNITO, ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY); @@ -2853,7 +2856,7 @@ diff --git a/components/content_settings/core/browser/content_settings_registry. diff --git a/components/content_settings/core/browser/content_settings_uma_util.cc b/components/content_settings/core/browser/content_settings_uma_util.cc --- a/components/content_settings/core/browser/content_settings_uma_util.cc +++ b/components/content_settings/core/browser/content_settings_uma_util.cc -@@ -212,11 +212,7 @@ void RecordContentSettingsHistogram(const std::string& name, +@@ -215,11 +215,7 @@ void RecordContentSettingsHistogram(const std::string& name, } int ContentSettingTypeToHistogramValue(ContentSettingsType content_setting) { @@ -3185,10 +3188,10 @@ diff --git a/components/content_settings/core/common/content_settings_mojom_trai diff --git a/components/content_settings/core/common/content_settings_types.mojom b/components/content_settings/core/common/content_settings_types.mojom --- a/components/content_settings/core/common/content_settings_types.mojom +++ b/components/content_settings/core/common/content_settings_types.mojom -@@ -475,5 +475,7 @@ enum ContentSettingsType { - - // Content settings for access to the Controlled Frame API. - CONTROLLED_FRAME, +@@ -479,5 +479,7 @@ enum ContentSettingsType { + // Website setting which is used for UnusedSitePermissionsService to + // store revoked notification permissions of disruptive sites. + REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS, + +#include "components/content_settings/core/common/bromite_content_settings.inc" }; @@ -3403,7 +3406,7 @@ diff --git a/components/page_info/page_info.cc b/components/page_info/page_info. #include "components/content_settings/browser/ui/cookie_controls_controller.h" #include "components/content_settings/core/browser/content_settings_registry.h" #include "components/content_settings/core/browser/content_settings_uma_util.h" -@@ -1267,6 +1268,14 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, +@@ -1258,6 +1259,14 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, // applies to permissions listed in |kPermissionType|. bool PageInfo::ShouldShowPermission( const PageInfo::PermissionInfo& info) const { @@ -3418,7 +3421,7 @@ diff --git a/components/page_info/page_info.cc b/components/page_info/page_info. // Note |ContentSettingsType::ADS| will show up regardless of its default // value when it has been activated on the current origin. if (info.type == ContentSettingsType::ADS) { -@@ -1383,7 +1392,19 @@ void PageInfo::PresentSitePermissions() { +@@ -1374,7 +1383,19 @@ void PageInfo::PresentSitePermissions() { HostContentSettingsMap* content_settings = GetContentSettings(); DCHECK(web_contents_); @@ -3464,7 +3467,7 @@ diff --git a/components/page_info/page_info_ui.cc b/components/page_info/page_in switch (type) { case ContentSettingsType::GEOLOCATION: message_id = IDS_PAGE_INFO_STATE_TEXT_LOCATION_ASK; -@@ -626,6 +634,12 @@ PageInfoUI::~PageInfoUI() = default; +@@ -628,6 +636,12 @@ PageInfoUI::~PageInfoUI() = default; // static std::u16string PageInfoUI::PermissionTypeToUIString(ContentSettingsType type) { @@ -3477,7 +3480,7 @@ diff --git a/components/page_info/page_info_ui.cc b/components/page_info/page_in for (const PermissionUIInfo& info : GetContentSettingsUIInfo()) { if (info.type == type) return l10n_util::GetStringUTF16(info.string_id); -@@ -636,6 +650,12 @@ std::u16string PageInfoUI::PermissionTypeToUIString(ContentSettingsType type) { +@@ -638,6 +652,12 @@ std::u16string PageInfoUI::PermissionTypeToUIString(ContentSettingsType type) { // static std::u16string PageInfoUI::PermissionTypeToUIStringMidSentence( ContentSettingsType type) { @@ -3490,7 +3493,7 @@ diff --git a/components/page_info/page_info_ui.cc b/components/page_info/page_in for (const PermissionUIInfo& info : GetContentSettingsUIInfo()) { if (info.type == type) return l10n_util::GetStringUTF16(info.string_id_mid_sentence); -@@ -1077,6 +1097,11 @@ bool PageInfoUI::ContentSettingsTypeInPageInfo(ContentSettingsType type) { +@@ -1079,6 +1099,11 @@ bool PageInfoUI::ContentSettingsTypeInPageInfo(ContentSettingsType type) { if (info.type == type) return true; } @@ -3571,15 +3574,15 @@ diff --git a/third_party/blink/renderer/core/execution_context/execution_context diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h --- a/third_party/blink/renderer/core/execution_context/execution_context.h +++ b/third_party/blink/renderer/core/execution_context/execution_context.h -@@ -44,6 +44,7 @@ - #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink-forward.h" +@@ -43,6 +43,7 @@ + #include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink-forward.h" #include "third_party/blink/public/mojom/permissions_policy/policy_disposition.mojom-blink-forward.h" #include "third_party/blink/public/mojom/v8_cache_options.mojom-blink-forward.h" +#include "third_party/blink/public/platform/web_content_settings_client.h" #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/execution_context/security_context.h" -@@ -103,6 +104,10 @@ class SecurityOrigin; +@@ -102,6 +103,10 @@ class SecurityOrigin; class ScriptState; class ScriptWrappable; class TrustedTypePolicyFactory; diff --git a/build/cromite_patches/Disable-Accessibility-service-by-default.patch b/build/cromite_patches/Disable-Accessibility-service-by-default.patch index 0fd469477ec5cf4d4919a71f971f47557b315c59..a5113b1bcb115a0276e1eece3d8ca91239c6f660 100644 --- a/build/cromite_patches/Disable-Accessibility-service-by-default.patch +++ b/build/cromite_patches/Disable-Accessibility-service-by-default.patch @@ -42,7 +42,7 @@ diff --git a/components/browser_ui/accessibility/android/java/res/xml/accessibil diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java --- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java -@@ -1095,6 +1095,11 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa +@@ -1100,6 +1100,11 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProviderCompa structure.setChildCount(0); return; } diff --git a/build/cromite_patches/Disable-Android-Tab-Declutter.patch b/build/cromite_patches/Disable-Android-Tab-Declutter.patch new file mode 100644 index 0000000000000000000000000000000000000000..2abdaf7815f29b18c0dd8209723fb9103a884b11 --- /dev/null +++ b/build/cromite_patches/Disable-Android-Tab-Declutter.patch @@ -0,0 +1,40 @@ +From: uazo +Date: Tue, 1 Apr 2025 11:37:52 +0000 +Subject: Disable Android Tab Declutter + +Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html +License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html +--- + .../org/chromium/chrome/browser/flags/ChromeFeatureList.java | 4 ++-- + .../chrome_feature_list_cc/Disable-Android-Tab-Declutter.inc | 1 + + 2 files changed, 3 insertions(+), 2 deletions(-) + create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-Android-Tab-Declutter.inc + +diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java ++++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +@@ -1033,7 +1033,7 @@ public abstract class ChromeFeatureList { + public static final MutableFlagWithSafeDefault sAndroidDumpOnScrollWithoutResource = + newMutableFlagWithSafeDefault(ANDROID_DUMP_ON_SCROLL_WITHOUT_RESOURCE, false); + public static final MutableFlagWithSafeDefault sAndroidTabDeclutter = +- newMutableFlagWithSafeDefault(ANDROID_TAB_DECLUTTER, true); ++ newMutableFlagWithSafeDefault(ANDROID_TAB_DECLUTTER, false); + public static final MutableFlagWithSafeDefault sAndroidTabDeclutterArchiveAllButActiveTab = + newMutableFlagWithSafeDefault(ANDROID_TAB_DECLUTTER_ARCHIVE_ALL_BUT_ACTIVE, false); + public static final MutableFlagWithSafeDefault sAndroidTabDeclutterArchiveDuplicateTabs = +@@ -1515,7 +1515,7 @@ public abstract class ChromeFeatureList { + public static final MutableBooleanParamWithSafeDefault sShouldBlockCapturesForFullscreenParam = + sSuppressionToolbarCaptures.newBooleanParam("block_for_fullscreen", false); + public static final MutableBooleanParamWithSafeDefault sAndroidTabDeclutterArchiveEnabled = +- sAndroidTabDeclutter.newBooleanParam("android_tab_declutter_archive_enabled", true); ++ sAndroidTabDeclutter.newBooleanParam("android_tab_declutter_archive_enabled", false); + public static final MutableIntParamWithSafeDefault sAndroidTabDeclutterArchiveTimeDeltaHours = + sAndroidTabDeclutter.newIntParam( + "android_tab_declutter_archive_time_delta_hours", 21 * 24); +diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-Android-Tab-Declutter.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-Android-Tab-Declutter.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-Android-Tab-Declutter.inc +@@ -0,0 +1 @@ ++SET_CROMITE_FEATURE_DISABLED(kAndroidTabDeclutter); +-- diff --git a/build/cromite_patches/Disable-Feeback-Collector.patch b/build/cromite_patches/Disable-Feeback-Collector.patch index c4dfdb08f5f3c5b124e7457c4beb66f972b2ce40..4777b90edd2dcd569a72ef6db785f9d45f25b358 100644 --- a/build/cromite_patches/Disable-Feeback-Collector.patch +++ b/build/cromite_patches/Disable-Feeback-Collector.patch @@ -29,7 +29,7 @@ diff --git a/chrome/browser/feedback/BUILD.gn b/chrome/browser/feedback/BUILD.gn diff --git a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java --- a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java +++ b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java -@@ -28,6 +28,7 @@ import org.chromium.components.signin.identitymanager.ConsentLevel; +@@ -29,6 +29,7 @@ import org.chromium.components.signin.identitymanager.ConsentLevel; import org.chromium.components.signin.identitymanager.IdentityManager; import java.util.HashMap; @@ -37,7 +37,7 @@ diff --git a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browse import java.util.List; import java.util.Map; -@@ -69,31 +70,8 @@ public abstract class FeedbackCollector implements Runnable { +@@ -73,31 +74,8 @@ public abstract class FeedbackCollector implements Runnable { @Nullable ScreenshotSource screenshotTask, T initParams, Profile profile) { diff --git a/build/cromite_patches/Disable-all-predictors-code.patch b/build/cromite_patches/Disable-all-predictors-code.patch index 76b61403ed43d39d4f59e038c105073e56c279a8..01ff5336cb3f6e4d809d2b330ad963cc6253c17f 100644 --- a/build/cromite_patches/Disable-all-predictors-code.patch +++ b/build/cromite_patches/Disable-all-predictors-code.patch @@ -16,6 +16,8 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../predictors/autocomplete_action_predictor.cc | 2 ++ .../predictors/loading_predictor_config.cc | 4 ++-- chrome/browser/predictors/predictors_features.cc | 3 +-- + .../search_prefetch/field_trial_settings.cc | 4 ++++ + .../search_prefetch_url_loader_interceptor.cc | 1 + .../chrome_browser_main_extra_parts_profiles.cc | 1 - chrome/browser/safe_browsing/BUILD.gn | 1 - ...fication_content_detection_service_factory.cc | 5 +++++ @@ -23,11 +25,12 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../read_anything/read_aloud_app_model.cc | 16 ++++++++++++++-- .../read_anything/read_aloud_app_model.h | 9 ++++++++- .../read_anything_app_controller.cc | 10 ++++++++++ - .../read_anything/read_anything_app_controller.h | 4 ++++ + .../read_anything/read_anything_app_controller.h | 6 ++++++ chrome/utility/BUILD.gn | 2 +- chrome/utility/services.cc | 8 -------- .../integrators/autofill_optimization_guide.cc | 5 +++-- .../omnibox/browser/autocomplete_controller.cc | 4 ++++ + .../omnibox/browser/base_search_provider.cc | 4 ++-- .../optimization_guide/core/hints_fetcher.cc | 1 + .../optimization_guide/core/hints_manager.cc | 5 +++++ .../core/optimization_guide_features.cc | 15 +++++++++------ @@ -38,28 +41,30 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../page_content_annotations_model_manager.cc | 1 + .../notification_content_detection/BUILD.gn | 7 +++++++ components/safe_browsing/core/common/features.cc | 1 + - .../Disable-all-predictors-code.inc | 3 +++ + .../Disable-all-predictors-code.inc | 2 ++ .../Disable-all-predictors-code.inc | 1 + .../Disable-all-predictors-code.inc | 1 + .../features_cc/Disable-all-predictors-code.inc | 3 +++ .../features_cc/Disable-all-predictors-code.inc | 1 + + .../features_cc/Disable-all-predictors-code.inc | 1 + .../features_cc/Disable-all-predictors-code.inc | 2 ++ services/webnn/features.gni | 2 +- .../core/html/parser/html_document_parser.cc | 1 + .../document_speculation_rules.cc | 1 + .../platform/runtime_enabled_features.json5 | 2 +- - 43 files changed, 114 insertions(+), 45 deletions(-) + 47 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 cromite_flags/chrome/browser/browser_features_cc/Disable-all-predictors-code.inc create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-all-predictors-code.inc create mode 100644 cromite_flags/chrome/common/chrome_features_cc/Disable-all-predictors-code.inc create mode 100644 cromite_flags/components/permissions/features_cc/Disable-all-predictors-code.inc + create mode 100644 cromite_flags/net/base/features_cc/Disable-all-predictors-code.inc create mode 100644 cromite_flags/services/network/public/cpp/features_cc/Disable-all-predictors-code.inc create mode 100644 cromite_flags/third_party/blink/common/features_cc/Disable-all-predictors-code.inc diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn -@@ -746,8 +746,6 @@ static_library("browser") { +@@ -755,8 +755,6 @@ static_library("browser") { "navigation_predictor/navigation_predictor_keyed_service_factory.h", "navigation_predictor/navigation_predictor_metrics_document_data.cc", "navigation_predictor/navigation_predictor_metrics_document_data.h", @@ -68,9 +73,9 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn "navigation_predictor/preloading_model_keyed_service.cc", "navigation_predictor/preloading_model_keyed_service.h", "navigation_predictor/preloading_model_keyed_service_factory.cc", -@@ -2261,7 +2259,7 @@ static_library("browser") { +@@ -2281,7 +2279,7 @@ static_library("browser") { + "//components/resources", "//components/safe_browsing/content/browser", - "//components/safe_browsing/content/browser:client_side_detection", "//components/safe_browsing/content/browser:safe_browsing_service", - "//components/safe_browsing/content/browser/notification_content_detection", + "//components/safe_browsing/content/browser/notification_content_detection:constants", @@ -180,7 +185,7 @@ diff --git a/chrome/browser/optimization_guide/model_validator_keyed_service.cc diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc -@@ -498,7 +498,6 @@ void OptimizationGuideKeyedService::RemoveObserverForOptimizationTargetModel( +@@ -500,7 +500,6 @@ void OptimizationGuideKeyedService::RemoveObserverForOptimizationTargetModel( void OptimizationGuideKeyedService::RegisterOptimizationTypes( const std::vector& optimization_types) { @@ -191,10 +196,10 @@ diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service. diff --git a/chrome/browser/permissions/BUILD.gn b/chrome/browser/permissions/BUILD.gn --- a/chrome/browser/permissions/BUILD.gn +++ b/chrome/browser/permissions/BUILD.gn -@@ -41,8 +41,6 @@ source_set("permissions") { - "permission_manager_factory.h", - "permission_revocation_request.cc", +@@ -40,8 +40,6 @@ source_set("permissions") { "permission_revocation_request.h", + "permissions_ai_handler.cc", + "permissions_ai_handler.h", - "prediction_based_permission_ui_selector.cc", - "prediction_based_permission_ui_selector.h", "prediction_model_handler_provider.cc", @@ -203,8 +208,8 @@ diff --git a/chrome/browser/permissions/BUILD.gn b/chrome/browser/permissions/BU diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc --- a/chrome/browser/permissions/chrome_permissions_client.cc +++ b/chrome/browser/permissions/chrome_permissions_client.cc -@@ -416,8 +416,6 @@ ChromePermissionsClient::CreatePermissionUiSelectors( - std::make_unique()); +@@ -418,8 +418,6 @@ ChromePermissionsClient::CreatePermissionUiSelectors( + #endif selectors.emplace_back(std::make_unique( Profile::FromBrowserContext(browser_context))); - selectors.emplace_back(std::make_unique( @@ -251,10 +256,32 @@ diff --git a/chrome/browser/predictors/predictors_features.cc b/chrome/browser/p } // namespace +diff --git a/chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.cc b/chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.cc +--- a/chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.cc ++++ b/chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.cc +@@ -146,3 +146,7 @@ const base::FeatureParam + kSuppressesSearchPrefetchOnSlowNetworkThreshold{ + &kSuppressesSearchPrefetchOnSlowNetwork, + "slow_network_threshold_for_search_prefetch", base::Milliseconds(208)}; ++ ++SET_CROMITE_FEATURE_DISABLED(kSearchPrefetchServicePrefetching); ++SET_CROMITE_FEATURE_DISABLED(kSearchPrefetchWithNoVarySearchDiskCache); ++SET_CROMITE_FEATURE_DISABLED(kSearchNavigationPrefetch); +diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc +--- a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc ++++ b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader_interceptor.cc +@@ -84,6 +84,7 @@ SearchPrefetchURLLoader::RequestHandler + SearchPrefetchURLLoaderInterceptor::MaybeCreateLoaderForRequest( + const network::ResourceRequest& tentative_resource_request, + content::FrameTreeNodeId frame_tree_node_id) { ++ if ((true)) return {}; + // Do not intercept non-main frame navigations. + if (!tentative_resource_request.is_outermost_main_frame) { + // Use the is_outermost_main_frame flag instead of obtaining the diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -@@ -1134,7 +1134,6 @@ void ChromeBrowserMainExtraPartsProfiles:: +@@ -1122,7 +1122,6 @@ void ChromeBrowserMainExtraPartsProfiles:: base::FeatureList::IsEnabled( permissions::features::kPermissionOnDeviceGeolocationPredictions) || base::FeatureList::IsEnabled(permissions::features::kPermissionsAIv1)) { @@ -265,9 +292,9 @@ diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn --- a/chrome/browser/safe_browsing/BUILD.gn +++ b/chrome/browser/safe_browsing/BUILD.gn -@@ -52,7 +52,6 @@ static_library("safe_browsing") { +@@ -49,7 +49,6 @@ static_library("safe_browsing") { + "//components/safe_browsing:buildflags", "//components/safe_browsing/content/browser", - "//components/safe_browsing/content/browser:client_side_detection", "//components/safe_browsing/content/browser:client_side_detection_service", - "//components/safe_browsing/content/browser/notification_content_detection:notification_content_detection", "//components/safe_browsing/content/browser/triggers:suspicious_site_trigger", @@ -276,7 +303,7 @@ diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsin diff --git a/chrome/browser/safe_browsing/notification_content_detection_service_factory.cc b/chrome/browser/safe_browsing/notification_content_detection_service_factory.cc --- a/chrome/browser/safe_browsing/notification_content_detection_service_factory.cc +++ b/chrome/browser/safe_browsing/notification_content_detection_service_factory.cc -@@ -14,6 +14,7 @@ +@@ -15,6 +15,7 @@ #include "components/safe_browsing/content/browser/notification_content_detection/notification_content_detection_service.h" #include "components/safe_browsing/core/browser/db/database_manager.h" #include "components/safe_browsing/core/common/features.h" @@ -284,15 +311,15 @@ diff --git a/chrome/browser/safe_browsing/notification_content_detection_service #include "content/public/browser/browser_context.h" namespace safe_browsing { -@@ -63,6 +64,7 @@ std::unique_ptr NotificationContentDetectionServiceFactory:: - return nullptr; - } - +@@ -85,6 +86,7 @@ NotificationContentDetectionServiceFactory:: + content::BrowserContext* context) const { + CHECK(opt_guide); + CHECK(context); +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) auto database_manager = g_browser_process->safe_browsing_service()->database_manager(); scoped_refptr background_task_runner = -@@ -70,6 +72,9 @@ std::unique_ptr NotificationContentDetectionServiceFactory:: +@@ -92,6 +94,9 @@ NotificationContentDetectionServiceFactory:: {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); return std::make_unique( opt_guide, background_task_runner, database_manager, context); @@ -305,7 +332,7 @@ diff --git a/chrome/browser/safe_browsing/notification_content_detection_service diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc -@@ -419,7 +419,6 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { +@@ -430,7 +430,6 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { } MixedContentSettingsTabHelper::CreateForWebContents(web_contents); NavigationMetricsRecorder::CreateForWebContents(web_contents); @@ -316,7 +343,7 @@ diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc --- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc +++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc -@@ -15,7 +15,9 @@ +@@ -16,7 +16,9 @@ #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/values.h" @@ -326,7 +353,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc #include "chrome/renderer/accessibility/phrase_segmentation/dependency_tree.h" #include "chrome/renderer/accessibility/phrase_segmentation/phrase_segmenter.h" #include "chrome/renderer/accessibility/phrase_segmentation/token_boundaries.h" -@@ -25,6 +27,7 @@ +@@ -26,6 +28,7 @@ namespace { @@ -334,7 +361,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc // Returns the dependency parser model for this renderer process. DependencyParserModel& GetDependencyParserModel_() { static base::NoDestructor instance; -@@ -37,6 +40,7 @@ std::vector GetDependencyHeads(base::span input) { +@@ -38,6 +41,7 @@ std::vector GetDependencyHeads(base::span input) { ? dependency_parser_model.GetDependencyHeads(input) : std::vector(); } @@ -342,7 +369,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc } // namespace -@@ -148,10 +152,13 @@ void ReadAloudAppModel::PreprocessTextForSpeech( +@@ -149,10 +153,13 @@ void ReadAloudAppModel::PreprocessTextForSpeech( } } @@ -356,7 +383,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc void ReadAloudAppModel::CalculatePhrases( a11y::ReadAloudCurrentGranularity& granularity) { if (!features::IsReadAnythingReadAloudPhraseHighlightingEnabled()) { -@@ -208,19 +215,23 @@ void ReadAloudAppModel::CalculatePhrases( +@@ -209,19 +216,23 @@ void ReadAloudAppModel::CalculatePhrases( base::BindOnce(&ReadAloudAppModel::UpdatePhraseBoundaries, weak_ptr_factory_.GetWeakPtr(), phrase_tokens)); } @@ -382,7 +409,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc // Reset the phrase calculation flag, so that the next phrase calculation can // be scheduled, if needed. is_calculating_phrases = false; -@@ -282,6 +293,7 @@ void ReadAloudAppModel::UpdatePhraseBoundaries(std::vector tokens, +@@ -283,6 +294,7 @@ void ReadAloudAppModel::UpdatePhraseBoundaries(std::vector tokens, current_phrase_calculation_index_ = -1; LOG(WARNING) << "All phrases calculated!"; } @@ -393,10 +420,10 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h --- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h +++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h -@@ -9,7 +9,10 @@ +@@ -8,7 +8,10 @@ + #include "base/metrics/single_sample_metrics.h" #include "base/values.h" #include "chrome/common/read_anything/read_anything.mojom.h" - #include "chrome/common/read_anything/read_anything_constants.h" +#include "components/optimization_guide/machine_learning_tflite_buildflags.h" +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) #include "chrome/renderer/accessibility/phrase_segmentation/dependency_parser_model.h" @@ -404,7 +431,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h #include "chrome/renderer/accessibility/read_anything/read_aloud_traversal_utils.h" #include "ui/accessibility/ax_node_position.h" -@@ -87,8 +90,10 @@ class ReadAloudAppModel { +@@ -112,8 +115,10 @@ class ReadAloudAppModel { bool is_docs, const std::set* current_nodes); @@ -415,7 +442,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h // Increments the processed_granularity_index_, updating ReadAloud's state of // the current granularity to refer to the next granularity. The current -@@ -242,9 +247,11 @@ class ReadAloudAppModel { +@@ -269,9 +274,11 @@ class ReadAloudAppModel { // still needs to be read. bool NoValidTextRemainingInCurrentNode(bool is_pdf, bool is_docs) const; @@ -427,7 +454,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h // Once the phrase segmentation has completed for a given sentence, update the // granularity with the phrase boundaries, and calculate phrases for the next -@@ -306,7 +313,7 @@ class ReadAloudAppModel { +@@ -333,7 +340,7 @@ class ReadAloudAppModel { // Whether a phrase calculation for a sentence is currently underway. (We // do not initiate a second calculation before the first has completed.) @@ -439,17 +466,17 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc --- a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc +++ b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc -@@ -26,7 +26,9 @@ +@@ -27,7 +27,9 @@ #include "base/strings/utf_string_conversions.h" - #include "chrome/common/read_anything/read_anything_constants.h" + #include "chrome/common/read_anything/read_anything_util.h" #include "chrome/renderer/accessibility/ax_tree_distiller.h" +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) #include "chrome/renderer/accessibility/phrase_segmentation/dependency_parser_model.h" +#endif #include "chrome/renderer/accessibility/read_anything/read_aloud_traversal_utils.h" + #include "chrome/renderer/accessibility/read_anything/read_anything_app_model.h" #include "chrome/renderer/accessibility/read_anything/read_anything_node_utils.h" - #include "components/language/core/common/locale_util.h" -@@ -1452,6 +1454,7 @@ void ReadAnythingAppController::OnConnected() { +@@ -1462,6 +1464,7 @@ void ReadAnythingAppController::OnConnected() { render_frame()->GetBrowserInterfaceBroker().GetInterface( std::move(page_handler_factory_receiver)); @@ -457,7 +484,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_contr // Get the dependency parser model used by phrase-based highlighting. if (read_aloud_model_.GetDependencyParserModel().IsAvailable()) { return; -@@ -1460,6 +1463,7 @@ void ReadAnythingAppController::OnConnected() { +@@ -1470,6 +1473,7 @@ void ReadAnythingAppController::OnConnected() { page_handler_->GetDependencyParserModel( base::BindOnce(&ReadAnythingAppController::UpdateDependencyParserModel, weak_ptr_factory_.GetWeakPtr())); @@ -465,7 +492,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_contr } void ReadAnythingAppController::OnCopy() const { -@@ -1669,11 +1673,13 @@ std::vector ReadAnythingAppController::GetCurrentText() { +@@ -1674,11 +1678,13 @@ std::vector ReadAnythingAppController::GetCurrentText() { } void ReadAnythingAppController::PreprocessTextForSpeech() { @@ -479,7 +506,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_contr } void ReadAnythingAppController::MovePositionToNextGranularity() { -@@ -1851,14 +1857,18 @@ bool ReadAnythingAppController::IsDocsLoadMoreButtonVisible() const { +@@ -1856,14 +1862,18 @@ bool ReadAnythingAppController::IsDocsLoadMoreButtonVisible() const { void ReadAnythingAppController::UpdateDependencyParserModel( base::File model_file) { @@ -511,7 +538,7 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_contr class ReadAnythingAppControllerTest; class ReadAnythingAppControllerScreen2xDataCollectionModeTest; -@@ -378,7 +380,9 @@ class ReadAnythingAppController +@@ -275,7 +277,9 @@ class ReadAnythingAppController // available. void UpdateDependencyParserModel(base::File model_file); @@ -519,6 +546,16 @@ diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_contr DependencyParserModel& GetDependencyParserModelForTesting(); +#endif + // Called when distillation has completed. + void OnAXTreeDistilled(const ui::AXTreeID& tree_id, +@@ -376,7 +380,9 @@ class ReadAnythingAppController + + // Helpers for logging UmaHistograms based on times recorded in WebUI. + void IncrementMetricCount(const std::string& metric); ++#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) + void LogSpeechEventCounts(); ++#endif + // Stores a screenshot of the page and triggers distillation to record protos. // This function is not used in production and is behind the disabled diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn @@ -536,7 +573,7 @@ diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn diff --git a/chrome/utility/services.cc b/chrome/utility/services.cc --- a/chrome/utility/services.cc +++ b/chrome/utility/services.cc -@@ -243,13 +243,6 @@ auto RunMirroringService( +@@ -245,13 +245,6 @@ auto RunMirroringService( std::move(receiver), content::UtilityThread::Get()->GetIOTaskRunner()); } @@ -550,7 +587,7 @@ diff --git a/chrome/utility/services.cc b/chrome/utility/services.cc #endif // !BUILDFLAG(IS_ANDROID) #if BUILDFLAG(ENABLE_BROWSER_SPEECH_SERVICE) -@@ -466,7 +459,6 @@ void RegisterMainThreadServices(mojo::ServiceFactory& services) { +@@ -470,7 +463,6 @@ void RegisterMainThreadServices(mojo::ServiceFactory& services) { #if !BUILDFLAG(IS_ANDROID) services.Add(RunProfileImporter); services.Add(RunMirroringService); @@ -561,7 +598,7 @@ diff --git a/chrome/utility/services.cc b/chrome/utility/services.cc diff --git a/components/autofill/core/browser/integrators/autofill_optimization_guide.cc b/components/autofill/core/browser/integrators/autofill_optimization_guide.cc --- a/components/autofill/core/browser/integrators/autofill_optimization_guide.cc +++ b/components/autofill/core/browser/integrators/autofill_optimization_guide.cc -@@ -229,6 +229,7 @@ void AutofillOptimizationGuide::OnDidParseForm( +@@ -258,6 +258,7 @@ void AutofillOptimizationGuide::OnDidParseForm( // If we do not have any optimization types to register, do not do anything. if (!optimization_types.empty()) { // Register all optimization types that we need based on `form_structure`. @@ -569,7 +606,7 @@ diff --git a/components/autofill/core/browser/integrators/autofill_optimization_ decider_->RegisterOptimizationTypes( std::vector( std::move(optimization_types).extract())); -@@ -280,7 +281,7 @@ bool AutofillOptimizationGuide::ShouldBlockSingleFieldSuggestions( +@@ -335,7 +336,7 @@ bool AutofillOptimizationGuide::ShouldBlockSingleFieldSuggestions( const AutofillField* field) const { // If the field's storable type is `IBAN_VALUE`, check whether IBAN // suggestions should be blocked based on `url`. @@ -578,7 +615,7 @@ diff --git a/components/autofill/core/browser/integrators/autofill_optimization_ optimization_guide::OptimizationGuideDecision decision = decider_->CanApplyOptimization( url, optimization_guide::proto::IBAN_AUTOFILL_BLOCKED, -@@ -306,7 +307,7 @@ bool AutofillOptimizationGuide::ShouldBlockFormFieldSuggestion( +@@ -361,7 +362,7 @@ bool AutofillOptimizationGuide::ShouldBlockFormFieldSuggestion( const CreditCard& card) const { if (auto optimization_type = GetVcnMerchantOptOutOptimizationTypeForCard(card); @@ -616,10 +653,30 @@ diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/ // Appends available autocompletion of the given type, subtype, and number to // the existing available autocompletions string, encoding according to the +diff --git a/components/omnibox/browser/base_search_provider.cc b/components/omnibox/browser/base_search_provider.cc +--- a/components/omnibox/browser/base_search_provider.cc ++++ b/components/omnibox/browser/base_search_provider.cc +@@ -99,14 +99,14 @@ BaseSearchProvider::BaseSearchProvider(AutocompleteProvider::Type type, + bool BaseSearchProvider::ShouldPrefetch(const AutocompleteMatch& match) { + // TODO (manukh): `GetAdditionalInfoForDebugging()` shouldn't be used for + // non-debugging purposes. +- return match.GetAdditionalInfoForDebugging(kShouldPrefetchKey) == kTrue; ++ return false; + } + + // static + bool BaseSearchProvider::ShouldPrerender(const AutocompleteMatch& match) { + // TODO (manukh): `GetAdditionalInfoForDebugging()` shouldn't be used for + // non-debugging purposes. +- return match.GetAdditionalInfoForDebugging(kShouldPrerenderKey) == kTrue; ++ return false; + } + + // static diff --git a/components/optimization_guide/core/hints_fetcher.cc b/components/optimization_guide/core/hints_fetcher.cc --- a/components/optimization_guide/core/hints_fetcher.cc +++ b/components/optimization_guide/core/hints_fetcher.cc -@@ -196,6 +196,7 @@ bool HintsFetcher::FetchOptimizationGuideServiceHints( +@@ -198,6 +198,7 @@ bool HintsFetcher::FetchOptimizationGuideServiceHints( bool skip_cache, HintsFetchedCallback hints_fetched_callback, std::optional request_context_metadata) { @@ -630,7 +687,7 @@ diff --git a/components/optimization_guide/core/hints_fetcher.cc b/components/op diff --git a/components/optimization_guide/core/hints_manager.cc b/components/optimization_guide/core/hints_manager.cc --- a/components/optimization_guide/core/hints_manager.cc +++ b/components/optimization_guide/core/hints_manager.cc -@@ -391,6 +391,7 @@ void HintsManager::Shutdown() { +@@ -393,6 +393,7 @@ void HintsManager::Shutdown() { OptimizationGuideDecision HintsManager::GetOptimizationGuideDecisionFromOptimizationTypeDecision( OptimizationTypeDecision optimization_type_decision) { @@ -638,7 +695,7 @@ diff --git a/components/optimization_guide/core/hints_manager.cc b/components/op switch (optimization_type_decision) { case OptimizationTypeDecision::kAllowedByOptimizationFilter: case OptimizationTypeDecision::kAllowedByHint: -@@ -1100,6 +1101,7 @@ void HintsManager::CanApplyOptimizationOnDemand( +@@ -1102,6 +1103,7 @@ void HintsManager::CanApplyOptimizationOnDemand( OnDemandOptimizationGuideDecisionRepeatingCallback callback, std::optional request_context_metadata) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -646,7 +703,7 @@ diff --git a/components/optimization_guide/core/hints_manager.cc b/components/op InsertionOrderedSet urls_to_fetch; InsertionOrderedSet hosts_to_fetch; -@@ -1403,6 +1405,9 @@ OptimizationTypeDecision HintsManager::CanApplyOptimization( +@@ -1405,6 +1407,9 @@ OptimizationTypeDecision HintsManager::CanApplyOptimization( bool skip_cache, OptimizationMetadata* optimization_metadata) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -799,10 +856,10 @@ diff --git a/components/safe_browsing/content/browser/notification_content_detec diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc --- a/components/safe_browsing/core/common/features.cc +++ b/components/safe_browsing/core/common/features.cc -@@ -233,6 +233,7 @@ BASE_FEATURE(kLocalListsUseSBv5, +@@ -250,6 +250,7 @@ constexpr base::FeatureParam kMaliciousApkDownloadCheckTelemetryOnly{ BASE_FEATURE(kOnDeviceNotificationContentDetectionModel, "OnDeviceNotificationContentDetectionModel", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); +SET_CROMITE_FEATURE_DISABLED(kOnDeviceNotificationContentDetectionModel); constexpr base::FeatureParam @@ -811,10 +868,9 @@ diff --git a/cromite_flags/chrome/browser/browser_features_cc/Disable-all-predic new file mode 100644 --- /dev/null +++ b/cromite_flags/chrome/browser/browser_features_cc/Disable-all-predictors-code.inc -@@ -0,0 +1,3 @@ +@@ -0,0 +1,2 @@ +SET_CROMITE_FEATURE_DISABLED(kBookmarkTriggerForPrerender2); +SET_CROMITE_FEATURE_DISABLED(kNewTabPageTriggerForPrerender2); -+SET_CROMITE_FEATURE_DISABLED(kSupportSearchSuggestionForPrerender2); diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-all-predictors-code.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Disable-all-predictors-code.inc new file mode 100644 --- /dev/null @@ -835,6 +891,12 @@ new file mode 100644 +SET_CROMITE_FEATURE_DISABLED(kPermissionOnDeviceNotificationPredictions); +SET_CROMITE_FEATURE_DISABLED(kPermissionOnDeviceGeolocationPredictions); +SET_CROMITE_FEATURE_DISABLED(kPermissionPredictionsV2); +diff --git a/cromite_flags/net/base/features_cc/Disable-all-predictors-code.inc b/cromite_flags/net/base/features_cc/Disable-all-predictors-code.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/net/base/features_cc/Disable-all-predictors-code.inc +@@ -0,0 +1 @@ ++SET_CROMITE_FEATURE_DISABLED(kHttpCacheNoVarySearch); diff --git a/cromite_flags/services/network/public/cpp/features_cc/Disable-all-predictors-code.inc b/cromite_flags/services/network/public/cpp/features_cc/Disable-all-predictors-code.inc new file mode 100644 --- /dev/null @@ -851,19 +913,19 @@ new file mode 100644 diff --git a/services/webnn/features.gni b/services/webnn/features.gni --- a/services/webnn/features.gni +++ b/services/webnn/features.gni -@@ -5,7 +5,7 @@ - import("//build/config/features.gni") +@@ -7,7 +7,7 @@ import("//services/on_device_model/on_device_model.gni") declare_args() { -- webnn_use_tflite = is_android || is_chromeos || is_linux || is_win -+ webnn_use_tflite = false #is_android || is_chromeos || is_linux || is_win + # TFLite is used as a fallback option on macOS and Windows. +- webnn_use_tflite = is_android || is_chromeos || is_linux || is_mac || is_win ++ webnn_use_tflite = false #is_android || is_chromeos || is_linux || is_mac || is_win - # Enable logging of TFLite profiling information on MLGraph destruction. - webnn_enable_tflite_profiler = false + # Enable the GPU delegate provided by the Optimization Guide library. + webnn_use_chrome_ml_api = enable_ml_internal diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc --- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc +++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc -@@ -1757,6 +1757,7 @@ ALWAYS_INLINE bool HTMLDocumentParser::ShouldCheckTimeBudget( +@@ -1752,6 +1752,7 @@ ALWAYS_INLINE bool HTMLDocumentParser::ShouldCheckTimeBudget( } bool HTMLDocumentParser::ShouldSkipPreloadScan() { @@ -885,7 +947,7 @@ diff --git a/third_party/blink/renderer/core/speculation_rules/document_speculat diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -3401,7 +3401,7 @@ +@@ -3440,7 +3440,7 @@ // // It also has some feature params defined throughout the codebase. name: "Prerender2", diff --git a/build/cromite_patches/Disable-all-promo-dialogs.patch b/build/cromite_patches/Disable-all-promo-dialogs.patch index cb8cc8061b2ab7120ab65ceac2a32053804b3192..b1a676b05813cb8bb5b96455acd5f61461615e5f 100644 --- a/build/cromite_patches/Disable-all-promo-dialogs.patch +++ b/build/cromite_patches/Disable-all-promo-dialogs.patch @@ -17,7 +17,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java -@@ -1511,6 +1511,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -1597,6 +1597,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { } private boolean maybeShowPromo(Profile profile) { diff --git a/build/cromite_patches/Disable-conversion-measurement-api.patch b/build/cromite_patches/Disable-conversion-measurement-api.patch index f866c11628ab828281c3218e7d76d5833e061763..c6ed28b0c48b821ee41ec578e3e41ea5126d49b2 100644 --- a/build/cromite_patches/Disable-conversion-measurement-api.patch +++ b/build/cromite_patches/Disable-conversion-measurement-api.patch @@ -38,8 +38,8 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../browser/AttributionOsLevelManager.java | 349 +----------------- .../public/browser/content_browser_client.cc | 7 +- .../public/browser/navigation_controller.cc | 1 - - .../Disable-conversion-measurement-api.inc | 1 + - .../Disable-conversion-measurement-api.inc | 10 + + .../Disable-conversion-measurement-api.inc | 2 + + .../Disable-conversion-measurement-api.inc | 8 + .../attribution/request_headers_internal.cc | 1 + services/network/network_context.cc | 20 - .../network/public/cpp/attribution_utils.cc | 2 + @@ -47,7 +47,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html third_party/blink/renderer/core/page/page.cc | 2 +- .../platform/runtime_enabled_features.json5 | 20 +- ui/events/android/motion_event_android.cc | 8 +- - 36 files changed, 101 insertions(+), 430 deletions(-) + 36 files changed, 100 insertions(+), 430 deletions(-) create mode 100644 cromite_flags/services/network/public/cpp/features_cc/Disable-conversion-measurement-api.inc create mode 100644 cromite_flags/third_party/blink/common/features_cc/Disable-conversion-measurement-api.inc @@ -127,7 +127,7 @@ diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/Andro diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java -@@ -151,6 +151,8 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { +@@ -150,6 +150,8 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate { // not great to dynamically remove the preference in this way. case SiteSettingsCategory.Type.ADS: return SiteSettingsCategory.adsCategoryEnabled(); @@ -151,7 +151,7 @@ diff --git a/chrome/browser/k_anonymity_service/k_anonymity_trust_token_getter.c diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html --- a/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html -@@ -635,11 +635,6 @@ +@@ -674,11 +674,6 @@ @@ -210,7 +210,7 @@ diff --git a/components/attribution_reporting/features.cc b/components/attributi diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc --- a/components/content_settings/core/browser/content_settings_registry.cc +++ b/components/content_settings/core/browser/content_settings_registry.cc -@@ -689,7 +689,7 @@ void ContentSettingsRegistry::Init() { +@@ -690,7 +690,7 @@ void ContentSettingsRegistry::Init() { ContentSettingsInfo::INHERIT_IN_INCOGNITO, ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY); @@ -275,7 +275,7 @@ diff --git a/content/browser/aggregation_service/aggregatable_report_sender.cc b diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc --- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc +++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc -@@ -758,11 +758,9 @@ class AttributionDataHostManagerImpl::PendingRegistrationData { +@@ -797,11 +797,9 @@ class AttributionDataHostManagerImpl::PendingRegistrationData { headers->GetNormalizedHeader( attribution_reporting::kAttributionReportingRegisterTriggerHeader); @@ -289,7 +289,7 @@ diff --git a/content/browser/attribution_reporting/attribution_data_host_manager const bool has_source = web_source_header.has_value() || os_source_header.has_value(); -@@ -1121,6 +1119,8 @@ void AttributionDataHostManagerImpl::ParseHeader( +@@ -1160,6 +1158,8 @@ void AttributionDataHostManagerImpl::ParseHeader( Registrations& registrations, HeaderPendingDecode pending_decode, Registrar registrar) { @@ -310,7 +310,7 @@ diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content #endif } -@@ -318,6 +316,7 @@ void AttributionHost::NotifyNavigationRegistrationData( +@@ -332,6 +330,7 @@ void AttributionHost::NotifyNavigationRegistrationData( return; } @@ -321,7 +321,7 @@ diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc --- a/content/browser/attribution_reporting/attribution_manager_impl.cc +++ b/content/browser/attribution_reporting/attribution_manager_impl.cc -@@ -573,11 +573,7 @@ bool IsOperationAllowed( +@@ -552,11 +552,7 @@ bool IsOperationAllowed( } std::unique_ptr CreateOsLevelManager() { @@ -382,7 +382,7 @@ diff --git a/content/browser/attribution_reporting/attribution_report_network_se diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc --- a/content/browser/attribution_reporting/attribution_storage_sql.cc +++ b/content/browser/attribution_reporting/attribution_storage_sql.cc -@@ -532,6 +532,8 @@ void AssignSourceForDeactivationOrDeletion( +@@ -533,6 +533,8 @@ void AssignSourceForDeactivationOrDeletion( } } @@ -391,7 +391,7 @@ diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/ } // namespace // static -@@ -554,9 +556,9 @@ bool AttributionStorageSql::Transaction::Commit() { +@@ -555,9 +557,9 @@ bool AttributionStorageSql::Transaction::Commit() { AttributionStorageSql::AttributionStorageSql( const base::FilePath& user_data_directory, AttributionResolverDelegate* delegate) @@ -407,7 +407,7 @@ diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/ 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 -@@ -10241,6 +10241,7 @@ bool RenderFrameHostImpl::IsFencedFrameReportingFromRendererAllowed( +@@ -10254,6 +10254,7 @@ bool RenderFrameHostImpl::IsFencedFrameReportingFromRendererAllowed( return false; } @@ -415,7 +415,7 @@ diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/b if (!IsActive()) { // reportEvent is not allowed when this RenderFrameHost or one of its // ancestors is not active. -@@ -14045,6 +14046,16 @@ void RenderFrameHostImpl::BindTrustTokenQueryAnswerer( +@@ -14093,6 +14094,16 @@ void RenderFrameHostImpl::BindTrustTokenQueryAnswerer( return; } @@ -448,7 +448,7 @@ diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage - this, path, special_storage_policy_); - } - - if (base::FeatureList::IsEnabled(blink::features::kInterestGroupStorage)) { + if (base::FeatureList::IsEnabled(network::features::kInterestGroupStorage)) { // Auction worklets on non-Android use dedicated processes; on Android due // to high cost of process launch they try to reuse renderers. @@ -1542,9 +1533,6 @@ void StoragePartitionImpl::Initialize( @@ -986,23 +986,22 @@ diff --git a/cromite_flags/services/network/public/cpp/features_cc/Disable-conve new file mode 100644 --- /dev/null +++ b/cromite_flags/services/network/public/cpp/features_cc/Disable-conversion-measurement-api.inc -@@ -0,0 +1 @@ -+// none +@@ -0,0 +1,2 @@ ++SET_CROMITE_FEATURE_DISABLED(kBrowsingTopics); ++SET_CROMITE_FEATURE_DISABLED(kInterestGroupStorage); diff --git a/cromite_flags/third_party/blink/common/features_cc/Disable-conversion-measurement-api.inc b/cromite_flags/third_party/blink/common/features_cc/Disable-conversion-measurement-api.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/third_party/blink/common/features_cc/Disable-conversion-measurement-api.inc -@@ -0,0 +1,10 @@ +@@ -0,0 +1,8 @@ +SET_CROMITE_FEATURE_DISABLED(kAdAuctionSignals); -+SET_CROMITE_FEATURE_DISABLED(kBrowsingTopics); +SET_CROMITE_FEATURE_DISABLED(kBrowsingTopicsDocumentAPI); +SET_CROMITE_FEATURE_DISABLED(kBrowsingTopicsParameters); +SET_CROMITE_FEATURE_DISABLED(kFledge); +SET_CROMITE_FEATURE_DISABLED(kFledgeBiddingAndAuctionServer); -+SET_CROMITE_FEATURE_DISABLED(kInterestGroupStorage); +SET_CROMITE_FEATURE_DISABLED(kPrivateAggregationApi); -+SET_CROMITE_FEATURE_DISABLED(kBiddingAndScoringDebugReportingAPI); +SET_CROMITE_FEATURE_DISABLED(kAllowURNsInIframes); ++SET_CROMITE_FEATURE_DISABLED(kFledgeSampleDebugReports); diff --git a/services/network/attribution/request_headers_internal.cc b/services/network/attribution/request_headers_internal.cc --- a/services/network/attribution/request_headers_internal.cc +++ b/services/network/attribution/request_headers_internal.cc @@ -1017,7 +1016,7 @@ diff --git a/services/network/attribution/request_headers_internal.cc b/services diff --git a/services/network/network_context.cc b/services/network/network_context.cc --- a/services/network/network_context.cc +++ b/services/network/network_context.cc -@@ -2637,26 +2637,6 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext( +@@ -2683,26 +2683,6 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext( builder.set_network_quality_estimator( network_service_->network_quality_estimator()); } @@ -1078,7 +1077,7 @@ diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/t diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc --- a/third_party/blink/renderer/core/page/page.cc +++ b/third_party/blink/renderer/core/page/page.cc -@@ -1575,7 +1575,7 @@ void Page::UpdateBrowsingContextGroup( +@@ -1623,7 +1623,7 @@ void Page::UpdateBrowsingContextGroup( void Page::SetAttributionSupport( network::mojom::AttributionSupport attribution_support) { @@ -1101,7 +1100,7 @@ diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 origin_trial_feature_name: "AdInterestGroupAPI", implied_by: ["Fledge", "Parakeet"], public: true, -@@ -464,8 +464,8 @@ +@@ -458,8 +458,8 @@ name: "AtomicMoveRangePreservation", }, { @@ -1112,7 +1111,7 @@ diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 base_feature: "none", public: true, }, -@@ -3445,8 +3445,8 @@ +@@ -3482,8 +3482,8 @@ base_feature: "none", }, { @@ -1123,7 +1122,7 @@ diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 base_feature: "none", public: true, }, -@@ -4356,16 +4356,16 @@ +@@ -4450,16 +4450,16 @@ status: "experimental", }, { diff --git a/build/cromite_patches/Disable-crash-reporting.patch b/build/cromite_patches/Disable-crash-reporting.patch index 617259c9b343e0ec253aff5f05349e33d1b576bf..9a1854d5a545ac49dae65efbf65100ef330fc5e4 100644 --- a/build/cromite_patches/Disable-crash-reporting.patch +++ b/build/cromite_patches/Disable-crash-reporting.patch @@ -12,7 +12,7 @@ Subject: Disable crash reporting diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc -@@ -2708,15 +2708,6 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches( +@@ -2716,15 +2716,6 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches( base::NumberToString(pid)); } #endif @@ -31,7 +31,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch 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 -@@ -15650,6 +15650,7 @@ void RenderFrameHostImpl::OnSameDocumentCommitProcessed( +@@ -15707,6 +15707,7 @@ void RenderFrameHostImpl::OnSameDocumentCommitProcessed( void RenderFrameHostImpl::MaybeGenerateCrashReport( base::TerminationStatus status, int exit_code) { @@ -42,7 +42,7 @@ diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/b diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc -@@ -9851,7 +9851,7 @@ void WebContentsImpl::RendererUnresponsive( +@@ -9924,7 +9924,7 @@ void WebContentsImpl::RendererUnresponsive( base::UmaHistogramEnumeration( "ReportingAndNEL.UnresponsiveRenderer.CrashReportOutcome", outcome); @@ -54,7 +54,7 @@ diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser diff --git a/third_party/blink/renderer/controller/javascript_call_stack_collector.cc b/third_party/blink/renderer/controller/javascript_call_stack_collector.cc --- a/third_party/blink/renderer/controller/javascript_call_stack_collector.cc +++ b/third_party/blink/renderer/controller/javascript_call_stack_collector.cc -@@ -84,7 +84,7 @@ void GenerateJavaScriptCallStack(v8::Isolate* isolate, void* data) { +@@ -130,7 +130,7 @@ void GenerateJavaScriptCallStack(v8::Isolate* isolate, void* data) { return; } ExecutionContext* execution_context = ToExecutionContext(script_state); diff --git a/build/cromite_patches/Disable-fetching-of-all-field-trials.patch b/build/cromite_patches/Disable-fetching-of-all-field-trials.patch index 453cc3e3517cee3a46eb3cfffd6a789d375ed192..98b6357afc60eb5ba9245679683009df4571e1fe 100644 --- a/build/cromite_patches/Disable-fetching-of-all-field-trials.patch +++ b/build/cromite_patches/Disable-fetching-of-all-field-trials.patch @@ -39,7 +39,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc --- a/android_webview/browser/aw_browser_context.cc +++ b/android_webview/browser/aw_browser_context.cc -@@ -537,6 +537,7 @@ AwBrowserContext::RetrieveInProgressDownloadManager() { +@@ -489,6 +489,7 @@ AwBrowserContext::RetrieveInProgressDownloadManager() { content::OriginTrialsControllerDelegate* AwBrowserContext::GetOriginTrialsControllerDelegate() { @@ -136,7 +136,7 @@ diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/s diff --git a/chrome/browser/metrics/chrome_browser_sampling_trials.cc b/chrome/browser/metrics/chrome_browser_sampling_trials.cc --- a/chrome/browser/metrics/chrome_browser_sampling_trials.cc +++ b/chrome/browser/metrics/chrome_browser_sampling_trials.cc -@@ -60,6 +60,8 @@ void CreateFallbackSamplingTrial( +@@ -66,6 +66,8 @@ void CreateFallbackSamplingTrial( const int reporting_full_rate_per_mille, const bool starts_active, base::FeatureList* feature_list) { @@ -145,7 +145,7 @@ diff --git a/chrome/browser/metrics/chrome_browser_sampling_trials.cc b/chrome/b scoped_refptr trial( base::FieldTrialList::FactoryGetFieldTrial( trial_name, /*total_probability=*/1000, "Default", entropy_provider)); -@@ -111,6 +113,8 @@ void CreateFallbackUkmSamplingTrial( +@@ -118,6 +120,8 @@ void CreateFallbackUkmSamplingTrial( const base::FieldTrial::EntropyProvider& entropy_provider, bool is_stable_channel, base::FeatureList* feature_list) { @@ -157,7 +157,7 @@ diff --git a/chrome/browser/metrics/chrome_browser_sampling_trials.cc b/chrome/b diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc --- a/chrome/browser/net/system_network_context_manager.cc +++ b/chrome/browser/net/system_network_context_manager.cc -@@ -848,7 +848,6 @@ void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams( +@@ -847,7 +847,6 @@ void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams( void SystemNetworkContextManager::ConfigureDefaultNetworkContextParams( network::mojom::NetworkContextParams* network_context_params) { @@ -179,7 +179,7 @@ diff --git a/chrome/browser/origin_trials/origin_trials_factory.cc b/chrome/brow diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc --- a/components/ukm/ukm_recorder_impl.cc +++ b/components/ukm/ukm_recorder_impl.cc -@@ -189,6 +189,9 @@ std::string WebDXFeaturesToStringForDebug(const std::set& features) { +@@ -192,6 +192,9 @@ std::string WebDXFeaturesToStringForDebug(const std::set& features) { UkmRecorderImpl::UkmRecorderImpl() : sampling_seed_(static_cast(base::RandUint64())) { @@ -284,7 +284,7 @@ diff --git a/components/variations/net/variations_http_headers.cc b/components/v diff --git a/components/variations/service/variations_field_trial_creator_base.cc b/components/variations/service/variations_field_trial_creator_base.cc --- a/components/variations/service/variations_field_trial_creator_base.cc +++ b/components/variations/service/variations_field_trial_creator_base.cc -@@ -130,6 +130,7 @@ Study::CpuArchitecture GetCurrentCpuArchitecture() { +@@ -132,6 +132,7 @@ Study::CpuArchitecture GetCurrentCpuArchitecture() { } #if BUILDFLAG(FIELDTRIAL_TESTING_ENABLED) @@ -292,7 +292,7 @@ diff --git a/components/variations/service/variations_field_trial_creator_base.c // Determines whether the field trial testing config defined in // testing/variations/fieldtrial_testing_config.json should be applied. If the // "disable_fieldtrial_testing_config" GN flag is set to true, then the testing -@@ -337,9 +338,6 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials( +@@ -339,9 +340,6 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials( synthetic_trial_registry, std::move(client_filterable_state)); } @@ -302,7 +302,7 @@ diff --git a/components/variations/service/variations_field_trial_creator_base.c platform_field_trials->RegisterFeatureOverrides(feature_list.get()); base::FeatureList::SetInstance(std::move(feature_list)); -@@ -651,6 +649,7 @@ bool VariationsFieldTrialCreatorBase::CreateTrialsFromSeed( +@@ -653,6 +651,7 @@ bool VariationsFieldTrialCreatorBase::CreateTrialsFromSeed( SafeSeedManagerBase* safe_seed_manager, SyntheticTrialRegistry* synthetic_trial_registry, std::unique_ptr client_state) { @@ -310,7 +310,7 @@ diff --git a/components/variations/service/variations_field_trial_creator_base.c // This histogram name uses "VariationsFieldTrialCreator" rather than // "VariationsFieldTrialCreatorBase" for consistency with historical data TRACE_EVENT0("startup", "VariationsFieldTrialCreator::CreateTrialsFromSeed"); -@@ -763,6 +762,7 @@ bool VariationsFieldTrialCreatorBase::CreateTrialsFromSeed( +@@ -765,6 +764,7 @@ bool VariationsFieldTrialCreatorBase::CreateTrialsFromSeed( void VariationsFieldTrialCreatorBase::LoadSeedFromJsonFile( const base::FilePath& json_seed_path) { @@ -362,7 +362,7 @@ diff --git a/content/browser/loader/url_loader_throttles.cc b/content/browser/lo ClientHintsControllerDelegate* client_hint_delegate = browser_context->GetClientHintsControllerDelegate(); -@@ -141,11 +139,6 @@ CreateContentBrowserURLLoaderThrottlesForKeepAlive( +@@ -143,11 +141,6 @@ CreateContentBrowserURLLoaderThrottlesForKeepAlive( std::vector> throttles = GetContentClient()->browser()->CreateURLLoaderThrottlesForKeepAlive( request, browser_context, wc_getter, frame_tree_node_id); @@ -389,7 +389,7 @@ diff --git a/content/browser/origin_trials/origin_trials_utils.cc b/content/brow diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc --- a/content/browser/renderer_host/navigation_request.cc +++ b/content/browser/renderer_host/navigation_request.cc -@@ -836,6 +836,7 @@ void PersistOriginTrialsFromHeaders( +@@ -832,6 +832,7 @@ void PersistOriginTrialsFromHeaders( const network::mojom::URLResponseHead* response, BrowserContext* browser_context, ukm::SourceId source_id) { @@ -450,7 +450,7 @@ diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_p diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_request.h b/third_party/blink/renderer/platform/loader/fetch/resource_request.h --- a/third_party/blink/renderer/platform/loader/fetch/resource_request.h +++ b/third_party/blink/renderer/platform/loader/fetch/resource_request.h -@@ -553,7 +553,6 @@ class PLATFORM_EXPORT ResourceRequestHead { +@@ -556,7 +556,6 @@ class PLATFORM_EXPORT ResourceRequestHead { } void SetTrustTokenParams( std::optional params) { diff --git a/build/cromite_patches/Disable-idle-detection.patch b/build/cromite_patches/Disable-idle-detection.patch index ec0dbe716750b9f1351ca4ac3f99115f12c140cb..e609650c1cd7c03dad867535bd9aa30ba1017042 100644 --- a/build/cromite_patches/Disable-idle-detection.patch +++ b/build/cromite_patches/Disable-idle-detection.patch @@ -6,7 +6,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- .../common/content_features_cc/Disable-idle-detection.inc | 4 ++++ .../common/content_features_h/Disable-idle-detection.inc | 1 + - .../permissions_policy/permissions_policy_features.json5 | 1 + + .../cpp/permissions_policy/permissions_policy_features.json5 | 1 + third_party/blink/renderer/modules/idle/idle_detector.idl | 3 ++- .../blink/renderer/platform/runtime_enabled_features.json5 | 5 +++++ 5 files changed, 13 insertions(+), 1 deletion(-) @@ -28,10 +28,10 @@ new file mode 100644 +++ b/cromite_flags/content/public/common/content_features_h/Disable-idle-detection.inc @@ -0,0 +1 @@ +CONTENT_EXPORT BASE_DECLARE_FEATURE(kIdleDetection); -diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 ---- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 -+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 -@@ -339,6 +339,7 @@ +diff --git a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 +--- a/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 ++++ b/services/network/public/cpp/permissions_policy/permissions_policy_features.json5 +@@ -359,6 +359,7 @@ { name: "IdleDetection", permissions_policy_name: "idle-detection", diff --git a/build/cromite_patches/Disable-media-router-and-remoting-by-default.patch b/build/cromite_patches/Disable-media-router-and-remoting-by-default.patch index c541be81cd24c3d64979fcc0ca23d93e16e0641c..83b264d9ded0f1007c2245f275042a645e46cbcb 100644 --- a/build/cromite_patches/Disable-media-router-and-remoting-by-default.patch +++ b/build/cromite_patches/Disable-media-router-and-remoting-by-default.patch @@ -15,7 +15,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc --- a/chrome/browser/media/router/media_router_feature.cc +++ b/chrome/browser/media/router/media_router_feature.cc -@@ -111,6 +111,7 @@ void ClearMediaRouterStoredPrefsForTesting() { +@@ -107,6 +107,7 @@ void ClearMediaRouterStoredPrefsForTesting() { } bool MediaRouterEnabled(content::BrowserContext* context) { @@ -23,7 +23,7 @@ diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browse #if !BUILDFLAG(IS_ANDROID) if (!base::FeatureList::IsEnabled(kMediaRouter)) { return false; -@@ -157,7 +158,7 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) { +@@ -153,7 +154,7 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterStringPref(prefs::kMediaRouterReceiverIdHashToken, "", PrefRegistry::PUBLIC); registry->RegisterBooleanPref( @@ -32,7 +32,7 @@ diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browse registry->RegisterBooleanPref( media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices, true); -@@ -219,4 +220,7 @@ bool IsCastMessageLoggingEnabled() { +@@ -215,4 +216,7 @@ bool IsCastMessageLoggingEnabled() { } #endif // !BUILDFLAG(IS_ANDROID) @@ -55,7 +55,7 @@ diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/p diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc -@@ -542,6 +542,8 @@ void SetCustomizedRuntimeFeaturesFromCombinedArgs( +@@ -549,6 +549,8 @@ void SetCustomizedRuntimeFeaturesFromCombinedArgs( WebRuntimeFeatures::EnableV8IdleTasks(true); } @@ -79,7 +79,7 @@ diff --git a/third_party/blink/renderer/modules/remoteplayback/remote_playback.c diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -3558,14 +3558,10 @@ +@@ -3602,14 +3602,10 @@ base_feature: "none", }, { diff --git a/build/cromite_patches/Disable-minidump-upload-scheduling.patch b/build/cromite_patches/Disable-minidump-upload-scheduling.patch index dc8b3508dc4e9e94e41d2910563de729582daddd..8adb69cc555a8dba0d5fac55751adef1876f0a0c 100644 --- a/build/cromite_patches/Disable-minidump-upload-scheduling.patch +++ b/build/cromite_patches/Disable-minidump-upload-scheduling.patch @@ -11,7 +11,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/components/endpoint_fetcher/endpoint_fetcher.cc b/components/endpoint_fetcher/endpoint_fetcher.cc --- a/components/endpoint_fetcher/endpoint_fetcher.cc +++ b/components/endpoint_fetcher/endpoint_fetcher.cc -@@ -388,7 +388,7 @@ void EndpointFetcher::OnResponseFetched( +@@ -442,7 +442,7 @@ void EndpointFetcher::OnResponseFetched( } } else { std::string net_error = net::ErrorToString(net_error_code); diff --git a/build/cromite_patches/Disable-omission-of-URL-elements.patch b/build/cromite_patches/Disable-omission-of-URL-elements.patch index 2b8b8d778e8d5495418d635c09d7142b4e677059..cc75e02782667ac4640b1d2aa8c4f998a4480463 100644 --- a/build/cromite_patches/Disable-omission-of-URL-elements.patch +++ b/build/cromite_patches/Disable-omission-of-URL-elements.patch @@ -18,7 +18,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java --- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java +++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java -@@ -269,9 +269,7 @@ class ShareSheetBottomSheetContent implements BottomSheetContent, OnItemClickLis +@@ -270,9 +270,7 @@ class ShareSheetBottomSheetContent implements BottomSheetContent, OnItemClickLis ShareSheetLinkToggleCoordinator shareSheetLinkToggleCoordinator) { // Default preview is to show title + url. String title = mParams.getTitle(); @@ -32,7 +32,7 @@ diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/s diff --git a/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabSheetContent.java b/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabSheetContent.java --- a/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabSheetContent.java +++ b/chrome/browser/ui/android/ephemeraltab/java/src/org/chromium/chrome/browser/ephemeraltab/EphemeralTabSheetContent.java -@@ -235,8 +235,7 @@ public class EphemeralTabSheetContent implements BottomSheetContent { +@@ -236,8 +236,7 @@ public class EphemeralTabSheetContent implements BottomSheetContent { /** Sets the ephemeral tab URL. */ public void updateURL(GURL url) { TextView originView = mToolbarView.findViewById(R.id.origin); @@ -55,7 +55,7 @@ diff --git a/chrome/browser/ui/toolbar/chrome_location_bar_model_delegate.cc b/c diff --git a/chrome/browser/ui/views/page_info/page_info_main_view.cc b/chrome/browser/ui/views/page_info/page_info_main_view.cc --- a/chrome/browser/ui/views/page_info/page_info_main_view.cc +++ b/chrome/browser/ui/views/page_info/page_info_main_view.cc -@@ -600,38 +600,42 @@ void PageInfoMainView::ChildPreferredSizeChanged(views::View* child) { +@@ -603,38 +603,42 @@ void PageInfoMainView::ChildPreferredSizeChanged(views::View* child) { } std::unique_ptr PageInfoMainView::CreateBubbleHeaderView() { diff --git a/build/cromite_patches/Disable-plugins-enumeration.patch b/build/cromite_patches/Disable-plugins-enumeration.patch index 59527feb210cfd6aad7b2a9b1c93fed1cd4c1999..2c9a3c2008d15cd17c4ae2648957add17251ccbb 100644 --- a/build/cromite_patches/Disable-plugins-enumeration.patch +++ b/build/cromite_patches/Disable-plugins-enumeration.patch @@ -10,7 +10,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc -@@ -2292,9 +2292,7 @@ WebContentSettingsClient* LocalFrame::GetContentSettingsClient() { +@@ -2288,9 +2288,7 @@ WebContentSettingsClient* LocalFrame::GetContentSettingsClient() { } PluginData* LocalFrame::GetPluginData() const { diff --git a/build/cromite_patches/Disable-privacy-issues-in-password-manager.patch b/build/cromite_patches/Disable-privacy-issues-in-password-manager.patch index 9373be9c86f930682d110a289425a8e85bb5d953..15a5c9a4117a9c24a1b26a122fcb4a3021ebd86f 100644 --- a/build/cromite_patches/Disable-privacy-issues-in-password-manager.patch +++ b/build/cromite_patches/Disable-privacy-issues-in-password-manager.patch @@ -7,17 +7,17 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html --- chrome/android/java/AndroidManifest.xml | 9 --------- .../password_store_android_backend_bridge_helper_impl.cc | 1 + - .../android/password_store_android_local_backend.cc | 2 +- .../generated_password_leak_detection_pref.cc | 3 +-- .../password_receiver_service_factory.cc | 2 +- .../password_manager/password_sender_service_factory.cc | 2 +- .../affiliations/core/browser/affiliation_backend.cc | 1 + .../core/browser/affiliation_service_impl.cc | 4 ++++ components/affiliations/core/browser/facet_manager.cc | 4 ++++ + .../affiliation/password_affiliation_source_adapter.cc | 4 ++++ .../leak_detection/leak_detection_check_factory_impl.cc | 5 +++++ .../browser/leak_detection/leak_detection_check_impl.cc | 3 +-- .../Disable-privacy-issues-in-password-manager.inc | 1 + - 12 files changed, 21 insertions(+), 16 deletions(-) + 12 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 cromite_flags/components/password_manager/core/browser/features/password_features_cc/Disable-privacy-issues-in-password-manager.inc diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml @@ -50,18 +50,6 @@ diff --git a/chrome/browser/password_manager/android/password_store_android_back base::android::BuildInfo* info = base::android::BuildInfo::GetInstance(); int current_gms_core_version; if (!base::StringToInt(info->gms_version_code(), ¤t_gms_core_version)) { -diff --git a/chrome/browser/password_manager/android/password_store_android_local_backend.cc b/chrome/browser/password_manager/android/password_store_android_local_backend.cc ---- a/chrome/browser/password_manager/android/password_store_android_local_backend.cc -+++ b/chrome/browser/password_manager/android/password_store_android_local_backend.cc -@@ -37,7 +37,7 @@ PasswordStoreAndroidLocalBackend::PasswordStoreAndroidLocalBackend( - // AccountBackend doesn't call `DisableSource` when sync is turned off. - // This is why we have to explicitly call it here whenever local GMSCore is - // created. -- password_affiliation_adapter.DisableSource(); -+ if ((true)) password_affiliation_adapter.DisableSource(); - } - - PasswordStoreAndroidLocalBackend::~PasswordStoreAndroidLocalBackend() = default; diff --git a/chrome/browser/password_manager/generated_password_leak_detection_pref.cc b/chrome/browser/password_manager/generated_password_leak_detection_pref.cc --- a/chrome/browser/password_manager/generated_password_leak_detection_pref.cc +++ b/chrome/browser/password_manager/generated_password_leak_detection_pref.cc @@ -102,7 +90,7 @@ diff --git a/chrome/browser/password_manager/password_sender_service_factory.cc diff --git a/components/affiliations/core/browser/affiliation_backend.cc b/components/affiliations/core/browser/affiliation_backend.cc --- a/components/affiliations/core/browser/affiliation_backend.cc +++ b/components/affiliations/core/browser/affiliation_backend.cc -@@ -401,6 +401,7 @@ void AffiliationBackend::OnMalformedResponse( +@@ -400,6 +400,7 @@ void AffiliationBackend::OnMalformedResponse( } bool AffiliationBackend::OnCanSendNetworkRequest() { @@ -138,6 +126,20 @@ diff --git a/components/affiliations/core/browser/facet_manager.cc b/components/ if (IsCachedDataFresh()) { AffiliatedFacetsWithUpdateTime affiliation; if (!backend_->ReadAffiliationsAndBrandingFromDatabase(facet_uri_, +diff --git a/components/password_manager/core/browser/affiliation/password_affiliation_source_adapter.cc b/components/password_manager/core/browser/affiliation/password_affiliation_source_adapter.cc +--- a/components/password_manager/core/browser/affiliation/password_affiliation_source_adapter.cc ++++ b/components/password_manager/core/browser/affiliation/password_affiliation_source_adapter.cc +@@ -27,6 +27,10 @@ PasswordAffiliationSourceAdapter::~PasswordAffiliationSourceAdapter() = default; + + void PasswordAffiliationSourceAdapter::GetFacets( + AffiliationSource::ResultCallback response_callback) { ++ if (((true))) { ++ std::move(response_callback).Run({}); ++ return; ++ } + on_password_forms_received_callback_ = std::move(response_callback); + store_->GetAllLogins(weak_ptr_factory_.GetWeakPtr()); + } diff --git a/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl.cc b/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl.cc --- a/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl.cc +++ b/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl.cc diff --git a/build/cromite_patches/Disable-privacy-sandbox.patch b/build/cromite_patches/Disable-privacy-sandbox.patch index 98199388895ed0236b4ba71fc6ef490a06fb4cde..4ceb4155dab34965a02daf34e6366fca23aac409 100644 --- a/build/cromite_patches/Disable-privacy-sandbox.patch +++ b/build/cromite_patches/Disable-privacy-sandbox.patch @@ -27,17 +27,19 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../shared_storage_document_service_impl.cc | 9 +++++++++ .../features_cc/Disable-privacy-sandbox.inc | 1 + .../Disable-privacy-sandbox.inc | 1 + - .../features_cc/Disable-privacy-sandbox.inc | 2 ++ + .../features_cc/Disable-privacy-sandbox.inc | 1 + + .../features_cc/Disable-privacy-sandbox.inc | 1 + third_party/blink/common/features.cc | 1 + - 19 files changed, 48 insertions(+), 28 deletions(-) + 20 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 cromite_flags/content/common/features_cc/Disable-privacy-sandbox.inc create mode 100644 cromite_flags/content/public/common/content_features_cc/Disable-privacy-sandbox.inc + create mode 100644 cromite_flags/services/network/public/cpp/features_cc/Disable-privacy-sandbox.inc create mode 100644 cromite_flags/third_party/blink/common/features_cc/Disable-privacy-sandbox.inc diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc -@@ -3486,6 +3486,9 @@ bool ChromeContentBrowserClient::IsAttributionReportingOperationAllowed( +@@ -3498,6 +3498,9 @@ bool ChromeContentBrowserClient::IsAttributionReportingOperationAllowed( const url::Origin* destination_origin, const url::Origin* reporting_origin, bool* can_bypass) { @@ -82,7 +84,7 @@ diff --git a/chrome/browser/first_party_sets/first_party_sets_policy_service.cc diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -1296,7 +1296,7 @@ void RegisterProfilePrefsForMigration( +@@ -1200,7 +1200,7 @@ void RegisterProfilePrefsForMigration( registry->RegisterTimePref(kPlusAddressLastFetchedTime, base::Time()); // Deprecated 03/2024. @@ -94,7 +96,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc --- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc +++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc -@@ -1261,7 +1261,7 @@ bool PrivacySandboxServiceImpl::IsRestrictedNoticeEnabled() { +@@ -1276,7 +1276,7 @@ bool PrivacySandboxServiceImpl::IsRestrictedNoticeEnabled() { void PrivacySandboxServiceImpl::SetRelatedWebsiteSetsDataAccessEnabled( bool enabled) { pref_service_->SetBoolean(prefs::kPrivacySandboxRelatedWebsiteSetsEnabled, @@ -127,7 +129,7 @@ diff --git a/chrome/browser/ui/privacy_sandbox/privacy_sandbox_prompt.cc b/chrom diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc --- a/components/history/core/browser/history_backend.cc +++ b/components/history/core/browser/history_backend.cc -@@ -700,18 +700,7 @@ void HistoryBackend::SetBrowsingTopicsAllowed(ContextID context_id, +@@ -701,18 +701,7 @@ void HistoryBackend::SetBrowsingTopicsAllowed(ContextID context_id, if (!visit_id) return; @@ -162,7 +164,7 @@ diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_san diff --git a/components/privacy_sandbox/privacy_sandbox_features.cc b/components/privacy_sandbox/privacy_sandbox_features.cc --- a/components/privacy_sandbox/privacy_sandbox_features.cc +++ b/components/privacy_sandbox/privacy_sandbox_features.cc -@@ -283,4 +283,8 @@ BASE_FEATURE(kPrivacySandboxAllowPromptForBlocked3PCookies, +@@ -294,4 +294,8 @@ BASE_FEATURE(kPrivacySandboxAllowPromptForBlocked3PCookies, BASE_FEATURE(kPrivacySandboxEqualizedPromptButtons, "PrivacySandboxEqualizedPromptButtons", base::FEATURE_DISABLED_BY_DEFAULT); @@ -300,7 +302,7 @@ diff --git a/components/signin/public/identity_manager/account_capabilities.cc b diff --git a/content/browser/shared_storage/shared_storage_document_service_impl.cc b/content/browser/shared_storage/shared_storage_document_service_impl.cc --- a/content/browser/shared_storage/shared_storage_document_service_impl.cc +++ b/content/browser/shared_storage/shared_storage_document_service_impl.cc -@@ -128,6 +128,15 @@ void SharedStorageDocumentServiceImpl::CreateWorklet( +@@ -129,6 +129,15 @@ void SharedStorageDocumentServiceImpl::CreateWorklet( render_frame_host().GetLastCommittedOrigin().IsSameOriginWith( data_origin); @@ -328,17 +330,22 @@ new file mode 100644 +++ b/cromite_flags/content/public/common/content_features_cc/Disable-privacy-sandbox.inc @@ -0,0 +1 @@ +SET_CROMITE_FEATURE_DISABLED(kPrivacySandboxAdsAPIsOverride); +diff --git a/cromite_flags/services/network/public/cpp/features_cc/Disable-privacy-sandbox.inc b/cromite_flags/services/network/public/cpp/features_cc/Disable-privacy-sandbox.inc +new file mode 100644 +--- /dev/null ++++ b/cromite_flags/services/network/public/cpp/features_cc/Disable-privacy-sandbox.inc +@@ -0,0 +1 @@ ++SET_CROMITE_FEATURE_DISABLED(kSharedStorageAPI); diff --git a/cromite_flags/third_party/blink/common/features_cc/Disable-privacy-sandbox.inc b/cromite_flags/third_party/blink/common/features_cc/Disable-privacy-sandbox.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/third_party/blink/common/features_cc/Disable-privacy-sandbox.inc -@@ -0,0 +1,2 @@ +@@ -0,0 +1 @@ +SET_CROMITE_FEATURE_DISABLED(kFencedFrames); -+SET_CROMITE_FEATURE_DISABLED(kSharedStorageAPI); diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc -@@ -2843,6 +2843,7 @@ BASE_FEATURE(kWebviewAccelerateSmallCanvases, +@@ -2688,6 +2688,7 @@ BASE_FEATURE(kWebviewAccelerateSmallCanvases, // constants for features in the section above. bool IsAllowURNsInIframeEnabled() { diff --git a/build/cromite_patches/Disable-references-to-fonts.googleapis.com.patch b/build/cromite_patches/Disable-references-to-fonts.googleapis.com.patch index aa4f0cf9af5d17b370063e174ab9d919f09e0409..9914fdae1005c4dd110026bb4d9c66482e709bf5 100644 --- a/build/cromite_patches/Disable-references-to-fonts.googleapis.com.patch +++ b/build/cromite_patches/Disable-references-to-fonts.googleapis.com.patch @@ -15,7 +15,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts --- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts -@@ -371,9 +371,7 @@ export class ReadAnythingToolbarElement extends ReadAnythingToolbarElementBase { +@@ -369,9 +369,7 @@ export class ReadAnythingToolbarElement extends ReadAnythingToolbarElementBase { const link = document.createElement('link'); link.rel = 'preload'; link.as = 'style'; diff --git a/build/cromite_patches/Disable-safety-check.patch b/build/cromite_patches/Disable-safety-check.patch index 3b525e05ca2ba49fa3b094f3abca8833e85378b5..43eb723205e43329d4ab6951bea140d035801c11 100644 --- a/build/cromite_patches/Disable-safety-check.patch +++ b/build/cromite_patches/Disable-safety-check.patch @@ -29,7 +29,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//chrome/browser/safety_hub/android:java", "//chrome/browser/screenshot_monitor:java", "//chrome/browser/search_engines/android:java", -@@ -1078,8 +1077,6 @@ if (current_toolchain == default_toolchain) { +@@ -1086,8 +1085,6 @@ if (current_toolchain == default_toolchain) { "//chrome/browser/readaloud/android:junit", "//chrome/browser/recent_tabs:junit", "//chrome/browser/recent_tabs/internal:junit", @@ -38,9 +38,9 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//chrome/browser/search_engines/android:junit", "//chrome/browser/search_resumption:junit", "//chrome/browser/signin/services/android:junit", -@@ -1347,8 +1344,6 @@ if (current_toolchain == default_toolchain) { - "//chrome/browser/privacy_sandbox/android:javatests", +@@ -1360,8 +1357,6 @@ if (current_toolchain == default_toolchain) { "//chrome/browser/quick_delete:javatests", + "//chrome/browser/renderer_host/android:javatests", "//chrome/browser/safe_browsing/android:javatests", - "//chrome/browser/safety_check/android:javatests", - "//chrome/browser/safety_hub/android:javatests", @@ -50,7 +50,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -981,7 +981,6 @@ chrome_java_sources = [ +@@ -913,7 +913,6 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingPasswordReuseDialogBridge.java", "java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingReferringAppBridge.java", "java/src/org/chromium/chrome/browser/safe_browsing/SafeBrowsingSettingsNavigation.java", @@ -135,9 +135,9 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/Fragme diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java -@@ -274,7 +274,7 @@ public class MainSettings extends ChromeBaseSettingsFragment - .removePreference(findPreference(PREF_TOOLBAR_SHORTCUT)); - }); +@@ -316,7 +316,7 @@ public class MainSettings extends ChromeBaseSettingsFragment + removePreferenceIfPresent(PREF_UI_THEME); + } - if (BuildInfo.getInstance().isAutomotive) { + if (((true)) || BuildInfo.getInstance().isAutomotive) { @@ -184,9 +184,9 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/Settin diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn -@@ -3285,7 +3285,6 @@ static_library("browser") { - "//chrome/browser/reading_list/android", +@@ -3309,7 +3309,6 @@ static_library("browser") { "//chrome/browser/recent_tabs:jni_headers", + "//chrome/browser/regional_capabilities", "//chrome/browser/safe_browsing/android:safe_browsing_enums", - "//chrome/browser/safety_check/android", "//chrome/browser/safety_hub/android", @@ -208,7 +208,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc -@@ -544,7 +544,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { +@@ -559,7 +559,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { tpcd::trial::ValidityService::MaybeCreateForWebContents(web_contents); TrustedVaultEncryptionKeysTabHelper::CreateForWebContents(web_contents); #if BUILDFLAG(IS_ANDROID) @@ -220,15 +220,15 @@ diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java -@@ -87,7 +87,7 @@ public class SiteSettings extends BaseSiteSettingsFragment +@@ -96,7 +96,7 @@ public class SiteSettings extends BaseSiteSettingsFragment } // Remove the permission autorevocation preference if Safety Hub is not enabled. - if (!getSiteSettingsDelegate().isSafetyHubEnabled()) { + if (((false)) && !getSiteSettingsDelegate().isSafetyHubEnabled()) { - getPreferenceScreen().removePreference(findPreference(PERMISSION_AUTOREVOCATION_PREF)); - getPreferenceScreen().removePreference(findPreference(DIVIDER_PREF)); - } + Preference autorevocationPref = + assumeNonNull(findPreference(PERMISSION_AUTOREVOCATION_PREF)); + getPreferenceScreen().removePreference(autorevocationPref); diff --git a/cromite_flags/chrome/common/chrome_features_cc/Disable-safety-check.inc b/cromite_flags/chrome/common/chrome_features_cc/Disable-safety-check.inc new file mode 100644 --- /dev/null diff --git a/build/cromite_patches/Disable-smart-selection-by-default.patch b/build/cromite_patches/Disable-smart-selection-by-default.patch index be6add6523b9e7919b01214eaf0d6b2e83292714..bf4e5c031bb01a77796059b50e8e2427d0453aa2 100644 --- a/build/cromite_patches/Disable-smart-selection-by-default.patch +++ b/build/cromite_patches/Disable-smart-selection-by-default.patch @@ -55,7 +55,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -500,6 +500,7 @@ public abstract class ChromeFeatureList { +@@ -512,6 +512,7 @@ public abstract class ChromeFeatureList { public static final String SHOW_NEW_TAB_ANIMATIONS = "ShowNewTabAnimations"; public static final String SEARCH_RESUMPTION_MODULE_ANDROID = "SearchResumptionModuleAndroid"; public static final String SEED_ACCOUNTS_REVAMP = "SeedAccountsRevamp"; diff --git a/build/cromite_patches/Disable-some-signed-exchange-features.patch b/build/cromite_patches/Disable-some-signed-exchange-features.patch index f53d5612f7d7c8ae26d1b75d82e87966afa2144d..cf84fa73d8e421166c80a70f87110d4fff18aa8a 100644 --- a/build/cromite_patches/Disable-some-signed-exchange-features.patch +++ b/build/cromite_patches/Disable-some-signed-exchange-features.patch @@ -11,7 +11,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc -@@ -1558,7 +1558,7 @@ void ChromeContentBrowserClient::RegisterProfilePrefs( +@@ -1565,7 +1565,7 @@ void ChromeContentBrowserClient::RegisterProfilePrefs( site_isolation::prefs::kWebTriggeredIsolatedOrigins); registry->RegisterDictionaryPref( prefs::kDevToolsBackgroundServicesExpirationDict); diff --git a/build/cromite_patches/Disable-text-fragments-by-default.patch b/build/cromite_patches/Disable-text-fragments-by-default.patch index 2839d7919747b4e24d3cd940707f900e6a916062..f26e57507e691658f261678c4d0a16f1c408e86c 100644 --- a/build/cromite_patches/Disable-text-fragments-by-default.patch +++ b/build/cromite_patches/Disable-text-fragments-by-default.patch @@ -74,7 +74,7 @@ new file mode 100644 diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc -@@ -4578,9 +4578,14 @@ void Document::SetURL(const KURL& url) { +@@ -4579,9 +4579,14 @@ void Document::SetURL(const KURL& url) { TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "url", new_url.GetString().Utf8()); @@ -92,7 +92,7 @@ diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 -@@ -4300,10 +4300,9 @@ +@@ -4410,10 +4410,9 @@ }, { name: "TextFragmentIdentifiers", diff --git a/build/cromite_patches/Disable-third-party-origin-trials.patch b/build/cromite_patches/Disable-third-party-origin-trials.patch index 897fd5c8c1cf28f23d682a6503dc8a5dd4dab3c6..de58014bc9a9e59396e87a08f7509b26141fc5a5 100644 --- a/build/cromite_patches/Disable-third-party-origin-trials.patch +++ b/build/cromite_patches/Disable-third-party-origin-trials.patch @@ -76,7 +76,7 @@ diff --git a/components/embedder_support/origin_trials/origin_trial_policy_impl. diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc --- a/content/browser/renderer_host/navigation_request.cc +++ b/content/browser/renderer_host/navigation_request.cc -@@ -9295,7 +9295,6 @@ void NavigationRequest::SetSourceSiteInstanceToInitiatorIfNeeded() { +@@ -9381,7 +9381,6 @@ void NavigationRequest::SetSourceSiteInstanceToInitiatorIfNeeded() { void NavigationRequest::ForceEnableOriginTrials( const std::vector& trials) { DCHECK(!HasCommitted()); @@ -223,7 +223,7 @@ diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_p } bool IsPagePopupRunningInWebTest(LocalFrame* frame) { -@@ -3271,10 +3261,6 @@ void DocumentLoader::CreateParserPostCommit() { +@@ -3281,10 +3271,6 @@ void DocumentLoader::CreateParserPostCommit() { } #endif // BUILDFLAG(IS_CHROMEOS) @@ -274,6 +274,6 @@ diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context. bool OriginTrialContext::CanEnableTrialFromName(const StringView& trial_name) { + if ((true)) return false; if (trial_name == "FledgeBiddingAndAuctionServer") { - return base::FeatureList::IsEnabled(features::kInterestGroupStorage) && - base::FeatureList::IsEnabled( + return base::FeatureList::IsEnabled( + network::features::kInterestGroupStorage) && -- diff --git a/build/cromite_patches/Disable-various-metrics.patch b/build/cromite_patches/Disable-various-metrics.patch index e60cc2cd1729b7c27a1117d20e6619fca18beb14..b5fce273bca2cb6d6ec8a7da166169e73be74cb2 100644 --- a/build/cromite_patches/Disable-various-metrics.patch +++ b/build/cromite_patches/Disable-various-metrics.patch @@ -92,8 +92,8 @@ diff --git a/chrome/browser/android/ntp/most_visited_sites_bridge.cc b/chrome/br diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc --- a/chrome/browser/browser_process_impl.cc +++ b/chrome/browser/browser_process_impl.cc -@@ -1132,7 +1132,7 @@ void BrowserProcessImpl::RegisterPrefs(PrefRegistrySimple* registry) { - #endif // BUILDFLAG(IS_CHROMEOS_ASH) +@@ -1130,7 +1130,7 @@ void BrowserProcessImpl::RegisterPrefs(PrefRegistrySimple* registry) { + #endif // BUILDFLAG(IS_CHROMEOS) registry->RegisterBooleanPref(metrics::prefs::kMetricsReportingEnabled, - GoogleUpdateSettings::GetCollectStatsConsent()); @@ -112,7 +112,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch #include "chrome/browser/media/audio_service_util.h" #include "chrome/browser/media/prefs/capture_device_ranking.h" #include "chrome/browser/media/router/media_router_feature.h" -@@ -5236,16 +5235,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation( +@@ -5238,16 +5237,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation( content::NavigationHandle* handle) { std::vector> throttles; @@ -129,7 +129,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch #if BUILDFLAG(IS_ANDROID) // TODO(davidben): This is insufficient to integrate with prerender properly. // https://crbug.com/370595 -@@ -5378,10 +5367,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation( +@@ -5380,10 +5369,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation( } } diff --git a/build/cromite_patches/Do-not-build-API-keys-infobar.patch b/build/cromite_patches/Do-not-build-API-keys-infobar.patch index 70ec347c5c3a5e5be72bab0afa2a7b22fabb586a..cb244c0167aba1869107ba09bacaa24c866faa43 100644 --- a/build/cromite_patches/Do-not-build-API-keys-infobar.patch +++ b/build/cromite_patches/Do-not-build-API-keys-infobar.patch @@ -11,7 +11,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn -@@ -1178,8 +1178,6 @@ static_library("ui") { +@@ -1183,8 +1183,6 @@ static_library("ui") { "startup/default_browser_prompt/default_browser_prompt_manager.h", "startup/default_browser_prompt/default_browser_prompt_prefs.cc", "startup/default_browser_prompt/default_browser_prompt_prefs.h", diff --git a/build/cromite_patches/Do-not-compile-QR-code-sharing.patch b/build/cromite_patches/Do-not-compile-QR-code-sharing.patch index acc8d3ea201822589058ba319846b5ed020accde..94310bba4bb06af6cb07aa89bf350949a921a5bc 100644 --- a/build/cromite_patches/Do-not-compile-QR-code-sharing.patch +++ b/build/cromite_patches/Do-not-compile-QR-code-sharing.patch @@ -19,7 +19,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -2986,7 +2986,6 @@ generate_jni("chrome_jni_headers") { +@@ -3014,7 +3014,6 @@ generate_jni("chrome_jni_headers") { "../browser/share/android/java/src/org/chromium/chrome/browser/share/link_to_text/LinkToTextBridge.java", "../browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabService.java", "../browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceFactory.java", @@ -30,15 +30,15 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java -@@ -126,7 +126,6 @@ import org.chromium.chrome.browser.share.ShareButtonController; +@@ -110,7 +110,6 @@ import org.chromium.chrome.browser.recent_tabs.RestoreTabsFeatureHelper; + import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.chrome.browser.share.ShareDelegate; import org.chromium.chrome.browser.share.ShareDelegate.ShareOrigin; - import org.chromium.chrome.browser.share.ShareUtils; -import org.chromium.chrome.browser.share.qrcode.QrCodeDialog; import org.chromium.chrome.browser.share.scroll_capture.ScrollCaptureManager; import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler; import org.chromium.chrome.browser.tab.AutofillSessionLifetimeController; -@@ -587,10 +586,6 @@ public class RootUiCoordinator +@@ -565,10 +564,6 @@ public class RootUiCoordinator } public void onAttachFragment(Fragment fragment) { @@ -52,7 +52,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordi diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -1925,7 +1925,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) { +@@ -1830,7 +1830,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kUiAutomationProviderEnabled, false); #endif @@ -64,10 +64,10 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse diff --git a/chrome/browser/share/BUILD.gn b/chrome/browser/share/BUILD.gn --- a/chrome/browser/share/BUILD.gn +++ b/chrome/browser/share/BUILD.gn -@@ -47,7 +47,6 @@ source_set("share") { +@@ -45,7 +45,6 @@ source_set("share") { + "default_ranking_android.cc", + "editor_screenshot_task.cc", "link_to_text_bridge.cc", - "page_info_sharing_bridge.cc", - "page_info_sharing_bridge.h", - "qr_code_generator_android.cc", ] deps += [ @@ -83,7 +83,7 @@ diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/s import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfAndroidBridge; import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfCoordinator; import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback; -@@ -306,9 +305,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase { +@@ -300,9 +299,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase { } private void maybeAddQrCodeFirstPartyOption() { @@ -93,7 +93,7 @@ diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/s } private void maybeAddLongScreenshotFirstPartyOption() { -@@ -442,25 +438,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase { +@@ -436,25 +432,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase { .build(); } @@ -122,10 +122,10 @@ diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/s diff --git a/chrome/browser/share/android/java_sources.gni b/chrome/browser/share/android/java_sources.gni --- a/chrome/browser/share/android/java_sources.gni +++ b/chrome/browser/share/android/java_sources.gni -@@ -45,17 +45,6 @@ share_java_sources = [ - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/page_info_sheet/feedback/FeedbackSheetMediator.java", - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/page_info_sheet/feedback/FeedbackSheetProperties.java", - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/page_info_sheet/feedback/FeedbackSheetViewBinder.java", +@@ -30,17 +30,6 @@ share_java_sources = [ + "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabService.java", + "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/LongScreenshotsTabServiceFactory.java", + "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/long_screenshots/bitmap_generation/ScreenshotBoundsManager.java", - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QRCodeGenerator.java", - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java", - "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java", @@ -155,7 +155,7 @@ diff --git a/chrome/browser/sharing_hub/sharing_hub_features.cc b/chrome/browser diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc --- a/chrome/browser/ui/browser_command_controller.cc +++ b/chrome/browser/ui/browser_command_controller.cc -@@ -1798,10 +1798,10 @@ void BrowserCommandController::UpdateCommandsForFullscreenMode() { +@@ -1829,10 +1829,10 @@ void BrowserCommandController::UpdateCommandsForFullscreenMode() { command_updater_.UpdateCommandEnabled(IDC_CHROME_WHATS_NEW, show_main_ui); #endif command_updater_.UpdateCommandEnabled(IDC_CONTENT_CONTEXT_SHARING_SUBMENU, @@ -172,7 +172,7 @@ diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc --- a/chrome/browser/ui/browser_commands.cc +++ b/chrome/browser/ui/browser_commands.cc -@@ -1660,6 +1660,7 @@ void SendTabToSelf(Browser* browser) { +@@ -1656,6 +1656,7 @@ void SendTabToSelf(Browser* browser) { } bool CanGenerateQrCode(const Browser* browser) { diff --git a/build/cromite_patches/Do-not-link-with-libatomic.patch b/build/cromite_patches/Do-not-link-with-libatomic.patch index 6905dc07a6190c9cd135682dc2560b6c5cce4e3a..ad1ce60015a55486eccc0c9d1cc87b16b9744c04 100644 --- a/build/cromite_patches/Do-not-link-with-libatomic.patch +++ b/build/cromite_patches/Do-not-link-with-libatomic.patch @@ -14,7 +14,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/base/BUILD.gn b/base/BUILD.gn --- a/base/BUILD.gn +++ b/base/BUILD.gn -@@ -1090,7 +1090,7 @@ component("base") { +@@ -1096,7 +1096,7 @@ component("base") { # more robust check for this. if (!use_sysroot && (is_android || is_chromeos || (is_linux && !is_castos)) && host_toolchain != "//build/toolchain/cros:host") { diff --git a/build/cromite_patches/Do-not-store-passwords-by-default.patch b/build/cromite_patches/Do-not-store-passwords-by-default.patch index 3e3537cdc6e47f69d2ae2095e4e7f58bb98f91c7..71bb2215db80df26b60bbed308bcfa97df52ab61 100644 --- a/build/cromite_patches/Do-not-store-passwords-by-default.patch +++ b/build/cromite_patches/Do-not-store-passwords-by-default.patch @@ -10,7 +10,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc --- a/components/password_manager/core/browser/password_manager.cc +++ b/components/password_manager/core/browser/password_manager.cc -@@ -364,7 +364,7 @@ bool HasManuallyFilledFields(const PasswordForm& form) { +@@ -389,7 +389,7 @@ bool HasManuallyFilledFields(const PasswordForm& form) { void PasswordManager::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref( @@ -19,7 +19,7 @@ diff --git a/components/password_manager/core/browser/password_manager.cc b/comp user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); #if BUILDFLAG(IS_IOS) // Deprecated pref in profile prefs. -@@ -372,7 +372,7 @@ void PasswordManager::RegisterProfilePrefs( +@@ -397,7 +397,7 @@ void PasswordManager::RegisterProfilePrefs( false); #endif // BUILDFLAG(IS_IOS) registry->RegisterBooleanPref( @@ -28,7 +28,7 @@ diff --git a/components/password_manager/core/browser/password_manager.cc b/comp user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); registry->RegisterBooleanPref( prefs::kWasAutoSignInFirstRunExperienceShown, false, -@@ -409,9 +409,9 @@ void PasswordManager::RegisterProfilePrefs( +@@ -434,9 +434,9 @@ void PasswordManager::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); registry->RegisterBooleanPref(prefs::kPasswordsPrefWithNewLabelUsed, false); #if BUILDFLAG(IS_ANDROID) diff --git a/build/cromite_patches/DoH-improvements.patch b/build/cromite_patches/DoH-improvements.patch index 18fd6e1dafa6de8c2e79e531a6c626d8b0fdcc79..2d6f6f2366aee7fdca3ee52ecae19712f79cebe4 100644 --- a/build/cromite_patches/DoH-improvements.patch +++ b/build/cromite_patches/DoH-improvements.patch @@ -128,7 +128,7 @@ diff --git a/net/dns/dns_client.cc b/net/dns/dns_client.cc diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc --- a/net/dns/host_resolver_manager.cc +++ b/net/dns/host_resolver_manager.cc -@@ -636,6 +636,7 @@ void HostResolverManager::SetDnsConfigOverrides(DnsConfigOverrides overrides) { +@@ -648,6 +648,7 @@ void HostResolverManager::SetDnsConfigOverrides(DnsConfigOverrides overrides) { bool changed = dns_client_->SetConfigOverrides(std::move(overrides)); if (changed) { @@ -162,7 +162,7 @@ diff --git a/net/dns/public/doh_provider_entry.cc b/net/dns/public/doh_provider_ diff --git a/net/dns/public/doh_provider_entry.h b/net/dns/public/doh_provider_entry.h --- a/net/dns/public/doh_provider_entry.h +++ b/net/dns/public/doh_provider_entry.h -@@ -58,7 +58,7 @@ struct NET_EXPORT DohProviderEntry { +@@ -59,7 +59,7 @@ struct NET_EXPORT DohProviderEntry { std::set ip_addresses; base::flat_set dns_over_tls_hostnames; DnsOverHttpsServerConfig doh_server_config; @@ -171,7 +171,7 @@ diff --git a/net/dns/public/doh_provider_entry.h b/net/dns/public/doh_provider_e std::string_view privacy_policy; bool display_globally; base::flat_set display_countries; -@@ -75,7 +75,7 @@ struct NET_EXPORT DohProviderEntry { +@@ -76,7 +76,7 @@ struct NET_EXPORT DohProviderEntry { std::initializer_list dns_over_53_server_ip_strs, base::flat_set dns_over_tls_hostnames, std::string dns_over_https_template, diff --git a/build/cromite_patches/Enable-Certificate-Transparency.patch b/build/cromite_patches/Enable-Certificate-Transparency.patch index b14eaa2d0b75d9c3513c8ce3ae4a527dd85b4942..4564a8d4b75cf9681c4f9aa4a1447872e7fa0b7d 100644 --- a/build/cromite_patches/Enable-Certificate-Transparency.patch +++ b/build/cromite_patches/Enable-Certificate-Transparency.patch @@ -35,7 +35,7 @@ diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc --- a/chrome/browser/net/system_network_context_manager.cc +++ b/chrome/browser/net/system_network_context_manager.cc -@@ -1020,7 +1020,7 @@ bool SystemNetworkContextManager::IsCertificateTransparencyEnabled() { +@@ -1019,7 +1019,7 @@ bool SystemNetworkContextManager::IsCertificateTransparencyEnabled() { // - on an opt-in basis for other builds and embedders, controlled with the // kCertificateTransparencyAskBeforeEnabling flag return base::FeatureList::IsEnabled( diff --git a/build/cromite_patches/Enable-HEVC-by-default.patch b/build/cromite_patches/Enable-HEVC-by-default.patch index d3956ef8741afbca3662aa3a413f6540a79a2165..caccce8df6d8f8b6ac3da93a4deb89cdf114913a 100644 --- a/build/cromite_patches/Enable-HEVC-by-default.patch +++ b/build/cromite_patches/Enable-HEVC-by-default.patch @@ -12,7 +12,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc --- a/media/base/media_switches.cc +++ b/media/base/media_switches.cc -@@ -328,8 +328,8 @@ BASE_FEATURE(kEnableTabMuting, +@@ -315,8 +315,8 @@ BASE_FEATURE(kEnableTabMuting, #if BUILDFLAG(ENABLE_PLATFORM_HEVC) // Enables HEVC hardware accelerated decoding. BASE_FEATURE(kPlatformHEVCDecoderSupport, diff --git a/build/cromite_patches/Enable-ImprovedBookmarks-by-default.patch b/build/cromite_patches/Enable-ImprovedBookmarks-by-default.patch index b67ce08af4f26b1451639da3a6daaadb31248c93..cbd3d09e368ce4dd713a24b3fefd9f0bd751eb99 100644 --- a/build/cromite_patches/Enable-ImprovedBookmarks-by-default.patch +++ b/build/cromite_patches/Enable-ImprovedBookmarks-by-default.patch @@ -5,9 +5,9 @@ Subject: Enable ImprovedBookmarks by default License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html --- chrome/android/java/res/layout/bookmark_search_box_row.xml | 4 ++-- + .../chrome/browser/bookmarks/BookmarkManagerOpenerImpl.java | 2 +- .../browser/bookmarks/BookmarkSearchBoxRowViewBinder.java | 2 ++ .../chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java | 2 +- - .../org/chromium/chrome/browser/bookmarks/BookmarkUtils.java | 2 +- components/bookmarks/browser/bookmark_node.cc | 3 +-- 5 files changed, 7 insertions(+), 6 deletions(-) @@ -25,9 +25,21 @@ diff --git a/chrome/android/java/res/layout/bookmark_search_box_row.xml b/chrome android:orientation="horizontal"> callback = wrapCallback(model, BookmarkSearchBoxRowProperties.SEARCH_TEXT_CHANGE_CALLBACK); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java -@@ -21,7 +21,7 @@ import java.lang.annotation.RetentionPolicy; +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiPrefs.java +@@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; /** Self-documenting preference class for bookmarks. */ public class BookmarkUiPrefs { private static final @BookmarkRowDisplayPref int INITIAL_BOOKMARK_ROW_DISPLAY_PREF = @@ -56,18 +68,6 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm private static final @BookmarkRowSortOrder int INITIAL_BOOKMARK_ROW_SORT_ORDER = BookmarkRowSortOrder.MANUAL; -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java -@@ -562,7 +562,7 @@ public class BookmarkUtils { - RecordUserAction.record("MobileBookmarkManagerReopenBookmarksInSameSession"); - } - -- if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(context)) { -+ if ((true)) { - showBookmarkManagerOnTablet( - context, - activity == null ? null : activity.getComponentName(), diff --git a/components/bookmarks/browser/bookmark_node.cc b/components/bookmarks/browser/bookmark_node.cc --- a/components/bookmarks/browser/bookmark_node.cc +++ b/components/bookmarks/browser/bookmark_node.cc diff --git a/build/cromite_patches/Enable-StrictOriginIsolation-and-SitePerProcess.patch b/build/cromite_patches/Enable-StrictOriginIsolation-and-SitePerProcess.patch index 59ffa7e854b97ce982fd46dc6776aa33df32b018..773f2981749148ded93cf3f6a71bc7df2f9402d3 100644 --- a/build/cromite_patches/Enable-StrictOriginIsolation-and-SitePerProcess.patch +++ b/build/cromite_patches/Enable-StrictOriginIsolation-and-SitePerProcess.patch @@ -20,7 +20,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc -@@ -1525,7 +1525,7 @@ void ChromeContentBrowserClient::RegisterLocalStatePrefs( +@@ -1532,7 +1532,7 @@ void ChromeContentBrowserClient::RegisterLocalStatePrefs( registry->RegisterFilePathPref(prefs::kDiskCacheDir, base::FilePath()); registry->RegisterIntegerPref(prefs::kDiskCacheSize, 0); registry->RegisterStringPref(prefs::kIsolateOrigins, std::string()); @@ -29,7 +29,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch registry->RegisterBooleanPref(prefs::kTabFreezingEnabled, true); registry->RegisterIntegerPref(prefs::kSCTAuditingHashdanceReportCount, 0); registry->RegisterBooleanPref(prefs::kDataURLWhitespacePreservationEnabled, -@@ -1551,7 +1551,7 @@ void ChromeContentBrowserClient::RegisterProfilePrefs( +@@ -1558,7 +1558,7 @@ void ChromeContentBrowserClient::RegisterProfilePrefs( // user policy in addition to the same named ones in Local State (which are // used for mapping the command-line flags). registry->RegisterStringPref(prefs::kIsolateOrigins, std::string()); diff --git a/build/cromite_patches/Enable-darken-websites-checkbox-in-themes.patch b/build/cromite_patches/Enable-darken-websites-checkbox-in-themes.patch index b4dccda2a083e695967f7fa7db4a39b73f008b77..ee0bc9dc096436845dcfd0f6a2321461aa01d946 100644 --- a/build/cromite_patches/Enable-darken-websites-checkbox-in-themes.patch +++ b/build/cromite_patches/Enable-darken-websites-checkbox-in-themes.patch @@ -14,7 +14,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json -@@ -1856,9 +1856,9 @@ +@@ -1829,9 +1829,9 @@ "expiry_milestone": -1 }, { diff --git a/build/cromite_patches/Enable-native-Android-autofill.patch b/build/cromite_patches/Enable-native-Android-autofill.patch index 75890cd8932e15d65cf93939a9df955c5fa2c698..0a8f259870b43f7794dc8d5ba5e876ae16970279 100644 --- a/build/cromite_patches/Enable-native-Android-autofill.patch +++ b/build/cromite_patches/Enable-native-Android-autofill.patch @@ -190,7 +190,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manage diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java -@@ -430,8 +430,7 @@ public class MainSettings extends ChromeBaseSettingsFragment +@@ -461,8 +461,7 @@ public class MainSettings extends ChromeBaseSettingsFragment private void updateAutofillPreferences() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O @@ -217,7 +217,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.jav /** * Implementation of the interface {@link Tab}. Contains and manages a {@link ContentView}. This * class is not intended to be extended. -@@ -1079,6 +1086,11 @@ class TabImpl implements Tab { +@@ -1061,6 +1068,11 @@ class TabImpl implements Tab { for (TabObserver observer : mObservers) observer.onDestroyed(this); mObservers.clear(); @@ -229,7 +229,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.jav mUserDataHost.destroy(); mTabViewManager.destroy(); hideNativePage(false, null); -@@ -1372,6 +1384,7 @@ class TabImpl implements Tab { +@@ -1354,6 +1366,7 @@ class TabImpl implements Tab { * @return iff the AutofillProvider should provide a ViewStructure when prompted. */ boolean providesAutofillStructure() { @@ -237,7 +237,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.jav if (!ChromeFeatureList.isEnabled( AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID)) { return false; -@@ -2154,16 +2167,21 @@ class TabImpl implements Tab { +@@ -2136,16 +2149,21 @@ class TabImpl implements Tab { * @return true if the the provider is available for the given WebContents. */ private boolean prepareAutofillProvider(WebContents newWebContents) { @@ -265,7 +265,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.jav mAutofillProvider = new AutofillProvider( getContext(), -@@ -2172,7 +2190,16 @@ class TabImpl implements Tab { +@@ -2154,7 +2172,16 @@ class TabImpl implements Tab { getContext().getString(R.string.app_name)); TabImplJni.get().initializeAutofillIfNecessary(mNativeTabAndroid); } @@ -286,7 +286,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.jav diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc -@@ -3662,28 +3662,6 @@ const FeatureEntry::FeatureVariation kLinkPreviewTriggerTypeVariations[] = { +@@ -3651,28 +3651,6 @@ const FeatureEntry::FeatureVariation kLinkPreviewTriggerTypeVariations[] = { std::size(kLinkPreviewTriggerTypeLongPress), nullptr}}; #endif // !BUILDFLAG(IS_ANDROID) @@ -315,7 +315,7 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc const FeatureEntry::FeatureParam kPrerender2WarmUpCompositorTriggerPointDidCommitLoad[] = { {"trigger_point", "did_commit_load"}}; -@@ -6164,15 +6142,6 @@ const FeatureEntry kFeatureEntries[] = { +@@ -6182,15 +6160,6 @@ const FeatureEntry kFeatureEntries[] = { #endif // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) @@ -347,19 +347,19 @@ diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_ diff --git a/chrome/browser/autofill/android/BUILD.gn b/chrome/browser/autofill/android/BUILD.gn --- a/chrome/browser/autofill/android/BUILD.gn +++ b/chrome/browser/autofill/android/BUILD.gn -@@ -144,7 +144,7 @@ android_library("bottom_sheet_utils_java") { +@@ -148,7 +148,7 @@ android_library("bottom_sheet_utils_java") { generate_jni("jni_headers") { sources = [ "java/src/org/chromium/chrome/browser/autofill/AddressNormalizerFactory.java", - "java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java", + #"java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java", "java/src/org/chromium/chrome/browser/autofill/AutofillImageFetcher.java", + "java/src/org/chromium/chrome/browser/autofill/AutofillImageFetcherFactory.java", "java/src/org/chromium/chrome/browser/autofill/AutofillProfileBridge.java", - "java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java", diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java --- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java +++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java -@@ -54,8 +54,8 @@ public class AutofillClientProviderUtils { +@@ -55,8 +55,8 @@ public class AutofillClientProviderUtils { * @return {@link AndroidAutofillAvailabilityStatus.AVAILABLE} if Android Autofill can be used * or a reason why it can't. */ @@ -372,7 +372,7 @@ diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browse diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc -@@ -782,12 +782,6 @@ const char kAutofillVcnEnrollRequestTimeoutDescription[] = +@@ -808,12 +808,6 @@ const char kAutofillVcnEnrollRequestTimeoutDescription[] = "VCN enrollment request. Upon timeout, the client will terminate the VCN " "enrollment UI, but the request may still succeed server-side."; @@ -388,7 +388,7 @@ diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descripti diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h -@@ -463,9 +463,6 @@ extern const char kAutofillUploadCardRequestTimeoutDescription[]; +@@ -481,9 +481,6 @@ extern const char kAutofillUploadCardRequestTimeoutDescription[]; extern const char kAutofillVcnEnrollRequestTimeoutName[]; extern const char kAutofillVcnEnrollRequestTimeoutDescription[]; @@ -401,7 +401,7 @@ diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptio diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -700,6 +700,12 @@ For more settings that use data to improve your Chrome experience, go to Save passwords @@ -540,7 +540,7 @@ diff --git a/chrome/browser/ui/autofill/autofill_client_provider.h b/chrome/brow diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc -@@ -363,9 +363,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { +@@ -372,9 +372,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { web_contents); ChainedBackNavigationTracker::CreateForWebContents(web_contents); chrome_browser_net::NetErrorTabHelper::CreateForWebContents(web_contents); @@ -548,9 +548,9 @@ diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc - ChromePasswordManagerClient::CreateForWebContents(web_contents); - } + ChromePasswordManagerClient::CreateForWebContents(web_contents); + #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) ChromePasswordReuseDetectionManagerClient::CreateForWebContents(web_contents); - CreateSubresourceFilterWebContentsHelper(web_contents); - #if BUILDFLAG(ENABLE_RLZ) + #endif diff --git a/components/android_autofill/browser/BUILD.gn b/components/android_autofill/browser/BUILD.gn --- a/components/android_autofill/browser/BUILD.gn +++ b/components/android_autofill/browser/BUILD.gn @@ -589,7 +589,7 @@ diff --git a/components/android_autofill/browser/android_autofill_manager.h b/co diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java --- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java +++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java -@@ -51,6 +51,7 @@ public class AutofillManagerWrapper { +@@ -57,6 +57,7 @@ public class AutofillManagerWrapper { AutofillManagerWrapper manager = mManager.get(); if (manager == null) return; manager.mIsAutofillInputUiShowing = (event == EVENT_INPUT_SHOWN); @@ -597,7 +597,7 @@ diff --git a/components/android_autofill/browser/java/src/org/chromium/component if (event == EVENT_INPUT_SHOWN) manager.notifyInputUiChange(); } } -@@ -91,6 +92,7 @@ public class AutofillManagerWrapper { +@@ -99,6 +100,7 @@ public class AutofillManagerWrapper { // Uses Exception to catch various cases. (refer to crbug.com/1186406) Log.e(TAG, "getAutofillServiceComponentName", e); } @@ -605,7 +605,7 @@ diff --git a/components/android_autofill/browser/java/src/org/chromium/component if (componentName != null) { mPackageName = componentName.getPackageName(); mIsAwGCurrentAutofillService = -@@ -256,7 +258,7 @@ public class AutofillManagerWrapper { +@@ -266,7 +268,7 @@ public class AutofillManagerWrapper { /** Always check isLoggable() before call this method. */ public static void log(String log) { // Log.i() instead of Log.d() is used here because log.d() is stripped out in release build. @@ -614,7 +614,7 @@ diff --git a/components/android_autofill/browser/java/src/org/chromium/component } public static boolean isLoggable() { -@@ -269,6 +271,7 @@ public class AutofillManagerWrapper { +@@ -279,6 +281,7 @@ public class AutofillManagerWrapper { // NOTE: See the comment on TAG above for why this is still AwAutofillManager. // Check the system setting directly. sIsLoggable = android.util.Log.isLoggable(TAG, Log.DEBUG); @@ -625,7 +625,7 @@ diff --git a/components/android_autofill/browser/java/src/org/chromium/component diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java --- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java +++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java -@@ -195,13 +195,19 @@ public class AutofillProvider { +@@ -197,13 +197,19 @@ public class AutofillProvider { /** @return whether query autofill suggestion. */ public boolean shouldQueryAutofillSuggestion() { @@ -645,7 +645,7 @@ diff --git a/components/android_autofill/browser/java/src/org/chromium/component FocusField focusField = mRequest.getFocusField(); mAutofillManager.requestAutofill( mContainerView, -@@ -261,6 +267,7 @@ public class AutofillProvider { +@@ -263,6 +269,7 @@ public class AutofillProvider { float width, float height, boolean hasServerPrediction) { @@ -729,7 +729,7 @@ new file mode 100644 diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc --- a/components/autofill/content/browser/content_autofill_driver.cc +++ b/components/autofill/content/browser/content_autofill_driver.cc -@@ -252,6 +252,13 @@ void RouteToManager(ContentAutofillDriver& source, +@@ -251,6 +251,13 @@ void RouteToManager(ContentAutofillDriver& source, AutofillManager& manager = target.GetAutofillManager(); (manager.* manager_fun)(WithNewVersion(std::forward(args))...); @@ -743,7 +743,7 @@ diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/co }, source, Lift(source, std::forward(args))...); } -@@ -639,6 +646,10 @@ ContentAutofillDriver::GetAutofillAgent() { +@@ -637,6 +644,10 @@ ContentAutofillDriver::GetAutofillAgent() { return autofill_agent_; } @@ -813,7 +813,7 @@ diff --git a/components/autofill/content/browser/content_autofill_driver_factory diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc --- a/components/autofill/content/renderer/autofill_agent.cc +++ b/components/autofill/content/renderer/autofill_agent.cc -@@ -1423,12 +1423,16 @@ void AutofillAgent::ShowSuggestions( +@@ -1399,12 +1399,16 @@ void AutofillAgent::ShowSuggestions( password_generation_agent_->ShowPasswordGenerationSuggestions( input_element, form_cache)) { is_popup_possibly_visible_ = true; @@ -830,7 +830,7 @@ diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components } // Password field elements should only have suggestions shown by the -@@ -1436,11 +1440,13 @@ void AutofillAgent::ShowSuggestions( +@@ -1412,11 +1416,13 @@ void AutofillAgent::ShowSuggestions( // `FormControlTypeForAutofill()` because we are interested in whether the // field is *currently* a password field, not whether it has ever been a // password field. @@ -847,7 +847,7 @@ diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc --- a/components/autofill/content/renderer/password_autofill_agent.cc +++ b/components/autofill/content/renderer/password_autofill_agent.cc -@@ -912,7 +912,10 @@ void PasswordAutofillAgent::UpdatePasswordStateForTextChange( +@@ -843,7 +843,10 @@ void PasswordAutofillAgent::UpdatePasswordStateForTextChange( void PasswordAutofillAgent::TrackAutofilledElement( const WebFormControlElement& element) { @@ -900,7 +900,7 @@ diff --git a/components/autofill/core/browser/foundations/autofill_manager.h b/c diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc --- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc -@@ -644,6 +644,8 @@ base::WeakPtr BrowserAutofillManager::GetWeakPtr() { +@@ -639,6 +639,8 @@ base::WeakPtr BrowserAutofillManager::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } @@ -924,7 +924,7 @@ diff --git a/components/autofill/core/browser/foundations/browser_autofill_manag diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc -@@ -759,7 +759,7 @@ BASE_FEATURE(kAutofillThirdPartyModeContentProvider, +@@ -790,7 +790,7 @@ BASE_FEATURE(kAutofillThirdPartyModeContentProvider, BASE_FEATURE(kAutofillVirtualViewStructureAndroid, "AutofillVirtualViewStructureAndroid", base::FEATURE_DISABLED_BY_DEFAULT); @@ -936,7 +936,7 @@ diff --git a/components/autofill/core/common/autofill_features.cc b/components/a diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h --- a/components/autofill/core/common/autofill_features.h +++ b/components/autofill/core/common/autofill_features.h -@@ -269,28 +269,6 @@ COMPONENT_EXPORT(AUTOFILL) +@@ -278,28 +278,6 @@ COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillThirdPartyModeContentProvider); COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillVirtualViewStructureAndroid); @@ -989,7 +989,7 @@ diff --git a/components/autofill/core/common/autofill_prefs.cc b/components/auto diff --git a/components/autofill/core/common/autofill_prefs.h b/components/autofill/core/common/autofill_prefs.h --- a/components/autofill/core/common/autofill_prefs.h +++ b/components/autofill/core/common/autofill_prefs.h -@@ -121,6 +121,11 @@ inline constexpr char kAutofillUploadEventsLastResetTimestamp[] = +@@ -123,6 +123,11 @@ inline constexpr char kAutofillUploadEventsLastResetTimestamp[] = // retention policy was run. inline constexpr char kAutocompleteLastVersionRetentionPolicy[] = "autocomplete.retention_policy_last_version"; diff --git a/build/cromite_patches/Enable-network-isolation-features.patch b/build/cromite_patches/Enable-network-isolation-features.patch index ab372046bdddb8b5520fde8455af87941a3e68cb..d6eccc485a08e1671d23f00bb985ba0673c4b45e 100644 --- a/build/cromite_patches/Enable-network-isolation-features.patch +++ b/build/cromite_patches/Enable-network-isolation-features.patch @@ -9,10 +9,12 @@ PartitionNelAndReportingByNetworkIsolationKey, kSplitCacheByNavigationInitiator License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- - .../browser/net/profile_network_context_service.cc | 2 +- - .../Enable-network-isolation-features.inc | 12 ++++++++++++ - .../Enable-network-isolation-features.inc | 1 + - 3 files changed, 14 insertions(+), 1 deletion(-) + .../browser/net/profile_network_context_service.cc | 2 +- + .../Enable-network-isolation-features.inc | 9 +++++++++ + .../Enable-network-isolation-features.inc | 1 + + net/http/http_cache.cc | 14 +++++++++----- + net/http/http_cache.h | 2 +- + 5 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 cromite_flags/net/base/features_cc/Enable-network-isolation-features.inc create mode 100644 cromite_flags/services/network/public/cpp/features_cc/Enable-network-isolation-features.inc @@ -26,13 +28,13 @@ diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/brow - (field_trial ? field_trial->group_name() : "None"); + (field_trial ? field_trial->group_name() : "EnableFeatureForTests"); - // For the HTTP Cache keying experiments, if a flag indicates that the user is - // in an experiment group, modify `current_field_trial_status` to ensure that + if (disk_cache::InBackendExperiment()) { + if (disk_cache::InSimpleBackendExperimentGroup()) { diff --git a/cromite_flags/net/base/features_cc/Enable-network-isolation-features.inc b/cromite_flags/net/base/features_cc/Enable-network-isolation-features.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/net/base/features_cc/Enable-network-isolation-features.inc -@@ -0,0 +1,12 @@ +@@ -0,0 +1,9 @@ +SET_CROMITE_FEATURE_ENABLED(kSplitCodeCacheByNetworkIsolationKey); +SET_CROMITE_FEATURE_ENABLED(kPartitionConnectionsByNetworkIsolationKey); + @@ -40,15 +42,74 @@ new file mode 100644 + +// enable http cache partition +SET_CROMITE_FEATURE_ENABLED(kSplitCacheByNetworkIsolationKey); -+ -+// most restricted option (ByNavigationInitiator) used -+// it generates a cache key isolating all requests per initiator. -+// requests with initiators with an opaque origin are not cached -+SET_CROMITE_FEATURE_ENABLED(kSplitCacheByNavigationInitiator); ++// but disable cache for cross-site main frame navigation ++SET_CROMITE_FEATURE_ENABLED(kSplitCacheByCrossSiteMainFrameNavigationBoolean); diff --git a/cromite_flags/services/network/public/cpp/features_cc/Enable-network-isolation-features.inc b/cromite_flags/services/network/public/cpp/features_cc/Enable-network-isolation-features.inc new file mode 100644 --- /dev/null +++ b/cromite_flags/services/network/public/cpp/features_cc/Enable-network-isolation-features.inc @@ -0,0 +1 @@ +SET_CROMITE_FEATURE_ENABLED(kSplitAuthCacheByNetworkIsolationKey); +diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc +--- a/net/http/http_cache.cc ++++ b/net/http/http_cache.cc +@@ -633,7 +633,7 @@ bool HttpCache::CanGenerateCacheKeyForRequest(const HttpRequestInfo* request) { + + // static + // Generate a key that can be used inside the cache. +-std::string HttpCache::GenerateCacheKey( ++std::string HttpCache::GenerateCacheKey2( + const GURL& url, + int load_flags, + const NetworkIsolationKey& network_isolation_key, +@@ -671,8 +671,8 @@ std::string HttpCache::GenerateCacheKey( + const bool is_initiator_cross_site = + initiator_site != net::SchemefulSite(url); + if (is_initiator_cross_site) { +- is_cross_site_main_frame_navigation_prefix = +- kCrossSiteMainFrameNavigationPrefix; ++ DLOG(INFO) << "---initiator_site cs=" << base::StrCat({"ni_", initiator_site.Serialize(), " "}); ++ return ""; + } + } + isolation_key = base::StrCat( +@@ -687,9 +687,11 @@ std::string HttpCache::GenerateCacheKey( + // Strip out the reference, username, and password sections of the URL and + // concatenate with the credential_key, the post_key, and the network + // isolation key if we are splitting the cache. +- return base::StringPrintf("%c/%" PRId64 "/%s%s", credential_key, ++ auto key = base::StringPrintf("%c/%" PRId64 "/%s%s", credential_key, + upload_data_identifier, isolation_key.c_str(), + HttpUtil::SpecForRequest(url).c_str()); ++ DLOG(INFO) << "---key=" << key; ++ return key; + } + + // static +@@ -712,10 +714,12 @@ HttpCache::GenerateCacheKeyForRequestWithAlternateURL( + const int64_t upload_data_identifier = + request->upload_data_stream ? request->upload_data_stream->identifier() + : int64_t(0); +- return GenerateCacheKey( ++ auto key = GenerateCacheKey2( + url, request->load_flags, request->network_isolation_key, + upload_data_identifier, request->is_subframe_document_resource, + request->is_main_frame_navigation, request->initiator); ++ if (key.empty()) return std::nullopt; ++ return key; + } + + // static +diff --git a/net/http/http_cache.h b/net/http/http_cache.h +--- a/net/http/http_cache.h ++++ b/net/http/http_cache.h +@@ -509,7 +509,7 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory { + // Generates a cache key given the various pieces used to construct the key. + // Must not be called if a corresponding `CanGenerateCacheKeyForRequest` + // returns false. +- static std::string GenerateCacheKey( ++ static std::string GenerateCacheKey2( + const GURL& url, + int load_flags, + const NetworkIsolationKey& network_isolation_key, -- diff --git a/build/cromite_patches/Enable-share-intent.patch b/build/cromite_patches/Enable-share-intent.patch index e5e4c1c8ea947d0866f3a72250eebbabb90d77e1..edf2613dd651dce8135fbf0710e374aa1ad3dfb9 100644 --- a/build/cromite_patches/Enable-share-intent.patch +++ b/build/cromite_patches/Enable-share-intent.patch @@ -34,8 +34,8 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -577,6 +577,7 @@ chrome_java_resources = [ - "java/res/layout/signin_settings_card_view.xml", +@@ -582,6 +582,7 @@ chrome_java_resources = [ + "java/res/layout/signin_activity.xml", "java/res/layout/status_indicator_container.xml", "java/res/layout/suggestions_tile_view_condensed.xml", + "java/res/layout/sharing_intent_content.xml", @@ -45,9 +45,9 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -1029,6 +1029,7 @@ chrome_java_sources = [ +@@ -960,6 +960,7 @@ chrome_java_sources = [ + "java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java", "java/src/org/chromium/chrome/browser/signin/SyncConsentActivity.java", - "java/src/org/chromium/chrome/browser/signin/SyncConsentActivityLauncherImpl.java", "java/src/org/chromium/chrome/browser/signin/SyncConsentFragment.java", + "java/src/org/chromium/chrome/browser/sharing/shared_intent/SharedIntentShareActivity.java", "java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java", @@ -201,7 +201,7 @@ new file mode 100644 diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java --- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java -@@ -184,6 +184,9 @@ public class IntentHandler { +@@ -185,6 +185,9 @@ public class IntentHandler { private static final String EXTRA_TAB_LAUNCH_TYPE = "org.chromium.chrome.browser.tab_launch_type"; @@ -211,8 +211,8 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.j /** A hash code for the URL to verify intent data hasn't been modified. */ public static final String EXTRA_DATA_HASH_CODE = "org.chromium.chrome.browser.data_hash"; -@@ -1376,6 +1379,17 @@ public class IntentHandler { - return IntentUtils.safeGetSerializableExtra(intent, EXTRA_TAB_LAUNCH_TYPE); +@@ -1408,6 +1411,17 @@ public class IntentHandler { + return TabGroupMetadata.maybeCreateFromBundle(bundle); } + public static void setForceIncognitoMode(Intent intent) { @@ -229,7 +229,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.j /** * Creates an Intent that will launch a ChromeTabbedActivity on the new tab page. The Intent * will be trusted and therefore able to launch Incognito tabs. -@@ -1505,7 +1519,7 @@ public class IntentHandler { +@@ -1538,7 +1552,7 @@ public class IntentHandler { String headers = getExtraHeadersFromIntent(intent); headers = maybeAddAdditionalContentHeaders(intent, url, headers); @@ -293,8 +293,8 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessIni import org.chromium.chrome.browser.usb.UsbNotificationManager; import org.chromium.chrome.browser.util.AfterStartupTaskUtils; import org.chromium.chrome.browser.webapps.ChromeWebApkHost; -@@ -673,6 +674,7 @@ public class ProcessInitializationHandler { - tasks.add(ClearDataDialogResultRecorder::makeDeferredRecordings); +@@ -677,6 +678,7 @@ public class ProcessInitializationHandler { + tasks.add(WebApkUninstallTracker::runDeferredTasks); + tasks.add(SharedIntentShareActivity::updateComponentEnabledState); @@ -468,7 +468,7 @@ new file mode 100644 diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -324,6 +324,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -325,6 +325,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kSearchReadyOmniboxFeature, &kRelatedSearchesAllLanguage, &kRelatedSearchesSwitch, @@ -479,7 +479,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -474,6 +474,7 @@ public abstract class ChromeFeatureList { +@@ -485,6 +485,7 @@ public abstract class ChromeFeatureList { public static final String READALOUD_TAP_TO_SEEK = "ReadAloudTapToSeek"; public static final String READALOUD_IPH_MENU_BUTTON_HIGHLIGHT_CCT = "ReadAloudIPHMenuButtonHighlightCCT"; @@ -490,7 +490,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -5861,6 +5861,19 @@ To change this setting, BEGIN_LINKdelete the Chrome d +@@ -5923,6 +5923,19 @@ To change this setting, BEGIN_LINKdelete the Chrome d Make sure a phone app is enabled on this device diff --git a/build/cromite_patches/Experimental-user-scripts-support.patch b/build/cromite_patches/Experimental-user-scripts-support.patch index 509b907944308315698e8df62490d52ee0345bd1..a67fe71145de873c7058cc0ae9eb6a364bed1da1 100644 --- a/build/cromite_patches/Experimental-user-scripts-support.patch +++ b/build/cromite_patches/Experimental-user-scripts-support.patch @@ -226,7 +226,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -195,7 +195,11 @@ if (current_toolchain == default_toolchain) { +@@ -193,7 +193,11 @@ if (current_toolchain == default_toolchain) { sources = chrome_java_resources sources += [ "//chrome/android/java/res_app/layout/main.xml" ] @@ -239,7 +239,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn ":chrome_base_module_resources", ":ui_locale_string_resources", "//chrome/android/webapk/libs/common:splash_resources", -@@ -676,6 +680,7 @@ if (current_toolchain == default_toolchain) { +@@ -677,6 +681,7 @@ if (current_toolchain == default_toolchain) { "//components/ukm/android:java", "//components/url_formatter/android:url_formatter_java", "//components/user_prefs/android:java", @@ -250,7 +250,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/java/res/xml/main_preferences.xml --- a/chrome/android/java/res/xml/main_preferences.xml +++ b/chrome/android/java/res/xml/main_preferences.xml -@@ -149,6 +149,11 @@ for the previous order (main_preferences_legacy). --> +@@ -154,6 +154,11 @@ for the previous order (main_preferences_legacy). --> android:key="useragent_settings" android:order="20" android:title="@string/prefs_useragent_settings"/> @@ -273,7 +273,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/Downlo import java.io.File; -@@ -430,6 +431,11 @@ public class DownloadUtils { +@@ -429,6 +430,11 @@ public class DownloadUtils { String referrer, @DownloadOpenSource int source, Context context) { @@ -297,7 +297,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessIni /** * Handles the initialization dependences of the browser process. This is meant to handle the * initialization that is not tied to any particular Activity, and the logic that should only be -@@ -615,6 +617,8 @@ public class ProcessInitializationHandler { +@@ -620,6 +622,8 @@ public class ProcessInitializationHandler { DefaultBrowserInfo.initBrowserFetcher(); @@ -326,7 +326,7 @@ diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn -@@ -3480,6 +3480,13 @@ static_library("browser") { +@@ -3511,6 +3511,13 @@ static_library("browser") { ] deps += [ "//chrome/android/modules/dev_ui/provider:native" ] } @@ -343,7 +343,7 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc -@@ -187,6 +187,7 @@ +@@ -182,6 +182,7 @@ #include "components/ui_devtools/switches.h" #include "components/variations/variations_switches.h" #include "components/version_info/channel.h" @@ -354,7 +354,7 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -273,6 +273,7 @@ +@@ -268,6 +268,7 @@ #include "components/ntp_tiles/popular_sites_impl.h" #include "components/permissions/contexts/geolocation_permission_context_android.h" #include "components/webapps/browser/android/install_prompt_prefs.h" @@ -362,7 +362,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse #else // BUILDFLAG(IS_ANDROID) #include "chrome/browser/device_api/device_service_impl.h" #include "chrome/browser/gcm/gcm_product_util.h" -@@ -2050,6 +2051,9 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, +@@ -1958,6 +1959,9 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, translate::TranslatePrefs::RegisterProfilePrefs(registry); omnibox::RegisterProfilePrefs(registry); ZeroSuggestProvider::RegisterProfilePrefs(registry); @@ -375,7 +375,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn --- a/chrome/browser/profiles/BUILD.gn +++ b/chrome/browser/profiles/BUILD.gn -@@ -79,6 +79,9 @@ source_set("profile") { +@@ -80,6 +80,9 @@ source_set("profile") { "//content/public/browser", "//extensions/buildflags", ] @@ -388,9 +388,9 @@ diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -@@ -570,6 +570,10 @@ - #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || - // BUILDFLAG(IS_WIN) +@@ -560,6 +560,10 @@ + #include "chrome/browser/net/nss_service_factory.h" + #endif +#if BUILDFLAG(IS_ANDROID) +#include "components/user_scripts/browser/userscripts_browser_client.h" @@ -399,10 +399,10 @@ diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) { main_parts->AddParts(std::make_unique()); } -@@ -1374,6 +1378,9 @@ void ChromeBrowserMainExtraPartsProfiles:: +@@ -1359,6 +1363,9 @@ void ChromeBrowserMainExtraPartsProfiles:: + tab_groups::CollaborationMessagingObserverFactory::GetInstance(); } - #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || - // BUILDFLAG(IS_WIN) + #endif +#if BUILDFLAG(IS_ANDROID) + user_scripts::UserScriptsBrowserClient::GetInstance(); +#endif @@ -412,7 +412,7 @@ diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc --- a/chrome/browser/profiles/profile_manager.cc +++ b/chrome/browser/profiles/profile_manager.cc -@@ -123,6 +123,10 @@ +@@ -122,6 +122,10 @@ #include "extensions/common/manifest.h" #endif @@ -423,7 +423,7 @@ diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profile #if BUILDFLAG(ENABLE_SESSION_SERVICE) #include "chrome/browser/sessions/app_session_service_factory.h" #include "chrome/browser/sessions/session_service_factory.h" -@@ -1484,6 +1488,13 @@ void ProfileManager::DoFinalInitForServices(Profile* profile, +@@ -1494,6 +1498,13 @@ void ProfileManager::DoFinalInitForServices(Profile* profile, ChildAccountServiceFactory::GetForProfile(profile)->Init(); SupervisedUserServiceFactory::GetForProfile(profile)->Init(); ListFamilyMembersServiceFactory::GetForProfile(profile)->Init(); @@ -501,7 +501,7 @@ diff --git a/chrome/browser/profiles/renderer_updater.h b/chrome/browser/profile diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc --- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc +++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc -@@ -57,6 +57,9 @@ +@@ -56,6 +56,9 @@ #include "chrome/browser/ui/webui/proxy_config_ui.h" @@ -511,10 +511,10 @@ diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/u #if BUILDFLAG(ENABLE_NACL) #include "chrome/browser/ui/webui/nacl_ui.h" -@@ -392,4 +395,7 @@ void RegisterChromeWebUIConfigs() { - map.AddWebUIConfig(std::make_unique()); +@@ -399,4 +402,7 @@ void RegisterChromeWebUIConfigs() { + map.AddWebUIConfig( + std::make_unique()); #endif - map.AddWebUIConfig(std::make_unique()); +#if BUILDFLAG(IS_ANDROID) + map.AddWebUIConfig(std::make_unique()); +#endif @@ -2578,7 +2578,7 @@ new file mode 100644 --- /dev/null +++ b/components/user_scripts/browser/resources/browser_resources.grd @@ -0,0 +1,14 @@ -+ ++ + + + @@ -2592,7 +2592,6 @@ new file mode 100644 + + + -\ No newline at end of file diff --git a/components/user_scripts/browser/resources/user-script-ui/BUILD.gn b/components/user_scripts/browser/resources/user-script-ui/BUILD.gn new file mode 100644 --- /dev/null @@ -7587,7 +7586,7 @@ new file mode 100755 +++ b/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd @@ -0,0 +1,14 @@ + -+ ++ + + + @@ -10338,7 +10337,7 @@ diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h 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 -@@ -1099,6 +1099,12 @@ +@@ -1103,6 +1103,12 @@ "META": {"sizes": {"includes": [30],}}, "includes": [7480], }, diff --git a/build/cromite_patches/Eyeo-Adblock-for-Cromite.patch b/build/cromite_patches/Eyeo-Adblock-for-Cromite.patch index cd7898fa4260c9357aebd63a1a14d5d8d2125a7f..de0d60485953dd7574b4cb1192c90efcd3365422 100644 --- a/build/cromite_patches/Eyeo-Adblock-for-Cromite.patch +++ b/build/cromite_patches/Eyeo-Adblock-for-Cromite.patch @@ -3,24 +3,29 @@ Date: Thu, 29 Sep 2022 11:27:35 +0000 Subject: Eyeo Adblock for Cromite Change the normal behaviour of Eyeo Chromium SDK to -suit Bromite logic. -Add blocking in service workers -Activates the pop-up blocking management present in adblockplus -but not active in upstream. -Added "enable-stricter-popup-blocker" flag for the global -deactivation of all pop-ups (default disabled) +suit Cromite logic. --- .../android/java/res/xml/main_preferences.xml | 19 +- - chrome/browser/BUILD.gn | 2 - - .../adblock/adblock_content_browser_client.cc | 283 +- - .../adblock/adblock_content_browser_client.h | 46 +- + .../adblock_chrome_content_browser_client.cc | 6 + + .../adblock_chrome_content_browser_client.h | 4 + + chrome/browser/adblock/android/BUILD.gn | 2 +- + .../adblock/android/adblock_strings.grd | 56 +- + .../java/res/layout/adblock_custom_item.xml | 14 +- + .../layout/adblock_filter_lists_list_item.xml | 15 + + ...ences.xml => eyeo_adblock_preferences.xml} | 42 +- + .../AdblockAllowedDomainsFragment.java | 7 +- + .../AdblockCustomFilterListsFragment.java | 37 +- + .../AdblockCustomFiltersFragment.java | 7 +- + .../settings/AdblockCustomItemFragment.java | 20 +- + .../settings/AdblockFilterListsAdapter.java | 6 + + .../settings/AdblockSettingsFragment.java | 79 +- .../browser/chrome_content_browser_client.cc | 6 +- .../browser/chrome_content_browser_client.h | 6 +- - .../adblock_private/adblock_private_api.cc | 74 +- + .../adblock_private/adblock_private_api.cc | 76 +- .../api/adblock_private/adblock_private_api.h | 49 + - .../eyeo_filtering_private_api.cc | 25 +- + .../eyeo_filtering_private_api.cc | 24 +- + .../api/settings_private/prefs_util.cc | 14 + ...hrome_browser_main_extra_parts_profiles.cc | 2 - - .../resources/adblock_internals/BUILD.gn | 8 +- chrome/browser/resources/settings/BUILD.gn | 2 + .../settings/adblock_page/adblock_page.html | 212 + .../settings/adblock_page/adblock_page.ts | 286 + @@ -31,118 +36,105 @@ deactivation of all pop-ups (default disabled) chrome/browser/resources/settings/router.ts | 1 + chrome/browser/resources/settings/settings.ts | 1 + .../settings/settings_menu/settings_menu.html | 5 + - chrome/browser/ui/tab_helpers.cc | 6 +- - .../adblock_internals_page_handler_impl.cc | 21 +- - .../adblock_internals/adblock_internals_ui.cc | 3 +- - .../common/extensions/api/_api_features.json | 4 +- - .../extensions/api/_permission_features.json | 8 - + chrome/browser/ui/tab_helpers.cc | 3 +- + .../extensions/api/_permission_features.json | 14 - .../common/extensions/api/adblock_private.idl | 8 + - components/adblock/android/BUILD.gn | 3 +- - components/adblock/android/adblock_jni.cc | 53 +- - .../adblock/android/adblock_strings.grd | 54 +- - .../java/res/layout/adblock_custom_item.xml | 14 +- - .../layout/adblock_filter_lists_list_item.xml | 15 + - ...ences.xml => eyeo_adblock_preferences.xml} | 39 +- - .../components/adblock/AdblockController.java | 53 +- - .../AdblockAllowedDomainsFragment.java | 7 +- - .../AdblockCustomFilterListsFragment.java | 32 +- - .../AdblockCustomFiltersFragment.java | 7 +- - .../settings/AdblockCustomItemFragment.java | 20 +- - .../settings/AdblockFilterListsAdapter.java | 6 + - .../settings/AdblockSettingsFragment.java | 51 +- - components/adblock/content/browser/BUILD.gn | 18 +- - .../adblock_controller_factory_base.cc | 5 +- - .../browser/adblock_url_loader_factory.cc | 66 +- - .../browser/adblock_url_loader_factory.h | 7 +- - .../browser/adblock_webcontents_observer.cc | 25 +- + .../adblock/android/adblock_controller_jni.cc | 79 +- + .../components/adblock/AdblockController.java | 51 +- + components/adblock/content/browser/BUILD.gn | 24 +- + .../content/browser/adblock_blocking_page.cc | 182 + + .../content/browser/adblock_blocking_page.h | 75 + + .../browser/adblock_content_browser_client.h | 241 +- + .../browser/adblock_internals_page_handler.cc | 9 +- + .../browser/adblock_url_loader_factory.cc | 70 +- + .../browser/adblock_webcontents_observer.cc | 62 +- .../browser/adblock_webcontents_observer.h | 5 +- - .../content/browser/element_hider_impl.cc | 9 +- - .../browser/frame_hierarchy_builder.cc | 3 +- - .../browser/resource_classification_runner.h | 9 + - .../resource_classification_runner_impl.cc | 81 +- - .../resource_classification_runner_impl.h | 10 + - .../subscription_service_factory_base.cc | 11 +- - .../subscription_service_factory_base.h | 1 + + .../content/browser/element_hider_impl.cc | 13 +- + .../content/browser/eyeo_document_info.cc | 15 + + .../content/browser/eyeo_document_info.h | 9 + + .../adblock_request_throttle_factory.cc | 2 +- + .../adblock_telemetry_service_factory.cc | 7 - + .../browser/factories/embedding_utils.cc | 2 - + .../browser/factories/embedding_utils.h | 7 +- + .../factories/subscription_service_factory.cc | 22 +- + .../factories/subscription_service_factory.h | 1 + + .../browser/frame_hierarchy_builder.cc | 1 + + .../content/browser/page_view_stats.cc | 71 +- + .../browser/resource_classification_runner.h | 7 + + .../resource_classification_runner_impl.cc | 71 +- + .../resource_classification_runner_impl.h | 8 + components/adblock/core/BUILD.gn | 39 - - .../activeping_telemetry_topic_provider.cc | 285 - - .../activeping_telemetry_topic_provider.h | 87 - - .../adblock/core/adblock_controller_impl.cc | 10 +- - components/adblock/core/adblock_switches.cc | 1 - - components/adblock/core/adblock_switches.h | 1 - - .../adblock/core/adblock_telemetry_service.cc | 258 - - .../adblock/core/adblock_telemetry_service.h | 120 - components/adblock/core/common/BUILD.gn | 8 - .../adblock/core/common/adblock_constants.cc | 2 - .../adblock/core/common/adblock_constants.h | 1 - - .../adblock/core/common/adblock_prefs.cc | 47 +- - .../adblock/core/common/adblock_prefs.h | 1 + - .../adblock/core/common/adblock_utils.cc | 23 - - .../configuration/filtering_configuration.h | 3 + - .../persistent_filtering_configuration.cc | 10 + - .../persistent_filtering_configuration.h | 3 + + .../adblock/core/common/adblock_prefs.cc | 57 +- + .../adblock/core/common/adblock_prefs.h | 7 +- + components/adblock/core/common/app_info.cc | 12 - + .../configuration/filtering_configuration.h | 5 + + .../persistent_filtering_configuration.cc | 28 +- + .../persistent_filtering_configuration.h | 6 + .../core/converter/flatbuffer_converter.cc | 2 +- - .../adblock/core/converter/parser/metadata.cc | 10 +- + .../adblock/core/converter/parser/metadata.cc | 1 + .../core/converter/parser/test/test_rules.txt | 21 + - .../core/converter/parser/url_filter.cc | 10 +- + .../core/converter/parser/url_filter.cc | 15 +- .../converter/parser/url_filter_options.cc | 21 +- - .../converter/parser/url_filter_options.h | 1 + - .../serializer/flatbuffer_serializer.cc | 83 +- - components/adblock/core/features.cc | 3 +- - components/adblock/core/hash/schema_hash.h | 10 + - .../adblock/core/sitekey_storage_impl.cc | 11 +- + .../serializer/flatbuffer_serializer.cc | 102 +- + components/adblock/core/features.cc | 6 +- + .../core/net/adblock_resource_request_impl.cc | 26 +- + components/adblock/core/resources/.gitignore | 2 +- + components/adblock/core/resources/BUILD.gn | 5 +- + .../core/resources/adblock_resources.grd | 5 +- + .../core/resources/elemhide_for_selector.jst | 4 +- + .../adblock/core/resources/elemhideemu.jst | 2 + + .../resources/snippets/dist/dependencies.jst | 1690 ++++++ + .../dist/isolated-first-all.source.jst | 5256 +++++++++++++++++ + .../adblock/core/sitekey_storage_impl.cc | 17 +- .../core/subscription/conversion_executors.h | 1 + .../filtering_configuration_maintainer.h | 4 + - ...filtering_configuration_maintainer_impl.cc | 31 +- + ...filtering_configuration_maintainer_impl.cc | 35 +- .../filtering_configuration_maintainer_impl.h | 4 +- - .../ongoing_subscription_request_impl.cc | 33 +- .../preloaded_subscription_provider_impl.cc | 4 +- - .../adblock/core/subscription/subscription.cc | 18 + + ...recommended_subscription_installer_impl.cc | 1 + + .../adblock/core/subscription/subscription.cc | 20 + .../adblock/core/subscription/subscription.h | 3 + .../subscription_collection_impl.cc | 1 + - .../core/subscription/subscription_config.cc | 26 +- + .../core/subscription/subscription_config.cc | 40 +- .../core/subscription/subscription_config.h | 6 +- - .../subscription_downloader_impl.cc | 29 +- + .../subscription_downloader_impl.cc | 19 +- .../subscription_persistent_metadata.h | 1 + .../subscription_persistent_metadata_impl.cc | 7 + .../subscription_persistent_metadata_impl.h | 1 + .../subscription_persistent_storage_impl.cc | 11 +- - .../core/subscription/subscription_service.h | 9 + - .../subscription/subscription_service_impl.cc | 78 +- - .../subscription/subscription_service_impl.h | 9 + - .../subscription/subscription_updater_impl.cc | 8 +- + .../core/subscription/subscription_service.h | 11 + + .../subscription/subscription_service_impl.cc | 87 +- + .../subscription/subscription_service_impl.h | 10 + .../subscription_validator_impl.cc | 4 +- + components/adblock/features.gni | 2 +- components/blocked_content/popup_blocker.cc | 9 +- components/blocked_content/popup_blocker.h | 3 + .../browser/bromite_content_settings/ads.inc | 3 + - components/resources/BUILD.gn | 1 - - components/resources/adblock_resources.grdp | 3 - - components/resources/adblocking/.gitignore | 2 +- - components/resources/adblocking/BUILD.gn | 37 +- - .../adblocking/elemhide_for_selector.jst | 2 +- - .../resources/adblocking/elemhideemu.jst | 2 + - .../snippets/dist/isolated-first.jst | 66 + - .../snippets/dist/isolated-first.source.jst | 4793 +++++++++++++++++ + components/error_page_strings.grdp | 16 + .../websockets/websocket_connector_impl.cc | 6 +- .../public/browser/content_browser_client.cc | 4 +- .../public/browser/content_browser_client.h | 4 +- + content/public/common/isolated_world_ids.h | 2 +- .../about_flags_cc/Stricter-popup-blocker.inc | 14 + .../blink/renderer/core/css/style_engine.cc | 8 + .../blink/renderer/core/css/style_engine.h | 1 + - .../renderer/core/exported/web_document.cc | 15 +- + .../renderer/core/dom/events/event_target.cc | 5 +- + .../renderer/core/exported/web_document.cc | 13 +- + .../blink/renderer/core/html/html_element.cc | 8 +- .../definitions/adblock_private.d.ts | 14 + - 120 files changed, 6727 insertions(+), 1313 deletions(-) + 120 files changed, 9175 insertions(+), 617 deletions(-) + rename chrome/browser/adblock/android/java/res/xml/{adblock_preferences.xml => eyeo_adblock_preferences.xml} (59%) create mode 100644 chrome/browser/resources/settings/adblock_page/adblock_page.html create mode 100644 chrome/browser/resources/settings/adblock_page/adblock_page.ts - rename components/adblock/android/java/res/xml/{adblock_preferences.xml => eyeo_adblock_preferences.xml} (56%) - delete mode 100644 components/adblock/core/activeping_telemetry_topic_provider.cc - delete mode 100644 components/adblock/core/activeping_telemetry_topic_provider.h - delete mode 100644 components/adblock/core/adblock_telemetry_service.cc - delete mode 100644 components/adblock/core/adblock_telemetry_service.h + create mode 100644 components/adblock/content/browser/adblock_blocking_page.cc + create mode 100644 components/adblock/content/browser/adblock_blocking_page.h create mode 100644 components/adblock/core/converter/parser/test/test_rules.txt - create mode 100644 components/adblock/core/hash/schema_hash.h + create mode 100644 components/adblock/core/resources/snippets/dist/dependencies.jst + create mode 100644 components/adblock/core/resources/snippets/dist/isolated-first-all.source.jst create mode 100644 components/content_settings/core/browser/bromite_content_settings/ads.inc - create mode 100644 components/resources/adblocking/snippets/dist/isolated-first.jst - create mode 100644 components/resources/adblocking/snippets/dist/isolated-first.source.jst create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Stricter-popup-blocker.inc diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/java/res/xml/main_preferences.xml @@ -162,594 +154,685 @@ diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/j -@@ -129,6 +120,11 @@ for the previous order (main_preferences_legacy). --> +@@ -99,11 +90,6 @@ for the previous order (main_preferences_legacy). --> + android:key="autofill_options" + android:order="16" + android:title="@string/autofill_options_title" /> +- + + android:key="adblock" android:order="13" android:title="@string/prefs_adblock"/> + - android:key="accessibility" - android:order="24" - android:title="@string/prefs_accessibility"/> -- - GetOriginalProfile(); + } ++ ++HostContentSettingsMap* ++AdblockChromeContentBrowserClient::GetHostContentSettingsMap( ++ content::BrowserContext* current_browser_context) { ++ return HostContentSettingsMapFactory::GetForProfile(current_browser_context); ++} +diff --git a/chrome/browser/adblock/adblock_chrome_content_browser_client.h b/chrome/browser/adblock/adblock_chrome_content_browser_client.h +--- a/chrome/browser/adblock/adblock_chrome_content_browser_client.h ++++ b/chrome/browser/adblock/adblock_chrome_content_browser_client.h +@@ -20,12 +20,16 @@ + + #include "chrome/browser/chrome_content_browser_client.h" + #include "components/adblock/content/browser/adblock_content_browser_client.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" - #include "chrome/browser/profiles/profile.h" - #include "chrome/browser/ui/browser_navigator_params.h" - #include "components/adblock/content/browser/adblock_url_loader_factory.h" -@@ -40,6 +42,7 @@ - #include "content/public/browser/web_contents.h" - #include "mojo/public/cpp/bindings/self_owned_receiver.h" - #include "services/network/public/mojom/websocket.mojom.h" -+#include "services/network/public/mojom/web_transport.mojom.h" - #include "services/service_manager/public/cpp/binder_registry.h" - #include "third_party/blink/public/common/loader/url_loader_throttle.h" -@@ -53,17 +56,41 @@ + class AdblockChromeContentBrowserClient + : public adblock::AdblockContentBrowserClient { + private: + content::BrowserContext* GetBrowserContextForEyeoFactories( + content::BrowserContext* current_browser_context) override; ++ HostContentSettingsMap* GetHostContentSettingsMap( ++ content::BrowserContext* current_browser_context) override; + }; - namespace { + #endif // CHROME_BROWSER_ADBLOCK_ADBLOCK_CHROME_CONTENT_BROWSER_CLIENT_H_ +diff --git a/chrome/browser/adblock/android/BUILD.gn b/chrome/browser/adblock/android/BUILD.gn +--- a/chrome/browser/adblock/android/BUILD.gn ++++ b/chrome/browser/adblock/android/BUILD.gn +@@ -148,7 +148,7 @@ android_resources("java_ui_resources") { + "java/res/layout/adblock_custom_item_settings.xml", + "java/res/layout/adblock_filter_lists_list_item.xml", + "java/res/xml/adblock_more_options.xml", +- "java/res/xml/adblock_preferences.xml", ++ "java/res/xml/eyeo_adblock_preferences.xml", + ] + + deps = [ ":adblock_strings_grd" ] +diff --git a/chrome/browser/adblock/android/adblock_strings.grd b/chrome/browser/adblock/android/adblock_strings.grd +--- a/chrome/browser/adblock/android/adblock_strings.grd ++++ b/chrome/browser/adblock/android/adblock_strings.grd +@@ -15,7 +15,7 @@ + You should have received a copy of the GNU General Public License + along with eyeo Chromium SDK. If not, see . + --> +- ++ + + + +@@ -186,10 +186,52 @@ + + + +- Ad blocking ++ Adblock Plus settings ++ ++ ++ Enable Adblock Plus ++ ++ ++ Check for updates now ++ ++ ++ Enable anti-circumvention and snippets ++ ++ ++ Snippets are pieces of JavaScript code, injected by the Adblock Plus, that execute within the context of a website and combat advanced ads that circumvent ordinary blocking. ++The functionality is ONLY allowed for the list ++https://www.cromite.org/filters/abp-filters-anti-cv.txt ++which is activated by this setting. ++ ++ ++ Open ABP anti-circumvention filter list repo ++ ++ ++ Open https://gitlab.com/eyeo/anti-cv/abp-filters-anti-cv in the browser ++ ++ ++ https://gitlab.com/eyeo/anti-cv/abp-filters-anti-cv ++ ++ ++ Open ABP Snippets Overview ++ ++ ++ Open https://developers.eyeo.com/snippets/snippets-overview in the browser ++ ++ ++ https://developers.eyeo.com/snippets/snippets-overview ++ ++ ++ Filter lists (%s selected) ++ ++ ++ Custom ad filtering settings (%s selected) ++ ++ ++ Custom Filters (%s selected) + + +- Allow ad blocking on websites in this app ++ Block ads on websites + + + Filter lists +@@ -222,10 +264,10 @@ + More blocking options + + +- Custom ad filtering settings ++ Custom ad filtering urls + + +- Add custom filter lists ++ Add custom filter urls + + + https://example.org/myFilterList.txt +@@ -234,10 +276,10 @@ + Custom Filters + + +- Add custom filters ++ Add custom filters commands + + +- Enter filter ++ Enter filter commands + + + +diff --git a/chrome/browser/adblock/android/java/res/layout/adblock_custom_item.xml b/chrome/browser/adblock/android/java/res/layout/adblock_custom_item.xml +--- a/chrome/browser/adblock/android/java/res/layout/adblock_custom_item.xml ++++ b/chrome/browser/adblock/android/java/res/layout/adblock_custom_item.xml +@@ -25,14 +25,24 @@ + tools:ignore="UseCompoundDrawables"> + + +- ++ ++ ++ + + + + +@@ -32,6 +32,43 @@ + app:iconSpaceReserved="false" + android:summary="@string/fragment_adblock_settings_filter_lists_summary" /> -+bool IsFilteringNeeded(Profile* profile, const GURL& embedder_url) { -+ DCHECK(profile); ++ + -+ if(embedder_url.is_empty()) { -+ // in android can be empty because it was created by -+ // RenderFrameHostImpl::CreateSubresourceLoaderFactoriesForInitialEmptyDocument -+ return true; -+ } ++ + -+ if (embedder_url.SchemeIs(content_settings::kChromeUIScheme)) { -+ return false; -+ } ++ + -+ HostContentSettingsMap* settings_map = HostContentSettingsMapFactory::GetForProfile(profile); -+ if (settings_map && settings_map->GetContentSetting(embedder_url, GURL(), ContentSettingsType::ADS) -+ == CONTENT_SETTING_ALLOW) { -+ return false; -+ } -+ // Filtering may be needed if there's at least one enabled -+ // FilteringConfiguration. -+ bool ret = std::ranges::any_of( -+ adblock::SubscriptionServiceFactory::GetForBrowserContext(profile) -+ ->GetInstalledFilteringConfigurations(), -+ &adblock::FilteringConfiguration::IsEnabled); -+ return ret; -+} + - bool IsFilteringNeeded(content::RenderFrameHost* frame) { - if (frame) { - auto* profile = - Profile::FromBrowserContext(frame->GetProcess()->GetBrowserContext()); - if (profile) { -- // Filtering may be needed if there's at least one enabled -- // FilteringConfiguration. -- return base::ranges::any_of( -- adblock::SubscriptionServiceFactory::GetForBrowserContext(profile) -- ->GetInstalledFilteringConfigurations(), -- &adblock::FilteringConfiguration::IsEnabled); -+ content::RenderFrameHost* embedder = frame->GetOutermostMainFrameOrEmbedder(); -+ const auto& embedder_url = embedder->GetLastCommittedURL(); -+ return IsFilteringNeeded(profile, embedder_url); - } - } - return false; -@@ -78,10 +105,11 @@ class AdblockContextData : public base::SupportsUserData::Data { ++ ++ ++ ++ ++ ++ + + ++ android:summary="@string/fragment_adblock_settings_auto_install_enabled_summary" ++ app:isPreferenceVisible="false" /> - static void StartProxying( - Profile* profile, -+ content::BrowserContext* browser_context, -+ const url::Origin& request_initiator, - content::RenderFrameHost* frame, - int render_process_id, -- mojo::PendingReceiver receiver, -- mojo::PendingRemote target_factory, -+ network::URLLoaderFactoryBuilder& factory_builder, - bool use_test_loader) { - const void* const kAdblockContextUserDataKey = &kAdblockContextUserDataKey; - auto* self = static_cast( -@@ -90,8 +118,6 @@ class AdblockContextData : public base::SupportsUserData::Data { - self = new AdblockContextData(); - profile->SetUserData(kAdblockContextUserDataKey, base::WrapUnique(self)); - } -- auto* browser_context = -- content::WebContents::FromRenderFrameHost(frame)->GetBrowserContext(); - adblock::AdblockURLLoaderFactoryConfig config{ - adblock::SubscriptionServiceFactory::GetForBrowserContext( - browser_context), -@@ -101,28 +127,12 @@ class AdblockContextData : public base::SupportsUserData::Data { - adblock::SitekeyStorageFactory::GetForBrowserContext(browser_context), - adblock::ContentSecurityPolicyInjectorFactory::GetForBrowserContext( - browser_context)}; --#ifdef EYEO_INTERCEPT_DEBUG_URL -- if (use_test_loader) { -- auto proxy = std::make_unique( -- std::move(config), -- content::GlobalRenderFrameHostId(render_process_id, -- frame->GetRoutingID()), -- std::move(receiver), std::move(target_factory), -- embedder_support::GetUserAgent(), -- base::BindOnce(&AdblockContextData::RemoveProxy, -- self->weak_factory_.GetWeakPtr()), -- adblock::SubscriptionServiceFactory::GetForBrowserContext( -- Profile::FromBrowserContext( -- frame->GetProcess()->GetBrowserContext()))); -- self->proxies_.emplace(std::move(proxy)); -- return; -- } --#endif - auto proxy = std::make_unique( - std::move(config), -+ request_initiator.GetURL(), - content::GlobalRenderFrameHostId(render_process_id, -- frame->GetRoutingID()), -- std::move(receiver), std::move(target_factory), -+ frame ? frame->GetRoutingID() : MSG_ROUTING_NONE), -+ factory_builder, - embedder_support::GetUserAgent(), - base::BindOnce(&AdblockContextData::RemoveProxy, - self->weak_factory_.GetWeakPtr())); -@@ -162,47 +172,50 @@ void AdblockContentBrowserClient::ForceAdblockProxyForTesting() { - #endif + + GetBrowserContext(); -+ auto* profile = Profile::FromBrowserContext(browser_context); -+ if (IsFilteringNeeded(profile, origin.GetURL().GetAsReferrer())) { -+ return true; -+ } - } + import java.util.List; -- return ChromeContentBrowserClient::WillInterceptWebSocket(frame); -+ return ChromeContentBrowserClient::WillInterceptWebSocket(frame, process, origin); - } +-public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment { ++public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment { + public AdblockAllowedDomainsFragment() {} - void AdblockContentBrowserClient::CreateWebSocket( -+ content::RenderProcessHost* process, - content::RenderFrameHost* frame, - WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - handshake_client) { -- if (IsFilteringNeeded(frame)) { -- CreateWebSocketInternal(frame->GetGlobalId(), std::move(factory), url, -- site_for_cookies, user_agent, -- std::move(handshake_client)); -- } else { -- DCHECK(ChromeContentBrowserClient::WillInterceptWebSocket(frame)); -- ChromeContentBrowserClient::CreateWebSocket(frame, std::move(factory), url, -- site_for_cookies, user_agent, -- std::move(handshake_client)); -- } -+ CreateWebSocketInternal(process, -+ frame ? frame->GetGlobalId() : content::GlobalRenderFrameHostId(), -+ std::move(factory), url, initiator_origin, -+ site_for_cookies, user_agent, -+ std::move(handshake_client)); - } + @Override +@@ -34,6 +34,11 @@ public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment { + getActivity().setTitle(R.string.fragment_adblock_settings_allowed_domains_title); + } - void AdblockContentBrowserClient::CreateWebSocketInternal( -+ content::RenderProcessHost* process, - content::GlobalRenderFrameHostId render_frame_host_id, - WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - handshake_client) { -- auto* frame = content::RenderFrameHost::FromID(render_frame_host_id); -- if (!frame) { -- return; -- } -- auto* browser_context = frame->GetProcess()->GetBrowserContext(); -+ auto* browser_context = process->GetBrowserContext(); - auto* subscription_service = - adblock::SubscriptionServiceFactory::GetForBrowserContext( - browser_context); -@@ -210,33 +223,33 @@ void AdblockContentBrowserClient::CreateWebSocketInternal( - adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( - browser_context); - classification_runner->CheckRequestFilterMatchForWebSocket( -- subscription_service->GetCurrentSnapshot(), url, render_frame_host_id, -+ subscription_service->GetCurrentSnapshot(), url, -+ std::move(initiator_origin.GetURL().GetAsReferrer()), render_frame_host_id, - base::BindOnce( - &AdblockContentBrowserClient::OnWebSocketFilterCheckCompleted, -- weak_factory_.GetWeakPtr(), render_frame_host_id, std::move(factory), -- url, site_for_cookies, user_agent, std::move(handshake_client))); -+ weak_factory_.GetWeakPtr(), process, render_frame_host_id, std::move(factory), -+ url, initiator_origin, site_for_cookies, user_agent, std::move(handshake_client))); - } ++ @Override ++ protected String getItemText(String item) { ++ return item; ++ } ++ + @Override + protected List getItems() { + return AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java +@@ -26,13 +26,14 @@ import android.widget.Toast; + import org.chromium.chrome.browser.adblock.R; + import org.chromium.chrome.browser.profiles.ProfileManager; + import org.chromium.components.adblock.AdblockController; ++import org.chromium.components.adblock.AdblockController.Subscription; - void AdblockContentBrowserClient::OnWebSocketFilterCheckCompleted( -+ content::RenderProcessHost* process, - content::GlobalRenderFrameHostId render_frame_host_id, - ChromeContentBrowserClient::WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - 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 (ChromeContentBrowserClient::WillInterceptWebSocket(frame)) { -+ if (ChromeContentBrowserClient::WillInterceptWebSocket(frame, process, initiator_origin)) { - ChromeContentBrowserClient::CreateWebSocket( -- frame, std::move(factory), url, site_for_cookies, user_agent, -+ process, frame, std::move(factory), url, initiator_origin, site_for_cookies, user_agent, - std::move(handshake_client)); - return; + import java.net.MalformedURLException; + import java.net.URL; + import java.util.ArrayList; + import java.util.List; + +-public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment { ++public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment { + private static final String TAG = AdblockCustomFilterListsFragment.class.getSimpleName(); + + public AdblockCustomFilterListsFragment() {} +@@ -44,29 +45,19 @@ public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment } -@@ -253,15 +266,134 @@ void AdblockContentBrowserClient::OnWebSocketFilterCheckCompleted( - VLOG(1) << "[eyeo] Web socket blocked for " << url; - } --bool AdblockContentBrowserClient::WillCreateURLLoaderFactory( -+void AdblockContentBrowserClient::WillCreateWebTransport( -+ int process_id, -+ int frame_routing_id, -+ const GURL& url, -+ const url::Origin& initiator_origin, -+ mojo::PendingRemote -+ handshake_client, -+ WillCreateWebTransportCallback callback) { -+ auto* process = content::RenderProcessHost::FromID(process_id); -+ DCHECK(process); -+ -+ auto* browser_context = process->GetBrowserContext(); -+ auto* profile = Profile::FromBrowserContext(browser_context); -+ if (IsFilteringNeeded(profile, initiator_origin.GetURL().GetAsReferrer())) { -+ auto* subscription_service = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context); -+ auto* classification_runner = -+ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( -+ browser_context); -+ -+ classification_runner->CheckRequestFilterMatchForWebTransport( -+ subscription_service->GetCurrentSnapshot(), url, -+ std::move(initiator_origin.GetURL().GetAsReferrer()), -+ content::GlobalRenderFrameHostId(), -+ base::BindOnce( -+ &AdblockContentBrowserClient::OnWebTransportFilterCheckCompleted, -+ weak_factory_.GetWeakPtr(), -+ process_id, frame_routing_id, url, -+ std::move(initiator_origin), std::move(handshake_client), -+ std::move(callback))); -+ return; -+ } -+ -+ ChromeContentBrowserClient::WillCreateWebTransport( -+ process_id, frame_routing_id, -+ url, std::move(initiator_origin), -+ std::move(handshake_client), std::move(callback)); -+} -+ -+void AdblockContentBrowserClient::OnWebTransportFilterCheckCompleted( -+ int process_id, -+ int frame_routing_id, -+ const GURL& url, -+ const url::Origin& initiator_origin, -+ mojo::PendingRemote -+ handshake_client, -+ WillCreateWebTransportCallback callback, -+ adblock::FilterMatchResult result) { -+ const bool has_blocking_filter = -+ result == adblock::FilterMatchResult::kBlockRule; -+ if (!has_blocking_filter) { -+ VLOG(1) << "[eyeo] Web transport allowed for " << url; -+ ChromeContentBrowserClient::WillCreateWebTransport( -+ process_id, frame_routing_id, -+ url, std::move(initiator_origin), -+ std::move(handshake_client), std::move(callback)); -+ return; -+ } -+ VLOG(1) << "[eyeo] Web transport blocked for " << url; -+ std::move(callback).Run(std::move(handshake_client), -+ network::mojom::WebTransportError::New( -+ net::ERR_BLOCKED_BY_ADMINISTRATOR, quic::QUIC_INTERNAL_ERROR, -+ "Blocked", false)); -+} -+ -+bool AdblockContentBrowserClient::CanCreateWindow( -+ content::RenderFrameHost* opener, -+ const GURL& opener_url, -+ const GURL& opener_top_level_frame_url, -+ const url::Origin& source_origin, -+ content::mojom::WindowContainerType container_type, -+ const GURL& target_url, -+ const content::Referrer& referrer, -+ const std::string& frame_name, -+ WindowOpenDisposition disposition, -+ const blink::mojom::WindowFeatures& features, -+ bool user_gesture, -+ bool opener_suppressed, -+ bool* no_javascript_access) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ DCHECK(opener); -+ -+ if (IsFilteringNeeded(opener)) { -+ content::WebContents* web_contents = -+ content::WebContents::FromRenderFrameHost(opener); -+ auto* subscription_service = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ web_contents->GetBrowserContext()); -+ -+ GURL popup_url(target_url); -+ web_contents->GetPrimaryMainFrame()->GetProcess()->FilterURL(false, -+ &popup_url); -+ auto* classification_runner = -+ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( -+ web_contents->GetBrowserContext()); -+ const auto popup_blocking_decision = -+ classification_runner->ShouldBlockPopup( -+ subscription_service->GetCurrentSnapshot(), popup_url, opener); -+ if (popup_blocking_decision == adblock::FilterMatchResult::kAllowRule) { -+ return true; -+ } -+ if (popup_blocking_decision == adblock::FilterMatchResult::kBlockRule) { -+ return false; + @Override +- protected List getItems() { +- final List installed = +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .getInstalledSubscriptions(); +- final List recommended = +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .getRecommendedSubscriptions(); +- final List customStrings = new ArrayList(); +- for (final AdblockController.Subscription subscription : installed) { +- if (recommended.contains(subscription)) { +- continue; +- } +- // FIXME(kzlomek): Remove this after DPD-1613 +- if (subscription +- .url() +- .toString() +- .equals("https://easylist-downloads.adblockplus.org/exceptionrules.txt")) { +- continue; +- } +- customStrings.add(subscription.url().toString()); +- } ++ protected List getItems() { ++ return AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .getCustomSubscriptions(); + } -+ // Otherwise, if eyeo adblocking is disabled or there is no rule that -+ // explicitly allows or blocks a popup, fall back on Chromium's built-in -+ // popup blocker. -+ DCHECK(popup_blocking_decision == adblock::FilterMatchResult::kDisabled || -+ popup_blocking_decision == adblock::FilterMatchResult::kNoRule); -+ } -+ -+ return ChromeContentBrowserClient::CanCreateWindow( -+ opener, opener_url, opener_top_level_frame_url, source_origin, -+ container_type, target_url, referrer, frame_name, disposition, features, -+ user_gesture, opener_suppressed, no_javascript_access); -+} + -+void AdblockContentBrowserClient::WillCreateURLLoaderFactory( - content::BrowserContext* browser_context, - content::RenderFrameHost* frame, - int render_process_id, - URLLoaderFactoryType type, - const url::Origin& request_initiator, -+ const net::IsolationInfo& isolation_info, - std::optional navigation_id, - ukm::SourceIdObj ukm_source_id, -- mojo::PendingReceiver* factory_receiver, -+ network::URLLoaderFactoryBuilder& factory_builder, - mojo::PendingRemote* - header_client, - bool* bypass_redirect_checks, -@@ -270,30 +402,17 @@ bool AdblockContentBrowserClient::WillCreateURLLoaderFactory( - scoped_refptr navigation_response_task_runner) { - // Create Chromium proxy first as WebRequestProxyingURLLoaderFactory logic - // depends on being first proxy -- bool use_chrome_proxy = -- ChromeContentBrowserClient::WillCreateURLLoaderFactory( -- browser_context, frame, render_process_id, type, request_initiator, -- navigation_id, ukm_source_id, factory_receiver, header_client, -- bypass_redirect_checks, disable_secure_dns, factory_override, -- navigation_response_task_runner); -- auto* profile = frame ? Profile::FromBrowserContext( -- frame->GetProcess()->GetBrowserContext()) -- : nullptr; -- --#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 use_chrome_proxy; -- } --#endif -+ ChromeContentBrowserClient::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); -+ auto* profile = Profile::FromBrowserContext(browser_context); ++ @Override ++ protected String getItemText(Subscription item) { ++ return item.url().toString(); ++ } - bool use_adblock_proxy = -- (type == URLLoaderFactoryType::kDocumentSubResource || -- type == URLLoaderFactoryType::kNavigation) && -- IsFilteringNeeded(frame); -+ type != URLLoaderFactoryType::kDownload && -+ (frame ? IsFilteringNeeded(frame) -+ : IsFilteringNeeded(profile, request_initiator.GetURL().GetAsReferrer())); - - bool use_test_loader = false; - #ifdef EYEO_INTERCEPT_DEBUG_URL -@@ -308,12 +427,8 @@ bool AdblockContentBrowserClient::WillCreateURLLoaderFactory( - #endif +- return customStrings; ++ @Override ++ protected String getItemStatus(Subscription item) { ++ return item.getDescription(); + } - if (use_adblock_proxy) { -- auto proxied_receiver = std::move(*factory_receiver); -- mojo::PendingRemote target_factory_remote; -- *factory_receiver = target_factory_remote.InitWithNewPipeAndPassReceiver(); - AdblockContextData::StartProxying( -- profile, frame, render_process_id, std::move(proxied_receiver), -- std::move(target_factory_remote), use_test_loader); -+ profile, browser_context, request_initiator, frame, render_process_id, -+ factory_builder, use_test_loader); - } -- return use_adblock_proxy || use_chrome_proxy; - } -diff --git a/chrome/browser/adblock/adblock_content_browser_client.h b/chrome/browser/adblock/adblock_content_browser_client.h ---- a/chrome/browser/adblock/adblock_content_browser_client.h -+++ b/chrome/browser/adblock/adblock_content_browser_client.h -@@ -45,25 +45,37 @@ class AdblockContentBrowserClient : public ChromeContentBrowserClient { - static void ForceAdblockProxyForTesting(); - #endif + @Override +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java +@@ -25,9 +25,14 @@ import org.chromium.components.adblock.AdblockController; -- bool WillInterceptWebSocket(content::RenderFrameHost* frame) override; -+ bool WillInterceptWebSocket(content::RenderFrameHost* frame, content::RenderProcessHost* process, const url::Origin& origin) override; - void CreateWebSocket( -+ content::RenderProcessHost* process, - content::RenderFrameHost* frame, - WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - handshake_client) override; + import java.util.List; -- bool WillCreateURLLoaderFactory( -+ void WillCreateWebTransport( -+ int process_id, -+ int frame_routing_id, -+ const GURL& url, -+ const url::Origin& initiator_origin, -+ mojo::PendingRemote -+ handshake_client, -+ WillCreateWebTransportCallback callback) override; -+ -+ void WillCreateURLLoaderFactory( - content::BrowserContext* browser_context, - content::RenderFrameHost* frame, - int render_process_id, - URLLoaderFactoryType type, - const url::Origin& request_initiator, -+ const net::IsolationInfo& isolation_info, - std::optional navigation_id, - ukm::SourceIdObj ukm_source_id, -- mojo::PendingReceiver* factory_receiver, -+ network::URLLoaderFactoryBuilder& factory_builder, - mojo::PendingRemote* - header_client, - bool* bypass_redirect_checks, -@@ -72,25 +84,51 @@ class AdblockContentBrowserClient : public ChromeContentBrowserClient { - scoped_refptr navigation_response_task_runner) - override; - -+ bool CanCreateWindow(content::RenderFrameHost* opener, -+ const GURL& opener_url, -+ const GURL& opener_top_level_frame_url, -+ const url::Origin& source_origin, -+ content::mojom::WindowContainerType container_type, -+ const GURL& target_url, -+ const content::Referrer& referrer, -+ const std::string& frame_name, -+ WindowOpenDisposition disposition, -+ const blink::mojom::WindowFeatures& features, -+ bool user_gesture, -+ bool opener_suppressed, -+ bool* no_javascript_access) override; +-public class AdblockCustomFiltersFragment extends AdblockCustomItemFragment { ++public class AdblockCustomFiltersFragment extends AdblockCustomItemFragment { + public AdblockCustomFiltersFragment() {} + ++ @Override ++ protected String getItemText(String item) { ++ return item; ++ } + - private: - void CreateWebSocketInternal( -+ content::RenderProcessHost* process, - content::GlobalRenderFrameHostId render_frame_host_id, - WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - handshake_client); - void OnWebSocketFilterCheckCompleted( -+ content::RenderProcessHost* process, - content::GlobalRenderFrameHostId render_frame_host_id, - ChromeContentBrowserClient::WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, - mojo::PendingRemote - handshake_client, - adblock::FilterMatchResult result); -- -+ void OnWebTransportFilterCheckCompleted( -+ int process_id, -+ int frame_routing_id, -+ const GURL& url, -+ const url::Origin& initiator_origin, -+ mojo::PendingRemote -+ handshake_client, -+ WillCreateWebTransportCallback callback, -+ adblock::FilterMatchResult result); - base::WeakPtrFactory weak_factory_{this}; + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java +@@ -33,7 +33,7 @@ import org.chromium.chrome.browser.adblock.R; - #if BUILDFLAG(ENABLE_EXTENSIONS) -diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc ---- a/chrome/browser/chrome_content_browser_client.cc -+++ b/chrome/browser/chrome_content_browser_client.cc -@@ -6621,7 +6621,9 @@ ChromeContentBrowserClient:: - } + import java.util.List; - bool ChromeContentBrowserClient::WillInterceptWebSocket( -- content::RenderFrameHost* frame) { -+ content::RenderFrameHost* frame, -+ content::RenderProcessHost* process, -+ const url::Origin& origin) { - #if BUILDFLAG(ENABLE_EXTENSIONS) - if (!frame) { - return false; -@@ -6643,9 +6645,11 @@ bool ChromeContentBrowserClient::WillInterceptWebSocket( - } +-public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat { ++public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat { + private EditText mItem; + private ImageView mAddButton; + private ListView mListView; +@@ -75,7 +75,7 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat - void ChromeContentBrowserClient::CreateWebSocket( -+ content::RenderProcessHost* process, - content::RenderFrameHost* frame, - WebSocketFactory factory, - const GURL& url, -+ const url::Origin& initiator_origin, - const net::SiteForCookies& site_for_cookies, - const std::optional& user_agent, + protected abstract void removeItemImpl(String item); + +- protected abstract List getItems(); ++ protected abstract List getItems(); + + protected abstract String getCustomItemTextViewText(); + +@@ -90,10 +90,12 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat + // Holder for listview items + private class Holder { + TextView mItem; ++ TextView mStatus; + ImageView mRemoveButton; + + Holder(View rootView) { + mItem = rootView.findViewById(R.id.fragment_adblock_custom_item_title); ++ mStatus = rootView.findViewById(R.id.fragment_adblock_custom_item_status); + mRemoveButton = rootView.findViewById(R.id.fragment_adblock_custom_item_remove); + mRemoveButton.setContentDescription( + AdblockCustomItemFragment.this.getCustomItemRemoveButtonContentDescription()); +@@ -107,6 +109,7 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat + String item = (String) v.getTag(); + removeItemImpl(item); + mAdapter.notifyDataSetChanged(); ++ mItem.setText(item); + } + }; + +@@ -134,16 +137,23 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat + convertView.setTag(new Holder(convertView)); + } + +- String item = (String) getItem(position); ++ T item = (T) getItem(position); + Holder holder = (Holder) convertView.getTag(); +- holder.mItem.setText(item.toString()); ++ holder.mItem.setText(getItemText(item)); ++ holder.mStatus.setText(getItemStatus(item)); + holder.mRemoveButton.setOnClickListener(removeItemClickListener); +- holder.mRemoveButton.setTag(item.toString()); ++ holder.mRemoveButton.setTag(getItemText(item)); + + return convertView; + } + } + ++ protected abstract String getItemText(T item); ++ ++ protected String getItemStatus(T item) { ++ return null; ++ } ++ + private void initControls() { + mAddButton.setOnClickListener( + new View.OnClickListener() { +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java +@@ -79,10 +79,13 @@ public class AdblockFilterListsAdapter extends BaseAdapter implements OnClickLis + mController.getInstalledSubscriptions(); + boolean subscribed = false; + boolean autoinstalled = false; ++ TextView status = view.findViewById(R.id.status); ++ status.setText(""); + for (final AdblockController.Subscription subscription : subscriptions) { + if (subscription.url().equals(item.url())) { + subscribed = true; + autoinstalled = subscription.autoinstalled(); ++ status.setText(subscription.getDescription()); + break; + } + } +@@ -93,6 +96,9 @@ public class AdblockFilterListsAdapter extends BaseAdapter implements OnClickLis + TextView description = view.findViewById(R.id.name); + description.setText(item.title()); + description.setContentDescription(item.title() + "filer list item title text"); ++ ++ TextView url = view.findViewById(R.id.url); ++ url.setText(item.url().toString()); + return view; + } + +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java +@@ -13,6 +13,7 @@ + package org.chromium.chrome.browser.adblock.settings; + + import android.os.Bundle; ++import android.widget.Toast; + + import androidx.preference.Preference; + import androidx.preference.PreferenceFragmentCompat; +@@ -20,20 +21,33 @@ import androidx.preference.PreferenceFragmentCompat; + import org.chromium.build.BuildConfig; + import org.chromium.chrome.browser.adblock.R; + import org.chromium.chrome.browser.preferences.Pref; ++import org.chromium.chrome.browser.profiles.Profile; + import org.chromium.chrome.browser.profiles.ProfileManager; + import org.chromium.components.adblock.AdblockController; + import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; + import org.chromium.components.user_prefs.UserPrefs; + +-public class AdblockSettingsFragment extends PreferenceFragmentCompat +- implements Preference.OnPreferenceChangeListener { ++public class AdblockSettingsFragment ++ extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { + private ChromeSwitchPreference mAdblockEnabled; + private ChromeSwitchPreference mAcceptableAdsEnabled; + private ChromeSwitchPreference mAutoInstalledEnabled; + private Preference mFilterLists; + private Preference mAllowedDomains; + private Preference mMoreOptions; +- ++ private Preference mStartUpdate; ++ private ChromeSwitchPreference mPrivilegedFilters; ++ ++ private static final String START_UPDATE_KEY = ++ "fragment_adblock_settings_start_update"; ++ private static final String PRIVILEGED_FILTERS_KEY = ++ "fragment_adblock_privileged_filters_enabled_key"; ++ private static final String FILTER_LISTS_KEY = ++ "fragment_adblock_settings_filter_lists_key"; ++ private static final String CUSTOM_FILTER_LISTS_KEY = ++ "fragment_adblock_more_options_custom_filter_lists_key"; ++ private static final String CUSTOM_FILTER_KEY = ++ "fragment_adblock_more_options_custom_filter_key"; + private static final String SETTINGS_ENABLED_KEY = "fragment_adblock_settings_enabled_key"; + private static final String SETTINGS_FILTER_LISTS_KEY = + "fragment_adblock_settings_filter_lists_key"; +@@ -52,9 +66,21 @@ public class AdblockSettingsFragment extends PreferenceFragmentCompat + private long mOnOffTogleTimestamp; + + private void bindPreferences() { ++ mStartUpdate = findPreference(START_UPDATE_KEY); ++ mStartUpdate.setOnPreferenceClickListener(preference -> { ++ AdblockController.getInstance( ++ ProfileManager.getLastUsedRegularProfile()).startUpdate(); ++ Toast toast = Toast.makeText(getContext(), ++ "Checking for updates in progress", Toast.LENGTH_LONG); ++ toast.show(); ++ // handle the click so the default action isn't triggered. ++ return true; ++ }); ++ mPrivilegedFilters = (ChromeSwitchPreference) findPreference(PRIVILEGED_FILTERS_KEY); + mAdblockEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_ENABLED_KEY); + mFilterLists = findPreference(SETTINGS_FILTER_LISTS_KEY); + mAcceptableAdsEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_AA_ENABLED_KEY); ++ mAcceptableAdsEnabled.setVisible(false); + mAutoInstalledEnabled = + (ChromeSwitchPreference) findPreference(SETTINGS_AUTO_INSTALL_ENABLED_KEY); + mAllowedDomains = findPreference(SETTINGS_ALLOWED_DOMAINS_KEY); +@@ -62,35 +88,43 @@ public class AdblockSettingsFragment extends PreferenceFragmentCompat + } + + private boolean areMoreOptionsEnabled() { +- return UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) +- .getBoolean(Pref.ADBLOCK_MORE_OPTIONS_ENABLED); ++ return false; + } + + private void applyAdblockEnabled(boolean enabledValue) { +- mFilterLists.setEnabled(enabledValue); +- mAcceptableAdsEnabled.setEnabled(enabledValue); ++ mStartUpdate.setEnabled(enabledValue); + mAutoInstalledEnabled.setEnabled(enabledValue); ++ mPrivilegedFilters.setEnabled(enabledValue); + mAllowedDomains.setEnabled(enabledValue); + mMoreOptions.setEnabled(enabledValue); + mMoreOptions.setVisible(areMoreOptionsEnabled()); + } + + private void synchronizePreferences() { +- boolean enabled = +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .isEnabled(); ++ AdblockController controller = ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()); ++ ++ findPreference(FILTER_LISTS_KEY).setTitle( ++ getContext().getString(R.string.fragment_adblock_settings_filter_lists_title_count, ++ controller.getInstalledSubscriptions().size())); ++ findPreference(CUSTOM_FILTER_LISTS_KEY).setTitle( ++ getContext().getString(R.string.fragment_adblock_more_options_custom_filter_lists_title_count, ++ controller.getCustomSubscriptions().size())); ++ findPreference(CUSTOM_FILTER_KEY).setTitle( ++ getContext().getString(R.string.fragment_adblock_more_options_custom_filters_title_count, ++ controller.getCustomFilters().size())); ++ mPrivilegedFilters.setChecked(controller.isPrivilegedFiltersEnabled()); ++ mPrivilegedFilters.setOnPreferenceChangeListener(this); ++ boolean enabled = controller.isEnabled(); + mAdblockEnabled.setChecked(enabled); + mAdblockEnabled.setOnPreferenceChangeListener(this); + applyAdblockEnabled(enabled); + +- mAcceptableAdsEnabled.setChecked( +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .isAcceptableAdsEnabled()); ++ mAcceptableAdsEnabled.setChecked(controller.isAcceptableAdsEnabled()); + mAcceptableAdsEnabled.setOnPreferenceChangeListener(this); + + mAutoInstalledEnabled.setChecked( +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .isAutoInstallEnabled()); ++ controller.isAutoInstallEnabled()); + mAutoInstalledEnabled.setOnPreferenceChangeListener(this); + } + +@@ -122,7 +156,7 @@ public class AdblockSettingsFragment extends PreferenceFragmentCompat + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { +- addPreferencesFromResource(R.xml.adblock_preferences); ++ addPreferencesFromResource(R.xml.eyeo_adblock_preferences); + bindPreferences(); + synchronizePreferences(); + } +@@ -137,19 +171,16 @@ public class AdblockSettingsFragment extends PreferenceFragmentCompat + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference.getKey().equals(SETTINGS_ENABLED_KEY)) { + AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .setEnabled((Boolean) newValue); ++ .setEnabled( ++ (Boolean) newValue); + + maybeEnableMoreOptions(); + + applyAdblockEnabled((Boolean) newValue); +- } else if (preference.getKey().equals(SETTINGS_AA_ENABLED_KEY)) { +- AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .setAcceptableAdsEnabled((Boolean) newValue); +- } else { +- assert preference.getKey().equals(SETTINGS_AUTO_INSTALL_ENABLED_KEY); +- ++ } else if (preference.getKey().equals(PRIVILEGED_FILTERS_KEY)) { + AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) +- .setAutoInstallEnabled((Boolean) newValue); ++ .setPrivilegedFiltersEnabled( ++ (Boolean) newValue); + } + return true; + } +diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc +--- a/chrome/browser/chrome_content_browser_client.cc ++++ b/chrome/browser/chrome_content_browser_client.cc +@@ -6631,7 +6631,9 @@ ChromeContentBrowserClient:: + } + + bool ChromeContentBrowserClient::WillInterceptWebSocket( +- content::RenderFrameHost* frame) { ++ content::RenderFrameHost* frame, ++ content::RenderProcessHost* process, ++ const url::Origin& origin) { + #if BUILDFLAG(ENABLE_EXTENSIONS) + if (!frame) { + return false; +@@ -6653,9 +6655,11 @@ bool ChromeContentBrowserClient::WillInterceptWebSocket( + } + + void ChromeContentBrowserClient::CreateWebSocket( ++ content::RenderProcessHost* process, + content::RenderFrameHost* frame, + WebSocketFactory factory, + const GURL& url, ++ const url::Origin& initiator_origin, + const net::SiteForCookies& site_for_cookies, + const std::optional& user_agent, mojo::PendingRemote diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h -@@ -696,11 +696,15 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { +@@ -697,11 +697,15 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient { CreateURLLoaderHandlerForServiceWorkerNavigationPreload( content::FrameTreeNodeId frame_tree_node_id, const network::ResourceRequest& resource_request) override; @@ -769,17 +852,15 @@ diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chr diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.cc b/chrome/browser/extensions/api/adblock_private/adblock_private_api.cc --- a/chrome/browser/extensions/api/adblock_private/adblock_private_api.cc +++ b/chrome/browser/extensions/api/adblock_private/adblock_private_api.cc -@@ -18,7 +18,8 @@ +@@ -17,6 +17,7 @@ + #include "base/containers/flat_map.h" + #include "base/i18n/time_formatting.h" ++#include "base/strings/utf_string_conversions.h" #include "base/logging.h" #include "base/no_destructor.h" --#include "base/time/time_to_iso8601.h" -+#include "base/i18n/time_formatting.h" -+#include "base/strings/utf_string_conversions.h" #include "base/values.h" - #include "chrome/browser/adblock/resource_classification_runner_factory.h" - #include "chrome/browser/adblock/session_stats_factory.h" -@@ -77,26 +78,12 @@ std::vector CopySessionsStats( +@@ -94,27 +95,12 @@ std::vector CopySessionsStats( return result; } @@ -789,14 +870,15 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.c - switch (state) { - case State::Installed: - return "Installed"; -- case State::Installing: -- return "Installing"; +- case State::AutoInstalled: +- return "AutoInstalled"; - case State::Preloaded: - return "Preloaded"; +- case State::Installing: +- return "Installing"; - case State::Unknown: - return "Unknown"; - } -- NOTREACHED(); - return ""; -} - @@ -809,14 +891,17 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.c std::vector result; for (auto& sub : current_subscriptions) { api::adblock_private::Subscription js_sub; -@@ -104,9 +91,13 @@ std::vector CopySubscriptions( +@@ -122,12 +108,13 @@ std::vector CopySubscriptions( js_sub.title = sub->GetTitle(); js_sub.current_version = sub->GetCurrentVersion(); js_sub.installation_state = - SubscriptionInstallationStateToString(sub->GetInstallationState()); +- const auto installation_time = sub->GetInstallationTime(); + adblock::Subscription::SubscriptionInstallationStateToString(sub->GetInstallationState()); js_sub.last_installation_time = -- base::TimeToISO8601(sub->GetInstallationTime()); +- installation_time.is_null() +- ? "" +- : base::TimeFormatAsIso8601(sub->GetInstallationTime()); + base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(sub->GetInstallationTime())); + if (metadata) { + js_sub.download_success_count = metadata->GetDownloadSuccessCount(sub->GetSourceUrl()); @@ -825,7 +910,7 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.c result.emplace_back(std::move(js_sub)); } return result; -@@ -347,6 +338,47 @@ void AdblockPrivateAPI::OnListenerAdded( +@@ -367,6 +354,47 @@ void AdblockPrivateAPI::OnListenerAdded( namespace api { @@ -873,13 +958,13 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.c AdblockPrivateSetEnabledFunction::AdblockPrivateSetEnabledFunction() {} AdblockPrivateSetEnabledFunction::~AdblockPrivateSetEnabledFunction() {} -@@ -514,7 +546,7 @@ AdblockPrivateGetInstalledSubscriptionsFunction::Run() { - << "adblock_private expects \"adblock\" configuration"; +@@ -555,7 +583,7 @@ AdblockPrivateGetInstalledSubscriptionsFunction::Run() { + GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); return RespondNow(ArgumentList( api::adblock_private::GetInstalledSubscriptions::Results::Create( - CopySubscriptions(subscription_service->GetCurrentSubscriptions( + CopySubscriptions(subscription_service, subscription_service->GetCurrentSubscriptions( - subscription_service->GetAdblockFilteringConfiguration()))))); + adblock_configuration))))); } diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.h b/chrome/browser/extensions/api/adblock_private/adblock_private_api.h @@ -944,25 +1029,23 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.h diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc --- a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc +++ b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc -@@ -18,7 +18,8 @@ +@@ -17,6 +17,7 @@ + #include "base/containers/flat_map.h" + #include "base/i18n/time_formatting.h" ++#include "base/strings/utf_string_conversions.h" #include "base/logging.h" #include "base/no_destructor.h" --#include "base/time/time_to_iso8601.h" -+#include "base/i18n/time_formatting.h" -+#include "base/strings/utf_string_conversions.h" #include "base/values.h" - #include "chrome/browser/adblock/resource_classification_runner_factory.h" - #include "chrome/browser/adblock/session_stats_factory.h" -@@ -29,6 +30,7 @@ - #include "components/adblock/content/browser/resource_classification_runner.h" +@@ -30,6 +31,7 @@ + #include "components/adblock/core/common/adblock_prefs.h" #include "components/adblock/core/common/adblock_utils.h" #include "components/adblock/core/common/content_type.h" +#include "components/adblock/core/subscription/subscription.h" #include "components/adblock/core/configuration/filtering_configuration.h" #include "components/adblock/core/configuration/persistent_filtering_configuration.h" #include "components/adblock/core/session_stats.h" -@@ -94,23 +96,6 @@ std::vector CopySessionsStats( +@@ -95,24 +97,6 @@ std::vector CopySessionsStats( return result; } @@ -972,74 +1055,86 @@ diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering - switch (state) { - case State::Installed: - return "Installed"; -- case State::Installing: -- return "Installing"; +- case State::AutoInstalled: +- return "AutoInstalled"; - case State::Preloaded: - return "Preloaded"; +- case State::Installing: +- return "Installing"; - case State::Unknown: - return "Unknown"; - } -- NOTREACHED(); - return ""; -} - std::vector CopySubscriptions( const std::vector> current_subscriptions) { -@@ -121,9 +106,9 @@ std::vector CopySubscriptions( +@@ -123,12 +107,12 @@ std::vector CopySubscriptions( js_sub.title = sub->GetTitle(); js_sub.current_version = sub->GetCurrentVersion(); js_sub.installation_state = - SubscriptionInstallationStateToString(sub->GetInstallationState()); + adblock::Subscription::SubscriptionInstallationStateToString(sub->GetInstallationState()); + const auto installation_time = sub->GetInstallationTime(); js_sub.last_installation_time = -- base::TimeToISO8601(sub->GetInstallationTime()); -+ base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(sub->GetInstallationTime())); + installation_time.is_null() + ? "" +- : base::TimeFormatAsIso8601(sub->GetInstallationTime()); ++ : base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(sub->GetInstallationTime())); result.emplace_back(std::move(js_sub)); } return result; +diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc +--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc ++++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc +@@ -33,6 +33,7 @@ + #include "chrome/common/chrome_features.h" + #include "chrome/common/extensions/api/settings_private.h" + #include "chrome/common/pref_names.h" ++#include "components/adblock/core/common/adblock_prefs.h" + #include "components/autofill/core/common/autofill_prefs.h" + #include "components/bookmarks/common/bookmark_pref_names.h" + #include "components/browsing_data/core/pref_names.h" +@@ -176,6 +177,19 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { + } + s_allowlist = new PrefsUtil::TypedPrefMap(); + ++ // Adblock settings ++ (*s_allowlist)[adblock::common::prefs::kEnableAdblockLegacy] = ++ settings_api::PrefType::kBoolean; ++ (*s_allowlist)[adblock::common::prefs::kEnableAcceptableAdsLegacy] = ++ settings_api::PrefType::kBoolean; ++ (*s_allowlist)[adblock::common::prefs::kAdblockSubscriptionsLegacy] = ++ settings_api::PrefType::kList; ++ (*s_allowlist)[adblock::common::prefs::kAdblockCustomSubscriptionsLegacy] = ++ settings_api::PrefType::kList; ++ (*s_allowlist)[adblock::common::prefs::kAdblockAllowedDomainsLegacy] = ++ settings_api::PrefType::kList; ++ (*s_allowlist)[adblock::common::prefs::kAdblockCustomFiltersLegacy] = ++ settings_api::PrefType::kList; + // Miscellaneous + (*s_allowlist)[::embedder_support::kAlternateErrorPagesEnabled] = + settings_api::PrefType::kBoolean; diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -@@ -19,7 +19,6 @@ - #include "chrome/browser/affiliations/affiliation_service_factory.h" - #include "chrome/browser/ai/ai_data_keyed_service_factory.h" - #include "chrome/browser/adblock/adblock_controller_factory.h" --#include "chrome/browser/adblock/adblock_telemetry_service_factory.h" - #include "chrome/browser/adblock/content_security_policy_injector_factory.h" - #include "chrome/browser/adblock/element_hider_factory.h" - #include "chrome/browser/adblock/resource_classification_runner_factory.h" -@@ -906,7 +905,6 @@ void ChromeBrowserMainExtraPartsProfiles:: +@@ -235,7 +235,6 @@ + #include "chrome/common/buildflags.h" + #include "chrome/common/chrome_features.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/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" +@@ -887,7 +886,6 @@ void ChromeBrowserMainExtraPartsProfiles:: + // Makes manual testing possible. FakeSmartCardDeviceServiceFactory::GetInstance(); #endif - adblock::AdblockControllerFactory::GetInstance(); - adblock::AdblockTelemetryServiceFactory::GetInstance(); + adblock::AdblockRequestThrottleFactory::GetInstance(); adblock::ContentSecurityPolicyInjectorFactory::GetInstance(); adblock::ElementHiderFactory::GetInstance(); - adblock::ResourceClassificationRunnerFactory::GetInstance(); -diff --git a/chrome/browser/resources/adblock_internals/BUILD.gn b/chrome/browser/resources/adblock_internals/BUILD.gn ---- a/chrome/browser/resources/adblock_internals/BUILD.gn -+++ b/chrome/browser/resources/adblock_internals/BUILD.gn -@@ -18,12 +18,18 @@ import("//ui/webui/resources/tools/build_webui.gni") - - build_webui("build") { - grd_prefix = "adblock_internals" -+ - static_files = [ "adblock_internals.html" ] -+ - non_web_component_files = [ "adblock_internals.ts" ] -- mojo_files_deps = [ "//chrome/browser/ui/webui/adblock_internals:mojo_bindings_ts__generator" ] -+ -+ mojo_files_deps = -+ [ "//chrome/browser/ui/webui/adblock_internals:mojo_bindings_ts__generator" ] - mojo_files = [ "$root_gen_dir/chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom-webui.ts" ] -+ - ts_deps = [ - "//ui/webui/resources/js:build_ts", - "//ui/webui/resources/mojo:build_ts", - ] -+ webui_context_type = "trusted" - } diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn --- a/chrome/browser/resources/settings/BUILD.gn +++ b/chrome/browser/resources/settings/BUILD.gn @@ -1051,7 +1146,7 @@ diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resourc "appearance_page/appearance_fonts_page.ts", "appearance_page/appearance_page.ts", "appearance_page/home_url_input.ts", -@@ -381,6 +382,7 @@ build_webui("build") { +@@ -379,6 +380,7 @@ build_webui("build") { ts_definitions = [ "//tools/typescript/definitions/autofill_private.d.ts", "//tools/typescript/definitions/chrome_event.d.ts", @@ -1570,7 +1665,7 @@ new file mode 100644 diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chrome/browser/resources/settings/basic_page/basic_page.html --- a/chrome/browser/resources/settings/basic_page/basic_page.html +++ b/chrome/browser/resources/settings/basic_page/basic_page.html -@@ -53,6 +53,13 @@ +@@ -55,6 +55,13 @@ @@ -1587,10 +1682,10 @@ diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chro diff --git a/chrome/browser/resources/settings/basic_page/basic_page.ts b/chrome/browser/resources/settings/basic_page/basic_page.ts --- a/chrome/browser/resources/settings/basic_page/basic_page.ts +++ b/chrome/browser/resources/settings/basic_page/basic_page.ts -@@ -12,6 +12,7 @@ import 'chrome://resources/cr_elements/cr_icon/cr_icon.js'; - import 'chrome://resources/cr_elements/cr_shared_style.css.js'; - import 'chrome://resources/cr_elements/cr_shared_vars.css.js'; - import '../ai_page/ai_page.js'; +@@ -15,6 +15,7 @@ import '../ai_page/ai_page.js'; + // + import '../glic_page/glic_page.js'; + // +import '../adblock_page/adblock_page.js'; import '../appearance_page/appearance_page.js'; import '../privacy_page/privacy_guide/privacy_guide_promo.js'; @@ -1609,7 +1704,7 @@ diff --git a/chrome/browser/resources/settings/page_visibility.ts b/chrome/brows diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts --- a/chrome/browser/resources/settings/route.ts +++ b/chrome/browser/resources/settings/route.ts -@@ -237,6 +237,10 @@ function createRoutes(): SettingsRoutes { +@@ -245,6 +245,10 @@ function createRoutes(): SettingsRoutes { r.FONTS = r.APPEARANCE.createChild('/fonts'); } @@ -1645,12 +1740,12 @@ diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/reso diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html --- a/chrome/browser/resources/settings/settings_menu/settings_menu.html +++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html -@@ -157,6 +157,11 @@ +@@ -149,6 +149,11 @@ $i18n{a11yPageTitle} + @@ -1660,166 +1755,62 @@ diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc -@@ -20,6 +20,7 @@ - #include "chrome/browser/adblock/element_hider_factory.h" - #include "chrome/browser/adblock/sitekey_storage_factory.h" - #include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/adblock/adblock_controller_factory.h" - #include "chrome/browser/bookmarks/bookmark_model_factory.h" - #include "chrome/browser/breadcrumbs/breadcrumb_manager_tab_helper.h" - #include "chrome/browser/browser_process.h" -@@ -341,6 +342,8 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { - } - } +@@ -346,7 +346,8 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { + auto* original_profile = profile->GetOriginalProfile(); + adblock::EnsureBackgroundServicesStarted(original_profile); + adblock::RegisterAdblockWebContentObserver< +- adblock::AdblockWebContentObserver>(web_contents, original_profile); ++ adblock::AdblockWebContentObserver>(web_contents, original_profile, ++ HostContentSettingsMapFactory::GetForProfile(profile)); -+ adblock::AdblockControllerFactory::GetForBrowserContext( -+ web_contents->GetBrowserContext()); - AdblockWebContentObserver::CreateForWebContents( - web_contents, - adblock::SubscriptionServiceFactory::GetForBrowserContext( -@@ -349,7 +352,8 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { - web_contents->GetBrowserContext()), - adblock::SitekeyStorageFactory::GetForBrowserContext( - web_contents->GetBrowserContext()), -- std::make_unique()); -+ std::make_unique(), -+ HostContentSettingsMapFactory::GetForProfile(profile)); autofill::AutofillClientProvider& autofill_client_provider = autofill::AutofillClientProviderFactory::GetForProfile(profile); - autofill_client_provider.CreateClientForWebContents(web_contents); -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc ---- a/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc -@@ -17,11 +17,10 @@ - - #include "chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.h" - --#include "base/time/time_to_iso8601.h" --#include "chrome/browser/adblock/adblock_telemetry_service_factory.h" -+#include "base/i18n/time_formatting.h" -+#include "base/strings/utf_string_conversions.h" - #include "chrome/browser/adblock/session_stats_factory.h" - #include "chrome/browser/adblock/subscription_service_factory.h" --#include "components/adblock/core/adblock_telemetry_service.h" - #include "components/adblock/core/session_stats.h" - #include "components/adblock/core/subscription/subscription_config.h" - #include "components/adblock/core/subscription/subscription_service.h" -@@ -42,7 +41,6 @@ std::string SubscriptionInstallationStateToString( - return "Unknown"; - } - NOTREACHED(); -- return ""; - } - - std::string DebugLine(std::string name, std::string value, int level) { -@@ -53,6 +51,13 @@ 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 - - AdblockInternalsPageHandlerImpl::AdblockInternalsPageHandlerImpl( -@@ -90,17 +95,13 @@ void AdblockInternalsPageHandlerImpl::GetDebugInfo( - content += DebugLine("Title", it->GetTitle(), 2); - content += DebugLine("Version", it->GetCurrentVersion(), 2); - content += DebugLine("Last update", -- base::TimeToISO8601(it->GetInstallationTime()), 2); -+ FormatInstallationTime(it->GetInstallationTime()), 2); - content += DebugLine("Total allowed", allowed[url], 2); - content += DebugLine("Total blocked", blocked[url], 2); - } - } - -- auto* telemetry_service = -- adblock::AdblockTelemetryServiceFactory::GetForProfile(profile_); -- telemetry_service->GetTopicProvidersDebugInfo(base::BindOnce( -- &AdblockInternalsPageHandlerImpl::OnTelemetryServiceInfoArrived, -- std::move(callback), std::move(content))); -+ std::move(callback).Run(std::move(content)); - } - - void AdblockInternalsPageHandlerImpl::OnTelemetryServiceInfoArrived( -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc ---- a/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc -@@ -30,8 +30,7 @@ AdblockInternalsUI::AdblockInternalsUI(content::WebUI* web_ui) - content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd( - profile_, chrome::kChromeUIAdblockInternalsHost); - webui::SetupWebUIDataSource(source, -- base::make_span(kAdblockInternalsResources, -- kAdblockInternalsResourcesSize), -+ base::span(kAdblockInternalsResources), - IDR_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_HTML); - } - -diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json ---- a/chrome/common/extensions/api/_api_features.json -+++ b/chrome/common/extensions/api/_api_features.json -@@ -71,7 +71,7 @@ - }, - "adblockPrivate": [{ - "dependencies": ["permission:adblockPrivate"], -- "contexts": ["blessed_extension"] -+ "contexts": ["privileged_extension"] - }, { - "channel": "stable", - "contexts": ["webui"], -@@ -541,7 +541,7 @@ - }, - "eyeoFilteringPrivate": [{ - "dependencies": ["permission:eyeoFilteringPrivate"], -- "contexts": ["blessed_extension"] -+ "contexts": ["privileged_extension"] - }, { - "channel": "stable", - "contexts": ["webui"], diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json -@@ -66,10 +66,6 @@ +@@ -66,13 +66,6 @@ "extension" ] }, - "adblockPrivate": { - "channel": "stable", -- "extension_types": ["extension", "platform_app"] +- "extension_types": ["extension", "platform_app"], +- "allowlist" : [ +- "664F11343A17783FC7F6DC994BBC8AAF6823739C" // eyeo test extension +- ] - }, "autofillPrivate": { "channel": "trunk", "extension_types": ["extension", "platform_app"], -@@ -180,10 +176,6 @@ - "extension", "legacy_packaged_app", "hosted_app", "platform_app" - ] +@@ -387,13 +380,6 @@ + "channel": "beta", + "command_line_switch": "extension-ai-data-collection" }, - "eyeoFilteringPrivate": { - "channel": "stable", -- "extension_types": ["extension", "platform_app"] +- "extension_types": ["extension", "platform_app"], +- "allowlist" : [ +- "664F11343A17783FC7F6DC994BBC8AAF6823739C" // eyeo test extension +- ] - }, - "commandLinePrivate": { + "favicon": { "channel": "stable", - "extension_types": ["extension", "legacy_packaged_app", "platform_app"], + "extension_types": ["extension"] diff --git a/chrome/common/extensions/api/adblock_private.idl b/chrome/common/extensions/api/adblock_private.idl --- a/chrome/common/extensions/api/adblock_private.idl +++ b/chrome/common/extensions/api/adblock_private.idl -@@ -47,6 +47,8 @@ dictionary Subscription { - // Time of last successful installation or update, in ISO 8601 format. +@@ -48,6 +48,8 @@ dictionary Subscription { // May be passed directly to the Date constructor. + // Empty for subscriptions that are not installed yet. DOMString last_installation_time; + long download_success_count; + long download_error_count; }; dictionary SessionStatsEntry { -@@ -97,6 +99,12 @@ callback ListCallback = void(DOMString[] result); - callback SessionStatsCallback = void(SessionStatsEntry[] result); +@@ -99,6 +101,12 @@ callback SessionStatsCallback = void(SessionStatsEntry[] result); + [deprecated="Use eyeoFilteringPrivate methods instead."] interface Functions { + // Start an update cycle + static void startUpdate(); @@ -1830,336 +1821,151 @@ diff --git a/chrome/common/extensions/api/adblock_private.idl b/chrome/common/ex // Allows to turn Adblock on or off. static void setEnabled(boolean enabled); // Returns whether Adblock is on. -diff --git a/components/adblock/android/BUILD.gn b/components/adblock/android/BUILD.gn ---- a/components/adblock/android/BUILD.gn -+++ b/components/adblock/android/BUILD.gn -@@ -14,6 +14,7 @@ - import("//build/config/android/rules.gni") - import("//build/config/locales.gni") - import("//tools/grit/grit_rule.gni") -+import("//third_party/jni_zero/jni_zero.gni") - - source_set("java_bindings") { - sources = [ -@@ -119,7 +120,7 @@ android_resources("java_ui_resources") { - "java/res/layout/adblock_custom_item_settings.xml", - "java/res/layout/adblock_filter_lists_list_item.xml", - "java/res/xml/adblock_more_options.xml", -- "java/res/xml/adblock_preferences.xml", -+ "java/res/xml/eyeo_adblock_preferences.xml", - ] - - deps = [ ":adblock_strings_grd" ] -diff --git a/components/adblock/android/adblock_jni.cc b/components/adblock/android/adblock_jni.cc ---- a/components/adblock/android/adblock_jni.cc -+++ b/components/adblock/android/adblock_jni.cc -@@ -25,6 +25,8 @@ +diff --git a/components/adblock/android/adblock_controller_jni.cc b/components/adblock/android/adblock_controller_jni.cc +--- a/components/adblock/android/adblock_controller_jni.cc ++++ b/components/adblock/android/adblock_controller_jni.cc +@@ -23,6 +23,8 @@ #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/android/jni_weak_ref.h" +#include "base/i18n/time_formatting.h" +#include "base/strings/utf_string_conversions.h" #include "base/logging.h" - #include "components/adblock/android/java_bindings_getters.h" #include "components/adblock/android/jni_headers/AdblockController_jni.h" -@@ -54,6 +56,10 @@ ScopedJavaLocalRef ToJava(JNIEnv* env, + #include "components/adblock/content/browser/factories/subscription_service_factory.h" +@@ -48,6 +50,9 @@ ScopedJavaLocalRef ToJava(JNIEnv* env, const std::string& url, const std::string& title, const std::string& version, + adblock::Subscription::InstallationState state, + const std::string& installation_time, -+ long download_success_count, -+ long download_error_count, - const std::vector& languages) { ++ long download_success_count, long download_error_count, + const std::vector& languages, + const bool autoinstalled) { ScopedJavaLocalRef url_param( - env, env->NewObject(url_class.obj(), url_constructor, -@@ -62,12 +68,19 @@ ScopedJavaLocalRef ToJava(JNIEnv* env, +@@ -57,24 +62,40 @@ ScopedJavaLocalRef ToJava(JNIEnv* env, return Java_Subscription_Constructor(env, url_param, ConvertUTF8ToJavaString(env, title), ConvertUTF8ToJavaString(env, version), -+ ConvertUTF8ToJavaString(env, Subscription::SubscriptionInstallationStateToString(state)), ++ ConvertUTF8ToJavaString(env, adblock::Subscription::SubscriptionInstallationStateToString(state)), + ConvertUTF8ToJavaString(env, installation_time), -+ download_success_count, -+ download_error_count, - ToJavaArrayOfStrings(env, languages)); ++ download_success_count ? JNI_TRUE : JNI_FALSE, ++ download_error_count ? JNI_TRUE : JNI_FALSE, + ToJavaArrayOfStrings(env, languages), + autoinstalled ? JNI_TRUE : JNI_FALSE); } std::vector> CSubscriptionsToJObjects( JNIEnv* env, - const std::vector>& subscriptions) { -+ auto* subscription_service = adblock::GetSubscriptionService(); ++ const base::android::JavaParamRef& jbrowser_context_handle, + const std::vector>& subscriptions) { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); + raw_ptr metadata = -+ subscription_service->GetMetadata(); ++ subscription_service->GetMetadata(); ScopedJavaLocalRef url_class = GetClass(env, "java/net/URL"); jmethodID url_constructor = MethodID::Get( env, url_class.obj(), "", "(Ljava/lang/String;)V"); -@@ -76,7 +89,11 @@ std::vector> CSubscriptionsToJObjects( + std::vector> jobjects; + jobjects.reserve(subscriptions.size()); for (auto& sub : subscriptions) { - jobjects.push_back(ToJava( +- jobjects.push_back(ToJava( ++ if (sub->GetSourceUrl().SchemeIsHTTPOrHTTPS()) { ++ jobjects.push_back(ToJava( env, url_class, url_constructor, sub->GetSourceUrl().spec(), -- sub->GetTitle(), sub->GetCurrentVersion(), std::vector{})); +- sub->GetTitle(), sub->GetCurrentVersion(), std::vector{}, + sub->GetTitle(), sub->GetCurrentVersion(), + sub->GetInstallationState(), base::UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(sub->GetInstallationTime())), + metadata ? metadata->GetDownloadSuccessCount(sub->GetSourceUrl()) : 0, + metadata ? metadata->GetDownloadErrorCount(sub->GetSourceUrl()) : 0, -+ std::vector{})); ++ std::vector{}, + sub->GetInstallationState() == + adblock::InstalledSubscription::InstallationState::AutoInstalled)); ++ } } return jobjects; } -@@ -96,6 +113,9 @@ std::vector> CSubscriptionsToJObjects( +@@ -93,7 +114,11 @@ std::vector> CSubscriptionsToJObjects( + DCHECK(sub.url.is_valid()); if (sub.url.is_valid()) { jobjects.push_back(ToJava(env, url_class, url_constructor, - sub.url.spec(), sub.title, "", +- sub.url.spec(), sub.title, "", sub.languages, ++ sub.url.spec(), sub.title, /*version*/"", + adblock::Subscription::InstallationState::Unknown, + /*installation_time*/ "", + /*download_success_count*/ 0, /*download_error_count*/ 0, - sub.languages)); ++ sub.languages, + false)); } } -@@ -139,6 +159,37 @@ void AdblockJNI::OnSubscriptionInstalled(const GURL& url) { +@@ -103,6 +128,52 @@ std::vector> CSubscriptionsToJObjects( - } // namespace adblock + } // namespace +static void -+JNI_AdblockController_StartUpdate(JNIEnv* env) { -+ adblock::GetSubscriptionService()->StartUpdate(); ++JNI_AdblockController_StartUpdate(JNIEnv* env, ++ const base::android::JavaParamRef& jbrowser_context_handle) { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); ++ subscription_service->StartUpdate(); +} + +static base::android::ScopedJavaLocalRef -+JNI_AdblockController_GetCustomSubscriptions(JNIEnv* env) { ++JNI_AdblockController_GetCustomSubscriptions(JNIEnv* env, ++ const base::android::JavaParamRef& jbrowser_context_handle) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ auto* subscription_service = adblock::GetSubscriptionService(); ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); + if (!subscription_service) { + return ToJavaArrayOfObjects(env, + std::vector>{}); + } + + return ToJavaArrayOfObjects( -+ env, adblock::CSubscriptionsToJObjects( -+ env, subscription_service->GetCustomSubscriptions( ++ env, CSubscriptionsToJObjects( ++ env, jbrowser_context_handle, subscription_service->GetCustomSubscriptions( + subscription_service->GetAdblockFilteringConfiguration()))); +} + +static jboolean JNI_AdblockController_IsPrivilegedFiltersEnabled( -+ JNIEnv* env) { -+ return adblock::GetSubscriptionService()->IsPrivilegedFiltersEnabled() ? JNI_TRUE : JNI_FALSE; ++ JNIEnv* env, ++ const base::android::JavaParamRef& jbrowser_context_handle) { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); ++ return subscription_service->IsPrivilegedFiltersEnabled() ? JNI_TRUE : JNI_FALSE; +} + +static void JNI_AdblockController_SetPrivilegedFiltersEnabled( + JNIEnv* env, ++ const base::android::JavaParamRef& jbrowser_context_handle, + jboolean j_enabled) { -+ adblock::GetSubscriptionService()->SetPrivilegedFiltersEnabled(j_enabled == JNI_TRUE); ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); ++ subscription_service->SetPrivilegedFiltersEnabled(j_enabled == JNI_TRUE); +} + - static void JNI_AdblockController_Bind( + static base::android::ScopedJavaLocalRef + JNI_AdblockController_GetInstalledSubscriptions( JNIEnv* env, - const base::android::JavaParamRef& caller) { -diff --git a/components/adblock/android/adblock_strings.grd b/components/adblock/android/adblock_strings.grd ---- a/components/adblock/android/adblock_strings.grd -+++ b/components/adblock/android/adblock_strings.grd -@@ -186,10 +186,52 @@ - - - -- Ad blocking -+ Adblock Plus settings -+ -+ -+ Enable Adblock Plus -+ -+ -+ Check for updates now -+ -+ -+ Enable anti-circumvention and snippets -+ -+ -+ Snippets are pieces of JavaScript code, injected by the Adblock Plus, that execute within the context of a website and combat advanced ads that circumvent ordinary blocking. -+The functionality is ONLY allowed for the list -+https://www.cromite.org/filters/abp-filters-anti-cv.txt -+which is activated by this setting. -+ -+ -+ Open ABP anti-circumvention filter list repo -+ -+ -+ Open https://gitlab.com/eyeo/anti-cv/abp-filters-anti-cv in the browser -+ -+ -+ https://gitlab.com/eyeo/anti-cv/abp-filters-anti-cv -+ -+ -+ Open ABP Snippets Overview -+ -+ -+ Open https://developers.eyeo.com/snippets/snippets-overview in the browser -+ -+ -+ https://developers.eyeo.com/snippets/snippets-overview -+ -+ -+ Filter lists (%s selected) -+ -+ -+ Custom ad filtering settings (%s selected) -+ -+ -+ Custom Filters (%s selected) - - -- Allow ad blocking on websites in this app -+ Block ads on websites - - - Filter lists -@@ -216,10 +258,10 @@ - More blocking options - - -- Custom ad filtering settings -+ Custom ad filtering urls - - -- Add custom filter lists -+ Add custom filter urls - - - https://example.org/myFilterList.txt -@@ -228,10 +270,10 @@ - Custom Filters - - -- Add custom filters -+ Add custom filter commands - - -- Enter filter -+ Enter filter command - - - -diff --git a/components/adblock/android/java/res/layout/adblock_custom_item.xml b/components/adblock/android/java/res/layout/adblock_custom_item.xml ---- a/components/adblock/android/java/res/layout/adblock_custom_item.xml -+++ b/components/adblock/android/java/res/layout/adblock_custom_item.xml -@@ -25,14 +25,24 @@ - tools:ignore="UseCompoundDrawables"> - - -- -+ -+ -+ - - - - -@@ -32,6 +32,43 @@ - app:iconSpaceReserved="false" - android:summary="@string/fragment_adblock_settings_filter_lists_summary" /> - -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ - - GetCurrentSubscriptions( ++ env, jbrowser_context_handle, subscription_service->GetCurrentSubscriptions( + subscription_service->GetFilteringConfiguration( + adblock::kAdblockFilteringConfigurationName)))); + } diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java --- a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java +++ b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java -@@ -50,7 +50,7 @@ public final class AdblockController extends FilteringConfiguration { - super("adblock"); +@@ -53,7 +53,7 @@ public class AdblockController { + FilteringConfiguration.createConfiguration("adblock", mBrowserContextHandle); try { mAcceptableAds = - new URL("https://easylist-downloads.adblockplus.org/exceptionrules.txt"); @@ -2167,10 +1973,10 @@ diff --git a/components/adblock/android/java/src/org/chromium/components/adblock } catch (java.net.MalformedURLException e) { mAcceptableAds = null; } -@@ -73,6 +73,10 @@ public final class AdblockController extends FilteringConfiguration { - private String mTitle; +@@ -84,6 +84,10 @@ public class AdblockController { private String mVersion = ""; private String[] mLanguages = {}; + private boolean mAutoInstalled; + private String mState; + private String mInstallationTime; + private long mDownloadSuccessCount; @@ -2178,19 +1984,20 @@ diff --git a/components/adblock/android/java/src/org/chromium/components/adblock public Subscription(final URL url, final String title, final String version) { this.mUrl = url; -@@ -82,11 +86,30 @@ public final class AdblockController extends FilteringConfiguration { - - @CalledByNative("Subscription") - public Subscription( -- final URL url, final String title, final String version, final String[] languages) { -+ final URL url, final String title, final String version, -+ final String state, final String installation_time, +@@ -96,6 +100,9 @@ public class AdblockController { + final URL url, + final String title, + final String version, ++ final String state, ++ final String installation_time, + long download_success_count, long download_error_count, -+ final String[] languages) { + final String[] languages, + boolean autoinstalled) { this.mUrl = url; - this.mTitle = title; +@@ -103,6 +110,22 @@ public class AdblockController { this.mVersion = version; this.mLanguages = languages; + this.mAutoInstalled = autoinstalled; + this.mState = state; + this.mInstallationTime = installation_time; + this.mDownloadSuccessCount = download_success_count; @@ -2210,457 +2017,815 @@ diff --git a/components/adblock/android/java/src/org/chromium/components/adblock } public String title() { -@@ -117,6 +140,7 @@ public final class AdblockController extends FilteringConfiguration { +@@ -137,6 +160,7 @@ public class AdblockController { @UiThread public void setAcceptableAdsEnabled(boolean enabled) { + enabled = false; - if (enabled) - addFilterList(mAcceptableAds); - else -@@ -144,6 +168,27 @@ public final class AdblockController extends FilteringConfiguration { - removeFilterList(url); + if (enabled) mFilteringConfiguration.addFilterList(mAcceptableAds); + else mFilteringConfiguration.removeFilterList(mAcceptableAds); + } +@@ -172,6 +196,27 @@ public class AdblockController { + mFilteringConfiguration.removeFilterList(url); } + @UiThread + public void startUpdate() { -+ AdblockControllerJni.get().startUpdate(); ++ AdblockControllerJni.get().startUpdate(mBrowserContextHandle); + } + + @UiThread + public List getCustomSubscriptions() { + return (List) (List) Arrays.asList( -+ AdblockControllerJni.get().getCustomSubscriptions()); ++ AdblockControllerJni.get().getCustomSubscriptions(mBrowserContextHandle)); + } + + @UiThread + public void setPrivilegedFiltersEnabled(boolean enabled) { -+ AdblockControllerJni.get().setPrivilegedFiltersEnabled(enabled); ++ AdblockControllerJni.get().setPrivilegedFiltersEnabled(mBrowserContextHandle, enabled); + } + + @UiThread + public boolean isPrivilegedFiltersEnabled() { -+ return AdblockControllerJni.get().isPrivilegedFiltersEnabled(); ++ return AdblockControllerJni.get().isPrivilegedFiltersEnabled(mBrowserContextHandle); + } + @UiThread public List getInstalledSubscriptions() { - return (List) (List) Arrays.asList( -@@ -196,6 +241,10 @@ public final class AdblockController extends FilteringConfiguration { + return (List) +@@ -276,6 +321,10 @@ public class AdblockController { @NativeMethods interface Natives { -+ void startUpdate(); -+ boolean isPrivilegedFiltersEnabled(); -+ void setPrivilegedFiltersEnabled(boolean enabled); -+ Object[] getCustomSubscriptions(); - void bind(AdblockController caller); - Object[] getInstalledSubscriptions(); - Object[] getRecommendedSubscriptions(); -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java -@@ -26,9 +26,14 @@ import org.chromium.components.adblock.R; - - import java.util.List; - --public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment { -+public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment { - public AdblockAllowedDomainsFragment() {} - -+ @Override -+ protected String getItemText(String item) { -+ return item; -+ } -+ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java -@@ -25,6 +25,7 @@ import android.webkit.URLUtil; - import android.widget.Toast; ++ void startUpdate(BrowserContextHandle contextHandle); ++ boolean isPrivilegedFiltersEnabled(BrowserContextHandle contextHandle); ++ void setPrivilegedFiltersEnabled(BrowserContextHandle contextHandle, boolean enabled); ++ Object[] getCustomSubscriptions(BrowserContextHandle contextHandle); + Object[] getInstalledSubscriptions(BrowserContextHandle contextHandle); - import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.AdblockController.Subscription; - import org.chromium.components.adblock.R; + Object[] getRecommendedSubscriptions(); +diff --git a/components/adblock/content/browser/BUILD.gn b/components/adblock/content/browser/BUILD.gn +--- a/components/adblock/content/browser/BUILD.gn ++++ b/components/adblock/content/browser/BUILD.gn +@@ -14,15 +14,8 @@ + # You should have received a copy of the GNU General Public License + # along with eyeo Chromium SDK. If not, see . - import java.net.MalformedURLException; -@@ -32,7 +33,7 @@ import java.net.URL; - import java.util.ArrayList; - import java.util.List; +-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" ] +- } + } --public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment { -+public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment { - private static final String TAG = AdblockCustomFilterListsFragment.class.getSimpleName(); - public AdblockCustomFilterListsFragment() {} + source_set("browser_impl") { +@@ -44,6 +37,8 @@ source_set("browser_impl") { + "adblock_web_ui_controller_factory.h", + "adblock_webcontents_observer.cc", + "adblock_webcontents_observer.h", ++ "adblock_blocking_page.cc", ++ "adblock_blocking_page.h", + "content_security_policy_injector.h", + "content_security_policy_injector_impl.cc", + "content_security_policy_injector_impl.h", +@@ -56,8 +51,6 @@ source_set("browser_impl") { + "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", +@@ -89,15 +82,10 @@ source_set("browser_impl") { + "session_stats_impl.h", + ] -@@ -43,25 +44,18 @@ public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment - } +- if (eyeo_intercept_debug_url) { +- sources += [ +- "adblock_url_loader_factory_for_test.cc", +- "adblock_url_loader_factory_for_test.h", +- ] +- } +- + deps = [ + "//base", ++ "//components/content_settings/browser", ++ "//components/content_settings/core/browser", + "//components/adblock/content/browser/mojom:adblock_internals", + "//components/adblock/content/resources/adblock_internals:resources", + "//components/adblock/core/converter:converter", +@@ -218,9 +206,5 @@ source_set("browser_tests") { + "test/adblock_web_ui_browsertest.cc", + ] - @Override -- protected List getItems() { -- final List installed = -- AdblockController.getInstance().getInstalledSubscriptions(); -- final List recommended = -- AdblockController.getInstance().getRecommendedSubscriptions(); -- final List customStrings = new ArrayList(); -- for (final AdblockController.Subscription subscription : installed) { -- if (recommended.contains(subscription)) { -- continue; -- } -- // FIXME(kzlomek): Remove this after DPD-1613 -- if (subscription.url().toString().equals( -- "https://easylist-downloads.adblockplus.org/exceptionrules.txt")) { -- continue; -- } -- customStrings.add(subscription.url().toString()); -- } -+ protected List getItems() { -+ return AdblockController.getInstance().getCustomSubscriptions(); -+ } +- if (eyeo_intercept_debug_url) { +- sources += [ "test/adblock_debug_url_browsertest.cc" ] +- } +- + deps = [ ":browser_tests_support" ] + } +diff --git a/components/adblock/content/browser/adblock_blocking_page.cc b/components/adblock/content/browser/adblock_blocking_page.cc +new file mode 100644 +--- /dev/null ++++ b/components/adblock/content/browser/adblock_blocking_page.cc +@@ -0,0 +1,182 @@ ++/* ++ This file is part of Cromite. + -+ @Override -+ protected String getItemText(Subscription item) { -+ return item.url().toString(); -+ } - -- return customStrings; -+ @Override -+ protected String getItemStatus(Subscription item) { -+ return item.getDescription(); - } - - @Override -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java -@@ -25,9 +25,14 @@ import org.chromium.components.adblock.R; - - import java.util.List; ++ Cromite 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 2 of the License, or ++ (at your option) any later version. ++ ++ Cromite is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with Cromite. If not, see . ++*/ ++ ++#include "components/adblock/content/browser/adblock_blocking_page.h" ++ ++#include ++ ++#include "base/i18n/rtl.h" ++#include "base/strings/escape.h" ++#include "base/strings/strcat.h" ++#include "base/strings/utf_string_conversions.h" ++#include "components/grit/components_resources.h" ++#include "components/security_interstitials/content/security_interstitial_controller_client.h" ++#include "components/security_interstitials/core/common_string_util.h" ++#include "components/security_interstitials/core/metrics_helper.h" ++#include "components/security_interstitials/core/urls.h" ++#include "components/strings/grit/components_strings.h" ++#include "components/url_formatter/url_formatter.h" ++#include "net/base/net_errors.h" ++#include "ui/base/l10n/l10n_util.h" ++ ++namespace { ++ ++std::unique_ptr GetMetricsHelper( ++ const GURL& url) { ++ security_interstitials::MetricsHelper::ReportDetails settings; ++ settings.metric_prefix = "main_blocking_page"; ++ ++ return std::make_unique(url, settings, ++ nullptr); ++} ++ ++} ++ ++// static ++const security_interstitials::SecurityInterstitialPage::TypeID ++ AdBlockPage::kTypeForTesting = ++ &AdBlockPage::kTypeForTesting; ++ ++std::unique_ptr ++AdBlockPage::CreateBlockingPage( ++ content::WebContents* web_contents, ++ const GURL& url, ++ adblock::SubscriptionService* subscription_service, ++ const GURL& subscription_url) { ++ return make_unique( ++ web_contents, url, subscription_service, subscription_url, ++ std::make_unique< ++ security_interstitials::SecurityInterstitialControllerClient>( ++ web_contents, GetMetricsHelper(url), /*pref_service*/nullptr, ++ base::i18n::GetConfiguredLocale(), GURL(url::kAboutBlankURL), ++ /*settings_page_helper=*/nullptr)); ++} ++ ++AdBlockPage::AdBlockPage( ++ content::WebContents* web_contents, ++ const GURL& request_url, ++ adblock::SubscriptionService* subscription_service, ++ const GURL& subscription_url, ++ std::unique_ptr< ++ security_interstitials::SecurityInterstitialControllerClient> ++ controller_client) ++ : security_interstitials::SecurityInterstitialPage( ++ web_contents, ++ request_url, ++ std::move(controller_client)), ++ subscription_service_(subscription_service), ++ subscription_url_(subscription_url) { ++} ++ ++AdBlockPage::~AdBlockPage() = default; ++ ++security_interstitials::SecurityInterstitialPage::TypeID ++AdBlockPage::GetTypeForTesting() { ++ return AdBlockPage::kTypeForTesting; ++} ++ ++void AdBlockPage::PopulateInterstitialStrings( ++ base::Value::Dict& load_time_data) { ++ load_time_data.Set("type", "ENTERPRISE_WARN"); ++ load_time_data.Set("tabTitle", ++ l10n_util::GetStringUTF16(IDS_ERRORPAGES_TITLE_BLOCKED_BY_ADMINISTRATOR)); ++ load_time_data.Set("heading", ++ l10n_util::GetStringUTF16(IDS_ERRORPAGES_HEADING_BLOCKED_BY_ADMINISTRATOR)); ++ ++ std::u16string subscription_url_string(url_formatter::FormatUrl( ++ subscription_url_, url_formatter::kFormatUrlOmitNothing, ++ base::UnescapeRule::NORMAL, nullptr, nullptr, nullptr)); ++ load_time_data.Set("primaryParagraph", ++ l10n_util::GetStringFUTF16(IDS_ERRORPAGES_PRIMARY_PARAGRAPH_BLOCKED_BY_ADMINISTRATOR, ++ base::EscapeForHTML(subscription_url_string))); ++ ++ load_time_data.Set("show_recurrent_error_paragraph", false); ++ load_time_data.Set("recurrentErrorParagraph", ""); ++ load_time_data.Set("openDetails", ""); ++ load_time_data.Set("explanationParagraph", ""); ++ load_time_data.Set("finalParagraph", ""); ++ load_time_data.Set("optInLink", "optInLink"); ++ ++ load_time_data.Set("enhancedProtectionMessage", ""); ++ ++ load_time_data.Set("hide_primary_button", false); ++ load_time_data.Set("primaryButtonText", ++ l10n_util::GetStringUTF16(IDS_ERRORPAGES_PRIMARYBUTTONTEXT_BLOCKED_BY_ADMINISTRATOR)); ++ load_time_data.Set("proceedButtonText", ++ l10n_util::GetStringUTF16(IDS_ERRORPAGES_PROCEEDBUTTONTEXT_BLOCKED_BY_ADMINISTRATOR)); ++ load_time_data.Set("fontsize", ""); ++ ++ load_time_data.Set("overridable", false); ++ load_time_data.Set("bad_clock", false); ++ load_time_data.Set(security_interstitials::kDisplayCheckBox, false); ++} ++ ++void AdBlockPage::OnInterstitialClosing() {} ++ ++void AdBlockPage::CommandReceived(const std::string& command) { ++ if (command == "\"pageLoadComplete\"") { ++ // content::WaitForRenderFrameReady sends this message when the page ++ // load completes. Ignore it. ++ return; ++ } ++ ++ int cmd = 0; ++ bool retval = base::StringToInt(command, &cmd); ++ DCHECK(retval); ++ ++ switch (cmd) { ++ case security_interstitials::CMD_DONT_PROCEED: ++ case security_interstitials::CMD_PROCEED: ++ { ++ adblock::FilteringConfiguration* adblock_configuration = ++ subscription_service_->GetAdblockFilteringConfiguration(); ++ CHECK(adblock_configuration); ++ ++ GURL request_url = ++ security_interstitials::SecurityInterstitialPage::request_url(); ++ std::string filter = ++ "@@||" + request_url.host() + "^$document,popup"; ++ ++ if (cmd == security_interstitials::CMD_DONT_PROCEED) ++ adblock_configuration->AddTemporaryCustomFilter(filter); ++ else ++ adblock_configuration->AddCustomFilter(filter); ++ controller()->Reload(); ++ break; ++ } ++ case security_interstitials::CMD_OPEN_HELP_CENTER: ++ case security_interstitials::CMD_DO_REPORT: ++ case security_interstitials::CMD_DONT_REPORT: ++ case security_interstitials::CMD_SHOW_MORE_SECTION: ++ case security_interstitials::CMD_OPEN_DATE_SETTINGS: ++ case security_interstitials::CMD_OPEN_REPORTING_PRIVACY: ++ case security_interstitials::CMD_OPEN_WHITEPAPER: ++ case security_interstitials::CMD_RELOAD: ++ case security_interstitials::CMD_OPEN_DIAGNOSTIC: ++ case security_interstitials::CMD_OPEN_LOGIN: ++ case security_interstitials::CMD_REPORT_PHISHING_ERROR: ++ case security_interstitials::CMD_ERROR: ++ case security_interstitials::CMD_TEXT_FOUND: ++ case security_interstitials::CMD_TEXT_NOT_FOUND: ++ // Not supported by the URL blocking page. ++ NOTREACHED() << "Unsupported command: " << command; ++ } ++} ++ ++int AdBlockPage::GetHTMLTemplateId() { ++ return IDR_SECURITY_INTERSTITIAL_HTML; ++} +diff --git a/components/adblock/content/browser/adblock_blocking_page.h b/components/adblock/content/browser/adblock_blocking_page.h +new file mode 100644 +--- /dev/null ++++ b/components/adblock/content/browser/adblock_blocking_page.h +@@ -0,0 +1,75 @@ ++/* ++ This file is part of Cromite. ++ ++ Cromite 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 2 of the License, or ++ (at your option) any later version. ++ ++ Cromite is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with Cromite. If not, see . ++*/ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_BLOCKING_PAGE_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_BLOCKING_PAGE_H_ ++ ++#include ++ ++#include "components/adblock/core/subscription/subscription_service.h" ++#include "components/adblock/content/browser/adblock_filter_match.h" ++#include "components/adblock/content/browser/eyeo_document_info.h" ++#include "components/security_interstitials/content/security_interstitial_page.h" ++#include "components/security_interstitials/content/settings_page_helper.h" ++ ++class GURL; ++ ++class AdBlockPage ++ : public security_interstitials::SecurityInterstitialPage { ++ public: ++ // Interstitial type, used in tests. ++ static const security_interstitials::SecurityInterstitialPage::TypeID ++ kTypeForTesting; ++ ++ static std::unique_ptr CreateBlockingPage( ++ content::WebContents* web_contents, ++ const GURL& main_frame_url, ++ adblock::SubscriptionService* subscription_service, ++ const GURL& subscription_url); ++ ++ // |request_url| is the URL which triggered the interstitial page. It can be ++ // a main frame or a subresource URL. ++ AdBlockPage( ++ content::WebContents* web_contents, ++ const GURL& request_url, ++ adblock::SubscriptionService* subscription_service, ++ const GURL& subscription_url, ++ std::unique_ptr< ++ security_interstitials::SecurityInterstitialControllerClient> ++ controller); ++ ++ AdBlockPage(const AdBlockPage&) = delete; ++ AdBlockPage& operator=(const AdBlockPage&) = delete; ++ ++ ~AdBlockPage() override; ++ ++ // SecurityInterstitialPage: ++ security_interstitials::SecurityInterstitialPage::TypeID GetTypeForTesting() ++ override; ++ ++ protected: ++ void CommandReceived(const std::string& command) override; ++ void PopulateInterstitialStrings(base::Value::Dict& load_time_data) override; ++ void OnInterstitialClosing() override; ++ int GetHTMLTemplateId() override; ++ ++ private: ++ raw_ptr subscription_service_; ++ GURL subscription_url_; ++}; ++ ++#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_BLOCKING_PAGE_H_ +diff --git a/components/adblock/content/browser/adblock_content_browser_client.h b/components/adblock/content/browser/adblock_content_browser_client.h +--- a/components/adblock/content/browser/adblock_content_browser_client.h ++++ b/components/adblock/content/browser/adblock_content_browser_client.h +@@ -27,6 +27,7 @@ + #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 "components/content_settings/core/browser/host_content_settings_map.h" + #include "content/public/browser/browser_context.h" + #include "content/public/browser/browser_thread.h" + #include "content/public/browser/content_browser_client.h" +@@ -34,6 +35,7 @@ + #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 "services/network/public/mojom/web_transport.mojom.h" + #include "url/url_util.h" --public class AdblockCustomFiltersFragment extends AdblockCustomItemFragment { -+public class AdblockCustomFiltersFragment extends AdblockCustomItemFragment { - public AdblockCustomFiltersFragment() {} + #ifdef EYEO_INTERCEPT_DEBUG_URL +@@ -76,15 +78,44 @@ class AdblockContentBrowserClient : public ContentBrowserClientBase { + static void ForceAdblockProxyForTesting(); + #endif -+ @Override -+ protected String getItemText(String item) { -+ return item; -+ } +- bool WillInterceptWebSocket(content::RenderFrameHost* frame) override; ++ bool WillInterceptWebSocket(content::RenderFrameHost* frame, ++ content::RenderProcessHost* process, ++ const url::Origin& origin) override; + void CreateWebSocket( ++ content::RenderProcessHost* process, + content::RenderFrameHost* frame, + content::ContentBrowserClient::WebSocketFactory factory, + const GURL& url, ++ const url::Origin& initiator_origin, + const net::SiteForCookies& site_for_cookies, + const absl::optional& user_agent, + mojo::PendingRemote + handshake_client) override; + - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java -@@ -34,7 +34,7 @@ import org.chromium.components.adblock.R; - import java.util.ArrayList; - import java.util.List; ++ void WillCreateWebTransport( ++ int process_id, ++ int frame_routing_id, ++ const GURL& url, ++ const url::Origin& initiator_origin, ++ mojo::PendingRemote ++ handshake_client, ++ ChromeContentBrowserClient::WillCreateWebTransportCallback callback) override; ++ ++ bool CanCreateWindow( ++ content::RenderFrameHost* opener, ++ const GURL& opener_url, ++ const GURL& opener_top_level_frame_url, ++ const url::Origin& source_origin, ++ content::mojom::WindowContainerType container_type, ++ const GURL& target_url, ++ const content::Referrer& referrer, ++ const std::string& frame_name, ++ WindowOpenDisposition disposition, ++ const blink::mojom::WindowFeatures& features, ++ bool user_gesture, ++ bool opener_suppressed, ++ bool* no_javascript_access) override; ++ + void WillCreateURLLoaderFactory( + content::BrowserContext* browser_context, + content::RenderFrameHost* frame, +@@ -108,7 +139,8 @@ class AdblockContentBrowserClient : public ContentBrowserClientBase { + mojo::BinderMapWithContext* map) override; --public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat { -+public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat { - private EditText mItem; - private ImageView mAddButton; - private ListView mListView; -@@ -74,7 +74,7 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat + protected: +- static bool IsFilteringNeeded(content::BrowserContext* browser_context); ++ bool IsFilteringNeeded(content::RenderFrameHost* frame, const GURL& embedder_url); ++ bool IsFilteringNeeded(content::BrowserContext* browser_context, const GURL& embedder_url); + + // current_browser_context is the BrowserContext relevant for the currently + // processed request. It might be an off-the-record browser context. This +@@ -117,6 +149,8 @@ class AdblockContentBrowserClient : public ContentBrowserClientBase { + // "original" BrowserContext, depending on platform. + virtual content::BrowserContext* GetBrowserContextForEyeoFactories( + content::BrowserContext* current_browser_context) = 0; ++ virtual HostContentSettingsMap* GetHostContentSettingsMap( ++ content::BrowserContext* current_browser_context) = 0; - protected abstract void addItemImpl(String item); - protected abstract void removeItemImpl(String item); -- protected abstract List getItems(); -+ protected abstract List getItems(); - protected abstract String getCustomItemTextViewText(); - protected abstract String getCustomItemTextViewContentDescription(); - protected abstract String getCustomItemAddButtonContentDescription(); -@@ -84,10 +84,12 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat - // Holder for listview items - private class Holder { - TextView mItem; -+ TextView mStatus; - ImageView mRemoveButton; + private: + content::BrowserContext* GetBrowserContext(content::RenderFrameHost* frame) { +@@ -126,15 +160,27 @@ class AdblockContentBrowserClient : public ContentBrowserClientBase { + } - Holder(View rootView) { - mItem = rootView.findViewById(R.id.fragment_adblock_custom_item_title); -+ mStatus = rootView.findViewById(R.id.fragment_adblock_custom_item_status); - mRemoveButton = rootView.findViewById(R.id.fragment_adblock_custom_item_remove); - mRemoveButton.setContentDescription( - AdblockCustomItemFragment.this.getCustomItemRemoveButtonContentDescription()); -@@ -100,6 +102,7 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat - String item = (String) v.getTag(); - removeItemImpl(item); - mAdapter.notifyDataSetChanged(); -+ mItem.setText(item); - } - }; + void OnWebSocketFilterCheckCompleted( ++ content::RenderProcessHost* process, + content::GlobalRenderFrameHostId render_frame_host_id, + content::ContentBrowserClient::WebSocketFactory factory, + const GURL& url, ++ const url::Origin& initiator_origin, + const net::SiteForCookies& site_for_cookies, + const absl::optional& user_agent, + mojo::PendingRemote + handshake_client, + adblock::FilterMatchResult result); -@@ -127,16 +130,23 @@ public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat - convertView.setTag(new Holder(convertView)); - } ++ void OnWebTransportFilterCheckCompleted( ++ int process_id, ++ int frame_routing_id, ++ const GURL& url, ++ const url::Origin& initiator_origin, ++ mojo::PendingRemote ++ handshake_client, ++ ChromeContentBrowserClient::WillCreateWebTransportCallback callback, ++ adblock::FilterMatchResult result); ++ + base::WeakPtrFactory> + weak_factory_{this}; -- String item = (String) getItem(position); -+ T item = (T) getItem(position); - Holder holder = (Holder) convertView.getTag(); -- holder.mItem.setText(item.toString()); -+ holder.mItem.setText(getItemText(item)); -+ holder.mStatus.setText(getItemStatus(item)); - holder.mRemoveButton.setOnClickListener(removeItemClickListener); -- holder.mRemoveButton.setTag(item.toString()); -+ holder.mRemoveButton.setTag(getItemText(item)); +@@ -159,41 +205,46 @@ void AdblockContentBrowserClient< - return convertView; - } - } + template + bool AdblockContentBrowserClient:: +- WillInterceptWebSocket(content::RenderFrameHost* frame) { +- if (frame && IsFilteringNeeded(GetBrowserContext(frame))) { ++ WillInterceptWebSocket(content::RenderFrameHost* frame, ++ content::RenderProcessHost* process, ++ const url::Origin& origin) { ++ if (IsFilteringNeeded(frame, origin.GetURL())) { + return true; + } +- return ContentBrowserClientBase::WillInterceptWebSocket(frame); ++ return ContentBrowserClientBase::WillInterceptWebSocket(frame, process, origin); + } -+ protected abstract String getItemText(T item); + template + void AdblockContentBrowserClient::CreateWebSocket( ++ content::RenderProcessHost* process, + content::RenderFrameHost* frame, + content::ContentBrowserClient::WebSocketFactory factory, + const GURL& url, ++ const url::Origin& initiator_origin, + const net::SiteForCookies& site_for_cookies, + const absl::optional& user_agent, + mojo::PendingRemote + handshake_client) { +- if (frame && IsFilteringNeeded(GetBrowserContext(frame))) { ++ if (IsFilteringNeeded(frame, url)) { + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( + GetBrowserContext(frame)); + auto* classification_runner = + adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( + GetBrowserContext(frame)); ++ std::vector dns_aliases; + classification_runner->CheckRequestFilterMatch( +- subscription_service->GetCurrentSnapshot(), url, ContentType::Websocket, +- RequestInitiator(frame), ++ subscription_service->GetCurrentSnapshot(), url, dns_aliases, ContentType::Websocket, ++ RequestInitiator(initiator_origin.GetURL().GetAsReferrer()), + base::BindOnce( + &AdblockContentBrowserClient< + ContentBrowserClientBase>::OnWebSocketFilterCheckCompleted, +- weak_factory_.GetWeakPtr(), frame->GetGlobalId(), +- std::move(factory), url, site_for_cookies, user_agent, ++ weak_factory_.GetWeakPtr(), process, frame->GetGlobalId(), ++ std::move(factory), url, initiator_origin, site_for_cookies, user_agent, + std::move(handshake_client))); + } else { +- DCHECK(ContentBrowserClientBase::WillInterceptWebSocket(frame)); +- ContentBrowserClientBase::CreateWebSocket(frame, std::move(factory), url, ++ DCHECK(ContentBrowserClientBase::WillInterceptWebSocket(frame, process, initiator_origin)); ++ ContentBrowserClientBase::CreateWebSocket(process, frame, std::move(factory), url, initiator_origin, + site_for_cookies, user_agent, + std::move(handshake_client)); + } +@@ -211,6 +262,97 @@ void AdblockContentBrowserClient:: + adblock::AdblockInternalsUI>(map); + } + ++template ++void AdblockContentBrowserClient:: ++ WillCreateWebTransport( ++ int process_id, ++ int frame_routing_id, ++ const GURL& url, ++ const url::Origin& initiator_origin, ++ mojo::PendingRemote ++ handshake_client, ++ ChromeContentBrowserClient::WillCreateWebTransportCallback callback) { ++ auto* process = content::RenderProcessHost::FromID(process_id); ++ DCHECK(process); + -+ protected String getItemStatus(T item) { -+ return null; -+ } ++ auto* browser_context = process->GetBrowserContext(); ++ if (IsFilteringNeeded(browser_context, initiator_origin.GetURL().GetAsReferrer())) { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ browser_context); ++ auto* classification_runner = ++ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser_context); + - private void initControls() { - mAddButton.setOnClickListener(new View.OnClickListener() { - @Override -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java -@@ -82,9 +82,12 @@ public class AdblockFilterListsAdapter extends BaseAdapter implements OnClickLis - final List subscriptions = - mController.getInstalledSubscriptions(); - boolean subscribed = false; -+ TextView status = view.findViewById(R.id.status); -+ status.setText(""); - for (final AdblockController.Subscription subscription : subscriptions) { - if (subscription.url().equals(item.url())) { - subscribed = true; -+ status.setText(subscription.getDescription()); - break; - } - } -@@ -94,6 +97,9 @@ public class AdblockFilterListsAdapter extends BaseAdapter implements OnClickLis - TextView description = view.findViewById(R.id.name); - description.setText(item.title()); - description.setContentDescription(item.title() + "filer list item title text"); ++ classification_runner->CheckRequestFilterMatchForWebTransport( ++ subscription_service->GetCurrentSnapshot(), url, ++ adblock::RequestInitiator(initiator_origin.GetURL().GetAsReferrer()), ++ content::GlobalRenderFrameHostId(), ++ base::BindOnce( ++ &AdblockContentBrowserClient::OnWebTransportFilterCheckCompleted, ++ weak_factory_.GetWeakPtr(), ++ process_id, frame_routing_id, url, ++ initiator_origin, std::move(handshake_client), ++ std::move(callback))); ++ return; ++ } + -+ TextView url = view.findViewById(R.id.url); -+ url.setText(item.url().toString()); - return view; - } - -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java -@@ -13,6 +13,7 @@ - package org.chromium.components.adblock.settings; - - import android.os.Bundle; -+import android.widget.Toast; - - import androidx.preference.Preference; - import androidx.preference.PreferenceFragmentCompat; -@@ -20,6 +21,7 @@ import androidx.preference.PreferenceFragmentCompat; - import org.chromium.build.BuildConfig; - import org.chromium.chrome.browser.preferences.Pref; - import org.chromium.chrome.browser.profiles.Profile; -+import org.chromium.chrome.browser.profiles.ProfileManager; - import org.chromium.components.adblock.AdblockController; - import org.chromium.components.adblock.R; - import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; -@@ -32,7 +34,19 @@ public class AdblockSettingsFragment - private Preference mFilterLists; - private Preference mAllowedDomains; - private Preference mMoreOptions; -- -+ private Preference mStartUpdate; -+ private ChromeSwitchPreference mPrivilegedFilters; ++ ChromeContentBrowserClient::WillCreateWebTransport( ++ process_id, frame_routing_id, ++ url, initiator_origin, ++ std::move(handshake_client), std::move(callback)); ++} + -+ private static final String START_UPDATE_KEY = -+ "fragment_adblock_settings_start_update"; -+ private static final String PRIVILEGED_FILTERS_KEY = -+ "fragment_adblock_privileged_filters_enabled_key"; -+ private static final String FILTER_LISTS_KEY = -+ "fragment_adblock_settings_filter_lists_key"; -+ private static final String CUSTOM_FILTER_LISTS_KEY = -+ "fragment_adblock_more_options_custom_filter_lists_key"; -+ private static final String CUSTOM_FILTER_KEY = -+ "fragment_adblock_more_options_custom_filter_key"; - private static final String SETTINGS_ENABLED_KEY = "fragment_adblock_settings_enabled_key"; - private static final String SETTINGS_FILTER_LISTS_KEY = - "fragment_adblock_settings_filter_lists_key"; -@@ -49,27 +63,48 @@ public class AdblockSettingsFragment - private long mOnOffTogleTimestamp; - - private void bindPreferences() { -+ mStartUpdate = findPreference(START_UPDATE_KEY); -+ mStartUpdate.setOnPreferenceClickListener(preference -> { -+ AdblockController.getInstance().startUpdate(); -+ Toast toast = Toast.makeText(getContext(), -+ "Checking for updates in progress", Toast.LENGTH_LONG); -+ toast.show(); -+ // handle the click so the default action isn't triggered. -+ return true; -+ }); -+ mPrivilegedFilters = (ChromeSwitchPreference) findPreference(PRIVILEGED_FILTERS_KEY); - mAdblockEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_ENABLED_KEY); - mFilterLists = findPreference(SETTINGS_FILTER_LISTS_KEY); - mAcceptableAdsEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_AA_ENABLED_KEY); -+ mAcceptableAdsEnabled.setVisible(false); - mAllowedDomains = findPreference(SETTINGS_ALLOWED_DOMAINS_KEY); - mMoreOptions = findPreference(SETTINGS_MORE_OPTIONS_KEY); - } - - private boolean areMoreOptionsEnabled() { -- return UserPrefs.get(Profile.getLastUsedRegularProfile()) -- .getBoolean(Pref.ADBLOCK_MORE_OPTIONS_ENABLED); -+ return false; - } - - private void applyAdblockEnabled(boolean enabledValue) { -- mFilterLists.setEnabled(enabledValue); -- mAcceptableAdsEnabled.setEnabled(enabledValue); -+ mStartUpdate.setEnabled(enabledValue); -+ mPrivilegedFilters.setEnabled(enabledValue); - mAllowedDomains.setEnabled(enabledValue); - mMoreOptions.setEnabled(enabledValue); - mMoreOptions.setVisible(areMoreOptionsEnabled()); - } - - private void synchronizePreferences() { -+ findPreference(FILTER_LISTS_KEY).setTitle( -+ getContext().getString(R.string.fragment_adblock_settings_filter_lists_title_count, -+ AdblockController.getInstance().getInstalledSubscriptions().size())); -+ findPreference(CUSTOM_FILTER_LISTS_KEY).setTitle( -+ getContext().getString(R.string.fragment_adblock_more_options_custom_filter_lists_title_count, -+ AdblockController.getInstance().getCustomSubscriptions().size())); -+ findPreference(CUSTOM_FILTER_KEY).setTitle( -+ getContext().getString(R.string.fragment_adblock_more_options_custom_filters_title_count, -+ AdblockController.getInstance().getCustomFilters().size())); -+ mPrivilegedFilters.setChecked(AdblockController.getInstance().isPrivilegedFiltersEnabled()); -+ mPrivilegedFilters.setOnPreferenceChangeListener(this); - boolean enabled = AdblockController.getInstance().isEnabled(); - mAdblockEnabled.setChecked(enabled); - mAdblockEnabled.setOnPreferenceChangeListener(this); -@@ -95,7 +130,7 @@ public class AdblockSettingsFragment ++template ++void AdblockContentBrowserClient:: ++ OnWebTransportFilterCheckCompleted( ++ int process_id, ++ int frame_routing_id, ++ const GURL& url, ++ const url::Origin& initiator_origin, ++ mojo::PendingRemote ++ handshake_client, ++ ChromeContentBrowserClient::WillCreateWebTransportCallback callback, ++ adblock::FilterMatchResult result) { ++ const bool has_blocking_filter = ++ result == adblock::FilterMatchResult::kBlockRule; ++ if (!has_blocking_filter) { ++ VLOG(1) << "[eyeo] Web transport allowed for " << url; ++ ChromeContentBrowserClient::WillCreateWebTransport( ++ process_id, frame_routing_id, ++ url, initiator_origin, ++ std::move(handshake_client), std::move(callback)); ++ return; ++ } ++ VLOG(1) << "[eyeo] Web transport blocked for " << url; ++ std::move(callback).Run(std::move(handshake_client), ++ network::mojom::WebTransportError::New( ++ net::ERR_BLOCKED_BY_ADMINISTRATOR, quic::QUIC_INTERNAL_ERROR, ++ "Blocked", false)); ++} ++ ++template ++bool AdblockContentBrowserClient:: ++ CanCreateWindow( ++ content::RenderFrameHost* opener, ++ const GURL& opener_url, ++ const GURL& opener_top_level_frame_url, ++ const url::Origin& source_origin, ++ content::mojom::WindowContainerType container_type, ++ const GURL& target_url, ++ const content::Referrer& referrer, ++ const std::string& frame_name, ++ WindowOpenDisposition disposition, ++ const blink::mojom::WindowFeatures& features, ++ bool user_gesture, ++ bool opener_suppressed, ++ bool* no_javascript_access) { ++ return ChromeContentBrowserClient::CanCreateWindow( ++ opener, opener_url, opener_top_level_frame_url, source_origin, ++ container_type, target_url, referrer, frame_name, disposition, features, ++ user_gesture, opener_suppressed, no_javascript_access); ++} ++ + template + void AdblockContentBrowserClient:: + WillCreateURLLoaderFactory( +@@ -250,29 +392,8 @@ void AdblockContentBrowserClient:: + 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 ++ type != content::ContentBrowserClient::URLLoaderFactoryType::kDownload && ++ IsFilteringNeeded(frame, request_initiator.GetURL()); - mOnOffTogleTimestamp = now; - if (mOnOffClickCount >= ON_OFF_TOGGLE_COUNT_TO_ENABLE_MORE_OPTIONS) { -- UserPrefs.get(Profile.getLastUsedRegularProfile()) -+ UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) - .setBoolean(Pref.ADBLOCK_MORE_OPTIONS_ENABLED, true); - } - } -@@ -107,7 +142,7 @@ public class AdblockSettingsFragment + if (use_adblock_proxy) { + auto [proxied_receiver, target_factory_remote] = factory_builder.Append(); +@@ -282,16 +403,18 @@ void AdblockContentBrowserClient:: + AdblockContextData::StartProxying( + eyeo_browser_context, initiator, std::move(proxied_receiver), + std::move(target_factory_remote), +- ContentBrowserClientBase::GetUserAgent(), use_test_loader); ++ ContentBrowserClientBase::GetUserAgent(), /*use_test_loader*/ false); + } + } - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { -- addPreferencesFromResource(R.xml.adblock_preferences); -+ addPreferencesFromResource(R.xml.eyeo_adblock_preferences); - bindPreferences(); - synchronizePreferences(); + template + void AdblockContentBrowserClient:: + OnWebSocketFilterCheckCompleted( ++ content::RenderProcessHost* process, + content::GlobalRenderFrameHostId render_frame_host_id, + content::ContentBrowserClient::WebSocketFactory factory, + const GURL& url, ++ const url::Origin& initiator_origin, + const net::SiteForCookies& site_for_cookies, + const absl::optional& user_agent, + mojo::PendingRemote +@@ -305,9 +428,9 @@ void AdblockContentBrowserClient:: + 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, ++ if (ContentBrowserClientBase::WillInterceptWebSocket(frame, process, initiator_origin)) { ++ ContentBrowserClientBase::CreateWebSocket(process, frame, std::move(factory), url, ++ initiator_origin, site_for_cookies, user_agent, + std::move(handshake_client)); + return; } -@@ -126,6 +161,8 @@ public class AdblockSettingsFragment - maybeEnableMoreOptions(); - - applyAdblockEnabled((Boolean) newValue); -+ } else if (preference.getKey().equals(PRIVILEGED_FILTERS_KEY)) { -+ AdblockController.getInstance().setPrivilegedFiltersEnabled((Boolean) newValue); - } else { - assert preference.getKey().equals(SETTINGS_AA_ENABLED_KEY); - AdblockController.getInstance().setAcceptableAdsEnabled((Boolean) newValue); -diff --git a/components/adblock/content/browser/BUILD.gn b/components/adblock/content/browser/BUILD.gn ---- a/components/adblock/content/browser/BUILD.gn -+++ b/components/adblock/content/browser/BUILD.gn -@@ -14,15 +14,8 @@ - # You should have received a copy of the GNU General Public License - # along with eyeo Chromium SDK. If not, see . +@@ -324,16 +447,42 @@ void AdblockContentBrowserClient:: + VLOG(1) << "[eyeo] Web socket blocked for " << url; + } --import("//components/adblock/features.gni") -- - config("adblock_content_common_config") { - defines = [] -- -- if (eyeo_intercept_debug_url) { -- print("WARNING! Enabled intercepting eyeo debug url \"adblock.test.data\"") -- defines += [ "EYEO_INTERCEPT_DEBUG_URL=1" ] -- } +-// static + template + bool AdblockContentBrowserClient::IsFilteringNeeded( +- content::BrowserContext* browser_context) { ++ content::RenderFrameHost* frame, const GURL& embedder_url) { ++ if (frame) { ++ auto* browser_context = frame->GetProcess()->GetBrowserContext(); ++ content::RenderFrameHost* embedder = frame->GetOutermostMainFrameOrEmbedder(); ++ const auto& top_frame_url = embedder->GetLastCommittedURL(); ++ if(top_frame_url.is_empty()) { ++ return IsFilteringNeeded(browser_context, embedder_url); ++ } else { ++ return IsFilteringNeeded(browser_context, top_frame_url); ++ } ++ } ++ return false; ++} ++ ++template ++bool AdblockContentBrowserClient::IsFilteringNeeded( ++ content::BrowserContext* browser_context, const GURL& embedder_url) { ++ if(embedder_url.is_empty()) { ++ // in android can be empty because it was created by ++ // RenderFrameHostImpl::CreateSubresourceLoaderFactoriesForInitialEmptyDocument ++ return true; ++ } ++ ++ if (embedder_url.SchemeIs(content_settings::kChromeUIScheme)) { ++ return false; ++ } ++ + if (browser_context) { +- return base::ranges::any_of( +- adblock::SubscriptionServiceFactory::GetForBrowserContext( +- browser_context) +- ->GetInstalledFilteringConfigurations(), +- &adblock::FilteringConfiguration::IsEnabled); ++ HostContentSettingsMap* settings_map = GetHostContentSettingsMap(browser_context); ++ if (settings_map && settings_map->GetContentSetting(embedder_url, GURL(), ContentSettingsType::ADS) ++ == CONTENT_SETTING_ALLOW) { ++ return false; ++ } ++ return true; + } + return false; } +diff --git a/components/adblock/content/browser/adblock_internals_page_handler.cc b/components/adblock/content/browser/adblock_internals_page_handler.cc +--- a/components/adblock/content/browser/adblock_internals_page_handler.cc ++++ b/components/adblock/content/browser/adblock_internals_page_handler.cc +@@ -19,7 +19,6 @@ - source_set("browser_impl") { -@@ -34,8 +27,6 @@ source_set("browser_impl") { - "adblock_controller_factory_base.cc", - "adblock_controller_factory_base.h", - "adblock_filter_match.h", -- "adblock_telemetry_service_factory_base.cc", -- "adblock_telemetry_service_factory_base.h", - "adblock_url_loader_factory.cc", - "adblock_url_loader_factory.h", - "adblock_webcontents_observer.cc", -@@ -62,15 +53,10 @@ source_set("browser_impl") { + #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" +@@ -120,11 +119,9 @@ void AdblockInternalsPageHandler::GetDebugInfo(GetDebugInfoCallback callback) { + 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))); ++ std::vector topic_provider_content; ++ AdblockInternalsPageHandler::OnTelemetryServiceInfoArrived( ++ std::move(callback), std::move(content), topic_provider_content); + } -- if (eyeo_intercept_debug_url) { -- sources += [ -- "adblock_url_loader_factory_for_test.cc", -- "adblock_url_loader_factory_for_test.h", -- ] -- } -- - deps = [ - "//base", -+ "//components/content_settings/browser", -+ "//components/content_settings/core/browser", - "//components/adblock/core/converter:converter", - "//components/keyed_service/content:content", - "//components/resources:components_resources_grit", -diff --git a/components/adblock/content/browser/adblock_controller_factory_base.cc b/components/adblock/content/browser/adblock_controller_factory_base.cc ---- a/components/adblock/content/browser/adblock_controller_factory_base.cc -+++ b/components/adblock/content/browser/adblock_controller_factory_base.cc -@@ -48,10 +48,7 @@ AdblockControllerFactoryBase::BuildServiceInstanceForBrowserContext( - std::make_unique( - prefs, kAdblockFilteringConfigurationName); - -- if (base::CommandLine::ForCurrentProcess()->HasSwitch( -- adblock::switches::kDisableAcceptableAds)) { -- adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl()); -- } -+ adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl()); - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableAdblock) || - base::CommandLine::ForCurrentProcess()->HasSwitch( + void AdblockInternalsPageHandler::ToggleTestpagesFLSubscription( diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/components/adblock/content/browser/adblock_url_loader_factory.cc --- a/components/adblock/content/browser/adblock_url_loader_factory.cc +++ b/components/adblock/content/browser/adblock_url_loader_factory.cc -@@ -80,7 +80,9 @@ ContentType ToAdblockResourceType(const network::ResourceRequest& request) { - case network::mojom::RequestDestination::kWorker: - case network::mojom::RequestDestination::kSharedWorker: - case network::mojom::RequestDestination::kServiceWorker: -- case network::mojom::RequestDestination::kJson: -+ case network::mojom::RequestDestination::kSharedStorageWorklet: -+ case network::mojom::RequestDestination::kAudioWorklet: -+ case network::mojom::RequestDestination::kPaintWorklet: - return ContentType::Script; - case network::mojom::RequestDestination::kImage: - return ContentType::Image; -@@ -101,16 +103,14 @@ ContentType ToAdblockResourceType(const network::ResourceRequest& request) { - return ContentType::Other; - case network::mojom::RequestDestination::kWebBundle: - return ContentType::WebBundle; -+ case network::mojom::RequestDestination::kJson: - case network::mojom::RequestDestination::kReport: -- case network::mojom::RequestDestination::kAudioWorklet: - case network::mojom::RequestDestination::kDictionary: - case network::mojom::RequestDestination::kManifest: -- case network::mojom::RequestDestination::kPaintWorklet: +@@ -117,7 +117,6 @@ ContentType ToAdblockResourceType(const network::ResourceRequest& request) { case network::mojom::RequestDestination::kWebIdentity: return ContentType::Other; } - NOTREACHED(); - return ContentType::Other; } + bool IsPopup(const RequestInitiator& initiator) { +@@ -145,8 +144,7 @@ bool IsPopup(const RequestInitiator& initiator) { + // 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"); ++ return false; + } + } // namespace -@@ -184,8 +184,13 @@ class AdblockURLLoaderFactory::InProgressRequest +@@ -220,8 +218,13 @@ class AdblockURLLoaderFactory::InProgressRequest void OnRequestError(int error_code); void CheckFilterMatch(CheckFilterMatchCallback callback); void ProcessResponseHeaders( @@ -2674,7 +2839,23 @@ diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/ void CheckRewriteFilterMatch(CheckRewriteFilterMatchCallback callback); void OnRequestUrlClassified(CheckFilterMatchCallback callback, FilterMatchResult result); -@@ -293,7 +298,9 @@ void AdblockURLLoaderFactory::InProgressRequest::OnReceiveResponse( +@@ -246,6 +249,7 @@ class AdblockURLLoaderFactory::InProgressRequest + GURL request_url_; + int request_id_; + bool is_document_request_; ++ bool is_popup_; + ContentType adblock_resource_type_; + const raw_ptr factory_; + // There are the mojo pipe endpoints between this proxy and the renderer. +@@ -275,6 +279,7 @@ AdblockURLLoaderFactory::InProgressRequest::InProgressRequest( + : request_url_(request.url), + request_id_(request_id), + is_document_request_(IsDocumentRequest(request)), ++ is_popup_(false), + adblock_resource_type_(ToAdblockResourceType(request)), + factory_(factory), + target_client_(std::move(client)), +@@ -358,7 +363,9 @@ void AdblockURLLoaderFactory::InProgressRequest::OnReceiveResponse( VLOG(1) << "[eyeo] Sending headers for processing: " << request_url_; client_receiver_.Pause(); const scoped_refptr& headers = head->headers; @@ -2684,41 +2865,45 @@ diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/ headers, base::BindOnce(&InProgressRequest::OnProcessHeadersResult, weak_factory_.GetWeakPtr(), std::move(head), std::move(body), std::move(cached_metadata))); -@@ -330,12 +337,6 @@ void AdblockURLLoaderFactory::InProgressRequest::OnRequestError( - - void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( - CheckFilterMatchCallback callback) { -- if (!factory_->CheckHostValid()) { -- PostFilterMatchCallbackToUI(std::move(callback), -- FilterMatchResult::kNoRule); -- return; -- } -- +@@ -405,6 +412,7 @@ void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( auto subscription_service = factory_->config_.subscription_service; if (is_document_request_) { - auto* host = content::RenderFrameHost::FromID(factory_->host_id_); -@@ -372,9 +373,10 @@ void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( - FilterMatchResult::kNoRule); + if (IsPopup(initiator)) { ++ is_popup_ = true; + auto* host = initiator.GetRenderFrameHost(); + factory_->config_.resource_classifier->CheckPopupFilterMatch( + subscription_service->GetCurrentSnapshot(), request_url_, *host, +@@ -415,13 +423,12 @@ void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( + base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest:: + PostFilterMatchCallbackToUI, + weak_factory_.GetWeakPtr(), std::move(callback)))); +- } else { +- PostFilterMatchCallbackToUI(std::move(callback), +- FilterMatchResult::kNoRule); ++ return; } - } else { +- } else { ++ } + std::vector dns_aliases; factory_->config_.resource_classifier->CheckRequestFilterMatch( - subscription_service->GetCurrentSnapshot(), request_url_, -- adblock_resource_type_, factory_->host_id_, + subscription_service->GetCurrentSnapshot(), request_url_, std::move(dns_aliases), -+ factory_->request_initiator_, adblock_resource_type_, factory_->host_id_, + adblock_resource_type_, initiator, base::BindOnce( &AdblockURLLoaderFactory::InProgressRequest::OnRequestUrlClassified, - weak_factory_.GetWeakPtr(), -@@ -385,6 +387,7 @@ void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( +@@ -429,10 +436,10 @@ void AdblockURLLoaderFactory::InProgressRequest::CheckFilterMatch( + base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest:: + PostFilterMatchCallbackToUI, + weak_factory_.GetWeakPtr(), std::move(callback)))); +- } } void AdblockURLLoaderFactory::InProgressRequest::ProcessResponseHeaders( + const std::vector& dns_aliases, const scoped_refptr& headers, ProcessResponseHeadersCallback callback) { - if (!factory_->CheckHostValid()) { -@@ -393,6 +396,36 @@ void AdblockURLLoaderFactory::InProgressRequest::ProcessResponseHeaders( + if (IsRequestInitiatorDestroyed()) { +@@ -441,6 +448,37 @@ void AdblockURLLoaderFactory::InProgressRequest::ProcessResponseHeaders( return; } @@ -2726,7 +2911,7 @@ diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/ + if (dns_aliases.size() > 1) { + factory_->config_.resource_classifier->CheckRequestFilterMatch( + subscription_service->GetCurrentSnapshot(), request_url_, std::move(dns_aliases), -+ factory_->request_initiator_, adblock_resource_type_, factory_->host_id_, ++ adblock_resource_type_, factory_->request_initiator_, + base::BindOnce( + &AdblockURLLoaderFactory::InProgressRequest::OnProcessResponseHeaders2, + weak_factory_.GetWeakPtr(), headers, std::move(callback))); @@ -2734,7 +2919,8 @@ diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/ + } + factory_->config_.resource_classifier->CheckResponseFilterMatch( + subscription_service->GetCurrentSnapshot(), request_url_, -+ adblock_resource_type_, factory_->host_id_, headers, ++ adblock_resource_type_, factory_->request_initiator_, ++ /*factory_->host_id_,*/ headers, + base::BindOnce( + &AdblockURLLoaderFactory::InProgressRequest:: + OnResponseHeadersClassified, @@ -2755,73 +2941,39 @@ diff --git a/components/adblock/content/browser/adblock_url_loader_factory.cc b/ auto subscription_service = factory_->config_.subscription_service; factory_->config_.resource_classifier->CheckResponseFilterMatch( subscription_service->GetCurrentSnapshot(), request_url_, -@@ -663,12 +696,13 @@ void AdblockURLLoaderFactory::InProgressRequest::OnRequestFilterMatchResult( - - AdblockURLLoaderFactory::AdblockURLLoaderFactory( - AdblockURLLoaderFactoryConfig config, -+ GURL request_initiator, - content::GlobalRenderFrameHostId host_id, -- mojo::PendingReceiver receiver, -- mojo::PendingRemote target_factory, -+ network::URLLoaderFactoryBuilder& factory_builder, - std::string user_agent_string, - DisconnectCallback on_disconnect) - : config_(std::move(config)), -+ request_initiator_(std::move(request_initiator)), - host_id_(host_id), - user_agent_string_(std::move(user_agent_string)), - on_disconnect_(std::move(on_disconnect)) { -@@ -677,10 +711,12 @@ AdblockURLLoaderFactory::AdblockURLLoaderFactory( - DCHECK(config_.element_hider); - DCHECK(config_.sitekey_storage); - DCHECK(config_.csp_injector); -+ auto [loader_receiver, target_factory] = factory_builder.Append(); -+ DCHECK(!target_factory_.is_bound()); - 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_.Add(this, std::move(loader_receiver)); - proxy_receivers_.set_disconnect_handler(base::BindRepeating( - &AdblockURLLoaderFactory::OnProxyBindingError, base::Unretained(this))); - } -diff --git a/components/adblock/content/browser/adblock_url_loader_factory.h b/components/adblock/content/browser/adblock_url_loader_factory.h ---- a/components/adblock/content/browser/adblock_url_loader_factory.h -+++ b/components/adblock/content/browser/adblock_url_loader_factory.h -@@ -25,6 +25,8 @@ - #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 "services/network/public/cpp/url_loader_factory_builder.h" -+#include "url/gurl.h" - - namespace adblock { +@@ -661,6 +699,10 @@ void AdblockURLLoaderFactory::InProgressRequest::Start( + VLOG(1) << "[eyeo] Checking filter match for: " << request.url << " (" + << request.resource_type << ")"; -@@ -57,9 +59,9 @@ class AdblockURLLoaderFactory : public network::mojom::URLLoaderFactory { - - AdblockURLLoaderFactory( - AdblockURLLoaderFactoryConfig config, -+ GURL request_initiator, - content::GlobalRenderFrameHostId host_id, -- mojo::PendingReceiver receiver, -- mojo::PendingRemote target_factory, -+ network::URLLoaderFactoryBuilder& factory_builder, - std::string user_agent_string, - DisconnectCallback on_disconnect); - ~AdblockURLLoaderFactory() override; -@@ -86,6 +88,7 @@ class AdblockURLLoaderFactory : public network::mojom::URLLoaderFactory { - void MaybeDestroySelf(); - - AdblockURLLoaderFactoryConfig config_; -+ const GURL request_initiator_; - content::GlobalRenderFrameHostId host_id_; - mojo::ReceiverSet proxy_receivers_; - std::set, base::UniquePtrComparator> ++ if (IsRequestInitiatedByFrame()) { ++ SetPreCommitUrlForFrame(); ++ } ++ + CheckFilterMatch(base::BindOnce( + &InProgressRequest::OnRequestFilterMatchResult, + weak_factory_.GetWeakPtr(), std::move(target_loader), options, request, +@@ -729,10 +771,12 @@ void AdblockURLLoaderFactory::InProgressRequest::ApplyPostBlockingBehavior() + // 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(); ++ if (is_popup_) { ++ // 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. diff --git a/components/adblock/content/browser/adblock_webcontents_observer.cc b/components/adblock/content/browser/adblock_webcontents_observer.cc --- a/components/adblock/content/browser/adblock_webcontents_observer.cc +++ b/components/adblock/content/browser/adblock_webcontents_observer.cc -@@ -21,6 +21,8 @@ - #include "components/adblock/content/browser/frame_opener_info.h" +@@ -22,10 +22,15 @@ + #include "components/adblock/content/browser/request_initiator.h" #include "components/adblock/core/common/sitekey.h" #include "components/adblock/core/subscription/subscription_service.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" @@ -2829,17 +2981,15 @@ diff --git a/components/adblock/content/browser/adblock_webcontents_observer.cc #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" -@@ -56,7 +58,7 @@ const char* WindowOpenDispositionToString(WindowOpenDisposition value) { - return ""; - } - } --} // namespace -+ - void TraceHandleLoadComplete( - intptr_t rfh_trace_id, -@@ -66,18 +68,32 @@ void TraceHandleLoadComplete( - TRACE_ID_LOCAL(rfh_trace_id)); ++#include "components/adblock/content/browser/adblock_blocking_page.h" ++#include "components/security_interstitials/content/security_interstitial_tab_helper.h" ++ + namespace { + const char* WindowOpenDispositionToString(WindowOpenDisposition value) { + switch (value) { +@@ -84,6 +89,16 @@ bool ShouldSkipElementHiding(const GURL& url) { + !url.IsAboutBlank(); } +bool IsFilteringNeeded(content::RenderFrameHost* frame, HostContentSettingsMap* settings_map) { @@ -2852,69 +3002,116 @@ diff --git a/components/adblock/content/browser/adblock_webcontents_observer.cc + return true; +} + -+} // namespace -+ - AdblockWebContentObserver::AdblockWebContentObserver( - content::WebContents* web_contents, - adblock::SubscriptionService* subscription_service, - adblock::ElementHider* element_hider, - adblock::SitekeyStorage* sitekey_storage, -- std::unique_ptr frame_hierarchy_builder) -+ std::unique_ptr frame_hierarchy_builder, + } // namespace + + namespace adblock { +@@ -94,14 +109,16 @@ AdblockWebContentObserver::AdblockWebContentObserver( + ElementHider* element_hider, + SitekeyStorage* sitekey_storage, + std::unique_ptr frame_hierarchy_builder, +- base::RepeatingCallback navigation_counter) ++ base::RepeatingCallback navigation_counter, + HostContentSettingsMap* settings_map) : content::WebContentsObserver(web_contents), content::WebContentsUserData(*web_contents), subscription_service_(subscription_service), element_hider_(element_hider), sitekey_storage_(sitekey_storage), -- frame_hierarchy_builder_(std::move(frame_hierarchy_builder)) {} -+ frame_hierarchy_builder_(std::move(frame_hierarchy_builder)), + frame_hierarchy_builder_(std::move(frame_hierarchy_builder)), +- navigation_counter_(std::move(navigation_counter)) {} ++ navigation_counter_(std::move(navigation_counter)), + settings_map_(settings_map) {} AdblockWebContentObserver::~AdblockWebContentObserver() = default; -@@ -122,6 +138,9 @@ void AdblockWebContentObserver::DidFinishNavigation( - if (!navigation_handle->GetRenderFrameHost()) { - return; - } -+ if (!IsFilteringNeeded(navigation_handle->GetRenderFrameHost(), settings_map_)) { -+ return; +@@ -141,7 +158,8 @@ void AdblockWebContentObserver::DidFinishNavigation( + VLOG(1) << "[eyeo] Finished navigation: URL=" << url + << ", has_commited=" << navigation_handle->HasCommitted() + << ", is_error=" << navigation_handle->IsErrorPage() +- << ", isInMainFrame=" << navigation_handle->IsInMainFrame(); ++ << ", isInMainFrame=" << navigation_handle->IsInMainFrame() ++ << ", NetErrorCode=" << navigation_handle->GetNetErrorCode(); + content::RenderFrameHost* frame = nullptr; + if (navigation_handle->HasCommitted()) { + frame = navigation_handle->GetRenderFrameHost(); +@@ -153,6 +171,44 @@ void AdblockWebContentObserver::DidFinishNavigation( + VLOG(1) << "[eyeo] Unsupported scheme, skipping injection."; + return; + } ++ if (navigation_handle->GetNetErrorCode() == net::ERR_BLOCKED_BY_ADMINISTRATOR ++ && navigation_handle->IsInMainFrame()) { ++ GURL subscription_url; ++ if (auto* rfh = content::RenderFrameHost::FromID( ++ navigation_handle->GetPreviousRenderFrameHostId())) { ++ EyeoDocumentInfo* document_info = ++ EyeoDocumentInfo::GetForCurrentDocument(rfh); ++ if (document_info) { ++ subscription_url = document_info->GetSubscriptionUrl(); ++ } ++ } ++ ++ auto* web_contents = navigation_handle->GetWebContents(); ++ security_interstitials::SecurityInterstitialTabHelper::CreateForWebContents(web_contents); ++ ++ std::unique_ptr blocking_page = ++ AdBlockPage::CreateBlockingPage( ++ navigation_handle->GetWebContents(), navigation_handle->GetURL(), ++ subscription_service_, ++ subscription_url); ++ std::string error_page_content = blocking_page->GetHTMLContents(); ++ ++ base::WeakPtr error_page_navigation_handle = ++ web_contents->GetController().LoadPostCommitErrorPage( ++ web_contents->GetPrimaryMainFrame(), url, ++ blocking_page->GetHTMLContents()); ++ if (error_page_navigation_handle) { ++ blocking_page->CreatedPostCommitErrorPageNavigation( ++ error_page_navigation_handle.get()); ++ security_interstitials::SecurityInterstitialTabHelper:: ++ AssociateBlockingPage(error_page_navigation_handle.get(), ++ std::move(blocking_page)); + } - if (!navigation_handle->IsErrorPage()) { - DVLOG(3) << "[eyeo] Ready to inject JS to " << url.spec(); - HandleOnLoad(navigation_handle->GetRenderFrameHost()); ++ return; ++ } ++ if (!IsFilteringNeeded(frame, settings_map_)) { ++ return; ++ } + if (!navigation_handle->IsErrorPage()) { + // Element hiding for ordinary main frame (or iframe) + DVLOG(3) << "[eyeo] Ready to inject element hiding to " << url.spec(); diff --git a/components/adblock/content/browser/adblock_webcontents_observer.h b/components/adblock/content/browser/adblock_webcontents_observer.h --- a/components/adblock/content/browser/adblock_webcontents_observer.h +++ b/components/adblock/content/browser/adblock_webcontents_observer.h -@@ -24,6 +24,7 @@ - #include "components/adblock/core/adblock_controller.h" +@@ -23,6 +23,7 @@ + #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 "components/content_settings/core/browser/host_content_settings_map.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" -@@ -49,7 +50,8 @@ class AdblockWebContentObserver - adblock::SubscriptionService* subscription_service, - adblock::ElementHider* element_hider, - adblock::SitekeyStorage* sitekey_storage, -- std::unique_ptr frame_hierarchy_builder); -+ std::unique_ptr frame_hierarchy_builder, +@@ -53,7 +54,8 @@ class AdblockWebContentObserver + SitekeyStorage* sitekey_storage, + std::unique_ptr frame_hierarchy_builder, + base::RepeatingCallback +- navigation_counter); ++ navigation_counter, + HostContentSettingsMap* settings_map); ~AdblockWebContentObserver() override; AdblockWebContentObserver(const AdblockWebContentObserver&) = delete; AdblockWebContentObserver& operator=(const AdblockWebContentObserver&) = -@@ -81,5 +83,6 @@ class AdblockWebContentObserver - raw_ptr sitekey_storage_; +@@ -86,6 +88,7 @@ class AdblockWebContentObserver - std::unique_ptr frame_hierarchy_builder_; + std::unique_ptr frame_hierarchy_builder_; + base::RepeatingCallback navigation_counter_; + raw_ptr settings_map_ = nullptr; }; - #endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_WEBCONTENTS_OBSERVER_H_ + + } // namespace adblock diff --git a/components/adblock/content/browser/element_hider_impl.cc b/components/adblock/content/browser/element_hider_impl.cc --- a/components/adblock/content/browser/element_hider_impl.cc +++ b/components/adblock/content/browser/element_hider_impl.cc -@@ -211,7 +211,7 @@ void InsertUserCSSAndApplyElemHidingEmuJS( +@@ -242,7 +242,7 @@ void InsertUserCSSAndApplyElemHidingEmuJS( if (!frame_host) { // Render frame host was destroyed before element hiding could be applied. // This is not a bug, just legitimate a race condition. @@ -2922,8 +3119,16 @@ diff --git a/components/adblock/content/browser/element_hider_impl.cc b/componen + std::move(on_finished).Run(ElementHider::ElemhideInjectionData{}); return; } - auto* info = ElementHiderInfo::GetOrCreateForCurrentDocument(frame_host); -@@ -239,13 +239,10 @@ void InsertUserCSSAndApplyElemHidingEmuJS( + auto* info = EyeoDocumentInfo::GetOrCreateForCurrentDocument(frame_host); +@@ -263,20 +263,17 @@ void InsertUserCSSAndApplyElemHidingEmuJS( + frame_host->ExecuteJavaScriptInIsolatedWorld( + base::UTF8ToUTF16(input.elemhide_js), + content::RenderFrameHost::JavaScriptResultCallback(), +- content::ISOLATED_WORLD_ID_ADBLOCK); ++ content::ISOLATED_WORLD_ID_ADBLOCK_VERIFIED); + + DVLOG(1) << "[eyeo] Element hiding emulation - executed JS in frame" << " '" + << frame_host->GetFrameName() << "'"; } if (!input.snippet_js.empty()) { @@ -2937,119 +3142,426 @@ diff --git a/components/adblock/content/browser/element_hider_impl.cc b/componen - content::ISOLATED_WORLD_ID_ADBLOCK); + content::ISOLATED_WORLD_ID_GLOBAL); - DVLOG(1) << "[eyeo] Snippet - executed JS in frame" - << " '" << frame_host->GetFrameName() << "'"; + DVLOG(1) << "[eyeo] Snippet - executed JS in frame" << " '" + << frame_host->GetFrameName() << "'"; +@@ -358,7 +355,7 @@ void ElementHiderImpl::HideBlockedElement( + render_frame_host->ExecuteJavaScriptInIsolatedWorld( + base::UTF8ToUTF16(js), + content::RenderFrameHost::JavaScriptResultCallback(), +- content::ISOLATED_WORLD_ID_ADBLOCK); ++ content::ISOLATED_WORLD_ID_ADBLOCK_VERIFIED); + + DVLOG(1) << "[eyeo] Element hiding - executed JS in frame" << " '" + << render_frame_host->GetFrameName() << "'"; +diff --git a/components/adblock/content/browser/eyeo_document_info.cc b/components/adblock/content/browser/eyeo_document_info.cc +--- a/components/adblock/content/browser/eyeo_document_info.cc ++++ b/components/adblock/content/browser/eyeo_document_info.cc +@@ -47,4 +47,19 @@ void EyeoDocumentInfo::SetElementHidingDone() { + element_hiding_done_ = true; + } + ++void EyeoDocumentInfo::SetFilterMatchResult(FilterMatchResult result, ++ const GURL& subscription, ++ const std::string& configuration_name) { ++ result_ = result; ++ subscription_url_ = subscription; ++} ++ ++std::optional EyeoDocumentInfo::GetFilterMatchResult() const { ++ return result_; ++} ++ ++const GURL& EyeoDocumentInfo::GetSubscriptionUrl() const { ++ return subscription_url_; ++} ++ + } // namespace adblock +diff --git a/components/adblock/content/browser/eyeo_document_info.h b/components/adblock/content/browser/eyeo_document_info.h +--- a/components/adblock/content/browser/eyeo_document_info.h ++++ b/components/adblock/content/browser/eyeo_document_info.h +@@ -19,6 +19,7 @@ + #define COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_DOCUMENT_INFO_H_ + + #include "content/public/browser/document_user_data.h" ++#include "components/adblock/content/browser/adblock_filter_match.h" + #include "url/gurl.h" + + namespace adblock { +@@ -43,11 +44,19 @@ class EyeoDocumentInfo final + bool IsElementHidingDone() const; + void SetElementHidingDone(); + ++ void SetFilterMatchResult(FilterMatchResult result, ++ const GURL& subscription, ++ const std::string& configuration_name); ++ std::optional GetFilterMatchResult() const; ++ const GURL& GetSubscriptionUrl() const; ++ + private: + explicit EyeoDocumentInfo(content::RenderFrameHost* rfh); + + bool element_hiding_done_ = false; + GURL pre_commit_url_; ++ std::optional result_; ++ GURL subscription_url_; + + friend DocumentUserData; + DOCUMENT_USER_DATA_KEY_DECL(); +diff --git a/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc b/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc +--- a/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc ++++ b/components/adblock/content/browser/factories/adblock_request_throttle_factory.cc +@@ -56,7 +56,7 @@ AdblockRequestThrottleFactory::BuildServiceInstanceForBrowserContext( + base::CommandLine::ForCurrentProcess()->HasSwitch( + adblock::switches::kDisableEyeoRequestThrottling) + ? base::TimeDelta() +- : base::Seconds(30); ++ : base::Seconds(5); + throttle->AllowRequestsAfter(initial_delay); + return std::move(throttle); + } +diff --git a/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc +--- a/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc ++++ b/components/adblock/content/browser/factories/adblock_telemetry_service_factory.cc +@@ -45,13 +45,6 @@ base::TimeDelta GetCheckInterval() { + + } // namespace + +-// static +-AdblockTelemetryService* AdblockTelemetryServiceFactory::GetForBrowserContext( +- content::BrowserContext* context) { +- return static_cast( +- GetInstance()->GetServiceForBrowserContext(context, true)); +-} +- + // static + AdblockTelemetryServiceFactory* AdblockTelemetryServiceFactory::GetInstance() { + static base::NoDestructor instance; +diff --git a/components/adblock/content/browser/factories/embedding_utils.cc b/components/adblock/content/browser/factories/embedding_utils.cc +--- a/components/adblock/content/browser/factories/embedding_utils.cc ++++ b/components/adblock/content/browser/factories/embedding_utils.cc +@@ -20,7 +20,6 @@ + #include + + #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" +@@ -34,7 +33,6 @@ namespace adblock { + + void EnsureBackgroundServicesStarted(content::BrowserContext* browser_context) { + ResourceClassificationRunnerFactory::GetForBrowserContext(browser_context); +- AdblockTelemetryServiceFactory::GetForBrowserContext(browser_context); + SessionStatsFactory::GetForBrowserContext(browser_context); + SitekeyStorageFactory::GetForBrowserContext(browser_context); + } +diff --git a/components/adblock/content/browser/factories/embedding_utils.h b/components/adblock/content/browser/factories/embedding_utils.h +--- a/components/adblock/content/browser/factories/embedding_utils.h ++++ b/components/adblock/content/browser/factories/embedding_utils.h +@@ -23,6 +23,7 @@ + #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 "components/content_settings/core/browser/host_content_settings_map.h" + #include "content/public/browser/browser_context.h" + #include "content/public/browser/web_contents.h" + +@@ -32,13 +33,15 @@ namespace adblock { + template + void RegisterAdblockWebContentObserver( + content::WebContents* web_contents, +- content::BrowserContext* browser_context) { ++ content::BrowserContext* browser_context, ++ HostContentSettingsMap* settings_map) { + ObserverClass::CreateForWebContents( + web_contents, + SubscriptionServiceFactory::GetForBrowserContext(browser_context), + ElementHiderFactory::GetForBrowserContext(browser_context), + SitekeyStorageFactory::GetForBrowserContext(browser_context), +- std::make_unique(), CountNavigationsCallback()); ++ std::make_unique(), CountNavigationsCallback(), ++ settings_map); + } + + // Ensures that all background services are started for the given browser +diff --git a/components/adblock/content/browser/factories/subscription_service_factory.cc b/components/adblock/content/browser/factories/subscription_service_factory.cc +--- a/components/adblock/content/browser/factories/subscription_service_factory.cc ++++ b/components/adblock/content/browser/factories/subscription_service_factory.cc +@@ -23,6 +23,7 @@ + #include + + #include "absl/types/optional.h" ++#include "base/rand_util.h" + #include "base/command_line.h" + #include "base/files/file_util.h" + #include "base/functional/bind.h" +@@ -65,7 +66,7 @@ base::TimeDelta GetUpdateCheckInterval() { + static base::TimeDelta kCheckInterval = + g_update_check_interval_for_testing + ? g_update_check_interval_for_testing.value() +- : base::Hours(1); ++ : base::Hours(12) + base::Minutes(base::RandInt(-60,60)); + return kCheckInterval; + } + +@@ -93,7 +94,8 @@ std::unique_ptr MakeSubscriptionRequest( + ConversionResult ConvertFilterFile( + const scoped_refptr& converter, + const GURL& subscription_url, +- const base::FilePath& path) { ++ const base::FilePath& path, ++ bool allow_privileged_filter) { + TRACE_EVENT1("eyeo", "ConvertFileToFlatbuffer", "url", + subscription_url.spec()); + ConversionResult result; +@@ -105,7 +107,7 @@ ConversionResult ConvertFilterFile( + } else { + result = + converter->Convert(input_stream, subscription_url, +- config::AllowPrivilegedFilters(subscription_url)); ++ allow_privileged_filter && config::AllowPrivilegedFilters(subscription_url)); + } + base::DeleteFile(path); + return result; +@@ -167,6 +169,7 @@ void CleanupPersistedConfiguration(PrefService* prefs, + + void InstallFirstRunDefaultAdblockSubscription( + std::unique_ptr& adblock_filtering_configuration) { ++ if ((true)) return; + if (std::ranges::any_of(config::GetKnownSubscriptions(), + [&](const KnownSubscriptionInfo& subscription) { + return subscription.url == +@@ -188,16 +191,8 @@ bool InstallFirstRunAdblockSubscriptionsCheckingLocale( + 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) { ++ if (subscription.first_run == SubscriptionFirstRunBehavior::SubscribeAtFirstRun) { + 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); + } + } +@@ -451,11 +446,12 @@ SubscriptionServiceFactory::ConvertCustomFilters( + void SubscriptionServiceFactory::ConvertFilterListFile( + const GURL& subscription_url, + const base::FilePath& path, ++ bool allow_privileged_filter, + base::OnceCallback result_callback) const { + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&ConvertFilterFile, flatbuffer_converter_, +- subscription_url, path), ++ subscription_url, path, allow_privileged_filter), + std::move(result_callback)); + } + +diff --git a/components/adblock/content/browser/factories/subscription_service_factory.h b/components/adblock/content/browser/factories/subscription_service_factory.h +--- a/components/adblock/content/browser/factories/subscription_service_factory.h ++++ b/components/adblock/content/browser/factories/subscription_service_factory.h +@@ -43,6 +43,7 @@ class SubscriptionServiceFactory : public BrowserContextKeyedServiceFactory, + void ConvertFilterListFile( + const GURL& subscription_url, + const base::FilePath& path, ++ bool allow_privileged_filter, + base::OnceCallback) const override; + + protected: diff --git a/components/adblock/content/browser/frame_hierarchy_builder.cc b/components/adblock/content/browser/frame_hierarchy_builder.cc --- a/components/adblock/content/browser/frame_hierarchy_builder.cc +++ b/components/adblock/content/browser/frame_hierarchy_builder.cc -@@ -76,9 +76,8 @@ std::vector FrameHierarchyBuilder::BuildFrameHierarchy( - content::RenderFrameHost* host) const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - -- DCHECK(host) << "RenderFrameHost is needed to build frame hierarchy"; -- - std::vector referrers_chain; -+ if (!host) return referrers_chain; - for (auto* iter = host; iter; iter = iter->GetParent()) { +@@ -66,6 +66,7 @@ GURL GetUrlAsReferrer(content::RenderFrameHost* frame_host) { + std::vector BuildFrameHierarchyForRenderFrameHost( + content::RenderFrameHost* frame_host) { + std::vector frame_hierarchy; ++ if (!frame_host) return frame_hierarchy; + for (auto* iter = frame_host; iter; iter = iter->GetParent()) { auto last_commited_referrer = GetUrlAsReferrer(iter); if (IsValidForFrameHierarchy(last_commited_referrer)) { +diff --git a/components/adblock/content/browser/page_view_stats.cc b/components/adblock/content/browser/page_view_stats.cc +--- a/components/adblock/content/browser/page_view_stats.cc ++++ b/components/adblock/content/browser/page_view_stats.cc +@@ -57,21 +57,6 @@ const char kBlockedStatsCountKey[] = "blocked_pageviews"; + // 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 g_last_used_instance; + + void RegisterNavigationWithLastUsedPageViewStats( +@@ -89,15 +74,6 @@ void RegisterAcceptableAdsBlockthroughtHitWithLastUsedPageViewStats( + } + } + +-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( +@@ -215,22 +191,6 @@ void PageViewStats::RecordParkedMetrics(content::Page& page) { + } + // 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; +@@ -239,42 +199,13 @@ void PageViewStats::RecordParkedMetrics(content::Page& page) { + } + + 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); ++ return 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 diff --git a/components/adblock/content/browser/resource_classification_runner.h b/components/adblock/content/browser/resource_classification_runner.h --- a/components/adblock/content/browser/resource_classification_runner.h +++ b/components/adblock/content/browser/resource_classification_runner.h -@@ -81,12 +81,21 @@ class ResourceClassificationRunner : public KeyedService { +@@ -77,9 +77,16 @@ class ResourceClassificationRunner : public KeyedService { + const GURL& popup_url, + content::RenderFrameHost& render_frame_host, + CheckFilterMatchCallback callback) = 0; ++ virtual void CheckRequestFilterMatchForWebTransport( ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL& request_url, ++ const RequestInitiator& request_initiator, ++ content::GlobalRenderFrameHostId render_frame_host_id, ++ CheckFilterMatchCallback callback) = 0; virtual void CheckRequestFilterMatch( SubscriptionService::Snapshot subscription_collections, const GURL& request_url, + const std::vector& dns_aliases, -+ const GURL& request_initiator, ContentType adblock_resource_type, - content::GlobalRenderFrameHostId render_frame_host_id, - CheckFilterMatchCallback callback) = 0; - virtual void CheckRequestFilterMatchForWebSocket( - SubscriptionService::Snapshot subscription_collections, - const GURL& request_url, -+ const GURL& request_initiator, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) = 0; -+ virtual void CheckRequestFilterMatchForWebTransport( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const GURL& request_initiator, - content::GlobalRenderFrameHostId render_frame_host_id, + const RequestInitiator& request_initiator, CheckFilterMatchCallback callback) = 0; - // No callback, just notify observers diff --git a/components/adblock/content/browser/resource_classification_runner_impl.cc b/components/adblock/content/browser/resource_classification_runner_impl.cc --- a/components/adblock/content/browser/resource_classification_runner_impl.cc +++ b/components/adblock/content/browser/resource_classification_runner_impl.cc -@@ -169,15 +169,30 @@ void ResourceClassificationRunnerImpl::CheckPopupFilterMatch( - void ResourceClassificationRunnerImpl::CheckRequestFilterMatchForWebSocket( - SubscriptionService::Snapshot subscription_collections, - const GURL& request_url, -+ const GURL& request_initiator, - content::GlobalRenderFrameHostId render_frame_host_id, - CheckFilterMatchCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(request_url.SchemeIsWSOrWSS()); -- CheckRequestFilterMatch(std::move(subscription_collections), request_url, -+ std::vector dns_aliases; -+ CheckRequestFilterMatch(std::move(subscription_collections), request_url, std::move(dns_aliases), request_initiator, - ContentType::Websocket, render_frame_host_id, - std::move(callback)); +@@ -21,6 +21,7 @@ + #include "base/task/task_traits.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/content/browser/frame_opener_info.h" + #include "components/adblock/content/browser/request_initiator.h" + #include "components/adblock/core/common/sitekey.h" +@@ -156,6 +157,19 @@ void ResourceClassificationRunnerImpl::CheckPopupFilterMatch( + render_frame_host.GetGlobalId(), std::move(callback))); } +void ResourceClassificationRunnerImpl::CheckRequestFilterMatchForWebTransport( + SubscriptionService::Snapshot subscription_collections, + const GURL& request_url, -+ const GURL& request_initiator, ++ const RequestInitiator& request_initiator, + content::GlobalRenderFrameHostId render_frame_host_id, + CheckFilterMatchCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::vector dns_aliases; -+ CheckRequestFilterMatch(std::move(subscription_collections), request_url, std::move(dns_aliases), request_initiator, -+ ContentType::Other, render_frame_host_id, ++ CheckRequestFilterMatch(std::move(subscription_collections), request_url, std::move(dns_aliases), ++ ContentType::Other, request_initiator, + std::move(callback)); +} + void ResourceClassificationRunnerImpl::CheckDocumentAllowlisted( SubscriptionService::Snapshot subscription_collections, const GURL& request_url, -@@ -212,6 +227,8 @@ void ResourceClassificationRunnerImpl::ProcessDocumentAllowlistedResponse( +@@ -208,6 +222,7 @@ void ResourceClassificationRunnerImpl::ProcessDocumentAllowlistedResponse( void ResourceClassificationRunnerImpl::CheckRequestFilterMatch( SubscriptionService::Snapshot subscription_collections, const GURL& request_url, + const std::vector& dns_aliases, -+ const GURL& request_initiator, ContentType adblock_resource_type, - content::GlobalRenderFrameHostId frame_host_id, + const RequestInitiator& request_initiator, CheckFilterMatchCallback callback) { -@@ -220,14 +237,10 @@ void ResourceClassificationRunnerImpl::CheckRequestFilterMatch( - DVLOG(1) << "[eyeo] CheckRequestFilterMatchImpl for " << request_url.spec(); - - auto* host = content::RenderFrameHost::FromID(frame_host_id); -- if (!host) { -- // Host has died, likely because this is a deferred execution. It does not -- // matter anymore whether the resource is blocked, the page is gone. -- std::move(callback).Run(FilterMatchResult::kNoRule); -- return; -- } -- const std::vector frame_hierarchy_chain = -+ std::vector frame_hierarchy_chain = - frame_hierarchy_builder_->BuildFrameHierarchy(host); -+ if (!host && frame_hierarchy_chain.size() == 0) -+ frame_hierarchy_chain.emplace_back(request_initiator.GetAsReferrer()); - - DVLOG(1) << "[eyeo] Got " << frame_hierarchy_chain.size() - << " frame_hierarchy for " << request_url.spec(); -@@ -246,7 +259,7 @@ void ResourceClassificationRunnerImpl::CheckRequestFilterMatch( +@@ -234,7 +249,7 @@ void ResourceClassificationRunnerImpl::CheckRequestFilterMatch( base::BindOnce( &ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal, resource_classifier_, std::move(subscription_collections), -- request_url, frame_hierarchy_chain, adblock_resource_type, -+ request_url, std::move(dns_aliases), frame_hierarchy_chain, adblock_resource_type, +- request_url, frame_hierarchy, adblock_resource_type, ++ request_url, std::move(dns_aliases), frame_hierarchy, adblock_resource_type, std::move(site_key)), base::BindOnce( &ResourceClassificationRunnerImpl::OnCheckResourceFilterMatchComplete, -@@ -260,9 +273,16 @@ ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal( +@@ -248,9 +263,16 @@ ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal( const scoped_refptr& resource_classifier, SubscriptionService::Snapshot subscription_collections, const GURL request_url, @@ -3066,7 +3578,7 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i TRACE_EVENT1("eyeo", "ResourceClassificationRunnerImpl::" "CheckRequestFilterMatchInternal", -@@ -270,26 +290,35 @@ ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal( +@@ -258,26 +280,35 @@ ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal( DVLOG(1) << "[eyeo] CheckRequestFilterMatchInternal start"; @@ -3075,7 +3587,7 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i - adblock_resource_type, sitekey); - - if (classification_result.decision == ClassificationDecision::Allowed) { -- VLOG(1) << "[eyeo] Document allowed due to allowing filter " << request_url; +- VLOG(1) << "[eyeo] Resource allowed due to allowing filter " << request_url; - return CheckResourceFilterMatchResult{ - FilterMatchResult::kAllowRule, - classification_result.decisive_subscription, @@ -3092,7 +3604,7 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i + adblock_resource_type, sitekey); + + if (classification_result.decision == ClassificationDecision::Allowed) { -+ VLOG(1) << "[eyeo] Document allowed due to allowing filter " << alias_url ++ VLOG(1) << "[eyeo] Resource allowed due to allowing filter " << alias_url + << " " << classification_result.decisive_subscription.spec(); + return CheckResourceFilterMatchResult{ + FilterMatchResult::kAllowRule, @@ -3101,13 +3613,13 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i + } - if (classification_result.decision == ClassificationDecision::Blocked) { -- VLOG(1) << "[eyeo] Document blocked " << request_url; +- VLOG(1) << "[eyeo] Resource blocked " << request_url; - return CheckResourceFilterMatchResult{ - FilterMatchResult::kBlockRule, - classification_result.decisive_subscription, - classification_result.decisive_configuration_name}; + if (classification_result.decision == ClassificationDecision::Blocked) { -+ VLOG(1) << "[eyeo] Document blocked " << alias_url; ++ VLOG(1) << "[eyeo] Resource blocked " << alias_url; + return CheckResourceFilterMatchResult{ + FilterMatchResult::kBlockRule, + classification_result.decisive_subscription, @@ -3119,39 +3631,42 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i return CheckResourceFilterMatchResult{FilterMatchResult::kNoRule, {}, {}}; } +@@ -320,7 +351,9 @@ void ResourceClassificationRunnerImpl::NotifyResourceMatched( + // The frame has been destroyed, so we can't notify observers. + return; + } +- ++ EyeoDocumentInfo* document_info = ++ EyeoDocumentInfo::GetOrCreateForCurrentDocument(render_frame_host); ++ document_info->SetFilterMatchResult(result, subscription, configuration_name); + for (auto& observer : observers_) { + observer.OnRequestMatched( + url, result, parent_frame_urls, static_cast(content_type), diff --git a/components/adblock/content/browser/resource_classification_runner_impl.h b/components/adblock/content/browser/resource_classification_runner_impl.h --- a/components/adblock/content/browser/resource_classification_runner_impl.h +++ b/components/adblock/content/browser/resource_classification_runner_impl.h -@@ -55,12 +55,15 @@ class ResourceClassificationRunnerImpl final +@@ -56,6 +56,7 @@ class ResourceClassificationRunnerImpl final void CheckRequestFilterMatch( SubscriptionService::Snapshot subscription_collections, const GURL& request_url, + const std::vector& dns_aliases, -+ const GURL& request_initiator, ContentType adblock_resource_type, - content::GlobalRenderFrameHostId render_frame_host_id, - CheckFilterMatchCallback callback) final; - void CheckRequestFilterMatchForWebSocket( - SubscriptionService::Snapshot subscription_collections, - const GURL& request_url, -+ const GURL& request_initiator, - content::GlobalRenderFrameHostId render_frame_host_id, + const RequestInitiator& request_initiator, CheckFilterMatchCallback callback) final; - // No callback, just notify observers -@@ -68,6 +71,12 @@ class ResourceClassificationRunnerImpl final +@@ -64,6 +65,12 @@ class ResourceClassificationRunnerImpl final SubscriptionService::Snapshot subscription_collections, const GURL& request_url, - content::GlobalRenderFrameHostId render_frame_host_id) final; + const RequestInitiator& request_initiator) final; + void CheckRequestFilterMatchForWebTransport( + SubscriptionService::Snapshot subscription_collections, + const GURL& request_url, -+ const GURL& request_initiator, ++ const RequestInitiator& request_initiator, + content::GlobalRenderFrameHostId render_frame_host_id, + CheckFilterMatchCallback callback) final; void CheckResponseFilterMatch( SubscriptionService::Snapshot subscription_collections, const GURL& response_url, -@@ -92,6 +101,7 @@ class ResourceClassificationRunnerImpl final +@@ -88,6 +95,7 @@ class ResourceClassificationRunnerImpl final const scoped_refptr& resource_classifier, SubscriptionService::Snapshot subscription_collections, const GURL request_url, @@ -3159,69 +3674,6 @@ diff --git a/components/adblock/content/browser/resource_classification_runner_i const std::vector frame_hierarchy, ContentType adblock_resource_type, const SiteKey sitekey); -diff --git a/components/adblock/content/browser/subscription_service_factory_base.cc b/components/adblock/content/browser/subscription_service_factory_base.cc ---- a/components/adblock/content/browser/subscription_service_factory_base.cc -+++ b/components/adblock/content/browser/subscription_service_factory_base.cc -@@ -21,6 +21,7 @@ - #include - #include - -+#include "base/rand_util.h" - #include "base/command_line.h" - #include "base/files/file_util.h" - #include "base/functional/bind.h" -@@ -65,7 +66,7 @@ base::TimeDelta GetUpdateCheckInterval() { - static base::TimeDelta kCheckInterval = - g_update_check_interval_for_testing - ? g_update_check_interval_for_testing.value() -- : base::Hours(1); -+ : base::Hours(24) + base::Minutes(base::RandInt(-60,60)); - return kCheckInterval; - } - -@@ -86,7 +87,8 @@ std::unique_ptr MakeOngoingSubscriptionRequest( - } - - ConversionResult ConvertFilterFile(const GURL& subscription_url, -- const base::FilePath& path) { -+ const base::FilePath& path, -+ bool allow_privileged_filter) { - TRACE_EVENT1("eyeo", "ConvertFileToFlatbuffer", "url", - subscription_url.spec()); - ConversionResult result; -@@ -96,7 +98,7 @@ ConversionResult ConvertFilterFile(const GURL& subscription_url, - } else { - result = FlatbufferConverter::Convert( - input_stream, subscription_url, -- config::AllowPrivilegedFilters(subscription_url)); -+ allow_privileged_filter && config::AllowPrivilegedFilters(subscription_url)); - } - base::DeleteFile(path); - return result; -@@ -202,10 +204,11 @@ SubscriptionServiceFactoryBase::ConvertCustomFilters( - void SubscriptionServiceFactoryBase::ConvertFilterListFile( - const GURL& subscription_url, - const base::FilePath& path, -+ bool allow_privileged_filter, - base::OnceCallback result_callback) const { - base::ThreadPool::PostTaskAndReplyWithResult( - FROM_HERE, {base::MayBlock()}, -- base::BindOnce(&ConvertFilterFile, subscription_url, path), -+ base::BindOnce(&ConvertFilterFile, subscription_url, path, allow_privileged_filter), - std::move(result_callback)); - } - -diff --git a/components/adblock/content/browser/subscription_service_factory_base.h b/components/adblock/content/browser/subscription_service_factory_base.h ---- a/components/adblock/content/browser/subscription_service_factory_base.h -+++ b/components/adblock/content/browser/subscription_service_factory_base.h -@@ -39,6 +39,7 @@ class SubscriptionServiceFactoryBase : public BrowserContextKeyedServiceFactory, - void ConvertFilterListFile( - const GURL& subscription_url, - const base::FilePath& path, -+ bool allow_privileged_filter, - base::OnceCallback) const override; - - protected: diff --git a/components/adblock/core/BUILD.gn b/components/adblock/core/BUILD.gn --- a/components/adblock/core/BUILD.gn +++ b/components/adblock/core/BUILD.gn @@ -3233,8 +3685,8 @@ diff --git a/components/adblock/core/BUILD.gn b/components/adblock/core/BUILD.gn import("//third_party/flatbuffers/flatbuffer.gni") flatbuffer("schema") { -@@ -61,48 +60,14 @@ generate_sha256_header("schema_hash") { - files_to_hash = [ "${target_gen_dir}/schema/filter_list_schema_generated.h" ] +@@ -49,43 +48,9 @@ source_set("schema_hash") { + deps = [ ":generate_schema_hash" ] } -config("eyeo_telemetry_config") { @@ -3272,17 +3724,12 @@ diff --git a/components/adblock/core/BUILD.gn b/components/adblock/core/BUILD.gn sources = [ - "activeping_telemetry_topic_provider.cc", - "activeping_telemetry_topic_provider.h", - "adblock_controller.h", - "adblock_controller_impl.cc", - "adblock_controller_impl.h", - "adblock_switches.cc", - "adblock_switches.h", - "adblock_telemetry_service.cc", - "adblock_telemetry_service.h", "features.cc", "features.h", "sitekey_storage.h", -@@ -126,8 +91,6 @@ source_set("core") { +@@ -109,8 +74,6 @@ source_set("core") { "//components/prefs", "//components/version_info", ] @@ -3291,872 +3738,39 @@ diff --git a/components/adblock/core/BUILD.gn b/components/adblock/core/BUILD.gn } source_set("test_support") { -@@ -170,6 +133,4 @@ source_set("unit_tests") { +@@ -151,6 +114,4 @@ source_set("unit_tests") { "//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 -deleted file mode 100644 ---- a/components/adblock/core/activeping_telemetry_topic_provider.cc -+++ /dev/null -@@ -1,285 +0,0 @@ --/* This file is part of eyeo Chromium SDK, -- * Copyright (C) 2006-present eyeo GmbH -- * -- * eyeo Chromium SDK is free software: you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 3 as -- * published by the Free Software Foundation. -- * -- * eyeo Chromium SDK is distributed in the hope that it will be useful, -- * but WITHOUT ANY WARRANTY; without even the implied warranty of -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- * GNU General Public License for more details. -- * -- * You should have received a copy of the GNU General Public License -- * along with eyeo Chromium SDK. If not, see . -- */ -- --#include "components/adblock/core/activeping_telemetry_topic_provider.h" -- --#include "base/json/json_reader.h" --#include "base/json/json_writer.h" --#include "base/system/sys_info.h" --#include "base/time/time.h" --#include "base/time/time_to_iso8601.h" --#include "base/uuid.h" --#include "components/adblock/core/common/adblock_prefs.h" --#include "components/adblock/core/subscription/subscription_config.h" -- --namespace adblock { --namespace { --int g_http_port_for_testing = 0; --std::optional g_time_delta_for_testing; +diff --git a/components/adblock/core/common/BUILD.gn b/components/adblock/core/common/BUILD.gn +--- a/components/adblock/core/common/BUILD.gn ++++ b/components/adblock/core/common/BUILD.gn +@@ -34,14 +34,6 @@ config("eyeo_filtering_config") { + + config("eyeo_application_config") { + defines = [] - --GURL GetUrl() { -- GURL url(EYEO_TELEMETRY_SERVER_URL); -- if (!g_http_port_for_testing) { -- return url; +- if (eyeo_application_name != "") { +- defines += [ "EYEO_APPLICATION_NAME=\"$eyeo_application_name\"" ] - } -- DCHECK_EQ(url::kHttpsScheme, url.scheme()); -- GURL::Replacements replacements; -- replacements.SetSchemeStr(url::kHttpScheme); -- const std::string port_str = base::NumberToString(g_http_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, -- base::StringPiece payload_key, -- base::Value::Dict& payload) { -- auto str = pref_service->GetString(pref_name); -- if (!str.empty()) { -- payload.Set(payload_key, std::move(str)); +- if (eyeo_application_version != "") { +- defines += [ "EYEO_APPLICATION_VERSION=\"$eyeo_application_version\"" ] - } --} --} // namespace -- --ActivepingTelemetryTopicProvider::ActivepingTelemetryTopicProvider( -- utils::AppInfo app_info, -- PrefService* pref_service, -- SubscriptionService* subscription_service, -- const GURL& base_url, -- const std::string& auth_token) -- : app_info_(std::move(app_info)), -- pref_service_(pref_service), -- subscription_service_(subscription_service), -- base_url_(base_url), -- auth_token_(auth_token) {} -- --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/1"); --} -- --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 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", -- base::TimeToISO8601(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)); -- debug_info.Set("next_ping", -- base::TimeToISO8601(pref_service_->GetTime( -- adblock::common::prefs::kTelemetryNextPingTime))); -- -- 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); --} -- --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_->GetAdblockFilteringConfiguration(); -- 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", app_info_.name); -- payload.Set("application_version", app_info_.version); -- payload.Set("aa_active", aa_enabled); -- payload.Set("platform", base::SysInfo::OperatingSystemName()); -- payload.Set("platform_version", base::SysInfo::OperatingSystemVersion()); -- // 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::SetHttpPortForTesting( -- int http_port_for_testing) { -- g_http_port_for_testing = http_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 -deleted file mode 100644 ---- a/components/adblock/core/activeping_telemetry_topic_provider.h -+++ /dev/null -@@ -1,87 +0,0 @@ --/* -- * This file is part of eyeo Chromium SDK, -- * Copyright (C) 2006-present eyeo GmbH -- * -- * eyeo Chromium SDK is free software: you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 3 as -- * published by the Free Software Foundation. -- * -- * eyeo Chromium SDK is distributed in the hope that it will be useful, -- * but WITHOUT ANY WARRANTY; without even the implied warranty of -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- * GNU General Public License for more details. -- * -- * You should have received a copy of the GNU General Public License -- * along with eyeo Chromium SDK. If not, see . -- */ -- --#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/common/adblock_utils.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: -- ActivepingTelemetryTopicProvider(utils::AppInfo app_info, -- PrefService* pref_service, -- SubscriptionService* subscription_service, -- const GURL& base_url, -- const std::string& auth_token); -- ~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 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 SetHttpPortForTesting(int http_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; -- -- const utils::AppInfo app_info_; -- raw_ptr pref_service_; -- raw_ptr subscription_service_; -- const GURL base_url_; -- const std::string auth_token_; --}; -- --} // namespace adblock -- --#endif // COMPONENTS_ADBLOCK_CORE_ACTIVEPING_TELEMETRY_TOPIC_PROVIDER_H_ -diff --git a/components/adblock/core/adblock_controller_impl.cc b/components/adblock/core/adblock_controller_impl.cc ---- a/components/adblock/core/adblock_controller_impl.cc -+++ b/components/adblock/core/adblock_controller_impl.cc -@@ -102,6 +102,7 @@ bool AdblockControllerImpl::IsAdblockEnabled() const { - } - - void AdblockControllerImpl::SetAcceptableAdsEnabled(bool enabled) { -+ enabled = false; - if (enabled) { - InstallSubscription(AcceptableAdsUrl()); - } else { -@@ -116,6 +117,7 @@ bool AdblockControllerImpl::IsAcceptableAdsEnabled() const { - } - - void AdblockControllerImpl::InstallSubscription(const GURL& url) { -+ if (url == AcceptableAdsUrl()) return; - adblock_filtering_configuration_->AddFilterList(url); - } - -@@ -164,10 +166,8 @@ void AdblockControllerImpl::RunFirstRunLogic(PrefService* pref_service) { - common::prefs::kInstallFirstStartSubscriptions)) { - // On first run, install additional subscriptions. - for (const auto& cur : known_subscriptions_) { -- if (cur.first_run == SubscriptionFirstRunBehavior::Subscribe) { -- if (cur.url == AcceptableAdsUrl() && -- base::CommandLine::ForCurrentProcess()->HasSwitch( -- switches::kDisableAcceptableAds)) { -+ if (cur.first_run == SubscriptionFirstRunBehavior::SubscribeAtFirstRun) { -+ if (cur.url == AcceptableAdsUrl()) { - // Do not install Acceptable Ads on first run because a command line - // switch forbids it. Mostly used for testing. - continue; -@@ -235,7 +235,7 @@ void AdblockControllerImpl::InstallLanguageBasedRecommendedSubscriptions() { - SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch && - std::find(subscription.languages.begin(), subscription.languages.end(), - language_) != subscription.languages.end()) { -- VLOG(1) << "[eyeo] Using recommended subscription for language \"" -+ LOG(INFO) << "[eyeo] Using recommended subscription for language \"" - << language_ << "\": " << subscription.title; - language_specific_subscription_installed = true; - InstallSubscription(subscription.url); -diff --git a/components/adblock/core/adblock_switches.cc b/components/adblock/core/adblock_switches.cc ---- a/components/adblock/core/adblock_switches.cc -+++ b/components/adblock/core/adblock_switches.cc -@@ -19,7 +19,6 @@ - - namespace adblock::switches { - --const char kDisableAcceptableAds[] = "disable-aa"; - const char kDisableAdblock[] = "disable-adblock"; - const char kDisableEyeoFiltering[] = "disable-eyeo-filtering"; - -diff --git a/components/adblock/core/adblock_switches.h b/components/adblock/core/adblock_switches.h ---- a/components/adblock/core/adblock_switches.h -+++ b/components/adblock/core/adblock_switches.h -@@ -20,7 +20,6 @@ - - namespace adblock::switches { - --extern const char kDisableAcceptableAds[]; - extern const char kDisableAdblock[]; - extern const char kDisableEyeoFiltering[]; - -diff --git a/components/adblock/core/adblock_telemetry_service.cc b/components/adblock/core/adblock_telemetry_service.cc -deleted file mode 100644 ---- a/components/adblock/core/adblock_telemetry_service.cc -+++ /dev/null -@@ -1,258 +0,0 @@ --/* -- * This file is part of eyeo Chromium SDK, -- * Copyright (C) 2006-present eyeo GmbH -- * -- * eyeo Chromium SDK is free software: you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 3 as -- * published by the Free Software Foundation. -- * -- * eyeo Chromium SDK is distributed in the hope that it will be useful, -- * but WITHOUT ANY WARRANTY; without even the implied warranty of -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- * GNU General Public License for more details. -- * -- * You should have received a copy of the GNU General Public License -- * along with eyeo Chromium SDK. If not, see . -- */ -- --#include "components/adblock/core/adblock_telemetry_service.h" -- --#include -- --#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_prefs.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 topic_provider, -- scoped_refptr url_loader_factory) -- : topic_provider_(std::move(topic_provider)), -- url_loader_factory_(url_loader_factory) {} -- -- 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"; -- topic_provider_->GetPayload(base::BindOnce(&Conversation::MakeRequest, -- weak_ptr_factory_.GetWeakPtr())); -- } -- -- void Stop() { -- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- url_loader_.reset(); -- } -- -- const std::unique_ptr& GetTopicProvider() const { -- return topic_provider_; -- } -- -- private: -- bool IsRequestInFlight() { -- return url_loader_ != nullptr || weak_ptr_factory_.HasWeakPtrs(); -- } -- -- void MakeRequest(std::string payload) { -- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- auto request = std::make_unique(); -- 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 server_response) { -- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- topic_provider_->ParseResponse(std::move(server_response)); -- url_loader_.reset(); -- } -- -- SEQUENCE_CHECKER(sequence_checker_); -- std::unique_ptr topic_provider_; -- scoped_refptr url_loader_factory_; -- std::unique_ptr url_loader_; -- base::WeakPtrFactory weak_ptr_factory_{this}; --}; -- --AdblockTelemetryService::AdblockTelemetryService( -- FilteringConfiguration* filtering_configuration, -- scoped_refptr url_loader_factory, -- base::TimeDelta initial_delay, -- base::TimeDelta check_interval) -- : adblock_filtering_configuration_(filtering_configuration), -- url_loader_factory_(url_loader_factory), -- initial_delay_(initial_delay), -- check_interval_(check_interval) { -- DCHECK(adblock_filtering_configuration_); -- adblock_filtering_configuration_->AddObserver(this); --} -- --AdblockTelemetryService::~AdblockTelemetryService() { -- DCHECK(adblock_filtering_configuration_); -- adblock_filtering_configuration_->RemoveObserver(this); --} -- --void AdblockTelemetryService::AddTopicProvider( -- std::unique_ptr topic_provider) { -- ongoing_conversations_.push_back(std::make_unique( -- std::move(topic_provider), url_loader_factory_)); --} -- --void AdblockTelemetryService::Start() { -- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- OnEnabledStateChangedInternal(); --} -- --void AdblockTelemetryService::OnEnabledStateChanged(FilteringConfiguration*) { -- OnEnabledStateChangedInternal(); --} -- --void AdblockTelemetryService::GetTopicProvidersDebugInfo( -- TopicProvidersDebugInfoCallback service_callback) const { -- const auto barrier_callback = base::BarrierCallback( -- ongoing_conversations_.size(), std::move(service_callback)); -- for (const auto& conversation : ongoing_conversations_) { -- conversation->GetTopicProvider()->FetchDebugInfo(barrier_callback); -- } --} -- --void AdblockTelemetryService::OnEnabledStateChangedInternal() { -- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- if (adblock_filtering_configuration_->IsEnabled() && !timer_.IsRunning()) { -- VLOG(1) << "[eyeo] Starting periodic Telemetry requests"; -- timer_.Start(FROM_HERE, initial_delay_, -- base::BindRepeating(&AdblockTelemetryService::RunPeriodicCheck, -- base::Unretained(this))); -- } else if (!adblock_filtering_configuration_->IsEnabled() && -- timer_.IsRunning()) { -- VLOG(1) << "[eyeo] Stopping periodic Telemetry requests"; -- 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(); -- } --} -- --} // namespace adblock -diff --git a/components/adblock/core/adblock_telemetry_service.h b/components/adblock/core/adblock_telemetry_service.h -deleted file mode 100644 ---- a/components/adblock/core/adblock_telemetry_service.h -+++ /dev/null -@@ -1,120 +0,0 @@ --/* -- * This file is part of eyeo Chromium SDK, -- * Copyright (C) 2006-present eyeo GmbH -- * -- * eyeo Chromium SDK is free software: you can redistribute it and/or modify -- * it under the terms of the GNU General Public License version 3 as -- * published by the Free Software Foundation. -- * -- * eyeo Chromium SDK is distributed in the hope that it will be useful, -- * but WITHOUT ANY WARRANTY; without even the implied warranty of -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- * GNU General Public License for more details. -- * -- * You should have received a copy of the GNU General Public License -- * along with eyeo Chromium SDK. If not, see . -- */ -- --#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_ --#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_ -- --#include --#include --#include -- --#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/subscription/subscription_service.h" --#include "components/keyed_service/core/keyed_service.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: -- // 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; -- using DebugInfoCallback = base::OnceCallback; -- 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 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( -- FilteringConfiguration* filtering_configuration, -- scoped_refptr url_loader_factory, -- base::TimeDelta initial_delay, -- base::TimeDelta check_interval); -- ~AdblockTelemetryService() override; -- using TopicProvidersDebugInfoCallback = -- base::OnceCallback)>; -- -- // Add all required topic providers before calling Start(). -- void AddTopicProvider(std::unique_ptr 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; -- -- private: -- void OnEnabledStateChangedInternal(); -- void RunPeriodicCheck(); -- -- SEQUENCE_CHECKER(sequence_checker_); -- raw_ptr adblock_filtering_configuration_; -- scoped_refptr url_loader_factory_; -- base::TimeDelta initial_delay_; -- base::TimeDelta check_interval_; -- -- class Conversation; -- std::vector> ongoing_conversations_; -- base::OneShotTimer timer_; --}; -- --} // namespace adblock -- --#endif // COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_ -diff --git a/components/adblock/core/common/BUILD.gn b/components/adblock/core/common/BUILD.gn ---- a/components/adblock/core/common/BUILD.gn -+++ b/components/adblock/core/common/BUILD.gn -@@ -59,14 +59,6 @@ source_set("common") { - - 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("utils") { -diff --git a/components/adblock/core/common/adblock_constants.cc b/components/adblock/core/common/adblock_constants.cc ---- a/components/adblock/core/common/adblock_constants.cc -+++ b/components/adblock/core/common/adblock_constants.cc -@@ -23,8 +23,6 @@ - - namespace adblock { - --const char kSiteKeyHeaderKey[] = "x-adblock-key"; + } + + source_set("common") { +diff --git a/components/adblock/core/common/adblock_constants.cc b/components/adblock/core/common/adblock_constants.cc +--- a/components/adblock/core/common/adblock_constants.cc ++++ b/components/adblock/core/common/adblock_constants.cc +@@ -23,8 +23,6 @@ + + namespace adblock { + +-const char kSiteKeyHeaderKey[] = "x-adblock-key"; - const char kAllowlistEverythingFilter[] = "@@*$document"; @@ -4164,7 +3778,7 @@ diff --git a/components/adblock/core/common/adblock_constants.cc b/components/ad diff --git a/components/adblock/core/common/adblock_constants.h b/components/adblock/core/common/adblock_constants.h --- a/components/adblock/core/common/adblock_constants.h +++ b/components/adblock/core/common/adblock_constants.h -@@ -27,7 +27,6 @@ namespace flat { +@@ -29,7 +29,6 @@ namespace flat { enum AbpResource : int8_t; } @@ -4178,7 +3792,7 @@ diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adbloc @@ -23,7 +23,9 @@ namespace adblock::common::prefs { - // Whether to block ads + // Legacy: Whether to block ads -const char kEnableAdblockLegacy[] = "adblock.enable"; +const char kEnableAdblockLegacy[] = "adblock.enabled"; + @@ -4186,7 +3800,7 @@ diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adbloc // Legacy: Whether to allow acceptable ads or block them all. // Used now just to map CLI switch. Otherwise use kAdblockSubscriptionsLegacy. -@@ -65,47 +67,11 @@ const char kLastUsedSchemaVersion[] = "adblock.last_used_schema_version"; +@@ -65,35 +67,6 @@ const char kLastUsedSchemaVersion[] = "adblock.last_used_schema_version"; // and for setting query parameters in subscription download requests. const char kSubscriptionMetadata[] = "adblock.subscription_metadata"; @@ -4218,6 +3832,15 @@ diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adbloc -// 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"; + +@@ -106,21 +79,12 @@ 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, ""); @@ -4225,8 +3848,9 @@ diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adbloc - registry->RegisterStringPref(kTelemetryPreviousLastPingTime, ""); - registry->RegisterStringPref(kTelemetryFirstPingTime, ""); - registry->RegisterTimePref(kTelemetryNextPingTime, base::Time()); +- registry->RegisterDictionaryPref(kTelemetryPageViewStats); -} -- + void RegisterProfilePrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(kAllowPrivilegedFilters, false); registry->RegisterBooleanPref(kEnableAdblockLegacy, true); @@ -4237,18 +3861,36 @@ diff --git a/components/adblock/core/common/adblock_prefs.cc b/components/adbloc registry->RegisterListPref(kAdblockAllowedDomainsLegacy, {}); registry->RegisterListPref(kAdblockCustomFiltersLegacy, {}); registry->RegisterListPref(kAdblockSubscriptionsLegacy, {}); -@@ -114,7 +80,6 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) { - registry->RegisterDictionaryPref(kSubscriptionSignatures); +@@ -130,11 +94,10 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterStringPref(kLastUsedSchemaVersion, ""); registry->RegisterDictionaryPref(kSubscriptionMetadata); + registry->RegisterDictionaryPref(kConfigurationsPrefsPath); +- registry->RegisterBooleanPref(kEnableAutoInstalledSubscriptions, true); ++ registry->RegisterBooleanPref(kEnableAutoInstalledSubscriptions, false); + // Set to |now| so the first update happens ASAP + registry->RegisterTimePref(kAutoInstalledSubscriptionsNextUpdateTime, + base::Time::Now()); - RegisterTelemetryPrefs(registry); VLOG(3) << "[eyeo] Registered prefs"; } +@@ -152,12 +115,6 @@ std::vector GetPrefs() { + kSubscriptionSignatures, + kLastUsedSchemaVersion, + kSubscriptionMetadata, +- kTelemetryLastPingTag, +- kTelemetryLastPingTime, +- kTelemetryPreviousLastPingTime, +- kTelemetryFirstPingTime, +- kTelemetryNextPingTime, +- kTelemetryPageViewStats, + kConfigurationsPrefsPath, + kEnableAutoInstalledSubscriptions, + kAutoInstalledSubscriptionsNextUpdateTime}; diff --git a/components/adblock/core/common/adblock_prefs.h b/components/adblock/core/common/adblock_prefs.h --- a/components/adblock/core/common/adblock_prefs.h +++ b/components/adblock/core/common/adblock_prefs.h -@@ -23,6 +23,7 @@ class PrefRegistrySimple; +@@ -26,6 +26,7 @@ class PrefRegistrySimple; namespace adblock::common::prefs { extern const char kEnableAdblockLegacy[]; @@ -4256,50 +3898,45 @@ diff --git a/components/adblock/core/common/adblock_prefs.h b/components/adblock extern const char kEnableAcceptableAdsLegacy[]; extern const char kAdblockAllowedDomainsLegacy[]; extern const char kAdblockCustomFiltersLegacy[]; -diff --git a/components/adblock/core/common/adblock_utils.cc b/components/adblock/core/common/adblock_utils.cc ---- a/components/adblock/core/common/adblock_utils.cc -+++ b/components/adblock/core/common/adblock_utils.cc -@@ -49,16 +49,6 @@ std::string CreateDomainAllowlistingFilter(const std::string& domain) { - - SiteKey GetSitekeyHeader( - const scoped_refptr& 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 {}; +@@ -36,12 +37,6 @@ 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[]; +diff --git a/components/adblock/core/common/app_info.cc b/components/adblock/core/common/app_info.cc +--- a/components/adblock/core/common/app_info.cc ++++ b/components/adblock/core/common/app_info.cc +@@ -29,18 +29,6 @@ const AppInfo& AppInfo::Get() { } -@@ -70,19 +60,6 @@ AppInfo::AppInfo(const AppInfo&) = default; - - AppInfo GetAppInfo() { - AppInfo info; -- + AppInfo::AppInfo() { -#if defined(EYEO_APPLICATION_NAME) -- info.name = EYEO_APPLICATION_NAME; +- name = EYEO_APPLICATION_NAME; -#else -- info.name = version_info::GetProductName(); +- name = version_info::GetProductName(); -#endif -#if defined(EYEO_APPLICATION_VERSION) -- info.version = EYEO_APPLICATION_VERSION; +- version = EYEO_APPLICATION_VERSION; -#else -- info.version = version_info::GetVersionNumber(); +- version = version_info::GetVersionNumber(); -#endif - base::ReplaceChars(version_info::GetOSType(), base::kWhitespaceASCII, "", -- &info.client_os); - return info; +- &client_os); } + } // namespace adblock diff --git a/components/adblock/core/configuration/filtering_configuration.h b/components/adblock/core/configuration/filtering_configuration.h --- a/components/adblock/core/configuration/filtering_configuration.h +++ b/components/adblock/core/configuration/filtering_configuration.h -@@ -65,6 +65,9 @@ class FilteringConfiguration { +@@ -66,6 +66,9 @@ class FilteringConfiguration { virtual void SetEnabled(bool enabled) = 0; virtual bool IsEnabled() const = 0; @@ -4309,18 +3946,54 @@ diff --git a/components/adblock/core/configuration/filtering_configuration.h b/c // 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; +@@ -84,6 +87,8 @@ class FilteringConfiguration { + virtual void AddCustomFilter(const std::string& filter) = 0; + virtual void RemoveCustomFilter(const std::string& filter) = 0; + virtual std::vector GetCustomFilters() const = 0; ++ virtual void AddTemporaryCustomFilter(const std::string& filter) = 0; ++ virtual std::vector GetTemporaryCustomFilters() const = 0; + }; + + } // namespace adblock diff --git a/components/adblock/core/configuration/persistent_filtering_configuration.cc b/components/adblock/core/configuration/persistent_filtering_configuration.cc --- a/components/adblock/core/configuration/persistent_filtering_configuration.cc +++ b/components/adblock/core/configuration/persistent_filtering_configuration.cc -@@ -24,6 +24,7 @@ - #include "components/adblock/core/configuration/filtering_configuration_prefs.h" +@@ -25,6 +25,7 @@ + #include "components/adblock/core/common/adblock_prefs.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" +#include "components/adblock/core/common/adblock_prefs.h" #include "base/logging.h" -@@ -134,6 +135,7 @@ const std::string& PersistentFilteringConfiguration::GetName() const { +@@ -34,6 +35,7 @@ namespace { + constexpr auto kEnabledKey = std::string_view("enabled"); + constexpr auto kDomainsKey = std::string_view("domains"); + constexpr auto kCustomFiltersKey = std::string_view("filters"); ++constexpr auto kCustomTemporaryFiltersKey = std::string_view("temp_filters"); + constexpr auto kFilterListsKey = std::string_view("subscriptions"); + + base::Value::Dict ReadFromPrefs(PrefService* pref_service, +@@ -42,7 +44,9 @@ base::Value::Dict ReadFromPrefs(PrefService* pref_service, + 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()); ++ auto clone = base::Value::Dict(this_config->Clone()); ++ clone.Remove(kCustomTemporaryFiltersKey); ++ return clone; + } + return base::Value::Dict(); + } +@@ -63,6 +67,7 @@ void SetDefaultValuesIfNeeded(base::Value::Dict& configuration) { + } + configuration.EnsureList(kDomainsKey); + configuration.EnsureList(kCustomFiltersKey); ++ configuration.EnsureList(kCustomTemporaryFiltersKey); + configuration.EnsureList(kFilterListsKey); + } + +@@ -133,6 +138,7 @@ const std::string& PersistentFilteringConfiguration::GetName() const { } void PersistentFilteringConfiguration::SetEnabled(bool enabled) { @@ -4328,7 +4001,7 @@ diff --git a/components/adblock/core/configuration/persistent_filtering_configur if (IsEnabled() == enabled) { return; } -@@ -142,6 +144,14 @@ void PersistentFilteringConfiguration::SetEnabled(bool enabled) { +@@ -141,6 +147,14 @@ void PersistentFilteringConfiguration::SetEnabled(bool enabled) { NotifyEnabledStateChanged(); } @@ -4343,10 +4016,29 @@ diff --git a/components/adblock/core/configuration/persistent_filtering_configur bool PersistentFilteringConfiguration::IsEnabled() const { const auto pref_value = dictionary_.FindBool(kEnabledKey); DCHECK(pref_value); +@@ -209,6 +223,18 @@ void PersistentFilteringConfiguration::RemoveCustomFilter( + } + } + ++void PersistentFilteringConfiguration::AddTemporaryCustomFilter( ++ const std::string& filter) { ++ if (AppendToList(dictionary_, kCustomTemporaryFiltersKey, filter)) { ++ NotifyCustomFiltersChanged(); ++ } ++} ++ ++std::vector PersistentFilteringConfiguration::GetTemporaryCustomFilters() ++ const { ++ return GetFromList(dictionary_, kCustomTemporaryFiltersKey); ++} ++ + std::vector PersistentFilteringConfiguration::GetCustomFilters() + const { + return GetFromList(dictionary_, kCustomFiltersKey); diff --git a/components/adblock/core/configuration/persistent_filtering_configuration.h b/components/adblock/core/configuration/persistent_filtering_configuration.h --- a/components/adblock/core/configuration/persistent_filtering_configuration.h +++ b/components/adblock/core/configuration/persistent_filtering_configuration.h -@@ -49,6 +49,9 @@ class PersistentFilteringConfiguration final : public FilteringConfiguration { +@@ -48,6 +48,9 @@ class PersistentFilteringConfiguration final : public FilteringConfiguration { void SetEnabled(bool enabled) final; bool IsEnabled() const final; @@ -4356,10 +4048,20 @@ diff --git a/components/adblock/core/configuration/persistent_filtering_configur void AddFilterList(const GURL& url) final; void RemoveFilterList(const GURL& url) final; std::vector GetFilterLists() const final; +@@ -61,6 +64,9 @@ class PersistentFilteringConfiguration final : public FilteringConfiguration { + void RemoveCustomFilter(const std::string& filter) final; + std::vector GetCustomFilters() const final; + ++ void AddTemporaryCustomFilter(const std::string& filter) final; ++ std::vector GetTemporaryCustomFilters() const final; ++ + static std::vector> + GetPersistedConfigurations(PrefService* pref_service); + static void RemovePersistedData(PrefService* pref_service, diff --git a/components/adblock/core/converter/flatbuffer_converter.cc b/components/adblock/core/converter/flatbuffer_converter.cc --- a/components/adblock/core/converter/flatbuffer_converter.cc +++ b/components/adblock/core/converter/flatbuffer_converter.cc -@@ -124,7 +124,7 @@ void FlatbufferConverter::ConvertFilter( +@@ -144,7 +144,7 @@ void FlatbufferConverter::ConvertFilter( std::string(filter_str.data(), filter_str.size()))) { flatbuffer_serializer.SerializeUrlFilter(std::move(url_filter.value())); } else { @@ -4371,21 +4073,7 @@ diff --git a/components/adblock/core/converter/flatbuffer_converter.cc b/compone diff --git a/components/adblock/core/converter/parser/metadata.cc b/components/adblock/core/converter/parser/metadata.cc --- a/components/adblock/core/converter/parser/metadata.cc +++ b/components/adblock/core/converter/parser/metadata.cc -@@ -57,13 +57,6 @@ std::optional Metadata::FromStream(std::istream& filter_stream) { - 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") { -@@ -106,6 +99,7 @@ Metadata::~Metadata() = default; +@@ -108,6 +108,7 @@ Metadata::~Metadata() = default; // static bool Metadata::IsValidAdblockHeader(const std::string& adblock_header) { @@ -4393,15 +4081,6 @@ diff --git a/components/adblock/core/converter/parser/metadata.cc b/components/a static re2::RE2 adblock_header_re("^\\[Adblock.*\\]"); std::string adblock_header_trimmed; -@@ -130,7 +124,7 @@ base::TimeDelta Metadata::ParseExpirationTime( - - if (!re2::RE2::FullMatch(expiration_value, expiration_time_re, - &expiration_time, &expiration_unit)) { -- VLOG(1) << "[eyeo] Invalid expiration time format: " << expiration_value -+ LOG(ERROR) << "[eyeo] Invalid expiration time format: " << expiration_value - << ". Will use default value of " - << kDefaultExpirationInterval.InDays() << " days."; - return kDefaultExpirationInterval; diff --git a/components/adblock/core/converter/parser/test/test_rules.txt b/components/adblock/core/converter/parser/test/test_rules.txt new file mode 100644 --- /dev/null @@ -4431,7 +4110,7 @@ new file mode 100644 diff --git a/components/adblock/core/converter/parser/url_filter.cc b/components/adblock/core/converter/parser/url_filter.cc --- a/components/adblock/core/converter/parser/url_filter.cc +++ b/components/adblock/core/converter/parser/url_filter.cc -@@ -47,6 +47,10 @@ std::string SanitizePipeCharacters(std::string pattern) { +@@ -50,6 +50,10 @@ std::string SanitizePipeCharacters(std::string pattern) { // Skip up to one trailing | characters, this is the right anchor. bool pattern_has_right_anchor = base::EndsWith(piece, "|"); if (pattern_has_right_anchor) { @@ -4442,7 +4121,7 @@ diff --git a/components/adblock/core/converter/parser/url_filter.cc b/components piece.remove_suffix(1); } if (piece.find('|') == std::string_view::npos) { -@@ -108,21 +112,21 @@ std::optional UrlFilter::FromString(std::string filter_str) { +@@ -133,25 +137,18 @@ absl::optional UrlFilter::FromString(std::string filter_str) { if (options->Csp().has_value() && options->Csp().value().empty() && !is_allowing) { @@ -4460,19 +4139,22 @@ diff --git a/components/adblock/core/converter/parser/url_filter.cc b/components return {}; } - if (!options->IsSubresource() && !options->ExceptionTypes().empty() && - !is_allowing) { +- if (!options->IsSubresource() && !options->ExceptionTypes().empty() && +- !is_allowing) { - VLOG(1) << "[eyeo] Exception options can only be used with allowing " -+ LOG(INFO) << "[eyeo] Exception options can only be used with allowing " - "filters"; - return {}; - } +- "filters"; +- return {}; +- } +- + filter_str.erase(option_selector_it); + } + diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/components/adblock/core/converter/parser/url_filter_options.cc --- a/components/adblock/core/converter/parser/url_filter_options.cc +++ b/components/adblock/core/converter/parser/url_filter_options.cc -@@ -88,20 +88,14 @@ std::optional UrlFilterOptions::FromString( +@@ -92,20 +92,14 @@ absl::optional UrlFilterOptions::FromString( + return {}; } - domains = DomainOption::FromString(value, kDomainOrSitekeySeparator); } else if (key == "sitekey") { - if (value.empty()) { - VLOG(1) << "[eyeo] Sitekey option has to have a value."; @@ -4497,7 +4179,7 @@ diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/co } else { ContentType content_type = ContentTypeFromString(key); if (content_type != ContentType::Unknown) { -@@ -184,6 +178,7 @@ std::optional UrlFilterOptions::ParseRewrite( +@@ -188,6 +182,7 @@ absl::optional UrlFilterOptions::ParseRewrite( // static SiteKeys UrlFilterOptions::ParseSitekeys(const std::string& sitekey_value) { SiteKeys sitekeys; @@ -4505,7 +4187,7 @@ diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/co for (auto& sitekey : base::SplitString( base::ToUpperASCII(sitekey_value), kDomainOrSitekeySeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { -@@ -195,6 +190,7 @@ SiteKeys UrlFilterOptions::ParseSitekeys(const std::string& sitekey_value) { +@@ -199,6 +194,7 @@ SiteKeys UrlFilterOptions::ParseSitekeys(const std::string& sitekey_value) { // static bool UrlFilterOptions::IsValidCsp(const std::string& csp_value) { @@ -4513,7 +4195,7 @@ diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/co static re2::RE2 invalid_csp( "(;|^) " "?(base-uri|referrer|report-to|report-uri|upgrade-insecure-requests)\\b"); -@@ -205,6 +201,7 @@ bool UrlFilterOptions::IsValidCsp(const std::string& csp_value) { +@@ -209,6 +205,7 @@ bool UrlFilterOptions::IsValidCsp(const std::string& csp_value) { // static void UrlFilterOptions::ParseHeaders(std::string& headers_value) { @@ -4521,17 +4203,6 @@ diff --git a/components/adblock/core/converter/parser/url_filter_options.cc b/co // replace \x2c with actual , static re2::RE2 r1("([^\\\\])\\\\x2c"); re2::RE2::GlobalReplace(&headers_value, r1, "\\1,"); -diff --git a/components/adblock/core/converter/parser/url_filter_options.h b/components/adblock/core/converter/parser/url_filter_options.h ---- a/components/adblock/core/converter/parser/url_filter_options.h -+++ b/components/adblock/core/converter/parser/url_filter_options.h -@@ -18,6 +18,7 @@ - #ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_ - #define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_ - -+#include - #include - #include - #include diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer.cc b/components/adblock/core/converter/serializer/flatbuffer_serializer.cc --- a/components/adblock/core/converter/serializer/flatbuffer_serializer.cc +++ b/components/adblock/core/converter/serializer/flatbuffer_serializer.cc @@ -4543,7 +4214,7 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. #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" -@@ -27,6 +28,68 @@ +@@ -27,6 +28,70 @@ namespace adblock { @@ -4572,6 +4243,7 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. + u"hide-if-shadow-contains", + + // Behavioral Snippets ++ // u"array-override", (unsupported) + u"abort-current-inline-script", + u"abort-on-property-read", + u"abort-on-property-write", @@ -4587,6 +4259,7 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. + u"prevent-listener", + u"skip-video", + // u"strip-fetch-query-parameter", (unsupported) ++ // u"replace-fetch-response", (unsupported) + + // Performance Snippets + u"race" @@ -4612,7 +4285,7 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. class Buffer : public FlatbufferData { public: explicit Buffer(flatbuffers::DetachedBuffer&& buffer) -@@ -121,9 +184,25 @@ void FlatbufferSerializer::SerializeContentFilter( +@@ -129,9 +194,25 @@ void FlatbufferSerializer::SerializeContentFilter( void FlatbufferSerializer::SerializeSnippetFilter( const SnippetFilter snippet_filter) { if (!allow_privileged_) { @@ -4639,7 +4312,7 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. std::vector> offsets; offsets.reserve(snippet_filter.snippet_script.size()); -@@ -144,7 +223,7 @@ void FlatbufferSerializer::SerializeSnippetFilter( +@@ -152,7 +233,7 @@ void FlatbufferSerializer::SerializeSnippetFilter( void FlatbufferSerializer::SerializeUrlFilter(const UrlFilter url_filter) { const auto& options = url_filter.options; if (!allow_privileged_ && options.Headers().has_value()) { @@ -4648,291 +4321,94 @@ diff --git a/components/adblock/core/converter/serializer/flatbuffer_serializer. return; } +@@ -210,6 +291,23 @@ void FlatbufferSerializer::SerializeUrlFilter(const UrlFilter url_filter) { + keyword_pattern, offset); + } + ++ if (!url_filter.is_allowing) { ++ if (!options.IsSubresource()) { ++ for (auto exception_type : options.ExceptionTypes()) { ++ switch (exception_type) { ++ case UrlFilterOptions::ExceptionType::Document: ++ AddUrlFilterToIndex(url_subresource_block_, keyword_pattern, offset); ++ break; ++ case UrlFilterOptions::ExceptionType::Genericblock: ++ case UrlFilterOptions::ExceptionType::Generichide: ++ case UrlFilterOptions::ExceptionType::Elemhide: ++ break; ++ } ++ } ++ } ++ return; ++ } ++ + for (auto exception_type : options.ExceptionTypes()) { + switch (exception_type) { + case UrlFilterOptions::ExceptionType::Genericblock: diff --git a/components/adblock/core/features.cc b/components/adblock/core/features.cc --- a/components/adblock/core/features.cc +++ b/components/adblock/core/features.cc -@@ -19,7 +19,6 @@ +@@ -19,8 +19,8 @@ namespace adblock { --const base::Feature kAdblockPlusFeature{"AdblockPlus", -- base::FEATURE_ENABLED_BY_DEFAULT}; -+CROMITE_FEATURE(kAdblockPlus, "AdblockPlus", base::FEATURE_ENABLED_BY_DEFAULT); - - } -diff --git a/components/adblock/core/hash/schema_hash.h b/components/adblock/core/hash/schema_hash.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/hash/schema_hash.h -@@ -0,0 +1,10 @@ -+#include -+ -+#include -+ -+#ifndef GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_ -+#define GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_ -+ -+extern const std::array kSha256_filter_list_schema_generated_h; -+ -+#endif // GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_ -diff --git a/components/adblock/core/sitekey_storage_impl.cc b/components/adblock/core/sitekey_storage_impl.cc ---- a/components/adblock/core/sitekey_storage_impl.cc -+++ b/components/adblock/core/sitekey_storage_impl.cc -@@ -27,9 +27,7 @@ - - namespace adblock { +-BASE_FEATURE(kAdblockPlusFeature, +- "AdblockPlus", +- base::FEATURE_ENABLED_BY_DEFAULT); ++CROMITE_FEATURE(kAdblockPlusFeature, ++ "AdblockPlus", ++ base::FEATURE_ENABLED_BY_DEFAULT); --SitekeyStorageImpl::SitekeyStorageImpl() { -- crypto::EnsureOpenSSLInit(); --} -+SitekeyStorageImpl::SitekeyStorageImpl() {} + } // namespace adblock +diff --git a/components/adblock/core/net/adblock_resource_request_impl.cc b/components/adblock/core/net/adblock_resource_request_impl.cc +--- a/components/adblock/core/net/adblock_resource_request_impl.cc ++++ b/components/adblock/core/net/adblock_resource_request_impl.cc +@@ -31,7 +31,7 @@ namespace adblock { + namespace { - SitekeyStorageImpl::~SitekeyStorageImpl() = default; + const net::NetworkTrafficAnnotationTag kTrafficAnnotation = +- net::DefineNetworkTrafficAnnotation("adblock_resource_request", R"( ++ net::DefineNetworkTrafficAnnotation("adblock_subscription_download", R"( + semantics { + sender: "AdblockResourceRequest" + description: +@@ -52,22 +52,7 @@ const net::NetworkTrafficAnnotationTag kTrafficAnnotation = + })"); -@@ -37,6 +35,8 @@ void SitekeyStorageImpl::ProcessResponseHeaders( - const GURL& request_url, - const scoped_refptr& headers, - const std::string& user_agent) { -+ // remove Acceptable Ads site key processing -+ if ((true)) return; - if (user_agent.empty()) { - LOG(WARNING) << "[eyeo] No user agent info"; - return; -@@ -53,6 +53,7 @@ void SitekeyStorageImpl::ProcessResponseHeaders( - - std::optional> - SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& urls) const { -+ if ((true)) return {}; - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - for (const auto& url : urls) { - auto elem = url_to_sitekey_map_.find(url); -@@ -66,6 +67,8 @@ SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& urls) const { - void SitekeyStorageImpl::ProcessSiteKey(const GURL& request_url, - const SiteKey& site_key, - const std::string& user_agent) { -+ // remove Acceptable Ads site key processing -+ if ((true)) return; // simple caution, never invoked being private - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(!site_key.value().empty()); - auto site_key_pair = FindSiteKeyForAnyUrl({request_url}); -@@ -116,6 +119,8 @@ bool SitekeyStorageImpl::IsSitekeySignatureValid( - const std::string& public_key_b64, - const std::string& signature_b64, - const std::string& data) const { -+ // remove Acceptable Ads site key -+ if ((true)) return false; // simple caution, never invoked being private - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - std::string signature; - if (!base::Base64Decode(signature_b64, &signature, -diff --git a/components/adblock/core/subscription/conversion_executors.h b/components/adblock/core/subscription/conversion_executors.h ---- a/components/adblock/core/subscription/conversion_executors.h -+++ b/components/adblock/core/subscription/conversion_executors.h -@@ -40,6 +40,7 @@ class ConversionExecutors { - virtual void ConvertFilterListFile( - const GURL& subscription_url, - const base::FilePath& path, -+ bool allow_privileged_filter, - base::OnceCallback result_callback) const = 0; - - virtual ~ConversionExecutors() = default; -diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer.h b/components/adblock/core/subscription/filtering_configuration_maintainer.h ---- a/components/adblock/core/subscription/filtering_configuration_maintainer.h -+++ b/components/adblock/core/subscription/filtering_configuration_maintainer.h -@@ -24,6 +24,7 @@ - #include "base/memory/scoped_refptr.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" - - namespace adblock { - -@@ -41,6 +42,9 @@ class FilteringConfigurationMaintainer { - virtual std::unique_ptr GetSubscriptionCollection() - const = 0; - -+ virtual void StartUpdate() = 0; -+ virtual raw_ptr GetMetadata() = 0; -+ - // Allows inspecting what Subscriptions are currently in use. This includes - // ongoing downloads, preloaded subscriptions and installed subscriptions. - virtual std::vector> GetCurrentSubscriptions() -diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc ---- a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc -+++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc -@@ -254,6 +254,19 @@ void FilteringConfigurationMaintainerImpl::RemoveDuplicateSubscriptions() { - unique_subscriptions.end()); - } - -+void FilteringConfigurationMaintainerImpl::StartUpdate() { -+ LOG(INFO) << "[eyeo] Running forced update"; -+ for (auto& subscription : current_state_) { -+ const auto& url = subscription->GetSourceUrl(); -+ DownloadAndInstallSubscription(url); -+ } -+} -+ -+raw_ptr -+ FilteringConfigurationMaintainerImpl::GetMetadata() { -+ return persistent_metadata_; -+} -+ - void FilteringConfigurationMaintainerImpl::RunUpdateCheck() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(1) << "[eyeo] Running update check"; -@@ -286,7 +299,6 @@ void FilteringConfigurationMaintainerImpl::RunUpdateCheck() { - AcceptableAdsUrl(); - }) && - persistent_metadata_->IsExpired(AcceptableAdsUrl())) { -- PingAcceptableAds(); - } - } - -@@ -378,15 +390,6 @@ void FilteringConfigurationMaintainerImpl::SubscriptionAddedToStorage( - 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())); --} + 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"}); - - void FilteringConfigurationMaintainerImpl::OnHeadRequestDone( - const std::string version) { - if (version.empty()) { -@@ -399,20 +402,20 @@ void FilteringConfigurationMaintainerImpl::OnHeadRequestDone( - - void FilteringConfigurationMaintainerImpl::UninstallSubscription( - const GURL& subscription_url) { -- DVLOG(1) << "[eyeo] Removing subscription " << subscription_url; -+ LOG(INFO) << "[eyeo] Removing subscription " << subscription_url; - if (!UninstallSubscriptionInternal(subscription_url)) { -- VLOG(1) << "[eyeo] Nothing to remove, subscription not installed " -+ LOG(INFO) << "[eyeo] Nothing to remove, subscription not installed " - << subscription_url; - return; - } -- if (subscription_url != AcceptableAdsUrl()) { -+ if ((true) || 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; -+ LOG(INFO) << "[eyeo] Removed subscription " << subscription_url; +- if (!extra_query_params.empty()) { +- query += "&"; +- query += extra_query_params; +- } +- +- GURL::Replacements replacements; +- replacements.SetQueryStr(query); +- return url.ReplaceComponents(replacements); ++ return url; } - bool FilteringConfigurationMaintainerImpl::UninstallSubscriptionInternal( -diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h ---- a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h -+++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h -@@ -57,6 +57,9 @@ class FilteringConfigurationMaintainerImpl - std::vector> GetCurrentSubscriptions() - const override; - -+ void StartUpdate() final; -+ raw_ptr GetMetadata() final; -+ - // FilteringConfiguration::Observer: - void OnFilterListsChanged(FilteringConfiguration* config) final; - void OnAllowedDomainsChanged(FilteringConfiguration* config) final; -@@ -84,7 +87,6 @@ class FilteringConfigurationMaintainerImpl - void SubscriptionAddedToStorage( - scoped_refptr ongoing_installation, - scoped_refptr subscription); -- void PingAcceptableAds(); - void OnHeadRequestDone(const std::string version); - void UninstallSubscription(const GURL& subscription_url); - bool UninstallSubscriptionInternal(const GURL& subscription_url); -diff --git a/components/adblock/core/subscription/ongoing_subscription_request_impl.cc b/components/adblock/core/subscription/ongoing_subscription_request_impl.cc ---- a/components/adblock/core/subscription/ongoing_subscription_request_impl.cc -+++ b/components/adblock/core/subscription/ongoing_subscription_request_impl.cc -@@ -21,6 +21,7 @@ - - #include "base/task/thread_pool.h" - #include "base/trace_event/trace_event.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" -@@ -43,11 +44,23 @@ const net::NetworkTrafficAnnotationTag kTrafficAnnotation = - "Application name (ex. Chromium) " - "Application version (93.0.4572.0) " - destination: WEBSITE -+ internal { -+ contacts { -+ email: "uazo@users.noreply.github.com" -+ } -+ contacts { -+ email: "uazo@users.noreply.github.com" -+ } -+ } -+ user_data { -+ type: NONE -+ } -+ last_reviewed: "2023-01-01" - } - policy { - cookies_allowed: NO - setting: -- "You enable or disable this feature via 'Ad blocking' setting." -+ "You enable or disable this feature via 'Adblock Enable' pref." - policy_exception_justification: "Not implemented." - })"); - -@@ -65,7 +78,7 @@ OngoingSubscriptionRequestImpl::OngoingSubscriptionRequestImpl( - - OngoingSubscriptionRequestImpl::~OngoingSubscriptionRequestImpl() { - if (!url_.is_empty()) { -- VLOG(1) << "[eyeo] Cancelling download of " << url_; -+ LOG(INFO) << "[eyeo] Finished download of " << url_; - } - net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); - } -@@ -91,7 +104,7 @@ void OngoingSubscriptionRequestImpl::Retry() { - return; - } - backoff_entry_->InformOfRequest(false); -- VLOG(1) << "[eyeo] Will retry downloading " << url_ << " in " -+ LOG(INFO) << "[eyeo] Will retry downloading " << url_ << " in " - << backoff_entry_->GetTimeUntilRelease(); - retry_timer_->Start( - FROM_HERE, backoff_entry_->GetTimeUntilRelease(), -@@ -105,7 +118,7 @@ void OngoingSubscriptionRequestImpl::Redirect(GURL redirect_url) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(!url_.is_empty()) << "Redirect() called before Start()"; - DCHECK(url_ != redirect_url) << "Invalid redirect. Same URL"; -- VLOG(1) << "[eyeo] Will redirect " << url_ << " to " << redirect_url; -+ LOG(INFO) << "[eyeo] Will redirect " << url_ << " to " << redirect_url; - ++number_of_redirects_; - url_ = std::move(redirect_url); - StartInternal(); -@@ -125,10 +138,15 @@ void OngoingSubscriptionRequestImpl::StartInternal() { + } // namespace +@@ -129,7 +114,7 @@ void AdblockResourceRequestImpl::StartInternal() { // indefinitely. return; } - VLOG(1) << "[eyeo] Downloading " << url_; -+ LOG(INFO) << "[eyeo] Downloading " << url_ -+ << " with method " << (method_ == Method::GET ? "get" : "headers only"); ++ LOG(INFO) << "[eyeo] Downloading " << url_; auto request = std::make_unique(); request->url = url_; request->method = MethodToString(); -+ request->credentials_mode = network::mojom::CredentialsMode::kOmit; -+ request->load_flags = net::LOAD_BYPASS_CACHE | -+ net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES | -+ net::LOAD_MINIMAL_HEADERS; - loader_ = - network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation); - -@@ -151,11 +169,8 @@ void OngoingSubscriptionRequestImpl::OnDownloadFinished( - base::FilePath downloaded_file) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo", "Downloading subscription", this); +@@ -181,11 +166,8 @@ void AdblockResourceRequestImpl::OnDownloadFinished( + return; + } + - GURL::Replacements strip_query; - strip_query.ClearQuery(); - GURL url = url_.ReplaceComponents(strip_query); @@ -4942,906 +4418,1884 @@ diff --git a/components/adblock/core/subscription/ongoing_subscription_request_i loader_->ResponseInfo() ? loader_->ResponseInfo()->headers : nullptr); // response_callback_ may delete this, do not call any member variables now. } -diff --git a/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc ---- a/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc -+++ b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc -@@ -61,10 +61,10 @@ class PreloadedSubscriptionProviderImpl::SingleSubscriptionProvider { - utils::MakeFlatbufferDataFromResourceBundle( - info_.flatbuffer_resource_id), - Subscription::InstallationState::Preloaded, base::Time()); -- VLOG(1) << "[eyeo] Preloaded subscription now in use: " -+ LOG(INFO) << "[eyeo] Preloaded subscription now in use: " - << subscription_->GetSourceUrl(); - } else if (!needs_subscription && subscription_) { -- VLOG(1) << "[eyeo] Preloaded subscription no longer in use: " -+ LOG(INFO) << "[eyeo] Preloaded subscription no longer in use: " - << subscription_->GetSourceUrl(); - subscription_.reset(); +diff --git a/components/adblock/core/resources/.gitignore b/components/adblock/core/resources/.gitignore +--- a/components/adblock/core/resources/.gitignore ++++ b/components/adblock/core/resources/.gitignore +@@ -1 +1 @@ +-snippets ++# snippets +diff --git a/components/adblock/core/resources/BUILD.gn b/components/adblock/core/resources/BUILD.gn +--- a/components/adblock/core/resources/BUILD.gn ++++ b/components/adblock/core/resources/BUILD.gn +@@ -54,7 +54,7 @@ make_preloaded_subscription("make_anticv") { + + action("prepare_snippets") { + script = "//components/adblock/core/resources/snippets_deps.py" +- if (is_debug) { ++ if (true) { + _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" +@@ -86,9 +86,6 @@ grit("adblock_resources") { + "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 +--- a/components/adblock/core/resources/adblock_resources.grd ++++ b/components/adblock/core/resources/adblock_resources.grd +@@ -15,7 +15,7 @@ + You should have received a copy of the GNU General Public License + along with eyeo Chromium SDK. If not, see . + --> +- ++ + + + +@@ -30,9 +30,6 @@ + + + +- +- +- + + + +diff --git a/components/adblock/core/resources/elemhide_for_selector.jst b/components/adblock/core/resources/elemhide_for_selector.jst +--- a/components/adblock/core/resources/elemhide_for_selector.jst ++++ b/components/adblock/core/resources/elemhide_for_selector.jst +@@ -34,7 +34,7 @@ if (typeof(elemhideForSelector) !== typeof(Function)) } -diff --git a/components/adblock/core/subscription/subscription.cc b/components/adblock/core/subscription/subscription.cc ---- a/components/adblock/core/subscription/subscription.cc -+++ b/components/adblock/core/subscription/subscription.cc -@@ -16,9 +16,27 @@ - */ - - #include "components/adblock/core/subscription/subscription.h" -+#include "base/notreached.h" - - namespace adblock { - - Subscription::~Subscription() = default; + else + { +- console.debug("Nothing found for: " + selector); ++ // console.debug("Nothing found for: " + selector); + } + } + } +@@ -50,4 +50,4 @@ if (typeof(elemhideForSelector) !== typeof(Function)) + { + elemhideForSelector(url_to_hide, "[src$='" + filename_to_hide + "'], [srcset$='" + filename_to_hide + "']"); + } +-})("{{url}}", "{{filename_with_query}}"); +\ No newline at end of file ++})("{{url}}", "{{filename_with_query}}"); +diff --git a/components/adblock/core/resources/elemhideemu.jst b/components/adblock/core/resources/elemhideemu.jst +--- a/components/adblock/core/resources/elemhideemu.jst ++++ b/components/adblock/core/resources/elemhideemu.jst +@@ -1,3 +1,4 @@ ++(function() { + /* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH +@@ -1408,3 +1409,4 @@ let elemHidingEmulatedPatterns = [{{elemHidingEmulatedPatternsDef}}]; + let elemHideEmulation = new ElemHideEmulation(); -+// static -+const std::string Subscription::SubscriptionInstallationStateToString( -+ Subscription::InstallationState state) { -+ using State = Subscription::InstallationState; -+ switch (state) { -+ case State::Installed: -+ return "Installed"; -+ case State::Installing: -+ return "Installing"; -+ case State::Preloaded: -+ return "Preloaded"; -+ case State::Unknown: -+ return "Unknown"; -+ } -+ NOTREACHED(); -+} + elemHideEmulation.apply(elemHidingEmulatedPatterns); ++})() +diff --git a/components/adblock/core/resources/snippets/dist/dependencies.jst b/components/adblock/core/resources/snippets/dist/dependencies.jst +new file mode 100644 +--- /dev/null ++++ b/components/adblock/core/resources/snippets/dist/dependencies.jst +@@ -0,0 +1,1690 @@ ++/*! ++ * snippets v2.0.0 ++ * https://gitlab.com/eyeo/anti-cv/snippets/-/blob/v2.0.0/dist/dependencies.jst?ref_type=heads ++ */ ++ /*! ++ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * @eyeo/snippets is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * @eyeo/snippets is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with @eyeo/snippets. If not, see . ++ */ ++/**! Start hide-if-matches-xpath3 dependency !**/ + - } // namespace adblock -diff --git a/components/adblock/core/subscription/subscription.h b/components/adblock/core/subscription/subscription.h ---- a/components/adblock/core/subscription/subscription.h -+++ b/components/adblock/core/subscription/subscription.h -@@ -68,6 +68,9 @@ class Subscription : public base::RefCountedThreadSafe { - // Typically, update checks are performed once per expiration interval. - virtual base::TimeDelta GetExpirationInterval() const = 0; - -+ const static std::string SubscriptionInstallationStateToString( -+ InstallationState state); ++ function hideIfMatchesXPath3Dependency() { ++ // whynot.js 5.0.0 + - protected: - friend class base::RefCountedThreadSafe; - virtual ~Subscription(); -diff --git a/components/adblock/core/subscription/subscription_collection_impl.cc b/components/adblock/core/subscription/subscription_collection_impl.cc ---- a/components/adblock/core/subscription/subscription_collection_impl.cc -+++ b/components/adblock/core/subscription/subscription_collection_impl.cc -@@ -341,6 +341,7 @@ std::set SubscriptionCollectionImpl::GetHeaderFilters( - ContentType content_type, - FilterCategory category) const { - std::set filters{}; -+ if ((true)) return filters; - for (const auto& subscription : subscriptions_) { - subscription->FindHeaderFilters( - request_url, content_type, DocumentDomain(request_url, frame_hierarchy), -diff --git a/components/adblock/core/subscription/subscription_config.cc b/components/adblock/core/subscription/subscription_config.cc ---- a/components/adblock/core/subscription/subscription_config.cc -+++ b/components/adblock/core/subscription/subscription_config.cc -@@ -24,7 +24,7 @@ namespace { - int g_port_for_testing = 0; - - std::string GetHost() { -- GURL url("https://easylist-downloads.adblockplus.org"); -+ GURL url("https://www.cromite.org/filters/"); - if (!g_port_for_testing) { - return url.spec(); - } -@@ -239,17 +239,23 @@ const std::vector& config::GetKnownSubscriptions() { - SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, - SubscriptionPrivilegedFilterStatus::Forbidden}, - {AcceptableAdsUrl(), -- "Acceptable Ads", -+ "Acceptable Ads", // Always disable - {}, - SubscriptionUiVisibility::Invisible, -- SubscriptionFirstRunBehavior::Subscribe, -+ SubscriptionFirstRunBehavior::Ignore, // in bromite - SubscriptionPrivilegedFilterStatus::Forbidden}, - {AntiCVUrl(), - "ABP filters", - {}, -+ SubscriptionUiVisibility::Invisible, -+ SubscriptionFirstRunBehavior::Ignore, -+ SubscriptionPrivilegedFilterStatus::AllowedAndChecked}, -+ {GURL("https://raw.githubusercontent.com/uazo/cromite/master/tools/filters/experimental-cromite-filters.txt"), -+ "Cromite experimental filters", -+ {}, - SubscriptionUiVisibility::Visible, -- SubscriptionFirstRunBehavior::Subscribe, -- SubscriptionPrivilegedFilterStatus::Allowed}, -+ SubscriptionFirstRunBehavior::Ignore, -+ SubscriptionPrivilegedFilterStatus::AllowedAndChecked}, - {GURL(GetHost() + "i_dont_care_about_cookies.txt"), - "I don't care about cookies", - {}, -@@ -280,13 +286,13 @@ const std::vector& config::GetKnownSubscriptions() { - {}, - SubscriptionUiVisibility::Invisible, - SubscriptionFirstRunBehavior::Ignore, -- SubscriptionPrivilegedFilterStatus::Allowed}, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, - {TestPagesSubscriptionUrl(), - "ABP Test filters", - {}, - SubscriptionUiVisibility::Invisible, - SubscriptionFirstRunBehavior::Ignore, -- SubscriptionPrivilegedFilterStatus::Allowed} -+ SubscriptionPrivilegedFilterStatus::Forbidden} - - // You can customize subscriptions available on first run and in settings - // here. Items are displayed in settings in order declared here. See -@@ -315,7 +321,7 @@ bool config::AllowPrivilegedFilters(const GURL& url) { - for (const auto& cur : GetKnownSubscriptions()) { - if (cur.url == url) { - return cur.privileged_status == -- SubscriptionPrivilegedFilterStatus::Allowed; -+ SubscriptionPrivilegedFilterStatus::AllowedAndChecked; - } - } - -@@ -325,9 +331,7 @@ bool config::AllowPrivilegedFilters(const GURL& url) { - const std::vector& - config::GetPreloadedSubscriptionConfiguration() { - static const std::vector 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; - } - -diff --git a/components/adblock/core/subscription/subscription_config.h b/components/adblock/core/subscription/subscription_config.h ---- a/components/adblock/core/subscription/subscription_config.h -+++ b/components/adblock/core/subscription/subscription_config.h -@@ -36,7 +36,7 @@ enum class SubscriptionUiVisibility { Visible, Invisible }; - - enum class SubscriptionFirstRunBehavior { - // Download and install as soon as possible. -- Subscribe, -+ SubscribeAtFirstRun, - // Download and install as soon as possible but only if the device's region - // matches one of the |languages| defined in KnownSubscriptionInfo. - SubscribeIfLocaleMatch, -@@ -47,7 +47,7 @@ enum class SubscriptionFirstRunBehavior { - // Privileged filters include: - // - Snippet filters - // - Header filters --enum class SubscriptionPrivilegedFilterStatus { Allowed, Forbidden }; -+enum class SubscriptionPrivilegedFilterStatus { AllowedAndChecked, 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 -@@ -71,7 +71,7 @@ struct KnownSubscriptionInfo { - std::vector languages; - SubscriptionUiVisibility ui_visibility = SubscriptionUiVisibility::Visible; - SubscriptionFirstRunBehavior first_run = -- SubscriptionFirstRunBehavior::Subscribe; -+ SubscriptionFirstRunBehavior::Ignore; - SubscriptionPrivilegedFilterStatus privileged_status = - SubscriptionPrivilegedFilterStatus::Forbidden; - }; -diff --git a/components/adblock/core/subscription/subscription_downloader_impl.cc b/components/adblock/core/subscription/subscription_downloader_impl.cc ---- a/components/adblock/core/subscription/subscription_downloader_impl.cc -+++ b/components/adblock/core/subscription/subscription_downloader_impl.cc -@@ -54,6 +54,7 @@ GURL AddUrlParameters(const GURL& subscription_url, - const SubscriptionPersistentMetadata* persistent_metadata, - const utils::AppInfo& client_metadata, - const bool is_disabled) { -+ if ((true)) return subscription_url; - const std::string query = base::StrCat( - {"addonName=", "eyeo-chromium-sdk", "&addonVersion=", "1.0", - "&application=", base::EscapeQueryParamValue(client_metadata.name, true), -@@ -114,6 +115,8 @@ void SubscriptionDownloaderImpl::StartDownload( - } - - void SubscriptionDownloaderImpl::CancelDownload(const GURL& subscription_url) { -+ LOG(WARNING) << "[eyeo] Download cancelled: " -+ << subscription_url; - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - ongoing_downloads_.erase(subscription_url); - } -@@ -138,10 +141,6 @@ void SubscriptionDownloaderImpl::DoHeadRequest( - - 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; -@@ -155,12 +154,14 @@ void SubscriptionDownloaderImpl::OnHeadersOnlyDownloaded( - scoped_refptr headers) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(ongoing_ping_.has_value()); -- base::Time date; ++ // The MIT License (MIT) ++ ++ // Copyright (c) 2017 Stef Busking ++ ++ // Permission is hereby granted, free of charge, to any person obtaining a copy of ++ // this software and associated documentation files (the "Software"), to deal in ++ // the Software without restriction, including without limitation the rights to ++ // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of ++ // the Software, and to permit persons to whom the Software is furnished to do so, ++ // subject to the following conditions: ++ ++ // The above copyright notice and this permission notice shall be included in all ++ // copies or substantial portions of the Software. ++ ++ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS ++ // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ++ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER ++ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ !function (t, s) { s((t = "undefined" != typeof globalThis ? globalThis : t || self).whynot = {}) }(this, (function (t) { "use strict"; function s(t, s, i, r) { const n = { op: s, func: i, data: r }; return t.push(n), n } function i(t, s) { return t } class r { constructor() { this.program = [] } test(t, i) { return s(this.program, 5, t, void 0 === i ? null : i) } jump(t) { return s(this.program, 3, null, t) } record(t, r) { return s(this.program, 4, void 0 === r ? i : r, t) } bad(t = 1) { return s(this.program, 1, null, t) } accept() { return s(this.program, 0, null, null) } fail(t) { return s(this.program, 2, t || null, null) } } class n { constructor(t, s, i) { this.programLength = t, this.maxFromByPc = s, this.maxSurvivorFromByPc = i } static fromProgram(t) { const s = t.length, i = [], r = []; return t.forEach((t => { i.push(0), r.push(0) })), t.forEach(((t, n) => { switch (t.op) { case 2: if (null === t.func) return; if (n + 1 >= s) throw new Error("Invalid program: program could run past end"); i[n + 1] += 1; break; case 1: case 4: if (n + 1 >= s) throw new Error("Invalid program: program could run past end"); i[n + 1] += 1; break; case 3: t.data.forEach((t => { if (t < 0 || t >= s) throw new Error("Invalid program: program could run past end"); i[t] += 1 })); break; case 5: if (n + 1 >= s) throw new Error("Invalid program: program could run past end"); r[n + 1] += 1; break; case 0: r[n] += 1 } })), new n(s, i, r) } static createStub(t) { const s = [], i = []; for (let r = 0; r < t; ++r)s.push(t), i.push(t); return new n(t, s, i) } } class e { constructor(t) { this.acceptingTraces = t, this.success = t.length > 0 } } const h = 255; class l { constructor(t) { this.t = 0, this.i = 0, this.h = new Uint16Array(t), this.l = new Uint8Array(t) } getBadness(t) { return this.l[t] } add(t, s) { this.l[t] = s > h ? h : s; const i = function (t, s, i, r, n) { let e = r, h = n; for (; e < h;) { const r = e + h >>> 1; i < s[t[r]] ? h = r : e = r + 1 } return e }(this.h, this.l, s, this.i, this.t); this.h.copyWithin(i + 1, i, this.t), this.h[i] = t, this.t += 1 } reschedule(t, s) { const i = Math.max(this.l[t], s > h ? h : s); if (this.l[t] !== i) { const s = this.h.indexOf(t, this.i); if (s < 0 || s >= this.t) return void (this.l[t] = i); this.h.copyWithin(s, s + 1, this.t), this.t -= 1, this.add(t, i) } } getNextPc() { return this.i >= this.t ? null : this.h[this.i++] } reset() { this.t = 0, this.i = 0, this.l.fill(0) } } class o { constructor(t) { this.o = []; let s = t.length; t.forEach((t => { this.o.push(t > 0 ? s : -1), s += t })), this.u = new Uint16Array(s) } clear() { this.u.fill(0, 0, this.o.length) } add(t, s) { const i = this.u[s], r = this.o[s]; this.u[s] += 1, this.u[r + i] = t } has(t) { return this.u[t] > 0 } forEach(t, s) { const i = this.u[t], r = this.o[t]; for (let t = r; t < r + i; ++t)s(this.u[t]) } } function c(t, s, i = !1) { return null === t ? s : Array.isArray(t) ? (-1 === t.indexOf(s) && (i && (t = t.slice()), t.push(s)), t) : t === s ? t : [t, s] } class u { constructor(t, s) { this.prefixes = t, this.record = s } } function a(t, s) { let i; if (null === s) { if (!Array.isArray(t)) return t; i = t } else i = t === u.EMPTY ? [] : Array.isArray(t) ? t : [t]; return new u(i, s) } u.EMPTY = new u([], null); class f { constructor(t) { this.p = [], this.v = []; for (let s = 0; s < t; ++s)this.p.push(0), this.v.push(null) } mergeTraces(t, s, i, r, n, e) { let h = !1; return i.forEach(s, (s => { const i = this.trace(s, r, n, e); var l, o, u; o = i, u = h, t = null === (l = t) ? o : null === o ? l : Array.isArray(o) ? o.reduce(((t, s) => c(t, s, t === o)), l) : c(l, o, u), h = t === i })), t } trace(t, s, i, r) { switch (this.p[t]) { case 2: return this.v[t]; case 1: return null }this.p[t] = 1; let n = null; const e = s[t]; if (null !== e) n = e; else if (!i.has(t)) throw new Error(`Trace without source at pc ${t}`); if (n = this.mergeTraces(n, t, i, s, i, r), null !== n) { const s = r[t]; null !== s && (n = a(n, s)) } return this.v[t] = n, this.p[t] = 2, n } buildSurvivorTraces(t, s, i, r, n) { for (let e = 0, h = t.length; e < h; ++e) { if (!i.has(e)) { s[e] = null; continue } this.v.fill(null), this.p.fill(0); const h = this.mergeTraces(null, e, i, t, r, n); if (null === h) throw new Error(`No non-cyclic paths found to survivor ${e}`); s[e] = a(h, null) } this.v.fill(null) } } class d { constructor(t) { this.g = [], this.k = [], this.m = [], this.A = new o(t.maxFromByPc), this.T = new o(t.maxSurvivorFromByPc), this.S = new f(t.programLength); for (let s = 0; s < t.programLength; ++s)this.g.push(null), this.k.push(null), this.m.push(null); this.k[0] = u.EMPTY } reset(t) { this.A.clear(), this.T.clear(), this.g.fill(null), t && (this.k.fill(null), this.m.fill(null), this.k[0] = u.EMPTY) } record(t, s) { this.g[t] = s } has(t) { return this.A.has(t) || null !== this.k[t] } add(t, s) { this.A.add(t, s) } hasSurvivor(t) { return this.T.has(t) } addSurvivor(t, s) { this.T.add(t, s) } buildSurvivorTraces() { const t = this.k; this.S.buildSurvivorTraces(t, this.m, this.T, this.A, this.g), this.k = this.m, this.m = t } getTraces(t) { const s = t.reduce(((t, s) => c(t, this.k[s])), null); return null === s ? [] : Array.isArray(s) ? s : [s] } } class w { constructor(t) { this.I = [], this.M = new l(t.programLength), this.N = new l(t.programLength), this.j = new d(t) } reset() { this.M.reset(), this.M.add(0, 0), this.I.length = 0, this.j.reset(!0) } getNextThreadPc() { return this.M.getNextPc() } step(t, s, i) { const r = this.j.has(s); this.j.add(t, s); const n = this.M.getBadness(t) + i; r ? this.M.reschedule(s, n) : this.M.add(s, n) } stepToNextGeneration(t, s) { const i = this.j.hasSurvivor(s); this.j.addSurvivor(t, s); const r = this.M.getBadness(t); i ? this.N.reschedule(s, r) : this.N.add(s, r) } accept(t) { this.I.push(t), this.j.addSurvivor(t, t) } fail(t) { } record(t, s) { this.j.record(t, s) } nextGeneration() { this.j.buildSurvivorTraces(), this.j.reset(!1); const t = this.M; t.reset(), this.M = this.N, this.N = t } getAcceptingTraces() { return this.j.getTraces(this.I) } } class p { constructor(t) { this.P = [], this.U = t, this.G = n.fromProgram(t), this.P.push(new w(this.G)) } execute(t, s) { const i = this.P.pop() || new w(this.G); i.reset(); const r = t.length; let n, h = -1; do { let e = i.getNextThreadPc(); if (null === e) break; for (++h, n = h >= r ? null : t[h]; null !== e;) { const t = this.U[e]; switch (t.op) { case 0: null === n ? i.accept(e) : i.fail(e); break; case 2: { const r = t.func; if (null === r || r(s)) { i.fail(e); break } i.step(e, e + 1, 0); break } case 1: i.step(e, e + 1, t.data); break; case 5: if (null === n) { i.fail(e); break } if (!(0, t.func)(n, t.data, s)) { i.fail(e); break } i.stepToNextGeneration(e, e + 1); break; case 3: { const s = t.data, r = s.length; if (0 === r) { i.fail(e); break } for (let t = 0; t < r; ++t)i.step(e, s[t], 0); break } case 4: { const r = (0, t.func)(t.data, h, s); null != r && i.record(e, r), i.step(e, e + 1, 0); break } }e = i.getNextThreadPc() } i.nextGeneration() } while (null !== n); const l = new e(i.getAcceptingTraces()); return i.reset(), this.P.push(i), l } } function b(t) { const s = new r; return t(s), new p(s.program) } var v = { Assembler: r, VM: p, compileVM: b }; t.Assembler = r, t.VM = p, t.compileVM = b, t.default = v, Object.defineProperty(t, "V", { value: !0 }) })); ++ ++ // prsc 4.0.0 ++ ++ // The MIT License (MIT) ++ ++ // Copyright (c) 2019 Stef Busking ++ ++ // Permission is hereby granted, free of charge, to any person obtaining a copy of ++ // this software and associated documentation files (the "Software"), to deal in ++ // the Software without restriction, including without limitation the rights to ++ // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of ++ // the Software, and to permit persons to whom the Software is furnished to do so, ++ // subject to the following conditions: ++ ++ // The above copyright notice and this permission notice shall be included in all ++ // copies or substantial portions of the Software. ++ ++ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS ++ // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ++ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER ++ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ ++ !function (n, t) { t((n = "undefined" != typeof globalThis ? globalThis : n || self).prsc = {}) }(this, (function (n) { "use strict"; function t(n, t) { return { success: !0, offset: n, value: t } } function e(n) { return t(n, void 0) } function r(n, t, e = !1) { return { success: !1, offset: n, expected: t, fatal: e } } function o(n) { return n > 65535 ? 2 : 1 } function u(n, t) { return (u, c) => { const s = u.codePointAt(c); return void 0 !== s && n(s) ? e(c + o(s)) : r(c, t) } } function c(n, e) { return (r, o) => { const u = n(r, o); return u.success ? t(u.offset, e(u.value)) : u } } function s(n) { return (e, r) => { let o = [], u = r; for (; ;) { const t = n(e, u); if (!t.success) { if (t.fatal) return t; break } if (o.push(t.value), t.offset === u) break; u = t.offset } return t(u, o) } } function f(n) { return (t, r) => { let o = r; for (; ;) { const e = n(t, o); if (!e.success) { if (e.fatal) return e; break } if (e.offset === o) break; o = e.offset } return e(o) } } function i(n, e, r) { return (o, u) => { const c = n(o, u); if (!c.success) return c; const s = e(o, c.offset); return s.success ? t(s.offset, r(c.value, s.value)) : s } } function l(n, t) { return n } function d(n, t) { return t } function a(n, t) { return i(n, t, d) } function p(n, t) { return i(n, t, l) } function v(n, t) { return (o, u) => n(o, u).success ? r(u, t) : e(u) } function m(n) { return (t, e) => { const o = n(t, e); return o.success ? o : r(o.offset, o.expected, !0) } } const x = (n, t) => n.length === t ? e(t) : r(t, ["end of input"]); function g(n) { const t = []; let e = n.next(); for (; !e.done;)t.push(e.value), e = n.next(); return [t, e.value] } n.codepoint = u, n.codepoints = function (n, t) { return (o, u) => { const c = u; for (; ;) { const t = o.codePointAt(u); if (void 0 === t) break; if (!n(t)) break; u += t > 65535 ? 2 : 1 } return void 0 !== t && u === c ? r(u, t) : e(u) } }, n.collect = g, n.complete = function (n) { return i(n, x, l) }, n.consume = function (n) { return c(n, (() => { })) }, n.cut = m, n.delimited = function (n, t, e, r = !1) { return a(n, r ? m(p(t, e)) : p(t, e)) }, n.dispatch = function (n, t, e = 0, o = []) { return (u, c) => { const s = u.codePointAt(c + e); if (void 0 === s) return r(c, o); const f = n[s]; return void 0 === f ? void 0 === t ? r(c, o) : t(u, c) : f(u, c) } }, n.end = x, n.error = r, n.except = function (n, t, e) { return a(v(t, e), n) }, n.filter = function (n, t, e, o) { return (u, c) => { const s = n(u, c); return s.success ? t(s.value) ? s : r(c, e, o) : s } }, n.filterUndefined = function (n) { return c(n, (n => n.filter((n => void 0 !== n)))) }, n.first = l, n.followed = p, n.map = c, n.not = v, n.ok = e, n.okWithValue = t, n.optional = function (n) { return (e, r) => { const o = n(e, r); return o.success || o.fatal ? o : t(r, null) } }, n.or = function (n, t) { return (e, o) => { let u = null; for (const r of n) { const n = r(e, o); if (n.success) return n; if (null === u || n.offset > u.offset ? u = n : n.offset === u.offset && void 0 === t && (u.expected = u.expected.concat(n.expected)), n.fatal) return n } return t = t || (null == u ? void 0 : u.expected) || [], u && (u.expected = t), u || r(o, t) } }, n.peek = function (n) { return (e, r) => { const o = n(e, r); return o.success ? t(r, o.value) : o } }, n.plus = function (n) { return i(n, s(n), ((n, t) => [n].concat(t))) }, n.plusConsumed = function (n) { return i(n, f(n), d) }, n.preceded = a, n.range = function (n, t, e) { return u((e => n <= e && e <= t), e || [`${String.fromCodePoint(n)}-${String.fromCodePoint(t)}`]) }, n.recognize = function (n) { return (e, r) => { const o = n(e, r); return o.success ? t(o.offset, e.slice(r, o.offset)) : o } }, n.second = d, n.sequence = function (...n) { return (e, r) => { const o = []; for (const t of n) { const n = t(e, r); if (!n.success) return n; r = n.offset, o.push(n.value) } return t(r, o) } }, n.sequenceConsumed = function (...n) { return (t, r) => { for (const e of n) { const n = e(t, r); if (!n.success) return n; r = n.offset } return e(r) } }, n.skipChars = function (n) { return (t, u) => { let c = n; for (; c > 0;) { const n = t.codePointAt(u); if (void 0 === n) return r(u, ["any character"]); u += o(n), c -= 1 } return e(u) } }, n.star = s, n.starConsumed = f, n.start = (n, t) => 0 === t ? e(t) : r(t, ["start of input"]), n.streaming = function (n) { return function* (t, e) { const r = n(t, e); return r.success && (yield r.value), r } }, n.streamingComplete = function (n) { return function* (t, e) { const r = yield* n(t, e); return r.success ? x(t, r.offset) : r } }, n.streamingFilterUndefined = function (n) { return function* (t, e) { const r = n(t, e); let o = r.next(); for (; !o.done;) { const n = o.value; void 0 !== n && (yield n), o = r.next() } return o.value } }, n.streamingOptional = function (n) { return function* (t, r) { const [o, u] = g(n(t, r)); return u.success ? (yield* o, u) : u.fatal ? u : e(r) } }, n.streamingStar = function (n) { return function* (t, r) { for (; ;) { const [o, u] = g(n(t, r)); if (!u.success) return u.fatal ? u : e(r); if (yield* o, r === u.offset) return e(r); r = u.offset } } }, n.streamingThen = function (n, t) { return function* (e, r) { const o = yield* n(e, r); return o.success ? yield* t(e, o.offset) : o } }, n.then = i, n.token = function (n) { return (e, o) => { const u = o + n.length; return e.slice(o, u) === n ? t(u, n) : r(o, [n]) } }, Object.defineProperty(n, "__esModule", { value: !0 }) })); ++ ++ // xspattern 3.1.0 ++ ++ // The MIT License (MIT) ++ ++ // Copyright (c) 2019 Stef Busking ++ ++ // Permission is hereby granted, free of charge, to any person obtaining a copy of ++ // this software and associated documentation files (the "Software"), to deal in ++ // the Software without restriction, including without limitation the rights to ++ // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of ++ // the Software, and to permit persons to whom the Software is furnished to do so, ++ // subject to the following conditions: ++ ++ // The above copyright notice and this permission notice shall be included in all ++ // copies or substantial portions of the Software. ++ ++ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS ++ // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR ++ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER ++ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ ++ !function (A, B) { B((A = "undefined" != typeof globalThis ? globalThis : A || self).xspattern = {}, A.whynot) }(this, (function (A, B) { "use strict"; function a(A) { return B => B === A } function n(A, B) { if (null === A || null === B) throw new Error("unescaped hyphen may not be used as a range endpoint"); if (B < A) throw new Error("character range is in the wrong order"); return a => A <= a && a <= B } function e(A) { return !0 } function t() { return !1 } function G(A, B) { return a => A(a) || B(a) } const i = -1, r = -2; function o(A, B) { switch (B.kind) { case "predicate": return void A.test(B.value); case "regexp": return void H(A, B.value, !1) } } function l(A, B) { B.forEach((B => { !function (A, B) { const [a, { min: n, max: e }] = B; if (null !== e) { for (let B = 0; B < n; ++B)o(A, a); for (let B = n; B < e; ++B) { const B = A.jump([]); B.data.push(A.program.length), o(A, a), B.data.push(A.program.length) } } else if (n > 0) { for (let B = 0; B < n - 1; ++B)o(A, a); const B = A.program.length; o(A, a), A.jump([B]).data.push(A.program.length) } else { const B = A.program.length, n = A.jump([]); n.data.push(A.program.length), o(A, a), A.jump([B]), n.data.push(A.program.length) } }(A, B) })) } function H(A, B, a) { const n = A.program.length, e = A.jump([]); a && (e.data.push(A.program.length), A.test((() => !0)), A.jump([n])); const t = []; if (B.forEach((B => { e.data.push(A.program.length), l(A, B), t.push(A.jump([])) })), t.forEach((B => { B.data.push(A.program.length) })), a) { const B = A.program.length, a = A.jump([]); a.data.push(A.program.length), A.test((() => !0)), A.jump([B]), a.data.push(A.program.length) } } function u(A, B) { return { success: !0, offset: A, value: B } } function C(A) { return u(A, void 0) } function s(A, B, a = !1) { return { success: !1, offset: A, expected: B, fatal: a } } function c(A) { return (B, a) => { const n = a + A.length; return B.slice(a, n) === A ? u(n, A) : s(a, [A]) } } function D(A, B) { return (a, n) => { const e = A(a, n); return e.success ? u(e.offset, B(e.value)) : e } } function m(A, B, a, n) { return (e, t) => { const G = A(e, t); return G.success ? B(G.value) ? G : s(t, a, n) : G } } function d(A, B) { return (a, n) => { let e = null; for (const t of A) { const A = t(a, n); if (A.success) return A; if (null === e || A.offset > e.offset ? e = A : A.offset === e.offset && void 0 === B && (e.expected = e.expected.concat(A.expected)), A.fatal) return A } return B = B || (null == e ? void 0 : e.expected) || [], e && (e.expected = B), e || s(n, B) } } function I(A) { return (B, a) => { const n = A(B, a); return n.success || n.fatal ? n : u(a, null) } } function h(A) { return (B, a) => { let n = [], e = a; for (; ;) { const a = A(B, e); if (!a.success) { if (a.fatal) return a; break } if (n.push(a.value), a.offset === e) break; e = a.offset } return u(e, n) } } function p(A, B, a) { return (n, e) => { const t = A(n, e); if (!t.success) return t; const G = B(n, t.offset); return G.success ? u(G.offset, a(t.value, G.value)) : G } } function T(A) { return p(A, h(A), ((A, B) => [A].concat(B))) } function f(A, B) { return A } function F(A, B) { return B } function E(A, B) { return p(A, B, F) } function g(A, B) { return p(A, B, f) } function M(A, B, a, n = !1) { return E(A, n ? J(g(B, a)) : g(B, a)) } function P(A, B) { return (a, n) => A(a, n).success ? s(n, B) : C(n) } function J(A) { return (B, a) => { const n = A(B, a); return n.success ? n : s(n.offset, n.expected, !0) } } const S = (A, B) => A.length === B ? C(B) : s(B, ["end of input"]); const K = ["Lu", "Ll", "Lt", "Lm", "Lo", "Mn", "Mc", "Me", "Nd", "Nl", "No", "Pc", "Pd", "Ps", "Pe", "Pi", "Pf", "Po", "Zs", "Zl", "Zp", "Sm", "Sc", "Sk", "So", "Cc", "Cf", "Co", "Cn"]; const y = {}; function b(A) { return A.codePointAt(0) } "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("").forEach(((A, B) => { y[A] = B })); const x = A => A === i || A === r; function Q(A) { return B => !x(B) && !A(B) } function L(A, B) { return null === B ? A : a => A(a) && !B(a) } const X = function (A, B) { const a = new Map; let e = 0; return A.forEach(((A, t) => { const i = B[t]; null !== A && A.split("|").forEach((A => { const B = a.get(A), t = n(e, e + i - 1); a.set(A, B ? G(B, t) : t) })), e += i })), a }(["BasicLatin", "Latin-1Supplement", "LatinExtended-A", "LatinExtended-B", "IPAExtensions", "SpacingModifierLetters", "CombiningDiacriticalMarks", "GreekandCoptic|Greek", "Cyrillic", "CyrillicSupplement", "Armenian", "Hebrew", "Arabic", "Syriac", "ArabicSupplement", "Thaana", "NKo", "Samaritan", "Mandaic", "SyriacSupplement", "ArabicExtended-B", "ArabicExtended-A", "Devanagari", "Bengali", "Gurmukhi", "Gujarati", "Oriya", "Tamil", "Telugu", "Kannada", "Malayalam", "Sinhala", "Thai", "Lao", "Tibetan", "Myanmar", "Georgian", "HangulJamo", "Ethiopic", "EthiopicSupplement", "Cherokee", "UnifiedCanadianAboriginalSyllabics", "Ogham", "Runic", "Tagalog", "Hanunoo", "Buhid", "Tagbanwa", "Khmer", "Mongolian", "UnifiedCanadianAboriginalSyllabicsExtended", "Limbu", "TaiLe", "NewTaiLue", "KhmerSymbols", "Buginese", "TaiTham", "CombiningDiacriticalMarksExtended", "Balinese", "Sundanese", "Batak", "Lepcha", "OlChiki", "CyrillicExtended-C", "GeorgianExtended", "SundaneseSupplement", "VedicExtensions", "PhoneticExtensions", "PhoneticExtensionsSupplement", "CombiningDiacriticalMarksSupplement", "LatinExtendedAdditional", "GreekExtended", "GeneralPunctuation", "SuperscriptsandSubscripts", "CurrencySymbols", "CombiningDiacriticalMarksforSymbols|CombiningMarksforSymbols", "LetterlikeSymbols", "NumberForms", "Arrows", "MathematicalOperators", "MiscellaneousTechnical", "ControlPictures", "OpticalCharacterRecognition", "EnclosedAlphanumerics", "BoxDrawing", "BlockElements", "GeometricShapes", "MiscellaneousSymbols", "Dingbats", "MiscellaneousMathematicalSymbols-A", "SupplementalArrows-A", "BraillePatterns", "SupplementalArrows-B", "MiscellaneousMathematicalSymbols-B", "SupplementalMathematicalOperators", "MiscellaneousSymbolsandArrows", "Glagolitic", "LatinExtended-C", "Coptic", "GeorgianSupplement", "Tifinagh", "EthiopicExtended", "CyrillicExtended-A", "SupplementalPunctuation", "CJKRadicalsSupplement", "KangxiRadicals", null, "IdeographicDescriptionCharacters", "CJKSymbolsandPunctuation", "Hiragana", "Katakana", "Bopomofo", "HangulCompatibilityJamo", "Kanbun", "BopomofoExtended", "CJKStrokes", "KatakanaPhoneticExtensions", "EnclosedCJKLettersandMonths", "CJKCompatibility", "CJKUnifiedIdeographsExtensionA", "YijingHexagramSymbols", "CJKUnifiedIdeographs", "YiSyllables", "YiRadicals", "Lisu", "Vai", "CyrillicExtended-B", "Bamum", "ModifierToneLetters", "LatinExtended-D", "SylotiNagri", "CommonIndicNumberForms", "Phags-pa", "Saurashtra", "DevanagariExtended", "KayahLi", "Rejang", "HangulJamoExtended-A", "Javanese", "MyanmarExtended-B", "Cham", "MyanmarExtended-A", "TaiViet", "MeeteiMayekExtensions", "EthiopicExtended-A", "LatinExtended-E", "CherokeeSupplement", "MeeteiMayek", "HangulSyllables", "HangulJamoExtended-B", "HighSurrogates", "HighPrivateUseSurrogates", "LowSurrogates", "PrivateUseArea|PrivateUse", "CJKCompatibilityIdeographs", "AlphabeticPresentationForms", "ArabicPresentationForms-A", "VariationSelectors", "VerticalForms", "CombiningHalfMarks", "CJKCompatibilityForms", "SmallFormVariants", "ArabicPresentationForms-B", "HalfwidthandFullwidthForms", "Specials", "LinearBSyllabary", "LinearBIdeograms", "AegeanNumbers", "AncientGreekNumbers", "AncientSymbols", "PhaistosDisc", null, "Lycian", "Carian", "CopticEpactNumbers", "OldItalic", "Gothic", "OldPermic", "Ugaritic", "OldPersian", null, "Deseret", "Shavian", "Osmanya", "Osage", "Elbasan", "CaucasianAlbanian", "Vithkuqi", null, "LinearA", "LatinExtended-F", null, "CypriotSyllabary", "ImperialAramaic", "Palmyrene", "Nabataean", null, "Hatran", "Phoenician", "Lydian", null, "MeroiticHieroglyphs", "MeroiticCursive", "Kharoshthi", "OldSouthArabian", "OldNorthArabian", null, "Manichaean", "Avestan", "InscriptionalParthian", "InscriptionalPahlavi", "PsalterPahlavi", null, "OldTurkic", null, "OldHungarian", "HanifiRohingya", null, "RumiNumeralSymbols", "Yezidi", "ArabicExtended-C", "OldSogdian", "Sogdian", "OldUyghur", "Chorasmian", "Elymaic", "Brahmi", "Kaithi", "SoraSompeng", "Chakma", "Mahajani", "Sharada", "SinhalaArchaicNumbers", "Khojki", null, "Multani", "Khudawadi", "Grantha", null, "Newa", "Tirhuta", null, "Siddham", "Modi", "MongolianSupplement", "Takri", null, "Ahom", null, "Dogra", null, "WarangCiti", "DivesAkuru", null, "Nandinagari", "ZanabazarSquare", "Soyombo", "UnifiedCanadianAboriginalSyllabicsExtended-A", "PauCinHau", "DevanagariExtended-A", null, "Bhaiksuki", "Marchen", null, "MasaramGondi", "GunjalaGondi", null, "Makasar", "Kawi", null, "LisuSupplement", "TamilSupplement", "Cuneiform", "CuneiformNumbersandPunctuation", "EarlyDynasticCuneiform", null, "Cypro-Minoan", "EgyptianHieroglyphs", "EgyptianHieroglyphFormatControls", null, "AnatolianHieroglyphs", null, "BamumSupplement", "Mro", "Tangsa", "BassaVah", "PahawhHmong", null, "Medefaidrin", null, "Miao", null, "IdeographicSymbolsandPunctuation", "Tangut", "TangutComponents", "KhitanSmallScript", "TangutSupplement", null, "KanaExtended-B", "KanaSupplement", "KanaExtended-A", "SmallKanaExtension", "Nushu", null, "Duployan", "ShorthandFormatControls", null, "ZnamennyMusicalNotation", null, "ByzantineMusicalSymbols", "MusicalSymbols", "AncientGreekMusicalNotation", null, "KaktovikNumerals", "MayanNumerals", "TaiXuanJingSymbols", "CountingRodNumerals", null, "MathematicalAlphanumericSymbols", "SuttonSignWriting", null, "LatinExtended-G", "GlagoliticSupplement", "CyrillicExtended-D", null, "NyiakengPuachueHmong", null, "Toto", "Wancho", null, "NagMundari", null, "EthiopicExtended-B", "MendeKikakui", null, "Adlam", null, "IndicSiyaqNumbers", null, "OttomanSiyaqNumbers", null, "ArabicMathematicalAlphabeticSymbols", null, "MahjongTiles", "DominoTiles", "PlayingCards", "EnclosedAlphanumericSupplement", "EnclosedIdeographicSupplement", "MiscellaneousSymbolsandPictographs", "Emoticons", "OrnamentalDingbats", "TransportandMapSymbols", "AlchemicalSymbols", "GeometricShapesExtended", "SupplementalArrows-C", "SupplementalSymbolsandPictographs", "ChessSymbols", "SymbolsandPictographsExtended-A", "SymbolsforLegacyComputing", null, "CJKUnifiedIdeographsExtensionB", null, "CJKUnifiedIdeographsExtensionC", "CJKUnifiedIdeographsExtensionD", "CJKUnifiedIdeographsExtensionE", "CJKUnifiedIdeographsExtensionF", null, "CJKCompatibilityIdeographsSupplement", null, "CJKUnifiedIdeographsExtensionG", "CJKUnifiedIdeographsExtensionH", null, "Tags", null, "VariationSelectorsSupplement", null, "SupplementaryPrivateUseArea-A|PrivateUse", "SupplementaryPrivateUseArea-B|PrivateUse"], [128, 128, 128, 208, 96, 80, 112, 144, 256, 48, 96, 112, 256, 80, 48, 64, 64, 64, 32, 16, 48, 96, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 256, 160, 96, 256, 384, 32, 96, 640, 32, 96, 32, 32, 32, 32, 128, 176, 80, 80, 48, 96, 32, 32, 144, 80, 128, 64, 64, 80, 48, 16, 48, 16, 48, 128, 64, 64, 256, 256, 112, 48, 48, 48, 80, 64, 112, 256, 256, 64, 32, 160, 128, 32, 96, 256, 192, 48, 16, 256, 128, 128, 256, 256, 96, 32, 128, 48, 80, 96, 32, 128, 128, 224, 16, 16, 64, 96, 96, 48, 96, 16, 32, 48, 16, 256, 256, 6592, 64, 20992, 1168, 64, 48, 320, 96, 96, 32, 224, 48, 16, 64, 96, 32, 48, 48, 32, 96, 32, 96, 32, 96, 32, 48, 64, 80, 64, 11184, 80, 896, 128, 1024, 6400, 512, 80, 688, 16, 16, 16, 32, 32, 144, 240, 16, 128, 128, 64, 80, 64, 48, 128, 32, 64, 32, 48, 32, 48, 32, 64, 32, 80, 48, 48, 80, 48, 64, 80, 64, 384, 64, 64, 64, 32, 32, 48, 48, 32, 32, 32, 64, 32, 96, 96, 32, 32, 32, 64, 64, 32, 32, 48, 80, 80, 48, 128, 64, 288, 32, 64, 64, 48, 64, 64, 48, 32, 128, 80, 48, 80, 48, 96, 32, 80, 48, 48, 80, 128, 128, 128, 96, 160, 128, 96, 32, 80, 48, 80, 176, 80, 80, 96, 96, 64, 96, 80, 96, 16, 64, 96, 160, 112, 80, 64, 96, 80, 304, 32, 96, 80, 16, 64, 1024, 128, 208, 2624, 112, 1072, 48, 4e3, 640, 8576, 576, 48, 96, 48, 144, 688, 96, 96, 160, 64, 32, 6144, 768, 512, 128, 8816, 16, 256, 48, 64, 400, 2304, 160, 16, 4688, 208, 48, 256, 256, 80, 112, 32, 32, 96, 32, 128, 1024, 688, 1104, 256, 48, 96, 112, 80, 320, 48, 64, 464, 48, 736, 32, 224, 32, 96, 784, 80, 64, 80, 176, 256, 256, 48, 112, 96, 256, 256, 768, 80, 48, 128, 128, 128, 256, 256, 112, 144, 256, 1024, 42720, 32, 4160, 224, 5776, 7488, 3088, 544, 1504, 4944, 4192, 711760, 128, 128, 240, 65040, 65536, 65536]), Z = function (A) { const B = new Map, e = A.split(""), i = K.map((() => [])); let r = 0, o = 0; for (; o < e.length;) { const A = y[e[o]], B = (31 & A) - 2; let t = 1 + y[e[o + 1]]; switch (32 & A ? (t += y[e[o + 2]] << 6, t += y[e[o + 3]] << 12, t += y[e[o + 4]] << 18, o += 5) : o += 2, B) { case -2: { let A = 0; for (let B = r; B < r + t; ++B) { i[A].push(a(B)), A = (A + 1) % 2 } break } case -1: break; default: { const A = i[B]; 1 === t ? A.push(a(r)) : A.push(n(r, r + t - 1)); break } }r += t } const l = new Map; return K.forEach(((A, a) => { const n = i[a].reduce(G, t); B.set(A, n); const e = A.charAt(0), r = l.get(e) || []; l.set(e, r), r.push(n) })), l.forEach(((A, a) => { B.set(a, A.reduce(G, t)) })), B }("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"); function O(A) { return 32 === A || 9 === A || 10 === A || 13 === A } const k = [a(b(":")), n(b("A"), b("Z")), a(b("_")), n(b("a"), b("z")), n(192, 214), n(216, 246), n(192, 214), n(216, 246), n(248, 767), n(880, 893), n(895, 8191), n(8204, 8205), n(8304, 8591), n(11264, 12271), n(12289, 55295), n(63744, 64975), n(65008, 65533), n(65536, 983039)].reduce(G), N = [k, a(b("-")), a(b(".")), n(b("0"), b("9")), a(183), n(768, 879), n(8255, 8256)].reduce(G), v = Z.get("Nd"), w = Q(v), Y = L(n(0, 1114111), [Z.get("P"), Z.get("Z"), Z.get("C")].reduce(G)), U = Q(Y); function j(A) { return 10 !== A && 13 !== A && !x(A) } const R = { s: O, S: Q(O), i: k, I: Q(k), c: N, C: Q(N), d: v, D: w, w: Y, W: U }, V = c("*"), W = c("\\"), q = c("{"), z = c("}"), $ = c("["), _ = c("]"), AA = c("^"), BA = c("$"), aA = c(","), nA = c("-"), eA = c("("), tA = c(")"), GA = c("."), iA = c("|"), rA = c("+"), oA = c("?"), lA = c("-["), HA = b("0"); function uA(A) { function B(A) { return new Set(A.split("").map((A => b(A)))) } function t(A, B) { const a = A.codePointAt(B); return void 0 === a ? s(B, ["any character"]) : u(B + String.fromCodePoint(a).length, a) } const o = "xpath" === A.language ? E(W, d([D(c("n"), (() => 10)), D(c("r"), (() => 13)), D(c("t"), (() => 9)), D(d([W, iA, GA, nA, AA, oA, V, rA, q, z, BA, eA, tA, $, _]), (A => b(A)))])) : E(W, d([D(c("n"), (() => 10)), D(c("r"), (() => 13)), D(c("t"), (() => 9)), D(d([W, iA, GA, nA, AA, oA, V, rA, q, z, eA, tA, $, _]), (A => b(A)))])); function l(A, a) { const n = B(a); return p(c(A), I(m(t, (A => n.has(A)), a.split(""))), ((A, B) => function (A) { const B = Z.get(A); if (null == B) throw new Error(`${A} is not a valid unicode category`); return B }(null === B ? A : A + String.fromCodePoint(B)))) } const H = d([l("L", "ultmo"), l("M", "nce"), l("N", "dlo"), l("P", "cdseifo"), l("Z", "slp"), l("S", "mcko"), l("C", "cfon")]), C = [n(b("a"), b("z")), n(b("A"), b("Z")), n(b("0"), b("9")), a(45)].reduce(G), F = D(E(c("Is"), function (A) { return (B, a) => { const n = A(B, a); return n.success ? u(n.offset, B.slice(a, n.offset)) : n } }(T(m(t, C, ["block identifier"])))), (B => function (A, B) { const a = X.get(A); if (void 0 === a) { if (B) return e; throw new Error(`The unicode block identifier "${A}" is not known.`) } return a }(B, "xpath" !== A.language))), K = d([H, F]), y = M(c("\\p{"), K, z, !0), x = D(M(c("\\P{"), K, z, !0), Q), O = E(W, D(d("sSiIcCdDwW".split("").map((A => c(A)))), (A => R[A]))), k = D(GA, (() => j)), N = d([O, y, x]), v = B("\\[]"), w = m(t, (A => !v.has(A)), ["unescaped character"]), Y = d([o, w]), U = d([D(nA, (() => null)), Y]), uA = p(U, E(nA, U), n); function CA(A, B) { return [A].concat(B || []) } const sA = D(function (A) { return (B, a) => { const n = A(B, a); return n.success ? u(a, n.value) : n } }(d([_, lA])), (() => null)), cA = b("-"), DA = d([D(g(g(nA, P($, ["not ["])), sA), (() => cA)), E(P(nA, ["not -"]), Y)]), mA = d([p(D(DA, a), d([function (A, B) { return mA(A, B) }, sA]), CA), p(d([uA, N]), d([IA, sA]), CA)]); const dA = d([p(D(Y, a), d([mA, sA]), CA), p(d([uA, N]), d([IA, sA]), CA)]); function IA(A, B) { return dA(A, B) } const hA = D(dA, (A => A.reduce(G))), pA = D(E(AA, hA), Q), TA = p(d([E(P(AA, ["not ^"]), hA), pA]), I(E(nA, (function (A, B) { return fA(A, B) }))), L), fA = M($, TA, _, !0); const FA = "xpath" === A.language ? d([D(o, a), N, fA, k, D(AA, (() => A => A === i)), D(BA, (() => A => A === r))]) : d([D(o, a), N, fA, k]), EA = "xpath" === A.language ? B(".\\?*+{}()|^$[]") : B(".\\?*+{}()|[]"), gA = m(t, (A => !EA.has(A)), ["NormalChar"]), MA = D(E(W, p(D(m(t, n(b("1"), b("9")), ["digit"]), (A => A - HA)), h(D(m(t, n(HA, b("9")), ["digit"]), (A => A - HA))), ((A, B) => { B.reduce(((A, B) => 10 * A + B), A) }))), (A => { throw new Error("Backreferences in XPath patterns are not yet implemented.") })), PA = "xpath" === A.language ? d([D(gA, (A => ({ kind: "predicate", value: a(A) }))), D(FA, (A => ({ kind: "predicate", value: A }))), D(M(eA, E(I(c("?:")), QA), tA, !0), (A => ({ kind: "regexp", value: A }))), MA]) : d([D(gA, (A => ({ kind: "predicate", value: a(A) }))), D(FA, (A => ({ kind: "predicate", value: A }))), D(M(eA, QA, tA, !0), (A => ({ kind: "regexp", value: A })))]), JA = D(T(D(m(t, n(HA, b("9")), ["digit"]), (A => A - HA))), (A => A.reduce(((A, B) => 10 * A + B)))), SA = d([p(JA, E(aA, JA), ((A, B) => { if (B < A) throw new Error("quantifier range is in the wrong order"); return { min: A, max: B } })), p(JA, aA, (A => ({ min: A, max: null }))), D(JA, (A => ({ min: A, max: A })))]), KA = "xpath" === A.language ? p(d([D(oA, (() => ({ min: 0, max: 1 }))), D(V, (() => ({ min: 0, max: null }))), D(rA, (() => ({ min: 1, max: null }))), M(q, SA, z, !0)]), I(oA), ((A, B) => A)) : d([D(oA, (() => ({ min: 0, max: 1 }))), D(V, (() => ({ min: 0, max: null }))), D(rA, (() => ({ min: 1, max: null }))), M(q, SA, z, !0)]), yA = p(PA, D(I(KA), (A => null === A ? { min: 1, max: 1 } : A)), ((A, B) => [A, B])), bA = h(yA), xA = p(bA, h(E(iA, J(bA))), ((A, B) => [A].concat(B))); function QA(A, B) { return xA(A, B) } const LA = function (A) { return p(A, S, f) }(xA); return function (A) { let B; try { B = LA(A, 0) } catch (B) { throw new Error(`Error parsing pattern "${A}": ${B instanceof Error ? B.message : B}`) } return B.success ? B.value : function (A, B, a) { const n = a.map((A => `"${A}"`)); throw new Error(`Error parsing pattern "${A}" at offset ${B}: expected ${n.length > 1 ? "one of " + n.join(", ") : n[0]} but found "${A.slice(B, B + 1)}"`) }(A, B.offset, B.expected) } } function CA(A) { return [...A].map((A => A.codePointAt(0))) } A.compile = function (A, a = { language: "xsd" }) { const n = uA(a)(A), e = B.compileVM((A => { H(A, n, "xpath" === a.language), A.accept() })); return function (A) { const B = "xpath" === a.language ? [i, ...CA(A), r] : CA(A); return e.execute(B).success } }, Object.defineProperty(A, "__esModule", { value: !0 }) })); ++ ++ // fontoxpath 3.29.0 ++ ++ // MIT License ++ ++ // Copyright (c) 2017 Fonto Group BV ++ ++ // Permission is hereby granted, free of charge, to any person obtaining a copy ++ // of this software and associated documentation files (the "Software"), to deal ++ // in the Software without restriction, including without limitation the rights ++ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ++ // copies of the Software, and to permit persons to whom the Software is ++ // furnished to do so, subject to the following conditions: ++ ++ // The above copyright notice and this permission notice shall be included in all ++ // copies or substantial portions of the Software. ++ ++ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ++ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ++ // SOFTWARE. ++ ++ (function (root, factory) { ++ ++ // Browser globals (root is window) ++ // Maybe it is in scope: ++ root.fontoxpath = factory(root.xspattern, root.prsc); ++ })(this, function (xspattern, prsc) { ++ return (function (xspattern, prsc) { ++ const VERSION = '3.29.0'; ++ const fontoxpathGlobal = {}; ++ var h; function aa(a) { var b = 0; return function () { return b < a.length ? { done: !1, value: a[b++] } : { done: !0 } } } var ba = "function" == typeof Object.defineProperties ? Object.defineProperty : function (a, b, c) { if (a == Array.prototype || a == Object.prototype) return a; a[b] = c.value; return a }; ++ function ca(a) { a = ["object" == typeof globalThis && globalThis, a, "object" == typeof window && window, "object" == typeof self && self, "object" == typeof global && global]; for (var b = 0; b < a.length; ++b) { var c = a[b]; if (c && c.Math == Math) return c } throw Error("Cannot find global object"); } var da = ca(this); "function" === typeof Symbol && Symbol("x"); ++ function fa(a, b) { if (b) a: { var c = da; a = a.split("."); for (var d = 0; d < a.length - 1; d++) { var e = a[d]; if (!(e in c)) break a; c = c[e] } a = a[a.length - 1]; d = c[a]; b = b(d); b != d && null != b && ba(c, a, { configurable: !0, writable: !0, value: b }) } } ++ fa("Symbol", function (a) { function b(f) { if (this instanceof b) throw new TypeError("Symbol is not a constructor"); return new c(d + (f || "") + "_" + e++, f) } function c(f, g) { this.h = f; ba(this, "description", { configurable: !0, writable: !0, value: g }) } if (a) return a; c.prototype.toString = function () { return this.h }; var d = "jscomp_symbol_" + (1E9 * Math.random() >>> 0) + "_", e = 0; return b }); ++ fa("Symbol.iterator", function (a) { if (a) return a; a = Symbol("Symbol.iterator"); for (var b = "Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "), c = 0; c < b.length; c++) { var d = da[b[c]]; "function" === typeof d && "function" != typeof d.prototype[a] && ba(d.prototype, a, { configurable: !0, writable: !0, value: function () { return ha(aa(this)) } }) } return a }); fa("Symbol.asyncIterator", function (a) { return a ? a : Symbol("Symbol.asyncIterator") }); ++ function ha(a) { a = { next: a }; a[Symbol.iterator] = function () { return this }; return a } function p(a) { var b = "undefined" != typeof Symbol && Symbol.iterator && a[Symbol.iterator]; return b ? b.call(a) : { next: aa(a) } } function ja(a) { for (var b, c = []; !(b = a.next()).done;)c.push(b.value); return c } function t(a) { return a instanceof Array ? a : ja(p(a)) } var ka = "function" == typeof Object.create ? Object.create : function (a) { function b() { } b.prototype = a; return new b }, la; ++ if ("function" == typeof Object.setPrototypeOf) la = Object.setPrototypeOf; else { var ma; a: { var na = { a: !0 }, pa = {}; try { pa.__proto__ = na; ma = pa.a; break a } catch (a) { } ma = !1 } la = ma ? function (a, b) { a.__proto__ = b; if (a.__proto__ !== b) throw new TypeError(a + " is not extensible"); return a } : null } var qa = la; ++ function v(a, b) { a.prototype = ka(b.prototype); a.prototype.constructor = a; if (qa) qa(a, b); else for (var c in b) if ("prototype" != c) if (Object.defineProperties) { var d = Object.getOwnPropertyDescriptor(b, c); d && Object.defineProperty(a, c, d) } else a[c] = b[c]; a.zc = b.prototype } function ra() { this.B = !1; this.o = null; this.l = void 0; this.h = 1; this.da = 0; this.v = null } function sa(a) { if (a.B) throw new TypeError("Generator is already running"); a.B = !0 } ra.prototype.s = function (a) { this.l = a }; function ta(a, b) { a.v = { kc: b, mc: !0 }; a.h = a.da } ++ ra.prototype.return = function (a) { this.v = { return: a }; this.h = this.da }; function ua(a) { this.h = new ra; this.o = a } function va(a, b) { sa(a.h); var c = a.h.o; if (c) return wa(a, "return" in c ? c["return"] : function (d) { return { value: d, done: !0 } }, b, a.h.return); a.h.return(b); return xa(a) } ++ function wa(a, b, c, d) { try { var e = b.call(a.h.o, c); if (!(e instanceof Object)) throw new TypeError("Iterator result " + e + " is not an object"); if (!e.done) return a.h.B = !1, e; var f = e.value } catch (g) { return a.h.o = null, ta(a.h, g), xa(a) } a.h.o = null; d.call(a.h, f); return xa(a) } function xa(a) { for (; a.h.h;)try { var b = a.o(a.h); if (b) return a.h.B = !1, { value: b.value, done: !1 } } catch (c) { a.h.l = void 0, ta(a.h, c) } a.h.B = !1; if (a.h.v) { b = a.h.v; a.h.v = null; if (b.mc) throw b.kc; return { value: b.return, done: !0 } } return { value: void 0, done: !0 } } ++ function ya(a) { this.next = function (b) { sa(a.h); a.h.o ? b = wa(a, a.h.o.next, b, a.h.s) : (a.h.s(b), b = xa(a)); return b }; this.throw = function (b) { sa(a.h); a.h.o ? b = wa(a, a.h.o["throw"], b, a.h.s) : (ta(a.h, b), b = xa(a)); return b }; this.return = function (b) { return va(a, b) }; this[Symbol.iterator] = function () { return this } } function za(a) { function b(d) { return a.next(d) } function c(d) { return a.throw(d) } return new Promise(function (d, e) { function f(g) { g.done ? d(g.value) : Promise.resolve(g.value).then(b, c).then(f, e) } f(a.next()) }) } ++ function Aa() { for (var a = Number(this), b = [], c = a; c < arguments.length; c++)b[c - a] = arguments[c]; return b } ++ fa("Promise", function (a) { ++ function b(g) { this.o = 0; this.v = void 0; this.h = []; this.da = !1; var k = this.B(); try { g(k.resolve, k.reject) } catch (l) { k.reject(l) } } function c() { this.h = null } function d(g) { return g instanceof b ? g : new b(function (k) { k(g) }) } if (a) return a; c.prototype.o = function (g) { if (null == this.h) { this.h = []; var k = this; this.v(function () { k.l() }) } this.h.push(g) }; var e = da.setTimeout; c.prototype.v = function (g) { e(g, 0) }; c.prototype.l = function () { ++ for (; this.h && this.h.length;) { ++ var g = this.h; this.h = []; for (var k = 0; k < g.length; ++k) { ++ var l = ++ g[k]; g[k] = null; try { l() } catch (m) { this.B(m) } ++ } ++ } this.h = null ++ }; c.prototype.B = function (g) { this.v(function () { throw g; }) }; b.prototype.B = function () { function g(m) { return function (q) { l || (l = !0, m.call(k, q)) } } var k = this, l = !1; return { resolve: g(this.la), reject: g(this.l) } }; b.prototype.la = function (g) { ++ if (g === this) this.l(new TypeError("A Promise cannot resolve to itself")); else if (g instanceof b) this.ab(g); else { ++ a: switch (typeof g) { case "object": var k = null != g; break a; case "function": k = !0; break a; default: k = !1 }k ? this.S(g) : ++ this.s(g) ++ } ++ }; b.prototype.S = function (g) { var k = void 0; try { k = g.then } catch (l) { this.l(l); return } "function" == typeof k ? this.Ba(k, g) : this.s(g) }; b.prototype.l = function (g) { this.ta(2, g) }; b.prototype.s = function (g) { this.ta(1, g) }; b.prototype.ta = function (g, k) { if (0 != this.o) throw Error("Cannot settle(" + g + ", " + k + "): Promise already settled in state" + this.o); this.o = g; this.v = k; 2 === this.o && this.pa(); this.A() }; b.prototype.pa = function () { ++ var g = this; e(function () { if (g.K()) { var k = da.console; "undefined" !== typeof k && k.error(g.v) } }, ++ 1) ++ }; b.prototype.K = function () { if (this.da) return !1; var g = da.CustomEvent, k = da.Event, l = da.dispatchEvent; if ("undefined" === typeof l) return !0; "function" === typeof g ? g = new g("unhandledrejection", { cancelable: !0 }) : "function" === typeof k ? g = new k("unhandledrejection", { cancelable: !0 }) : (g = da.document.createEvent("CustomEvent"), g.initCustomEvent("unhandledrejection", !1, !0, g)); g.promise = this; g.reason = this.v; return l(g) }; b.prototype.A = function () { ++ if (null != this.h) { ++ for (var g = 0; g < this.h.length; ++g)f.o(this.h[g]); this.h = ++ null ++ } ++ }; var f = new c; b.prototype.ab = function (g) { var k = this.B(); g.rb(k.resolve, k.reject) }; b.prototype.Ba = function (g, k) { var l = this.B(); try { g.call(k, l.resolve, l.reject) } catch (m) { l.reject(m) } }; b.prototype.then = function (g, k) { function l(z, A) { return "function" == typeof z ? function (D) { try { m(z(D)) } catch (F) { q(F) } } : A } var m, q, u = new b(function (z, A) { m = z; q = A }); this.rb(l(g, m), l(k, q)); return u }; b.prototype.catch = function (g) { return this.then(void 0, g) }; b.prototype.rb = function (g, k) { ++ function l() { ++ switch (m.o) { ++ case 1: g(m.v); ++ break; case 2: k(m.v); break; default: throw Error("Unexpected state: " + m.o); ++ } ++ } var m = this; null == this.h ? f.o(l) : this.h.push(l); this.da = !0 ++ }; b.resolve = d; b.reject = function (g) { return new b(function (k, l) { l(g) }) }; b.race = function (g) { return new b(function (k, l) { for (var m = p(g), q = m.next(); !q.done; q = m.next())d(q.value).rb(k, l) }) }; b.all = function (g) { ++ var k = p(g), l = k.next(); return l.done ? d([]) : new b(function (m, q) { ++ function u(D) { return function (F) { z[D] = F; A--; 0 == A && m(z) } } var z = [], A = 0; do z.push(void 0), A++, d(l.value).rb(u(z.length - ++ 1), q), l = k.next(); while (!l.done) ++ }) ++ }; return b ++ }); function Ca(a, b) { return Object.prototype.hasOwnProperty.call(a, b) } var Da = "function" == typeof Object.assign ? Object.assign : function (a, b) { for (var c = 1; c < arguments.length; c++) { var d = arguments[c]; if (d) for (var e in d) Ca(d, e) && (a[e] = d[e]) } return a }; fa("Object.assign", function (a) { return a || Da }); ++ function Ea(a, b, c) { if (null == a) throw new TypeError("The 'this' value for String.prototype." + c + " must not be null or undefined"); if (b instanceof RegExp) throw new TypeError("First argument to String.prototype." + c + " must not be a regular expression"); return a + "" } fa("String.prototype.repeat", function (a) { return a ? a : function (b) { var c = Ea(this, null, "repeat"); if (0 > b || 1342177279 < b) throw new RangeError("Invalid count value"); b |= 0; for (var d = ""; b;)if (b & 1 && (d += c), b >>>= 1) c += c; return d } }); ++ function Fa(a, b) { a = void 0 !== a ? String(a) : " "; return 0 < b && a ? a.repeat(Math.ceil(b / a.length)).substring(0, b) : "" } fa("String.prototype.padEnd", function (a) { return a ? a : function (b, c) { var d = Ea(this, null, "padStart"); return d + Fa(c, b - d.length) } }); ++ fa("WeakMap", function (a) { ++ function b(l) { this.h = (k += Math.random() + 1).toString(); if (l) { l = p(l); for (var m; !(m = l.next()).done;)m = m.value, this.set(m[0], m[1]) } } function c() { } function d(l) { var m = typeof l; return "object" === m && null !== l || "function" === m } function e(l) { if (!Ca(l, g)) { var m = new c; ba(l, g, { value: m }) } } function f(l) { var m = Object[l]; m && (Object[l] = function (q) { if (q instanceof c) return q; Object.isExtensible(q) && e(q); return m(q) }) } if (function () { ++ if (!a || !Object.seal) return !1; try { ++ var l = Object.seal({}), m = Object.seal({}), ++ q = new a([[l, 2], [m, 3]]); if (2 != q.get(l) || 3 != q.get(m)) return !1; q.delete(l); q.set(m, 4); return !q.has(l) && 4 == q.get(m) ++ } catch (u) { return !1 } ++ }()) return a; var g = "$jscomp_hidden_" + Math.random(); f("freeze"); f("preventExtensions"); f("seal"); var k = 0; b.prototype.set = function (l, m) { if (!d(l)) throw Error("Invalid WeakMap key"); e(l); if (!Ca(l, g)) throw Error("WeakMap key fail: " + l); l[g][this.h] = m; return this }; b.prototype.get = function (l) { return d(l) && Ca(l, g) ? l[g][this.h] : void 0 }; b.prototype.has = function (l) { ++ return d(l) && Ca(l, ++ g) && Ca(l[g], this.h) ++ }; b.prototype.delete = function (l) { return d(l) && Ca(l, g) && Ca(l[g], this.h) ? delete l[g][this.h] : !1 }; return b ++ }); ++ fa("Map", function (a) { ++ function b() { var k = {}; return k.Ga = k.next = k.head = k } function c(k, l) { var m = k.h; return ha(function () { if (m) { for (; m.head != k.h;)m = m.Ga; for (; m.next != m.head;)return m = m.next, { done: !1, value: l(m) }; m = null } return { done: !0, value: void 0 } }) } function d(k, l) { ++ var m = l && typeof l; "object" == m || "function" == m ? f.has(l) ? m = f.get(l) : (m = "" + ++g, f.set(l, m)) : m = "p_" + l; var q = k.o[m]; if (q && Ca(k.o, m)) for (k = 0; k < q.length; k++) { var u = q[k]; if (l !== l && u.key !== u.key || l === u.key) return { id: m, list: q, index: k, ka: u } } return { ++ id: m, ++ list: q, index: -1, ka: void 0 ++ } ++ } function e(k) { this.o = {}; this.h = b(); this.size = 0; if (k) { k = p(k); for (var l; !(l = k.next()).done;)l = l.value, this.set(l[0], l[1]) } } if (function () { ++ if (!a || "function" != typeof a || !a.prototype.entries || "function" != typeof Object.seal) return !1; try { ++ var k = Object.seal({ x: 4 }), l = new a(p([[k, "s"]])); if ("s" != l.get(k) || 1 != l.size || l.get({ x: 4 }) || l.set({ x: 4 }, "t") != l || 2 != l.size) return !1; var m = l.entries(), q = m.next(); if (q.done || q.value[0] != k || "s" != q.value[1]) return !1; q = m.next(); return q.done || 4 != q.value[0].x || ++ "t" != q.value[1] || !m.next().done ? !1 : !0 ++ } catch (u) { return !1 } ++ }()) return a; var f = new WeakMap; e.prototype.set = function (k, l) { k = 0 === k ? 0 : k; var m = d(this, k); m.list || (m.list = this.o[m.id] = []); m.ka ? m.ka.value = l : (m.ka = { next: this.h, Ga: this.h.Ga, head: this.h, key: k, value: l }, m.list.push(m.ka), this.h.Ga.next = m.ka, this.h.Ga = m.ka, this.size++); return this }; e.prototype.delete = function (k) { ++ k = d(this, k); return k.ka && k.list ? (k.list.splice(k.index, 1), k.list.length || delete this.o[k.id], k.ka.Ga.next = k.ka.next, k.ka.next.Ga = k.ka.Ga, ++ k.ka.head = null, this.size--, !0) : !1 ++ }; e.prototype.clear = function () { this.o = {}; this.h = this.h.Ga = b(); this.size = 0 }; e.prototype.has = function (k) { return !!d(this, k).ka }; e.prototype.get = function (k) { return (k = d(this, k).ka) && k.value }; e.prototype.entries = function () { return c(this, function (k) { return [k.key, k.value] }) }; e.prototype.keys = function () { return c(this, function (k) { return k.key }) }; e.prototype.values = function () { return c(this, function (k) { return k.value }) }; e.prototype.forEach = function (k, l) { ++ for (var m = this.entries(), ++ q; !(q = m.next()).done;)q = q.value, k.call(l, q[1], q[0], this) ++ }; e.prototype[Symbol.iterator] = e.prototype.entries; var g = 0; return e ++ }); function Ga(a, b, c) { a instanceof String && (a = String(a)); for (var d = a.length, e = 0; e < d; e++) { var f = a[e]; if (b.call(c, f, e, a)) return { Jb: e, Qb: f } } return { Jb: -1, Qb: void 0 } } fa("Array.prototype.find", function (a) { return a ? a : function (b, c) { return Ga(this, b, c).Qb } }); ++ fa("String.prototype.startsWith", function (a) { return a ? a : function (b, c) { var d = Ea(this, b, "startsWith"), e = d.length, f = b.length; c = Math.max(0, Math.min(c | 0, d.length)); for (var g = 0; g < f && c < e;)if (d[c++] != b[g++]) return !1; return g >= f } }); fa("Array.prototype.fill", function (a) { return a ? a : function (b, c, d) { var e = this.length || 0; 0 > c && (c = Math.max(0, e + c)); if (null == d || d > e) d = e; d = Number(d); 0 > d && (d = Math.max(0, e + d)); for (c = Number(c || 0); c < d; c++)this[c] = b; return this } }); function Ha(a) { return a ? a : Array.prototype.fill } ++ fa("Int8Array.prototype.fill", Ha); fa("Uint8Array.prototype.fill", Ha); fa("Uint8ClampedArray.prototype.fill", Ha); fa("Int16Array.prototype.fill", Ha); fa("Uint16Array.prototype.fill", Ha); fa("Int32Array.prototype.fill", Ha); fa("Uint32Array.prototype.fill", Ha); fa("Float32Array.prototype.fill", Ha); fa("Float64Array.prototype.fill", Ha); ++ fa("Array.from", function (a) { return a ? a : function (b, c, d) { c = null != c ? c : function (k) { return k }; var e = [], f = "undefined" != typeof Symbol && Symbol.iterator && b[Symbol.iterator]; if ("function" == typeof f) { b = f.call(b); for (var g = 0; !(f = b.next()).done;)e.push(c.call(d, f.value, g++)) } else for (f = b.length, g = 0; g < f; g++)e.push(c.call(d, b[g], g)); return e } }); fa("Object.is", function (a) { return a ? a : function (b, c) { return b === c ? 0 !== b || 1 / b === 1 / c : b !== b && c !== c } }); ++ fa("Array.prototype.includes", function (a) { return a ? a : function (b, c) { var d = this; d instanceof String && (d = String(d)); var e = d.length; c = c || 0; for (0 > c && (c = Math.max(c + e, 0)); c < e; c++) { var f = d[c]; if (f === b || Object.is(f, b)) return !0 } return !1 } }); fa("String.prototype.includes", function (a) { return a ? a : function (b, c) { return -1 !== Ea(this, b, "includes").indexOf(b, c || 0) } }); fa("Number.MAX_SAFE_INTEGER", function () { return 9007199254740991 }); fa("Number.MIN_SAFE_INTEGER", function () { return -9007199254740991 }); ++ fa("Math.trunc", function (a) { return a ? a : function (b) { b = Number(b); if (isNaN(b) || Infinity === b || -Infinity === b || 0 === b) return b; var c = Math.floor(Math.abs(b)); return 0 > b ? -c : c } }); fa("Number.isFinite", function (a) { return a ? a : function (b) { return "number" !== typeof b ? !1 : !isNaN(b) && Infinity !== b && -Infinity !== b } }); fa("String.prototype.padStart", function (a) { return a ? a : function (b, c) { var d = Ea(this, null, "padStart"); return Fa(c, b - d.length) + d } }); ++ function Ia(a, b) { a instanceof String && (a += ""); var c = 0, d = !1, e = { next: function () { if (!d && c < a.length) { var f = c++; return { value: b(f, a[f]), done: !1 } } d = !0; return { done: !0, value: void 0 } } }; e[Symbol.iterator] = function () { return e }; return e } fa("Array.prototype.keys", function (a) { return a ? a : function () { return Ia(this, function (b) { return b }) } }); fa("Number.isInteger", function (a) { return a ? a : function (b) { return Number.isFinite(b) ? b === Math.floor(b) : !1 } }); ++ fa("Number.isSafeInteger", function (a) { return a ? a : function (b) { return Number.isInteger(b) && Math.abs(b) <= Number.MAX_SAFE_INTEGER } }); fa("Array.prototype.findIndex", function (a) { return a ? a : function (b, c) { return Ga(this, b, c).Jb } }); fa("String.prototype.endsWith", function (a) { return a ? a : function (b, c) { var d = Ea(this, b, "endsWith"); void 0 === c && (c = d.length); c = Math.max(0, Math.min(c | 0, d.length)); for (var e = b.length; 0 < e && 0 < c;)if (d[--c] != b[--e]) return !1; return 0 >= e } }); ++ fa("String.fromCodePoint", function (a) { return a ? a : function (b) { for (var c = "", d = 0; d < arguments.length; d++) { var e = Number(arguments[d]); if (0 > e || 1114111 < e || e !== Math.floor(e)) throw new RangeError("invalid_code_point " + e); 65535 >= e ? c += String.fromCharCode(e) : (e -= 65536, c += String.fromCharCode(e >>> 10 & 1023 | 55296), c += String.fromCharCode(e & 1023 | 56320)) } return c } }); ++ fa("String.prototype.codePointAt", function (a) { return a ? a : function (b) { var c = Ea(this, null, "codePointAt"), d = c.length; b = Number(b) || 0; if (0 <= b && b < d) { b |= 0; var e = c.charCodeAt(b); if (55296 > e || 56319 < e || b + 1 === d) return e; b = c.charCodeAt(b + 1); return 56320 > b || 57343 < b ? e : 1024 * (e - 55296) + b + 9216 } } }); ++ fa("Set", function (a) { ++ function b(c) { this.h = new Map; if (c) { c = p(c); for (var d; !(d = c.next()).done;)this.add(d.value) } this.size = this.h.size } if (function () { ++ if (!a || "function" != typeof a || !a.prototype.entries || "function" != typeof Object.seal) return !1; try { ++ var c = Object.seal({ x: 4 }), d = new a(p([c])); if (!d.has(c) || 1 != d.size || d.add(c) != d || 1 != d.size || d.add({ x: 4 }) != d || 2 != d.size) return !1; var e = d.entries(), f = e.next(); if (f.done || f.value[0] != c || f.value[1] != c) return !1; f = e.next(); return f.done || f.value[0] == c || 4 != f.value[0].x || ++ f.value[1] != f.value[0] ? !1 : e.next().done ++ } catch (g) { return !1 } ++ }()) return a; b.prototype.add = function (c) { c = 0 === c ? 0 : c; this.h.set(c, c); this.size = this.h.size; return this }; b.prototype.delete = function (c) { c = this.h.delete(c); this.size = this.h.size; return c }; b.prototype.clear = function () { this.h.clear(); this.size = 0 }; b.prototype.has = function (c) { return this.h.has(c) }; b.prototype.entries = function () { return this.h.entries() }; b.prototype.values = function () { return this.h.values() }; b.prototype.keys = b.prototype.values; b.prototype[Symbol.iterator] = ++ b.prototype.values; b.prototype.forEach = function (c, d) { var e = this; this.h.forEach(function (f) { return c.call(d, f, f, e) }) }; return b ++ }); fa("Math.log10", function (a) { return a ? a : function (b) { return Math.log(b) / Math.LN10 } }); fa("Object.values", function (a) { return a ? a : function (b) { var c = [], d; for (d in b) Ca(b, d) && c.push(b[d]); return c } }); fa("Number.isNaN", function (a) { return a ? a : function (b) { return "number" === typeof b && isNaN(b) } }); ++ fa("Array.prototype.values", function (a) { return a ? a : function () { return Ia(this, function (b, c) { return c }) } }); fa("Object.getOwnPropertySymbols", function (a) { return a ? a : function () { return [] } }); function Ka(a, b) { if (!("0" !== a && "-0" !== a || "0" !== b && "-0" !== b)) return 0; var c = /(?:\+|(-))?(\d+)?(?:\.(\d+))?/; a = c.exec(a + ""); var d = c.exec(b + ""), e = !a[1], f = !d[1]; b = (a[2] || "").replace(/^0*/, ""); c = (d[2] || "").replace(/^0*/, ""); a = a[3] || ""; d = d[3] || ""; if (e && !f) return 1; if (!e && f) return -1; e = e && f; if (b.length > c.length) return e ? 1 : -1; if (b.length < c.length) return e ? -1 : 1; if (b > c) return e ? 1 : -1; if (b < c) return e ? -1 : 1; b = Math.max(a.length, d.length); c = a.padEnd(b, "0"); b = d.padEnd(b, "0"); return c > b ? e ? 1 : -1 : c < b ? e ? -1 : 1 : 0 }; function La(a, b) { a = a.toString(); if (-1 < a.indexOf(".") && 0 === b) return !1; a = /^[-+]?0*([1-9]\d*)?(?:\.((?:\d*[1-9])*)0*)?$/.exec(a); return a[2] ? a[2].length <= b : !0 } function Ma() { return function (a, b) { return 1 > Ka(a, b) } } function Na() { return function (a, b) { return 0 > Ka(a, b) } } function Oa() { return function (a, b) { return -1 < Ka(a, b) } } function Pa() { return function (a, b) { return 0 < Ka(a, b) } } ++ function Qa(a, b) { switch (b) { case "required": return /(Z)|([+-])([01]\d):([0-5]\d)$/.test(a.toString()); case "prohibited": return !/(Z)|([+-])([01]\d):([0-5]\d)$/.test(a.toString()); case "optional": return !0 } } function Sa(a) { switch (a) { case 1: case 0: case 6: case 3: return {}; case 4: return { na: La, ya: Ma(), xc: Na(), za: Oa(), yc: Pa() }; case 18: return {}; case 9: case 8: case 7: case 11: case 12: case 13: case 15: case 14: return { Ea: Qa }; case 22: case 21: case 20: case 23: case 44: return {}; default: return null } } var Ta = {}, Ua = {}; function Va(a) { return /^([+-]?(\d*(\.\d*)?([eE][+-]?\d*)?|INF)|NaN)$/.test(a) } function Wa(a) { return /^[_:A-Za-z][-._:A-Za-z0-9]*$/.test(a) } function Xa(a) { return Wa(a) && /^[_A-Za-z]([-._A-Za-z0-9])*$/.test(a) } function Ya(a) { a = a.split(":"); return 1 === a.length ? Xa(a[0]) : 2 !== a.length ? !1 : Xa(a[0]) && Xa(a[1]) } function Za(a) { return !/[\u0009\u000A\u000D]/.test(a) } function $a(a) { return Xa(a) } ++ var ab = new Map([[45, function () { return !0 }], [46, function () { return !0 }], [1, function () { return !0 }], [0, function (a) { return /^(0|1|true|false)$/.test(a) }], [6, function (a) { return Va(a) }], [3, Va], [4, function (a) { return /^[+-]?\d*(\.\d*)?$/.test(a) }], [18, function (a) { return /^(-)?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d*)?S)?)?$/.test(a) }], [9, function (a) { return /^-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], ++ [8, function (a) { return /^(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [7, function (a) { return /^-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [11, function (a) { return /^-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [12, function (a) { return /^-?([1-9][0-9]{3,}|0[0-9]{3})(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], ++ [13, function (a) { return /^--(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [15, function (a) { return /^---(0[1-9]|[12][0-9]|3[01])(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [14, function (a) { return /^--(0[1-9]|1[0-2])(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$/.test(a) }], [22, function (a) { return /^([0-9A-Fa-f]{2})*$/.test(a) }], [21, function (a) { return (new RegExp(/^((([A-Za-z0-9+/] ?){4})*((([A-Za-z0-9+/] ?){3}[A-Za-z0-9+/])|(([A-Za-z0-9+/] ?){2}[AEIMQUYcgkosw048] ?=)|(([A-Za-z0-9+/] ?)[AQgw] ?= ?=)))?$/)).test(a) }], ++ [20, function () { return !0 }], [44, Ya], [48, Za], [52, function (a) { return Za(a) && !/^ | {2,}| $/.test(a) }], [51, function (a) { return /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.test(a) }], [50, function (a) { return /^[-._:A-Za-z0-9]+$/.test(a) }], [25, Wa], [23, Ya], [24, Xa], [42, $a], [41, $a], [26, function (a) { return Xa(a) }], [5, function (a) { return /^[+-]?\d+$/.test(a) }], [16, function (a) { return /^-?P[0-9]+(Y([0-9]+M)?|M)$/.test(a) }], [17, function (a) { return /^-?P([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]+)?S)?)?$/.test(a) }]]); var bb = Object.create(null); ++ [{ D: 0, name: 59 }, { D: 0, name: 46, parent: 59, L: { whiteSpace: "preserve" } }, { D: 0, name: 19, parent: 46 }, { D: 0, name: 1, parent: 46 }, { D: 0, name: 0, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 4, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 6, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 3, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 18, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 9, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 8, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { ++ D: 0, name: 7, parent: 46, ++ L: { Ea: "optional", whiteSpace: "collapse" } ++ }, { D: 0, name: 11, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 12, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 13, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 15, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 14, parent: 46, L: { Ea: "optional", whiteSpace: "collapse" } }, { D: 0, name: 22, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 21, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 20, parent: 46, L: { whiteSpace: "collapse" } }, ++ { D: 0, name: 23, parent: 46, L: { whiteSpace: "collapse" } }, { D: 0, name: 44, parent: 46, L: { whiteSpace: "collapse" } }, { D: 1, name: 10, T: 9, L: { whiteSpace: "collapse", Ea: "required" } }, { D: 1, name: 48, T: 1, L: { whiteSpace: "replace" } }, { D: 1, name: 52, T: 48, L: { whiteSpace: "collapse" } }, { D: 1, name: 51, T: 52, L: { whiteSpace: "collapse" } }, { D: 1, name: 50, T: 52, L: { whiteSpace: "collapse" } }, { D: 2, name: 49, type: 50, L: { minLength: 1, whiteSpace: "collapse" } }, { D: 1, name: 25, T: 52, L: { whiteSpace: "collapse" } }, { D: 1, name: 24, T: 25, L: { whiteSpace: "collapse" } }, { ++ D: 1, name: 42, T: 24, ++ L: { whiteSpace: "collapse" } ++ }, { D: 1, name: 41, T: 24, L: { whiteSpace: "collapse" } }, { D: 2, name: 43, type: 41, L: { minLength: 1, whiteSpace: "collapse" } }, { D: 1, name: 26, T: 24, L: { whiteSpace: "collapse" } }, { D: 2, name: 40, type: 26, L: { minLength: 1, whiteSpace: "collapse" } }, { D: 0, name: 5, parent: 4, L: { na: 0, whiteSpace: "collapse" } }, { D: 1, name: 27, T: 5, L: { na: 0, ya: "0", whiteSpace: "collapse" } }, { D: 1, name: 28, T: 27, L: { na: 0, ya: "-1", whiteSpace: "collapse" } }, { D: 1, name: 31, T: 5, L: { na: 0, ya: "9223372036854775807", za: "-9223372036854775808", whiteSpace: "collapse" } }, ++ { D: 1, name: 32, T: 31, L: { na: 0, ya: "2147483647", za: "-2147483648", whiteSpace: "collapse" } }, { D: 1, name: 33, T: 32, L: { na: 0, ya: "32767", za: "-32768", whiteSpace: "collapse" } }, { D: 1, name: 34, T: 33, L: { na: 0, ya: "127", za: "-128", whiteSpace: "collapse" } }, { D: 1, name: 30, T: 5, L: { na: 0, za: "0", whiteSpace: "collapse" } }, { D: 1, name: 36, T: 30, L: { na: 0, ya: "18446744073709551615", za: "0", whiteSpace: "collapse" } }, { D: 1, name: 35, T: 36, L: { na: 0, ya: "4294967295", za: "0", whiteSpace: "collapse" } }, { D: 1, name: 38, T: 35, L: { na: 0, ya: "65535", za: "0", whiteSpace: "collapse" } }, ++ { D: 1, name: 37, T: 38, L: { na: 0, ya: "255", za: "0", whiteSpace: "collapse" } }, { D: 1, name: 29, T: 30, L: { na: 0, za: "1", whiteSpace: "collapse" } }, { D: 1, name: 16, T: 18, L: { whiteSpace: "collapse" } }, { D: 1, name: 17, T: 18, L: { whiteSpace: "collapse" } }, { D: 1, name: 60, T: 59 }, { D: 3, name: 39, Fa: [] }, { D: 1, name: 61, T: 60 }, { D: 1, name: 62, T: 60 }, { D: 0, name: 53, parent: 59 }, { D: 1, name: 54, T: 53 }, { D: 1, name: 58, T: 53 }, { D: 1, name: 47, T: 53 }, { D: 1, name: 56, T: 53 }, { D: 1, name: 57, T: 53 }, { D: 1, name: 55, T: 53 }, { D: 3, name: 2, Fa: [4, 5, 6, 3] }, { D: 3, name: 63, Fa: [] }].forEach(function (a) { ++ var b = ++ a.name, c = a.L || {}; switch (a.D) { case 0: a = a.parent ? bb[a.parent] : null; var d = ab.get(b) || null; bb[b] = { D: 0, type: b, Na: c, parent: a, kb: d, Pa: Sa(b), Fa: [] }; break; case 1: a = bb[a.T]; d = ab.get(b) || null; bb[b] = { D: 1, type: b, Na: c, parent: a, kb: d, Pa: a.Pa, Fa: [] }; break; case 2: bb[b] = { D: 2, type: b, Na: c, parent: bb[a.type], kb: null, Pa: Ta, Fa: [] }; break; case 3: a = a.Fa.map(function (e) { return bb[e] }), bb[b] = { D: 3, type: b, Na: c, parent: null, kb: null, Pa: Ua, Fa: a } } ++ }); function w(a, b) { if (!bb[b]) throw Error("Unknown type"); return { type: b, value: a } } var cb = w(!0, 0), db = w(!1, 0); function eb(a) { return Error("FORG0006: " + (void 0 === a ? "A wrong argument type was specified in a function call." : a)) }; function fb(a, b) { this.done = a; this.value = b } var x = new fb(!0, void 0); function y(a) { return new fb(!1, a) }; function gb(a, b) { if (3 === b.D) return !!b.Fa.find(function (c) { return gb(a, c) }); for (; a;) { if (a.type === b.type) return !0; if (3 === a.D) return !!a.Fa.find(function (c) { return B(c.type, b.type) }); a = a.parent } return !1 } function B(a, b) { return a === b ? !0 : gb(bb[a], bb[b]) }; function hb(a) { this.o = C; this.h = a; var b = -1; this.value = { next: function () { b++; return b >= a.length ? x : y(a[b]) } } } h = hb.prototype; h.sb = function () { return this }; h.filter = function (a) { var b = this, c = -1; return this.o.create({ next: function () { for (c++; c < b.h.length && !a(b.h[c], c, b);)c++; return c >= b.h.length ? x : y(b.h[c]) } }) }; h.first = function () { return this.h[0] }; h.O = function () { return this.h }; ++ h.ha = function () { if (B(this.h[0].type, 53)) return !0; throw eb("Cannot determine the effective boolean value of a sequence with a length higher than one."); }; h.Qa = function () { return this.h.length }; h.G = function () { return !1 }; h.wa = function () { return !1 }; h.map = function (a) { var b = this, c = -1; return this.o.create({ next: function () { return ++c >= b.h.length ? x : y(a(b.h[c], c, b)) } }, this.h.length) }; h.M = function (a) { return a(this.h) }; h.Z = function (a) { return a.multiple ? a.multiple(this) : a.default(this) }; function ib() { this.value = { next: function () { return x } } } h = ib.prototype; h.sb = function () { return this }; h.filter = function () { return this }; h.first = function () { return null }; h.O = function () { return [] }; h.ha = function () { return !1 }; h.Qa = function () { return 0 }; h.G = function () { return !0 }; h.wa = function () { return !1 }; h.map = function () { return this }; h.M = function (a) { return a([]) }; h.Z = function (a) { return a.empty ? a.empty(this) : a.default(this) }; function jb(a, b) { this.type = a; this.value = b } ++ var E = {}, mb = (E[0] = "xs:boolean", E[1] = "xs:string", E[2] = "xs:numeric", E[3] = "xs:double", E[4] = "xs:decimal", E[5] = "xs:integer", E[6] = "xs:float", E[7] = "xs:date", E[8] = "xs:time", E[9] = "xs:dateTime", E[10] = "xs:dateTimeStamp", E[11] = "xs:gYearMonth", E[12] = "xs:gYear", E[13] = "xs:gMonthDay", E[14] = "xs:gMonth", E[15] = "xs:gDay", E[16] = "xs:yearMonthDuration", E[17] = "xs:dayTimeDuration", E[18] = "xs:duration", E[19] = "xs:untypedAtomic", E[20] = "xs:anyURI", E[21] = "xs:base64Binary", E[22] = "xs:hexBinary", E[23] = "xs:QName", E[24] = "xs:NCName", ++ E[25] = "xs:Name", E[26] = "xs:ENTITY", E[27] = "xs:nonPositiveInteger", E[28] = "xs:negativeInteger", E[29] = "xs:positiveInteger", E[30] = "xs:nonNegativeInteger", E[31] = "xs:long", E[32] = "xs:int", E[33] = "xs:short", E[34] = "xs:byte", E[35] = "xs:unsignedInt", E[36] = "xs:unsignedLong", E[37] = "xs:unsignedByte", E[38] = "xs:unsignedShort", E[39] = "xs:error", E[40] = "xs:ENTITIES", E[41] = "xs:IDREF", E[42] = "xs:ID", E[43] = "xs:IDREFS", E[44] = "xs:NOTATION", E[45] = "xs:anySimpleType", E[46] = "xs:anyAtomicType", E[47] = "attribute()", E[48] = "xs:normalizedString", ++ E[49] = "xs:NMTOKENS", E[50] = "xs:NMTOKEN", E[51] = "xs:language", E[52] = "xs:token", E[53] = "node()", E[54] = "element()", E[55] = "document-node()", E[56] = "text()", E[57] = "processing-instruction()", E[58] = "comment()", E[59] = "item()", E[60] = "function(*)", E[61] = "map(*)", E[62] = "array(*)", E[63] = "none", E), nb = { ++ "xs:boolean": 0, "xs:string": 1, "xs:numeric": 2, "xs:double": 3, "xs:decimal": 4, "xs:integer": 5, "xs:float": 6, "xs:date": 7, "xs:time": 8, "xs:dateTime": 9, "xs:dateTimeStamp": 10, "xs:gYearMonth": 11, "xs:gYear": 12, "xs:gMonthDay": 13, "xs:gMonth": 14, ++ "xs:gDay": 15, "xs:yearMonthDuration": 16, "xs:dayTimeDuration": 17, "xs:duration": 18, "xs:untypedAtomic": 19, "xs:anyURI": 20, "xs:base64Binary": 21, "xs:hexBinary": 22, "xs:QName": 23, "xs:NCName": 24, "xs:Name": 25, "xs:ENTITY": 26, "xs:nonPositiveInteger": 27, "xs:negativeInteger": 28, "xs:positiveInteger": 29, "xs:nonNegativeInteger": 30, "xs:long": 31, "xs:int": 32, "xs:short": 33, "xs:byte": 34, "xs:unsignedInt": 35, "xs:unsignedLong": 36, "xs:unsignedByte": 37, "xs:unsignedShort": 38, "xs:error": 39, "xs:ENTITIES": 40, "xs:IDREF": 41, "xs:ID": 42, ++ "xs:IDREFS": 43, "xs:NOTATION": 44, "xs:anySimpleType": 45, "xs:anyAtomicType": 46, "attribute()": 47, "xs:normalizedString": 48, "xs:NMTOKENS": 49, "xs:NMTOKEN": 50, "xs:language": 51, "xs:token": 52, "node()": 53, "element()": 54, "document-node()": 55, "text()": 56, "processing-instruction()": 57, "comment()": 58, "item()": 59, "function(*)": 60, "map(*)": 61, "array(*)": 62 ++ }; function ob(a) { return 2 === a.g ? mb[a.type] + "*" : 1 === a.g ? mb[a.type] + "+" : 0 === a.g ? mb[a.type] + "?" : mb[a.type] } ++ function pb(a) { if ("none" === a) throw Error('XPST0051: The type "none" could not be found'); if (!a.startsWith("xs:") && 0 <= a.indexOf(":")) throw Error("XPST0081: Invalid prefix for input " + a); var b = nb[a]; if (void 0 === b) throw Error('XPST0051: The type "' + a + '" could not be found'); return b } ++ function qb(a) { switch (a[a.length - 1]) { case "*": return { type: pb(a.substr(0, a.length - 1)), g: 2 }; case "?": return { type: pb(a.substr(0, a.length - 1)), g: 0 }; case "+": return { type: pb(a.substr(0, a.length - 1)), g: 1 }; default: return { type: pb(a), g: 3 } } } function rb(a) { switch (a) { case "*": return 2; case "?": return 0; case "+": return 1; default: return 3 } }; function sb(a) { var b = a.value; if (B(a.type, 53)) return !0; if (B(a.type, 0)) return b; if (B(a.type, 1) || B(a.type, 20) || B(a.type, 19)) return 0 !== b.length; if (B(a.type, 2)) return !isNaN(b) && 0 !== b; throw eb("Cannot determine the effective boolean value of a value with the type " + mb[a.type]); }; function tb(a, b) { var c = this; this.B = C; this.value = { next: function (d) { if (null !== c.o && c.h >= c.o) return x; if (void 0 !== c.v[c.h]) return y(c.v[c.h++]); d = a.next(d); if (d.done) return c.o = c.h, d; if (c.l || 2 > c.h) c.v[c.h] = d.value; c.h++; return d } }; this.l = !1; this.v = []; this.h = 0; this.o = void 0 === b ? null : b } h = tb.prototype; h.sb = function () { return this.B.create(this.O()) }; h.filter = function (a) { var b = this, c = -1, d = this.value; return this.B.create({ next: function (e) { c++; for (e = d.next(e); !e.done && !a(e.value, c, b);)c++, e = d.next(0); return e } }) }; ++ h.first = function () { if (void 0 !== this.v[0]) return this.v[0]; var a = this.value.next(0); ub(this); return a.done ? null : a.value }; h.O = function () { if (this.h > this.v.length && this.o !== this.v.length) throw Error("Implementation error: Sequence Iterator has progressed."); var a = this.value; this.l = !0; for (var b = a.next(0); !b.done;)b = a.next(0); return this.v }; ++ h.ha = function () { var a = this.value, b = this.h; ub(this); var c = a.next(0); if (c.done) return ub(this, b), !1; c = c.value; if (B(c.type, 53)) return ub(this, b), !0; if (!a.next(0).done) throw eb("Cannot determine the effective boolean value of a sequence with a length higher than one."); ub(this, b); return sb(c) }; h.Qa = function (a) { if (null !== this.o) return this.o; if (void 0 === a ? 0 : a) return -1; a = this.h; var b = this.O().length; ub(this, a); return b }; h.G = function () { return 0 === this.o ? !0 : null === this.first() }; ++ h.wa = function () { if (null !== this.o) return 1 === this.o; var a = this.value, b = this.h; ub(this); if (a.next(0).done) return ub(this, b), !1; a = a.next(0); ub(this, b); return a.done }; h.map = function (a) { var b = this, c = 0, d = this.value; return this.B.create({ next: function (e) { e = d.next(e); return e.done ? x : y(a(e.value, c++, b)) } }, this.o) }; h.M = function (a, b) { var c = this.value, d, e = [], f = !0; (function () { for (var g = c.next(f ? 0 : b); !g.done; g = c.next(b))f = !1, e.push(g.value); d = a(e).value })(); return this.B.create({ next: function () { return d.next(0) } }) }; ++ h.Z = function (a) { function b(e) { d = e.value; e = e.Qa(!0); -1 !== e && (c.o = e) } var c = this, d = null; return this.B.create({ next: function (e) { if (d) return d.next(e); if (c.G()) return b(a.empty ? a.empty(c) : a.default(c)), d.next(e); if (c.wa()) return b(a.m ? a.m(c) : a.default(c)), d.next(e); b(a.multiple ? a.multiple(c) : a.default(c)); return d.next(e) } }) }; function ub(a, b) { a.h = void 0 === b ? 0 : b }; function vb(a) { this.v = C; this.h = a; var b = !1; this.value = { next: function () { if (b) return x; b = !0; return y(a) } }; this.o = null } h = vb.prototype; h.sb = function () { return this }; h.filter = function (a) { return a(this.h, 0, this) ? this : this.v.create() }; h.first = function () { return this.h }; h.O = function () { return [this.h] }; h.ha = function () { null === this.o && (this.o = sb(this.h)); return this.o }; h.Qa = function () { return 1 }; h.G = function () { return !1 }; h.wa = function () { return !0 }; h.map = function (a) { return this.v.create(a(this.h, 0, this)) }; h.M = function (a) { return a([this.h]) }; ++ h.Z = function (a) { return a.m ? a.m(this) : a.default(this) }; var wb = new ib; function xb(a, b) { a = void 0 === a ? null : a; if (null === a) return wb; if (Array.isArray(a)) switch (a.length) { case 0: return wb; case 1: return new vb(a[0]); default: return new hb(a) }return a.next ? new tb(a, void 0 === b ? null : b) : new vb(a) } var C = { create: xb, m: function (a) { return new vb(a) }, empty: function () { return xb() }, ba: function () { return xb(cb) }, W: function () { return xb(db) } }; function yb(a) { var b = [], c = a.value; return function () { var d = 0; return C.create({ next: function () { if (void 0 !== b[d]) return b[d++]; var e = c.next(0); return e.done ? e : b[d++] = e } }) } }; function zb(a, b, c) { this.namespaceURI = b || null; this.prefix = a || ""; this.localName = c } zb.prototype.Ca = function () { return this.prefix ? this.prefix + ":" + this.localName : this.localName }; function Ab(a) { var b = a.j, c = a.arity, d = void 0 === a.Sa ? !1 : a.Sa, e = void 0 === a.I ? !1 : a.I, f = a.localName, g = a.namespaceURI, k = a.i; a = a.value; jb.call(this, 60, null); this.value = a; this.I = e; e = -1; for (a = 0; a < b.length; a++)4 === b[a] && (e = a); -1 < e && (a = Array(c - (b.length - 1)).fill(b[e - 1]), b = b.slice(0, e).concat(a)); this.o = b; this.v = c; this.da = d; this.B = f; this.l = g; this.s = k } v(Ab, jb); ++ function Cb(a, b) { var c = a.value, d = b.map(function (e) { return null === e ? null : yb(e) }); b = b.reduce(function (e, f, g) { null === f && e.push(a.o[g]); return e }, []); b = new Ab({ j: b, arity: b.length, Sa: !0, I: a.I, localName: "boundFunction", namespaceURI: a.l, i: a.s, value: function (e, f, g) { var k = Array.from(arguments).slice(3), l = d.map(function (m) { return null === m ? k.shift() : m() }); return c.apply(void 0, [e, f, g].concat(l)) } }); return C.m(b) } Ab.prototype.Sa = function () { return this.da }; function Db(a, b) { var c = []; 2 !== a && 1 !== a || c.push("type-1-or-type-2"); c.push("type-" + a); b && c.push("name-" + b); return c } function Eb(a) { var b = a.node.nodeType; if (2 === b || 1 === b) var c = a.node.localName; return Db(b, c) } function Fb(a) { var b = a.nodeType; if (2 === b || 1 === b) var c = a.localName; return Db(b, c) }; function Gb() { } Gb.prototype.getAllAttributes = function (a, b) { b = void 0 === b ? null : b; if (1 !== a.nodeType) return []; a = Array.from(a.attributes); return null === b ? a : a.filter(function (c) { return Fb(c).includes(b) }) }; Gb.prototype.getAttribute = function (a, b) { return 1 !== a.nodeType ? null : a.getAttribute(b) }; Gb.prototype.getChildNodes = function (a, b) { b = void 0 === b ? null : b; a = Array.from(a.childNodes); return null === b ? a : a.filter(function (c) { return Fb(c).includes(b) }) }; ++ Gb.prototype.getData = function (a) { return 2 === a.nodeType ? a.value : a.data }; Gb.prototype.getFirstChild = function (a, b) { b = void 0 === b ? null : b; for (a = a.firstChild; a; a = a.nextSibling)if (null === b || Fb(a).includes(b)) return a; return null }; Gb.prototype.getLastChild = function (a, b) { b = void 0 === b ? null : b; for (a = a.lastChild; a; a = a.previousSibling)if (null === b || Fb(a).includes(b)) return a; return null }; ++ Gb.prototype.getNextSibling = function (a, b) { b = void 0 === b ? null : b; for (a = a.nextSibling; a; a = a.nextSibling)if (null === b || Fb(a).includes(b)) return a; return null }; Gb.prototype.getParentNode = function (a, b) { b = void 0 === b ? null : b; return (a = 2 === a.nodeType ? a.ownerElement : a.parentNode) ? null === b || Fb(a).includes(b) ? a : null : null }; Gb.prototype.getPreviousSibling = function (a, b) { b = void 0 === b ? null : b; for (a = a.previousSibling; a; a = a.previousSibling)if (null === b || Fb(a).includes(b)) return a; return null }; function Hb() { } h = Hb.prototype; h.insertBefore = function (a, b, c) { return a.insertBefore(b, c) }; h.removeAttributeNS = function (a, b, c) { return a.removeAttributeNS(b, c) }; h.removeChild = function (a, b) { return a.removeChild(b) }; h.setAttributeNS = function (a, b, c, d) { a.setAttributeNS(b, c, d) }; h.setData = function (a, b) { a.data = b }; var Ib = new Hb; function Jb(a) { this.h = a } h = Jb.prototype; h.insertBefore = function (a, b, c) { return this.h.insertBefore(a, b, c) }; h.removeAttributeNS = function (a, b, c) { return this.h.removeAttributeNS(a, b, c) }; h.removeChild = function (a, b) { return this.h.removeChild(a, b) }; h.setAttributeNS = function (a, b, c, d) { this.h.setAttributeNS(a, b, c, d) }; h.setData = function (a, b) { this.h.setData(a, b) }; function Kb(a) { return void 0 !== a.Ta }; function Lb(a, b, c) { var d = null; b && (Kb(b.node) ? d = { F: b.F, offset: c, parent: b.node } : b.F && (d = b.F)); return { node: a, F: d } } function Mb(a) { this.h = a; this.o = [] } function Nb(a, b, c) { return a.getAllAttributes(b.node, void 0 === c ? null : c).map(function (d) { return Lb(d, b, d.nodeName) }) } Mb.prototype.getAllAttributes = function (a, b) { return Kb(a) ? a.attributes : this.h.getAllAttributes(a, void 0 === b ? null : b) }; ++ function Ob(a, b, c) { b = b.node; return Kb(b) ? (a = b.attributes.find(function (d) { return c === d.name })) ? a.value : null : (a = a.h.getAttribute(b, c)) ? a : null } function Pb(a, b, c) { return a.getChildNodes(b.node, void 0 === c ? null : c).map(function (d, e) { return Lb(d, b, e) }) } Mb.prototype.getChildNodes = function (a, b) { b = Kb(a) ? a.childNodes : this.h.getChildNodes(a, void 0 === b ? null : b); return 9 === a.nodeType ? b.filter(function (c) { return 10 !== c.nodeType }) : b }; ++ Mb.prototype.getData = function (a) { return Kb(a) ? 2 === a.nodeType ? a.value : a.data : this.h.getData(a) || "" }; function Qb(a, b) { return a.getData(b.node) } function Sb(a, b, c) { var d = b.node; Kb(d) ? a = d.childNodes[0] : ((c = a.h.getFirstChild(d, void 0 === c ? null : c)) && 10 === c.nodeType && (c = a.h.getNextSibling(c)), a = c); return a ? Lb(a, b, 0) : null } ++ function Tb(a, b, c) { c = void 0 === c ? null : c; var d = b.node; Kb(d) ? (a = d.childNodes.length - 1, d = d.childNodes[a]) : ((d = a.h.getLastChild(d, c)) && 10 === d.nodeType && (d = a.h.getPreviousSibling(d)), a = a.getChildNodes(b.node, c).length - 1); return d ? Lb(d, b, a) : null } ++ function Ub(a, b, c) { c = void 0 === c ? null : c; var d = b.node, e = b.F; if (Kb(d)) { if (e) { var f = e.offset + 1; var g = e.parent.childNodes[f] } } else if (e) { f = e.offset + 1; var k = Vb(a, b, null); g = a.getChildNodes(k.node, c)[f] } else { for (g = d; g && (!(g = a.h.getNextSibling(g, c)) || 10 === g.nodeType);); return g ? { node: g, F: null } : null } return g ? Lb(g, k || Vb(a, b, c), f) : null } Mb.prototype.getParentNode = function (a, b) { return this.h.getParentNode(a, void 0 === b ? null : b) }; ++ function Vb(a, b, c) { c = void 0 === c ? null : c; var d = b.node, e = b.F; if (e) "number" === typeof e.offset && d === e.parent.childNodes[e.offset] || "string" === typeof e.offset && d === e.parent.attributes.find(function (f) { return e.offset === f.nodeName }) ? (a = e.parent, b = e.F) : (a = a.getParentNode(d, c), b = e); else { if (Kb(d)) return null; a = a.getParentNode(d, c); b = null } return a ? { node: a, F: b } : null } ++ function Wb(a, b, c) { c = void 0 === c ? null : c; var d = b.node, e = b.F; if (Kb(d)) { if (e) { var f = e.offset - 1; var g = e.parent.childNodes[f] } } else if (e) { f = e.offset - 1; var k = Vb(a, b, null); g = a.getChildNodes(k.node, c)[f] } else { for (g = d; g && (!(g = a.h.getPreviousSibling(g, c)) || 10 === g.nodeType);); return g ? { node: g, F: null } : null } return g ? Lb(g, k || Vb(a, b, c), f) : null }; function Xb(a, b, c, d, e) { return e.M(function (f) { var g = p(f).next().value; return d.M(function (k) { k = p(k).next().value; var l = g.value; if (0 >= l || l > k.P.length) throw Error("FOAY0001: array position out of bounds."); return k.P[l - 1]() }) }) }; function Yb(a) { Ab.call(this, { value: function (c, d, e, f) { return Xb(c, d, e, C.m(b), f) }, localName: "get", namespaceURI: "http://www.w3.org/2005/xpath-functions/array", j: [{ type: 5, g: 3 }], arity: 1, i: { type: 59, g: 2 } }); var b = this; this.type = 62; this.P = a } v(Yb, Ab); function Zb(a) { switch (a.node.nodeType) { case 2: return 47; case 1: return 54; case 3: case 4: return 56; case 7: return 57; case 8: return 58; case 9: return 55; default: return 53 } } function $b(a) { return { type: Zb(a), value: a } }; function ac(a, b) { a = a.map(function (c) { return c.first() }); return b(a) }; function bc(a, b) { var c = B(a.type, 1) || B(a.type, 20) || B(a.type, 19), d = B(b.type, 1) || B(b.type, 20) || B(b.type, 19); if (c && d) return a.value === b.value; c = B(a.type, 4) || B(a.type, 3) || B(a.type, 6); d = B(b.type, 4) || B(b.type, 3) || B(b.type, 6); if (c && d) return isNaN(a.value) && isNaN(b.value) ? !0 : a.value === b.value; c = B(a.type, 0) || B(a.type, 22) || B(a.type, 18) || B(a.type, 23) || B(a.type, 44); d = B(b.type, 0) || B(b.type, 22) || B(b.type, 18) || B(b.type, 23) || B(b.type, 44); return c && d ? a.value === b.value : !1 }; function cc(a, b, c, d, e) { return ac([d, e], function (f) { f = p(f); var g = f.next().value, k = f.next().value; return (f = g.h.find(function (l) { return bc(l.key, k) })) ? f.value() : C.empty() }) }; function dc(a) { Ab.call(this, { j: [{ type: 59, g: 3 }], arity: 1, localName: "get", namespaceURI: "http://www.w3.org/2005/xpath-functions/map", value: function (c, d, e, f) { return cc(c, d, e, C.m(b), f) }, i: { type: 59, g: 2 } }); var b = this; this.type = 61; this.h = a } v(dc, Ab); function ec() { } function fc(a, b) { return a.eb() === b.eb() && a.fb() === b.fb() } h = ec.prototype; h.bb = function () { return 0 }; h.getHours = function () { return 0 }; h.getMinutes = function () { return 0 }; h.cb = function () { return 0 }; h.eb = function () { return 0 }; h.fb = function () { return 0 }; h.getSeconds = function () { return 0 }; h.gb = function () { return 0 }; h.qa = function () { return !0 }; function gc(a) { if (a > Number.MAX_SAFE_INTEGER || a < Number.MIN_SAFE_INTEGER) throw Error("FODT0002: Number of seconds given to construct DayTimeDuration overflows MAX_SAFE_INTEGER or MIN_SAFE_INTEGER"); this.ea = a } v(gc, ec); h = gc.prototype; h.bb = function () { return Math.trunc(this.ea / 86400) }; h.getHours = function () { return Math.trunc(this.ea % 86400 / 3600) }; h.getMinutes = function () { return Math.trunc(this.ea % 3600 / 60) }; h.fb = function () { return this.ea }; h.getSeconds = function () { var a = this.ea % 60; return Object.is(-0, a) ? 0 : a }; ++ h.qa = function () { return Object.is(-0, this.ea) ? !1 : 0 <= this.ea }; h.toString = function () { return (this.qa() ? "P" : "-P") + hc(this) }; function hc(a) { var b = Math.abs(a.bb()), c = Math.abs(a.getHours()), d = Math.abs(a.getMinutes()); a = Math.abs(a.getSeconds()); b = b ? b + "D" : ""; c = (c ? c + "H" : "") + (d ? d + "M" : "") + (a ? a + "S" : ""); return b && c ? b + "T" + c : b ? b : c ? "T" + c : "T0S" } function ic(a, b, c, d, e, f) { a = 86400 * a + 3600 * b + 60 * c + d + e; return new gc(f || 0 === a ? a : -a) } ++ function jc(a) { return (a = /^(-)?P(\d+Y)?(\d+M)?(\d+D)?(?:T(\d+H)?(\d+M)?(\d+(\.\d*)?S)?)?$/.exec(a)) ? ic(a[4] ? parseInt(a[4], 10) : 0, a[5] ? parseInt(a[5], 10) : 0, a[6] ? parseInt(a[6], 10) : 0, a[7] ? parseInt(a[7], 10) : 0, a[8] ? parseFloat(a[8]) : 0, !a[1]) : null } function kc(a) { a = /^(Z)|([+-])([01]\d):([0-5]\d)$/.exec(a); return "Z" === a[1] ? ic(0, 0, 0, 0, 0, !0) : ic(0, a[3] ? parseInt(a[3], 10) : 0, a[4] ? parseInt(a[4], 10) : 0, 0, 0, "+" === a[2]) } ++ function lc(a, b) { if (isNaN(b)) throw Error("FOCA0005: Cannot multiply xs:dayTimeDuration by NaN"); a = a.ea * b; if (a > Number.MAX_SAFE_INTEGER || !Number.isFinite(a)) throw Error("FODT0002: Value overflow while multiplying xs:dayTimeDuration"); return new gc(a < Number.MIN_SAFE_INTEGER || Object.is(-0, a) ? 0 : a) }; function mc(a) { return a ? parseInt(a, 10) : null } function nc(a) { a += ""; var b = a.startsWith("-"); b && (a = a.substring(1)); return (b ? "-" : "") + a.padStart(4, "0") } function oc(a) { return (a + "").padStart(2, "0") } function pc(a) { a += ""; 1 === a.split(".")[0].length && (a = a.padStart(a.length + 1, "0")); return a } function qc(a) { return 0 === a.getHours() && 0 === a.getMinutes() ? "Z" : (a.qa() ? "+" : "-") + oc(Math.abs(a.getHours())) + ":" + oc(Math.abs(a.getMinutes())) } ++ function rc(a, b, c, d, e, f, g, k, l) { this.v = a; this.h = b; this.o = c + (24 === d ? 1 : 0); this.l = 24 === d ? 0 : d; this.s = e; this.B = f; this.sa = g; this.Y = k; this.type = void 0 === l ? 9 : l } ++ function sc(a) { ++ var b = /^(?:(-?\d{4,}))?(?:--?(\d\d))?(?:-{1,3}(\d\d))?(T)?(?:(\d\d):(\d\d):(\d\d))?(\.\d+)?(Z|(?:[+-]\d\d:\d\d))?$/.exec(a); a = b[1] ? parseInt(b[1], 10) : null; var c = mc(b[2]), d = mc(b[3]), e = b[4], f = mc(b[5]), g = mc(b[6]), k = mc(b[7]), l = b[8] ? parseFloat(b[8]) : 0; b = b[9] ? kc(b[9]) : null; if (a && (-271821 > a || 273860 < a)) throw Error("FODT0001: Datetime year is out of bounds"); return e ? new rc(a, c, d, f, g, k, l, b, 9) : null !== f && null !== g && null !== k ? new rc(1972, 12, 31, f, g, k, l, b, 8) : null !== a && null !== c && null !== d ? new rc(a, c, ++ d, 0, 0, 0, 0, b, 7) : null !== a && null !== c ? new rc(a, c, 1, 0, 0, 0, 0, b, 11) : null !== c && null !== d ? new rc(1972, c, d, 0, 0, 0, 0, b, 13) : null !== a ? new rc(a, 1, 1, 0, 0, 0, 0, b, 12) : null !== c ? new rc(1972, c, 1, 0, 0, 0, 0, b, 14) : new rc(1972, 12, d, 0, 0, 0, 0, b, 15) ++ } ++ function tc(a, b) { switch (b) { case 15: return new rc(1972, 12, a.o, 0, 0, 0, 0, a.Y, 15); case 14: return new rc(1972, a.h, 1, 0, 0, 0, 0, a.Y, 14); case 12: return new rc(a.v, 1, 1, 0, 0, 0, 0, a.Y, 12); case 13: return new rc(1972, a.h, a.o, 0, 0, 0, 0, a.Y, 13); case 11: return new rc(a.v, a.h, 1, 0, 0, 0, 0, a.Y, 11); case 8: return new rc(1972, 12, 31, a.l, a.s, a.B, a.sa, a.Y, 8); case 7: return new rc(a.v, a.h, a.o, 0, 0, 0, 0, a.Y, 7); default: return new rc(a.v, a.h, a.o, a.l, a.s, a.B, a.sa, a.Y, 9) } } h = rc.prototype; h.getDay = function () { return this.o }; h.getHours = function () { return this.l }; ++ h.getMinutes = function () { return this.s }; h.getMonth = function () { return this.h }; h.getSeconds = function () { return this.B }; h.getYear = function () { return this.v }; function uc(a, b) { b = a.Y || b || kc("Z"); return new Date(Date.UTC(a.v, a.h - 1, a.o, a.l - b.getHours(), a.s - b.getMinutes(), a.B, 1E3 * a.sa)) } ++ h.toString = function () { ++ switch (this.type) { ++ case 9: return nc(this.v) + "-" + oc(this.h) + "-" + oc(this.o) + "T" + oc(this.l) + ":" + oc(this.s) + ":" + pc(this.B + this.sa) + (this.Y ? qc(this.Y) : ""); case 7: return nc(this.v) + "-" + oc(this.h) + "-" + oc(this.o) + (this.Y ? qc(this.Y) : ""); case 8: return oc(this.l) + ":" + oc(this.s) + ":" + pc(this.B + this.sa) + (this.Y ? qc(this.Y) : ""); case 15: return "---" + oc(this.o) + (this.Y ? qc(this.Y) : ""); case 14: return "--" + oc(this.h) + (this.Y ? qc(this.Y) : ""); case 13: return "--" + oc(this.h) + "-" + oc(this.o) + (this.Y ? qc(this.Y) : ++ ""); case 12: return nc(this.v) + (this.Y ? qc(this.Y) : ""); case 11: return nc(this.v) + "-" + oc(this.h) + (this.Y ? qc(this.Y) : "") ++ }throw Error("Unexpected subType"); ++ }; function vc(a, b, c) { var d = uc(a, c).getTime(); c = uc(b, c).getTime(); return d === c ? a.sa === b.sa ? 0 : a.sa > b.sa ? 1 : -1 : d > c ? 1 : -1 } function wc(a, b, c) { return 0 === vc(a, b, c) } function xc(a, b, c) { a = (uc(a, c).getTime() - uc(b, c).getTime()) / 1E3; return new gc(a) } function yc(a) { throw Error("Not implemented: adding durations to " + mb[a.type]); } ++ function zc(a) { throw Error("Not implemented: subtracting durations from " + mb[a.type]); }; function Ac(a, b) { ++ if (null === a) return null; switch (typeof a) { ++ case "boolean": return a ? cb : db; case "number": return w(a, 3); case "string": return w(a, 1); case "object": if ("nodeType" in a) return $b({ node: a, F: null }); if (Array.isArray(a)) return new Yb(a.map(function (d) { if (void 0 === d) return function () { return C.empty() }; d = Ac(d, b); d = null === d ? C.empty() : C.m(d); return yb(d) })); if (a instanceof Date) { var c = sc(a.toISOString()); return w(c, c.type) } return new dc(Object.keys(a).filter(function (d) { return void 0 !== a[d] }).map(function (d) { ++ var e = ++ Ac(a[d], b); e = null === e ? C.empty() : C.m(e); return { key: w(d, 1), value: yb(e) } ++ })) ++ }throw Error("Value " + String(a) + ' of type "' + typeof a + '" is not adaptable to an XPath value.'); ++ } function Bc(a, b) { if ("number" !== typeof a && ("string" !== typeof a || !ab.get(b)(a))) throw Error("Cannot convert JavaScript value '" + a + "' to the XPath type " + mb[b] + " since it is not valid."); } ++ function Cc(a, b, c) { ++ if (null === b) return null; switch (a) { ++ case 0: return b ? cb : db; case 1: return w(b + "", 1); case 3: case 2: return Bc(b, 3), w(+b, 3); case 4: return Bc(b, a), w(+b, 4); case 5: return Bc(b, a), w(b | 0, 5); case 6: return Bc(b, a), w(+b, 6); case 7: case 8: case 9: case 11: case 12: case 13: case 14: case 15: if (!(b instanceof Date)) throw Error("The JavaScript value " + b + " with type " + typeof b + " is not a valid type to be converted to an XPath " + mb[a] + "."); return w(tc(sc(b.toISOString()), a), a); case 53: case 47: case 55: case 54: case 56: case 57: case 58: if ("object" !== ++ typeof b || !("nodeType" in b)) throw Error("The JavaScript value " + b + " with type " + typeof b + " is not a valid type to be converted to an XPath " + mb[a] + "."); return $b({ node: b, F: null }); case 59: return Ac(b, c); case 61: return Ac(b, c); default: throw Error('Values of the type "' + mb[a] + '" can not be adapted from JavaScript to equivalent XPath values.'); ++ } ++ } ++ function Dc(a, b, c) { if (0 === c.g) return b = Cc(c.type, b, a), null === b ? [] : [b]; if (2 === c.g || 1 === c.g) { if (!Array.isArray(b)) throw Error("The JavaScript value " + b + " should be an array if it is to be converted to " + ob(c) + "."); return b.map(function (e) { return Cc(c.type, e, a) }).filter(function (e) { return null !== e }) } var d = Cc(c.type, b, a); if (null === d) throw Error("The JavaScript value " + b + " should be a single entry if it is to be converted to " + ob(c) + "."); return [d] } ++ function Ec(a, b, c) { c = void 0 === c ? { type: 59, g: 0 } : c; return C.create(Dc(a, b, c)) }; function Fc() { var a = void 0 === a ? Math.floor(Math.random() * Gc) : a; this.h = Math.abs(a % Gc) } var Gc = Math.pow(2, 32); function Hc(a, b, c) { b = void 0 === b ? { yb: null, Db: null, tb: !1 } : b; c = void 0 === c ? new Fc : c; this.h = b; this.Ka = a.Ka; this.Da = a.Da; this.N = a.N; this.Aa = a.Aa || Object.create(null); this.o = c } function Ic(a, b) { var c = 0, d = b.value; return { next: function (e) { e = d.next(e); return e.done ? x : y(Jc(a, c++, e.value, b)) } } } function Kc(a) { a.h.tb || (a.h.tb = !0, a.h.yb = sc((new Date).toISOString()), a.h.Db = jc("PT0S")); return a.h.yb } function Lc(a) { a.h.tb || (a.h.tb = !0, a.h.yb = sc((new Date).toISOString()), a.h.Db = jc("PT0S")); return a.h.Db } ++ function Mc(a, b) { b = void 0 === b ? null : b; a = 29421 * (null !== b && void 0 !== b ? b : a.o.h) % Gc; return { zb: Math.floor(a), jc: a / Gc } } function Jc(a, b, c, d) { return new Hc({ N: c, Ka: b, Da: d || a.Da, Aa: a.Aa }, a.h, a.o) } function Nc(a, b) { return new Hc({ N: a.N, Ka: a.Ka, Da: a.Da, Aa: Object.assign(Object.create(null), a.Aa, b) }, a.h, a.o) }; function Oc(a, b, c, d, e, f, g, k, l) { this.debug = a; this.La = b; this.h = c; this.o = d; this.B = e; this.l = f; this.da = g; this.s = k; this.v = l }; function Pc(a) { var b = 0, c = null, d = !0; return C.create({ next: function (e) { for (; b < a.length;) { c || (c = a[b].value, d = !0); var f = c.next(d ? 0 : e); d = !1; if (f.done) b++, c = null; else return f } return x } }) }; function Qc(a, b, c) { return Error("FORG0001: Cannot cast " + a + " to " + mb[b] + (c ? ", " + c : "")) } function Rc(a) { return Error("XPDY0002: " + a) } function Sc(a) { return Error("XPTY0004: " + a) } function Tc(a) { return Error("FOTY0013: Atomization is not supported for " + mb[a] + ".") } function Uc(a) { return Error("XPST0081: The prefix " + a + " could not be resolved.") }; function Yc(a, b) { ++ if (B(a.type, 46) || B(a.type, 19) || B(a.type, 0) || B(a.type, 4) || B(a.type, 3) || B(a.type, 6) || B(a.type, 5) || B(a.type, 2) || B(a.type, 23) || B(a.type, 1)) return C.create(a); var c = b.h; if (B(a.type, 53)) { ++ var d = a.value; if (2 === d.node.nodeType || 3 === d.node.nodeType) return C.create(w(Qb(c, d), 19)); if (8 === d.node.nodeType || 7 === d.node.nodeType) return C.create(w(Qb(c, d), 1)); var e = []; (function k(g) { if (8 !== d.node.nodeType && 7 !== d.node.nodeType) { var l = g.nodeType; 3 === l || 4 === l ? e.push(c.getData(g)) : 1 !== l && 9 !== l || c.getChildNodes(g).forEach(function (m) { k(m) }) } })(d.node); ++ return C.create(w(e.join(""), 19)) ++ } if (B(a.type, 60) && !B(a.type, 62)) throw Tc(a.type); if (B(a.type, 62)) return Pc(a.P.map(function (f) { return Zc(f(), b) })); throw Error("Atomizing " + a.type + " is not implemented."); ++ } function Zc(a, b) { var c = !1, d = a.value, e = null; return C.create({ next: function () { for (; !c;) { if (!e) { var f = d.next(0); if (f.done) { c = !0; break } e = Yc(f.value, b).value } f = e.next(0); if (f.done) e = null; else return f } return x } }) }; function $c(a) { for (a = bb[a]; a && 0 !== a.D;)a = a.parent; return a ? a.type : null } function ad(a, b) { b = bb[b]; var c = b.Na; if (!c || !c.whiteSpace) return b.parent ? ad(a, b.parent.type) : a; switch (b.Na.whiteSpace) { case "replace": return a.replace(/[\u0009\u000A\u000D]/g, " "); case "collapse": return a.replace(/[\u0009\u000A\u000D]/g, " ").replace(/ {2,}/g, " ").replace(/^ | $/g, "") }return a } function bd(a, b) { for (b = bb[b]; b && null === b.kb;) { if (2 === b.D || 3 === b.D) return !0; b = b.parent } return b ? b.kb(a) : !0 } ++ function cd(a, b) { for (; a;) { if (a.Pa && a.Pa[b]) return a.Pa[b]; a = a.parent } return function () { return !0 } } function dd(a, b) { for (var c = bb[b]; c;) { if (c.Na && !Object.keys(c.Na).every(function (d) { if ("whiteSpace" === d) return !0; var e = cd(c, d); return e ? e(a, c.Na[d]) : !0 })) return !1; c = c.parent } return !0 } function ed(a) { return a ? 2 === a.g || 0 === a.g : !0 }; function fd(a) { return a(1) || a(19) ? function (b) { return { u: !0, value: w(b, 20) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:anyURI or any of its derived types.") } } }; function gd(a) { return a(22) ? function (b) { for (var c = "", d = 0; d < b.length; d += 2)c += String.fromCharCode(parseInt(b.substr(d, 2), 16)); return { u: !0, value: w(btoa(c), 21) } } : a(1) || a(19) ? function (b) { return { u: !0, value: w(b, 21) } } : function () { return { error: Error("XPTY0004: Casting not supported from given type to xs:base64Binary or any of its derived types."), u: !1 } } }; function hd(a) { return a(2) ? function (b) { return { u: !0, value: 0 === b || isNaN(b) ? db : cb } } : a(1) || a(19) ? function (b) { switch (b) { case "true": case "1": return { u: !0, value: cb }; case "false": case "0": return { u: !0, value: db }; default: return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:boolean or any of its derived types.") } } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:boolean or any of its derived types.") } } }; function id(a) { return a(9) ? function (b) { return { u: !0, value: w(tc(b, 7), 7) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 7) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:date or any of its derived types.") } } }; function jd(a) { return a(7) ? function (b) { return { u: !0, value: w(tc(b, 9), 9) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 9) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:dateTime or any of its derived types.") } } }; function kd(a) { return a(18) && !a(16) ? function (b) { return { u: !0, value: w(b.Ja, 17) } } : a(16) ? function () { return { u: !0, value: w(jc("PT0.0S"), 17) } } : a(19) || a(1) ? function (b) { var c = jc(b); return c ? { u: !0, value: w(c, 17) } : { u: !1, error: Error("FORG0001: Can not cast " + b + " to xs:dayTimeDuration") } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:dayTimeDuration or any of its derived types.") } } }; function ld(a) { ++ return a(5) ? function (b) { return { u: !0, value: w(b, 4) } } : a(6) || a(3) ? function (b) { return isNaN(b) || !isFinite(b) ? { u: !1, error: Error("FOCA0002: Can not cast " + b + " to xs:decimal") } : Math.abs(b) > Number.MAX_VALUE ? { u: !1, error: Error("FOAR0002: Can not cast " + b + " to xs:decimal, it is out of bounds for JavaScript numbers") } : { u: !0, value: w(b, 4) } } : a(0) ? function (b) { return { u: !0, value: w(b ? 1 : 0, 4) } } : a(1) || a(19) ? function (b) { ++ var c = parseFloat(b); return !isNaN(c) || isFinite(c) ? { u: !0, value: w(c, 4) } : { ++ u: !1, error: Error("FORG0001: Can not cast " + ++ b + " to xs:decimal") ++ } ++ } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:decimal or any of its derived types.") } } ++ }; function md(a, b) { ++ return a(2) ? function (c) { return { u: !0, value: c } } : a(0) ? function (c) { return { u: !0, value: c ? 1 : 0 } } : a(1) || a(19) ? function (c) { switch (c) { case "NaN": return { u: !0, value: NaN }; case "INF": case "+INF": return { u: !0, value: Infinity }; case "-INF": return { u: !0, value: -Infinity }; case "0": case "+0": return { u: !0, value: 0 }; case "-0": return { u: !0, value: -0 } }var d = parseFloat(c); return isNaN(d) ? { u: !1, error: Qc(c, b) } : { u: !0, value: d } } : function () { ++ return { ++ u: !1, error: Error("XPTY0004: Casting not supported from given type to " + b + ++ " or any of its derived types.") ++ } ++ } ++ }; function nd(a) { var b = md(a, 3); return function (c) { c = b(c); return c.u ? { u: !0, value: w(c.value, 3) } : c } }; function od(a) { if (a > Number.MAX_SAFE_INTEGER || a < Number.MIN_SAFE_INTEGER) throw Error("FODT0002: Number of months given to construct YearMonthDuration overflows MAX_SAFE_INTEGER or MIN_SAFE_INTEGER"); this.ga = a } v(od, ec); h = od.prototype; h.cb = function () { var a = this.ga % 12; return 0 === a ? 0 : a }; h.eb = function () { return this.ga }; h.gb = function () { return Math.trunc(this.ga / 12) }; h.qa = function () { return Object.is(-0, this.ga) ? !1 : 0 <= this.ga }; h.toString = function () { return (this.qa() ? "P" : "-P") + pd(this) }; ++ function pd(a) { var b = Math.abs(a.gb()); a = Math.abs(a.cb()); return (b ? b + "Y" : "") + (a ? a + "M" : "") || "0M" } function qd(a) { var b = /^(-)?P(\d+Y)?(\d+M)?(\d+D)?(?:T(\d+H)?(\d+M)?(\d+(\.\d*)?S)?)?$/.exec(a); if (b) { a = !b[1]; b = 12 * (b[2] ? parseInt(b[2], 10) : 0) + (b[3] ? parseInt(b[3], 10) : 0); if (b > Number.MAX_SAFE_INTEGER || !Number.isFinite(b)) throw Error("FODT0002: Value overflow while constructing xs:yearMonthDuration"); a = new od(a || 0 === b ? b : -b) } else a = null; return a } ++ function rd(a, b) { if (isNaN(b)) throw Error("FOCA0005: Cannot multiply xs:yearMonthDuration by NaN"); a = Math.round(a.ga * b); if (a > Number.MAX_SAFE_INTEGER || !Number.isFinite(a)) throw Error("FODT0002: Value overflow while constructing xs:yearMonthDuration"); return new od(a < Number.MIN_SAFE_INTEGER || 0 === a ? 0 : a) }; function sd(a, b) { this.Wa = a; this.Ja = b } v(sd, ec); h = sd.prototype; h.bb = function () { return this.Ja.bb() }; h.getHours = function () { return this.Ja.getHours() }; h.getMinutes = function () { return this.Ja.getMinutes() }; h.cb = function () { return this.Wa.cb() }; h.eb = function () { return this.Wa.eb() }; h.fb = function () { return this.Ja.fb() }; h.getSeconds = function () { return this.Ja.getSeconds() }; h.gb = function () { return this.Wa.gb() }; h.qa = function () { return this.Wa.qa() && this.Ja.qa() }; ++ h.toString = function () { var a = this.qa() ? "P" : "-P", b = pd(this.Wa), c = hc(this.Ja); return "0M" === b ? a + c : "T0S" === c ? a + b : a + b + c }; function td(a) { return a(16) ? function (b) { return { u: !0, value: w(new sd(b, new gc(b.qa() ? 0 : -0)), 18) } } : a(17) ? function (b) { b = new sd(new od(b.qa() ? 0 : -0), b); return { u: !0, value: w(b, 18) } } : a(18) ? function (b) { return { u: !0, value: w(b, 18) } } : a(19) || a(1) ? function (b) { var c; return c = new sd(qd(b), jc(b)), { u: !0, value: w(c, 18) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:duration or any of its derived types.") } } }; function ud(a) { var b = md(a, 6); return function (c) { c = b(c); return c.u ? { u: !0, value: w(c.value, 6) } : c } }; function vd(a) { return a(7) || a(9) ? function (b) { return { u: !0, value: w(tc(b, 15), 15) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 15) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:gDay or any of its derived types.") } } }; function wd(a) { return a(7) || a(9) ? function (b) { return { u: !0, value: w(tc(b, 14), 14) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 14) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:gMonth or any of its derived types.") } } }; function xd(a) { return a(7) || a(9) ? function (b) { return { u: !0, value: w(tc(b, 13), 13) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 13) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:gMonthDay or any of its derived types.") } } }; function yd(a) { return a(7) || a(9) ? function (b) { return { u: !0, value: w(tc(b, 12), 12) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 12) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:gYear or any of its derived types.") } } }; function zd(a) { return a(7) || a(9) ? function (b) { return { u: !0, value: w(tc(b, 11), 11) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 11) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:gYearMonth or any of its derived types.") } } }; function Ad(a) { return a(21) ? function (b) { b = atob(b); for (var c = "", d = 0, e = b.length; d < e; d++)c += Number(b.charCodeAt(d)).toString(16); return { u: !0, value: w(c.toUpperCase(), 22) } } : a(1) || a(19) ? function (b) { return { u: !0, value: w(b, 22) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:hexBinary or any of its derived types.") } } }; function Bd(a) { ++ return a(0) ? function (b) { return { u: !0, value: w(b ? 1 : 0, 5) } } : a(2) ? function (b) { var c = Math.trunc(b); return !isFinite(c) || isNaN(c) ? { u: !1, error: Error("FOCA0002: can not cast " + b + " to xs:integer") } : Number.isSafeInteger(c) ? { u: !0, value: w(c, 5) } : { u: !1, error: Error("FOAR0002: can not cast " + b + " to xs:integer, it is out of bounds for JavaScript numbers.") } } : a(1) || a(19) ? function (b) { ++ var c = parseInt(b, 10); return isNaN(c) ? { u: !1, error: Qc(b, 5) } : Number.isSafeInteger(c) ? { u: !0, value: w(c, 5) } : { ++ u: !1, error: Error("FOCA0003: can not cast " + ++ b + " to xs:integer, it is out of bounds for JavaScript numbers.") ++ } ++ } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:integer or any of its derived types.") } } ++ }; var Cd = [3, 6, 4, 5]; function Dd(a) { var b = Ed; return function (c) { for (var d = p(Cd), e = d.next(); !e.done; e = d.next())if (e = b(a, e.value)(c), e.u) return e; return { u: !1, error: Error('XPTY0004: Casting not supported from "' + c + '" given type to xs:numeric or any of its derived types.') } } }; function Fd(a) { ++ if (a(1) || a(19)) return function (b) { return { u: !0, value: b + "" } }; if (a(20)) return function (b) { return { u: !0, value: b } }; if (a(23)) return function (b) { return { u: !0, value: b.prefix ? b.prefix + ":" + b.localName : b.localName } }; if (a(44)) return function (b) { return { u: !0, value: b.toString() } }; if (a(2)) { ++ if (a(5) || a(4)) return function (b) { return { u: !0, value: (b + "").replace("e", "E") } }; if (a(6) || a(3)) return function (b) { ++ return isNaN(b) ? { u: !0, value: "NaN" } : isFinite(b) ? Object.is(b, -0) ? { u: !0, value: "-0" } : { ++ u: !0, value: (b + "").replace("e", ++ "E").replace("E+", "E") ++ } : { u: !0, value: (0 > b ? "-" : "") + "INF" } ++ } ++ } return a(9) || a(7) || a(8) || a(15) || a(14) || a(13) || a(12) || a(11) ? function (b) { return { u: !0, value: b.toString() } } : a(16) ? function (b) { return { u: !0, value: b.toString() } } : a(17) ? function (b) { return { u: !0, value: b.toString() } } : a(18) ? function (b) { return { u: !0, value: b.toString() } } : a(22) ? function (b) { return { u: !0, value: b.toUpperCase() } } : function (b) { return { u: !0, value: b + "" } } ++ }; function Hd(a) { var b = Fd(a); return function (c) { c = b(c); return c.u ? { u: !0, value: w(c.value, 1) } : c } }; function Id(a) { return a(9) ? function (b) { return { u: !0, value: w(tc(b, 8), 8) } } : a(19) || a(1) ? function (b) { return { u: !0, value: w(sc(b), 8) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:time or any of its derived types.") } } }; function Jd(a) { var b = Fd(a); return function (c) { c = b(c); return c.u ? { u: !0, value: w(c.value, 19) } : c } }; function Kd(a) { return a(18) && !a(17) ? function (b) { return { u: !0, value: w(b.Wa, 16) } } : a(17) ? function () { return { u: !0, value: w(qd("P0M"), 16) } } : a(19) || a(1) ? function (b) { var c = qd(b); return c ? { u: !0, value: w(c, 16) } : { u: !1, error: Qc(b, 16) } } : function () { return { u: !1, error: Error("XPTY0004: Casting not supported from given type to xs:yearMonthDuration or any of its derived types.") } } }; var Ld = [2, 5, 17, 16]; ++ function Ed(a, b) { ++ function c(d) { return B(a, d) } if (39 === b) return function () { return { u: !1, error: Error("FORG0001: Casting to xs:error is always invalid.") } }; switch (b) { ++ case 19: return Jd(c); case 1: return Hd(c); case 6: return ud(c); case 3: return nd(c); case 4: return ld(c); case 5: return Bd(c); case 2: return Dd(a); case 18: return td(c); case 16: return Kd(c); case 17: return kd(c); case 9: return jd(c); case 8: return Id(c); case 7: return id(c); case 11: return zd(c); case 12: return yd(c); case 13: return xd(c); case 15: return vd(c); ++ case 14: return wd(c); case 0: return hd(c); case 21: return gd(c); case 22: return Ad(c); case 20: return fd(c); case 23: throw Error("Casting to xs:QName is not implemented."); ++ }return function () { return { u: !1, error: Error("XPTY0004: Casting not supported from " + a + " to " + b + ".") } } ++ } var Md = Object.create(null); ++ function Nd(a, b) { ++ if (19 === a && 1 === b) return function (f) { return { u: !0, value: w(f, 1) } }; if (44 === b) return function () { return { u: !1, error: Error("XPST0080: Casting to xs:NOTATION is not permitted.") } }; if (39 === b) return function () { return { u: !1, error: Error("FORG0001: Casting to xs:error is not permitted.") } }; if (45 === a || 45 === b) return function () { return { u: !1, error: Error("XPST0080: Casting from or to xs:anySimpleType is not permitted.") } }; if (46 === a || 46 === b) return function () { return { u: !1, error: Error("XPST0080: Casting from or to xs:anyAtomicType is not permitted.") } }; ++ if (B(a, 60) && 1 === b) return function () { return { u: !1, error: Error("FOTY0014: Casting from function item to xs:string is not permitted.") } }; if (a === b) return function (f) { return { u: !0, value: { type: b, value: f } } }; var c = Ld.includes(a) ? a : $c(a), d = Ld.includes(b) ? b : $c(b); if (null === d || null === c) return function () { return { u: !1, error: Error("XPST0081: Can not cast: type " + (d ? mb[a] : mb[b]) + " is unknown.") } }; var e = []; 1 !== c && 19 !== c || e.push(function (f) { var g = ad(f, b); return bd(g, b) ? { u: !0, value: g } : { u: !1, error: Qc(f, b, "pattern validation failed.") } }); ++ c !== d && (e.push(Ed(c, d)), e.push(function (f) { return { u: !0, value: f.value } })); 19 !== d && 1 !== d || e.push(function (f) { return bd(f, b) ? { u: !0, value: f } : { u: !1, error: Qc(f, b, "pattern validation failed.") } }); e.push(function (f) { return dd(f, b) ? { u: !0, value: f } : { u: !1, error: Qc(f, b, "pattern validation failed.") } }); e.push(function (f) { return { u: !0, value: { type: b, value: f } } }); return function (f) { f = { u: !0, value: f }; for (var g = 0, k = e.length; g < k && (f = e[g](f.value), !1 !== f.u); ++g); return f } ++ } ++ function Od(a, b) { var c = a.type + 1E4 * b, d = Md[c]; d || (d = Md[c] = Nd(a.type, b)); return d.call(void 0, a.value, b) }; function Pd(a, b) { a = Od(a, b); if (!0 === a.u) return a.value; throw a.error; }; function Qd(a) { var b = !1; return { next: function () { if (b) return x; b = !0; return y(a) } } }; function Rd(a, b) { return a === b ? !0 : a && b && a.offset === b.offset && a.parent === b.parent ? Rd(a.F, b.F) : !1 } function Sd(a, b) { return a === b || a.node === b.node && Rd(a.F, b.F) ? !0 : !1 }; function Td(a, b, c) { var d = Vb(a, b, null); a = Pb(a, d, null); d = 0; for (var e = a.length; d < e; ++d) { var f = a[d]; if (Sd(f, b)) return -1; if (Sd(f, c)) return 1 } } function Ud(a, b) { for (var c = []; b; b = Vb(a, b, null))c.unshift(b); return c } function Vd(a, b) { for (var c = []; b; b = a.getParentNode(b, null))c.unshift(b); return c } ++ function Wd(a, b, c, d) { ++ if (c.F || d.F || Kb(c.node) || Kb(d.node)) { if (Sd(c, d)) return 0; c = Ud(b, c); d = Ud(b, d); var e = c[0], f = d[0]; if (!Sd(e, f)) return b = a.findIndex(function (q) { return Sd(q, e) }), c = a.findIndex(function (q) { return Sd(q, f) }), -1 === b && (b = a.push(e)), -1 === c && (c = a.push(f)), b - c; a = 1; for (var g = Math.min(c.length, d.length); a < g && Sd(c[a], d[a]); ++a); return c[a] ? d[a] ? Td(b, c[a], d[a]) : 1 : -1 } c = c.node; d = d.node; if (c === d) return 0; c = Vd(b, c); d = Vd(b, d); if (c[0] !== d[0]) { ++ var k = { node: c[0], F: null }, l = { node: d[0], F: null }; b = a.findIndex(function (q) { ++ return Sd(q, ++ k) ++ }); c = a.findIndex(function (q) { return Sd(q, l) }); -1 === b && (b = a.push(k)); -1 === c && (c = a.push(l)); return b - c ++ } g = 1; for (a = Math.min(c.length, d.length); g < a && c[g] === d[g]; ++g); a = c[g]; c = d[g]; if (!a) return -1; if (!c) return 1; b = b.getChildNodes(d[g - 1], null); d = 0; for (g = b.length; d < g; ++d) { var m = b[d]; if (m === a) return -1; if (m === c) return 1 } ++ } ++ function Xd(a, b, c, d) { var e = B(c.type, 47), f = B(d.type, 47); if (e && !f) { if (c = Vb(b, c.value), d = d.value, Sd(c, d)) return 1 } else if (f && !e) { if (c = c.value, d = Vb(b, d.value), Sd(c, d)) return -1 } else if (e && f) { if (Sd(Vb(b, d.value), Vb(b, c.value))) return c.value.node.localName > d.value.node.localName ? 1 : -1; c = Vb(b, c.value); d = Vb(b, d.value) } else c = c.value, d = d.value; return Wd(a, b, c, d) } function Yd(a, b, c) { return Xd(a.o, a, b, c) } ++ function Zd(a, b) { return $d(b, function (c, d) { return Xd(a.o, a, c, d) }).filter(function (c, d, e) { return 0 === d ? !0 : !Sd(c.value, e[d - 1].value) }) } function ae(a, b) { return a < b ? -1 : 0 } function $d(a, b) { b = void 0 === b ? ae : b; if (1 >= a.length) return a; var c = Math.floor(a.length / 2), d = $d(a.slice(0, c), b); a = $d(a.slice(c), b); for (c = []; d.length && a.length;)0 > b(d[0], a[0]) ? c.push(d.shift()) : c.push(a.shift()); return c.concat(d.concat(a)) }; var be = xspattern; function ce(a, b) { if (B(a.type, 2)) { if (B(a.type, 6)) return 3 === b ? w(a.value, 3) : null; if (B(a.type, 4)) { if (6 === b) return w(a.value, 6); if (3 === b) return w(a.value, 3) } return null } return B(a.type, 20) && 1 === b ? w(a.value, 1) : null }; function de(a, b, c, d, e) { if (B(a.type, b.type)) return a; B(b.type, 46) && B(a.type, 53) && (a = Yc(a, c).first()); if (B(a.type, b.type) || 46 === b.type) return a; if (B(a.type, 19)) { c = Pd(a, b.type); if (!c) throw Error("XPTY0004 Unable to convert " + (e ? "return" : "argument") + " of type " + mb[a.type] + " to type " + ob(b) + " while calling " + d); return c } c = ce(a, b.type); if (!c) throw Error("XPTY0004 Unable to cast " + (e ? "return" : "argument") + " of type " + mb[a.type] + " to type " + ob(b) + " while calling " + d); return c } ++ function ee(a) { switch (a) { case 2: return "*"; case 1: return "+"; case 0: return "?"; case 3: return "" } } ++ function fe(a, b, c, d, e) { ++ return 0 === a.g ? b.Z({ default: function () { return b.map(function (f) { return de(f, a, c, d, e) }) }, multiple: function () { throw Error("XPTY0004: Multiplicity of " + (e ? "function return value" : "function argument") + " of type " + mb[a.type] + ee(a.g) + " for " + d + ' is incorrect. Expected "?", but got "+".'); } }) : 1 === a.g ? b.Z({ ++ empty: function () { ++ throw Error("XPTY0004: Multiplicity of " + (e ? "function return value" : "function argument") + " of type " + mb[a.type] + ee(a.g) + " for " + d + ' is incorrect. Expected "+", but got "empty-sequence()"'); ++ }, default: function () { return b.map(function (f) { return de(f, a, c, d, e) }) } ++ }) : 2 === a.g ? b.map(function (f) { return de(f, a, c, d, e) }) : b.Z({ m: function () { return b.map(function (f) { return de(f, a, c, d, e) }) }, default: function () { throw Error("XPTY0004: Multiplicity of " + (e ? "function return value" : "function argument") + " of type " + mb[a.type] + ee(a.g) + " for " + d + " is incorrect. Expected exactly one"); } }) ++ }; function ge(a, b) { return B(a, 5) ? w(b, 5) : B(a, 6) ? w(b, 6) : B(a, 3) ? w(b, 3) : w(b, 4) } var he = [{ oa: "M", ma: 1E3 }, { oa: "CM", ma: 900 }, { oa: "D", ma: 500 }, { oa: "CD", ma: 400 }, { oa: "C", ma: 100 }, { oa: "XC", ma: 90 }, { oa: "L", ma: 50 }, { oa: "XL", ma: 40 }, { oa: "X", ma: 10 }, { oa: "IX", ma: 9 }, { oa: "V", ma: 5 }, { oa: "IV", ma: 4 }, { oa: "I", ma: 1 }]; function ie(a, b) { var c = parseInt(a, 10); a = 0 > c; c = Math.abs(c); if (!c) return "-"; var d = he.reduce(function (e, f) { var g = Math.floor(c / f.ma); c -= g * f.ma; return e + f.oa.repeat(g) }, ""); b && (d = d.toLowerCase()); a && (d = "-" + d); return d } ++ var je = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); function ke(a, b) { a = parseInt(a, 10); var c = 0 > a; a = Math.abs(a); if (!a) return "-"; for (var d = "", e; 0 < a;)e = (a - 1) % je.length, d = je[e] + d, a = (a - e) / je.length | 0; b && (d = d.toLowerCase()); c && (d = "-" + d); return d } function le(a) { if (Math.floor(a) === a || isNaN(a)) return 0; a = /\d+(?:\.(\d*))?(?:[Ee](-)?(\d+))*/.exec("" + a); var b = a[1] ? a[1].length : 0; if (a[3]) { if (a[2]) return b + parseInt(a[3], 10); a = b - parseInt(a[3], 10); return 0 > a ? 0 : a } return b } ++ function me(a, b, c) { return b && 0 === a * c % 1 % .5 ? 0 === Math.floor(a * c) % 2 ? Math.floor(a * c) / c : Math.ceil(a * c) / c : Math.round(a * c) / c } ++ function ne(a, b, c, d, e, f) { var g = !1; return C.create({ next: function () { if (g) return x; var k = e.first(); if (!k) return g = !0, x; if ((B(k.type, 6) || B(k.type, 3)) && (0 === k.value || isNaN(k.value) || Infinity === k.value || -Infinity === k.value)) return g = !0, y(k); var l; f ? l = f.first().value : l = 0; g = !0; if (le(k.value) < l) return y(k); var m = [5, 4, 3, 6].find(function (u) { return B(k.type, u) }), q = Pd(k, 4); l = me(q.value, a, Math.pow(10, l)); switch (m) { case 4: return y(w(l, 4)); case 3: return y(w(l, 3)); case 6: return y(w(l, 6)); case 5: return y(w(l, 5)) } } }) } ++ function oe(a, b, c, d) { return Zc(d, b).Z({ empty: function () { return C.m(w(NaN, 3)) }, m: function () { var e = Od(d.first(), 3); return e.u ? C.m(e.value) : C.m(w(NaN, 3)) }, multiple: function () { throw Error("fn:number may only be called with zero or one values"); } }) } function pe(a) { for (var b = 5381, c = 0; c < a.length; ++c)b = 33 * b + a.charCodeAt(c), b %= Number.MAX_SAFE_INTEGER; return b } ++ function qe(a, b, c, d) { ++ function e(f) { ++ function g(k, l, m, q) { if (q.G() || q.wa()) return q; k = q.O(); l = f; for (m = k.length - 1; 1 < m; m--) { l = Mc(a, l).zb; q = l % m; var u = k[q]; k[q] = k[m]; k[m] = u } return C.create(k) } return C.m(new dc([{ key: w("number", 1), value: function () { return C.m(w(Mc(a, f).jc, 3)) } }, { key: w("next", 1), value: function () { return C.m(new Ab({ value: function () { return e(Mc(a, f).zb) }, Sa: !0, localName: "", namespaceURI: "", j: [], arity: 0, i: { type: 61, g: 3 } })) } }, { ++ key: w("permute", 1), value: function () { ++ return C.m(new Ab({ ++ value: g, Sa: !0, ++ localName: "", namespaceURI: "", j: [{ type: 59, g: 2 }], arity: 1, i: { type: 59, g: 2 } ++ })) ++ } ++ }])) ++ } d = void 0 === d ? C.empty() : d; b = d.G() ? Mc(a) : Mc(a, pe(Pd(d.first(), 1).value)); return e(b.zb) ++ } ++ var re = [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "abs", j: [{ type: 2, g: 0 }], i: { type: 2, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return ge(e.type, Math.abs(e.value)) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "format-integer", j: [{ type: 5, g: 0 }, { type: 1, g: 3 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d, e) { ++ a = d.first(); e = e.first(); if (d.G()) return C.m(w("", 1)); switch (e.value) { ++ case "I": return d = ie(a.value), C.m(w(d, 1)); case "i": return d = ie(a.value, ++ !0), C.m(w(d, 1)); case "A": return C.m(w(ke(a.value), 1)); case "a": return C.m(w(ke(a.value, !0), 1)); default: throw Error("Picture: " + e.value + ' is not implemented yet. The supported picture strings are "A", "a", "I", and "i"'); ++ } ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "ceiling", j: [{ type: 2, g: 0 }], i: { type: 2, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return ge(e.type, Math.ceil(e.value)) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "floor", ++ j: [{ type: 2, g: 0 }], i: { type: 2, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return ge(e.type, Math.floor(e.value)) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "round", j: [{ type: 2, g: 0 }], i: { type: 2, g: 0 }, callFunction: ne.bind(null, !1) }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "round", j: [{ type: 2, g: 0 }, { type: 5, g: 3 }], i: { type: 2, g: 0 }, callFunction: ne.bind(null, !1) }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "round-half-to-even", ++ j: [{ type: 2, g: 0 }], i: { type: 2, g: 0 }, callFunction: ne.bind(null, !0) ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "round-half-to-even", j: [{ type: 2, g: 0 }, { type: 5, g: 3 }], i: { type: 2, g: 0 }, callFunction: ne.bind(null, !0) }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "number", j: [{ type: 46, g: 0 }], i: { type: 3, g: 3 }, callFunction: oe }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "number", j: [], i: { type: 3, g: 3 }, callFunction: function (a, b, c) { ++ var d = a.N && fe({ type: 46, g: 0 }, C.m(a.N), ++ b, "fn:number", !1); if (!d) throw Rc("fn:number needs an atomizable context item."); return oe(a, b, c, d) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "random-number-generator", j: [], i: { type: 61, g: 3 }, callFunction: qe }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "random-number-generator", j: [{ type: 46, g: 0 }], i: { type: 61, g: 3 }, callFunction: qe }]; function se() { throw Error("FOCH0002: No collations are supported"); } function te(a, b, c, d) { if (null === b.N) throw Rc("The function which was called depends on dynamic context, which is absent."); return a(b, c, d, C.m(b.N)) } function ue(a, b, c, d) { return d.Z({ empty: function () { return C.m(w("", 1)) }, default: function () { return d.map(function (e) { if (B(e.type, 53)) { var f = Yc(e, b).first(); return B(e.type, 47) ? Pd(f, 1) : f } return Pd(e, 1) }) } }) } ++ function ve(a, b, c, d, e) { return ac([e], function (f) { var g = p(f).next().value; return Zc(d, b).M(function (k) { k = k.map(function (l) { return Pd(l, 1).value }).join(g.value); return C.m(w(k, 1)) }) }) } function we(a, b, c, d) { if (d.G()) return C.m(w(0, 5)); a = d.first().value; return C.m(w(Array.from(a).length, 5)) } ++ function xe(a, b, c, d, e, f) { var g = ne(!1, a, b, c, e, null), k = null !== f ? ne(!1, a, b, c, f, null) : null, l = !1, m = null, q = null, u = null; return C.create({ next: function () { if (l) return x; if (!m && (m = d.first(), null === m)) return l = !0, y(w("", 1)); q || (q = g.first()); !u && f && (u = null, u = k.first()); l = !0; return y(w(Array.from(m.value).slice(Math.max(q.value - 1, 0), f ? q.value + u.value - 1 : void 0).join(""), 1)) } }) } ++ function ye(a, b, c, d, e) { if (d.G() || 0 === d.first().value.length) return C.empty(); a = d.first().value; e = e.first().value; e = ze(e); return C.create(a.split(e).map(function (f) { return w(f, 1) })) } function Ae(a, b, c, d) { if (d.G()) return C.m(w("", 1)); a = d.first().value.trim(); return C.m(w(a.replace(/\s+/g, " "), 1)) } var Be = new Map, Ce = new Map; ++ function ze(a) { if (Ce.has(a)) return Ce.get(a); try { var b = new RegExp(a, "g") } catch (c) { throw Error("FORX0002: " + c); } if (b.test("")) throw Error("FORX0003: the pattern " + a + " matches the zero length string"); Ce.set(a, b); return b } ++ var De = [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "compare", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d, e) { if (d.G() || e.G()) return C.empty(); a = d.first().value; e = e.first().value; return a > e ? C.m(w(1, 5)) : a < e ? C.m(w(-1, 5)) : C.m(w(0, 5)) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "compare", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }, { type: 1, g: 3 }], i: { type: 5, g: 0 }, callFunction: se }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "concat", ++ j: [{ type: 46, g: 0 }, { type: 46, g: 0 }, 4], i: { type: 1, g: 3 }, callFunction: function (a, b, c) { var d = Aa.apply(3, arguments); d = d.map(function (e) { return Zc(e, b).M(function (f) { return C.m(w(f.map(function (g) { return null === g ? "" : Pd(g, 1).value }).join(""), 1)) }) }); return ac(d, function (e) { return C.m(w(e.map(function (f) { return f.value }).join(""), 1)) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "contains", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 0, g: 3 }, callFunction: se }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "contains", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d, e) { a = d.G() ? "" : d.first().value; e = e.G() ? "" : e.first().value; return 0 === e.length ? C.ba() : 0 === a.length ? C.W() : a.includes(e) ? C.ba() : C.W() } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "ends-with", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d, e) { ++ a = e.G() ? "" : e.first().value; if (0 === a.length) return C.ba(); d = d.G() ? "" : d.first().value; return 0 === d.length ? C.W() : d.endsWith(a) ? ++ C.ba() : C.W() ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "ends-with", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }, { type: 1, g: 3 }], i: { type: 0, g: 3 }, callFunction: se }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "normalize-space", j: [{ type: 1, g: 0 }], i: { type: 1, g: 3 }, callFunction: Ae }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "normalize-space", j: [], i: { type: 1, g: 3 }, callFunction: te.bind(null, function (a, b, c, d) { return Ae(a, b, c, ue(a, b, c, d)) }) }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "starts-with", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d, e) { a = e.G() ? "" : e.first().value; if (0 === a.length) return C.ba(); d = d.G() ? "" : d.first().value; return 0 === d.length ? C.W() : d.startsWith(a) ? C.ba() : C.W() } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "starts-with", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }, { type: 1, g: 3 }], i: { type: 0, g: 3 }, callFunction: se }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "string", j: [{ type: 59, g: 0 }], i: { type: 1, g: 3 }, ++ callFunction: ue ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "string", j: [], i: { type: 1, g: 3 }, callFunction: te.bind(null, ue) }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "substring-before", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d, e) { a = d.G() ? "" : d.first().value; e = e.G() ? "" : e.first().value; if ("" === e) return C.m(w("", 1)); e = a.indexOf(e); return -1 === e ? C.m(w("", 1)) : C.m(w(a.substring(0, e), 1)) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "substring-after", j: [{ type: 1, g: 0 }, { type: 1, g: 0 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d, e) { a = d.G() ? "" : d.first().value; e = e.G() ? "" : e.first().value; if ("" === e) return C.m(w(a, 1)); b = a.indexOf(e); return -1 === b ? C.m(w("", 1)) : C.m(w(a.substring(b + e.length), 1)) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "substring", j: [{ type: 1, g: 0 }, { type: 3, g: 3 }], i: { type: 1, g: 3 }, callFunction: xe }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "substring", j: [{ type: 1, g: 0 }, { ++ type: 3, ++ g: 3 ++ }, { type: 3, g: 3 }], i: { type: 1, g: 3 }, callFunction: xe ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "upper-case", j: [{ type: 1, g: 0 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d) { return d.G() ? C.m(w("", 1)) : d.map(function (e) { return w(e.value.toUpperCase(), 1) }) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "lower-case", j: [{ type: 1, g: 0 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d) { return d.G() ? C.m(w("", 1)) : d.map(function (e) { return w(e.value.toLowerCase(), 1) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "string-join", j: [{ type: 46, g: 2 }, { type: 1, g: 3 }], i: { type: 1, g: 3 }, callFunction: ve ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "string-join", j: [{ type: 46, g: 2 }], i: { type: 1, g: 3 }, callFunction: function (a, b, c, d) { return ve(a, b, c, d, C.m(w("", 1))) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "string-length", j: [{ type: 1, g: 0 }], i: { type: 5, g: 3 }, callFunction: we }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "string-length", j: [], i: { type: 5, g: 3 }, callFunction: te.bind(null, ++ function (a, b, c, d) { return we(a, b, c, ue(a, b, c, d)) }) ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "tokenize", j: [{ type: 1, g: 0 }, { type: 1, g: 3 }, { type: 1, g: 3 }], i: { type: 1, g: 2 }, callFunction: function () { throw Error("Not implemented: Using flags in tokenize is not supported"); } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "tokenize", j: [{ type: 1, g: 0 }, { type: 1, g: 3 }], i: { type: 1, g: 2 }, callFunction: ye }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "tokenize", j: [{ ++ type: 1, ++ g: 0 ++ }], i: { type: 1, g: 2 }, callFunction: function (a, b, c, d) { return ye(a, b, c, Ae(a, b, c, d), C.m(w(" ", 1))) } ++ }, { ++ j: [{ type: 1, g: 0 }, { type: 1, g: 3 }, { type: 1, g: 3 }], callFunction: function (a, b, c, d, e, f) { return ac([d, e, f], function (g) { var k = p(g), l = k.next().value; g = k.next().value; k = k.next().value; l = Array.from(l ? l.value : ""); var m = Array.from(g.value), q = Array.from(k.value); g = l.map(function (u) { if (m.includes(u)) { if (u = m.indexOf(u), u <= q.length) return q[u] } else return u }); return C.m(w(g.join(""), 1)) }) }, localName: "translate", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 1, g: 3 } ++ }, { j: [{ type: 5, g: 2 }], callFunction: function (a, b, c, d) { return d.M(function (e) { e = e.map(function (f) { f = f.value; if (9 === f || 10 === f || 13 === f || 32 <= f && 55295 >= f || 57344 <= f && 65533 >= f || 65536 <= f && 1114111 >= f) return String.fromCodePoint(f); throw Error("FOCH0001"); }).join(""); return C.m(w(e, 1)) }) }, localName: "codepoints-to-string", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { ++ j: [{ type: 1, g: 0 }], callFunction: function (a, b, c, d) { ++ return ac([d], function (e) { ++ e = (e = p(e).next().value) ? e.value.split("") : ++ []; return 0 === e.length ? C.empty() : C.create(e.map(function (f) { return w(f.codePointAt(0), 5) })) ++ }) ++ }, localName: "string-to-codepoints", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 5, g: 2 } ++ }, { ++ j: [{ type: 1, g: 0 }], callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return null === e || 0 === e.value.length ? C.create(w("", 1)) : C.create(w(encodeURIComponent(e.value).replace(/[!'()*]/g, function (f) { return "%" + f.charCodeAt(0).toString(16).toUpperCase() }), 1)) }) }, localName: "encode-for-uri", ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } ++ }, { j: [{ type: 1, g: 0 }], callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return null === e || 0 === e.value.length ? C.create(w("", 1)) : C.create(w(e.value.replace(/([\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFEF "<>{}|\\^`/\n\u007f\u0080-\u009f]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g, function (f) { return encodeURI(f) }), 1)) }) }, localName: "iri-to-uri", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { ++ j: [{ ++ type: 1, ++ g: 0 ++ }, { type: 1, g: 0 }], callFunction: function (a, b, c, d, e) { return ac([d, e], function (f) { var g = p(f); f = g.next().value; g = g.next().value; if (null === f || null === g) return C.empty(); f = f.value; g = g.value; if (f.length !== g.length) return C.W(); f = f.split(""); g = g.split(""); for (var k = 0; k < f.length; k++)if (f[k].codePointAt(0) !== g[k].codePointAt(0)) return C.W(); return C.ba() }) }, localName: "codepoint-equal", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 0 } ++ }, { ++ j: [{ type: 1, g: 0 }, { type: 1, g: 3 }], callFunction: function (a, ++ b, c, d, e) { return ac([d, e], function (f) { var g = p(f); f = g.next().value; g = g.next().value; f = f ? f.value : ""; g = g.value; var k = Be.get(g); if (!k) { try { k = be.compile(g, { language: "xpath" }) } catch (l) { throw Error("FORX0002: " + l); } Be.set(g, k) } return k(f) ? C.ba() : C.W() }) }, localName: "matches", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } ++ }, { ++ j: [{ type: 1, g: 0 }, { type: 1, g: 3 }, { type: 1, g: 3 }], callFunction: function (a, b, c, d, e, f) { ++ return ac([d, e, f], function (g) { ++ var k = p(g); g = k.next().value; var l = k.next().value; k = k.next().value; ++ g = g ? g.value : ""; l = l.value; k = k.value; if (k.includes("$0")) throw Error("Using $0 in fn:replace to replace substrings with full matches is not supported."); k = k.split(/((?:\$\$)|(?:\\\$)|(?:\\\\))/).map(function (m) { switch (m) { case "\\$": return "$$"; case "\\\\": return "\\"; case "$$": throw Error('FORX0004: invalid replacement: "$$"'); default: return m } }).join(""); l = ze(l); g = g.replace(l, k); return C.m(w(g, 1)) ++ }) ++ }, localName: "replace", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } ++ }, { ++ j: [{ ++ type: 1, ++ g: 0 ++ }, { type: 1, g: 3 }, { type: 1, g: 3 }, { type: 1, g: 3 }], localName: "replace", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 }, callFunction: function () { throw Error("Not implemented: Using flags in replace is not supported"); } ++ }]; function Ee(a, b, c, d, e) { if (null === c.N) throw Rc("The function " + a + " depends on dynamic context, which is absent."); return b(c, d, e, C.m(c.N)) } function Fe(a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; if (null === e) return C.empty(); e = e.value; switch (e.node.nodeType) { case 1: case 2: return C.m(w(new zb(e.node.prefix, e.node.namespaceURI, e.node.localName), 23)); case 7: return C.m(w(new zb("", "", e.node.target), 23)); default: return C.empty() } }) } ++ function Ge(a, b, c, d) { return d.Z({ default: function () { return ue(a, b, c, Fe(a, b, c, d)) }, empty: function () { return C.m(w("", 1)) } }) } function He(a, b, c, d) { return Zc(d, b) } function Ie(a, b, c, d) { return ac([d], function (e) { e = (e = p(e).next().value) ? e.value : null; return null !== e && Sb(b.h, e, null) ? C.ba() : C.W() }) } ++ function Je(a, b, c, d) { ++ return ac([d], function (e) { ++ function f(m) { for (var q = 0, u = m; null !== u;)(m.node.nodeType !== u.node.nodeType ? 0 : 1 === u.node.nodeType ? u.node.localName === m.node.localName && u.node.namespaceURI === m.node.namespaceURI : 7 === u.node.nodeType ? u.node.target === m.node.target : 1) && q++, u = Wb(k, u, null); return q } var g = p(e).next().value; if (null === g) return C.empty(); var k = b.h; e = ""; for (g = g.value; null !== Vb(b.h, g, null); g = Vb(b.h, g, null))switch (g.node.nodeType) { ++ case 1: var l = g; e = "/Q{" + (l.node.namespaceURI || "") + "}" + ++ l.node.localName + "[" + f(l) + "]" + e; break; case 2: l = g; e = "/@" + (l.node.namespaceURI ? "Q{" + l.node.namespaceURI + "}" : "") + l.node.localName + e; break; case 3: e = "/text()[" + f(g) + "]" + e; break; case 7: l = g; e = "/processing-instruction(" + l.node.target + ")[" + f(l) + "]" + e; break; case 8: e = "/comment()[" + f(g) + "]" + e ++ }return 9 === g.node.nodeType ? C.create(w(e || "/", 1)) : C.create(w("Q{http://www.w3.org/2005/xpath-functions}root()" + e, 1)) ++ }) ++ } function Ke(a, b, c, d) { return d.map(function (e) { return w(e.value.node.namespaceURI || "", 20) }) } ++ function Le(a, b, c, d) { return d.Z({ default: function () { return d.map(function (e) { return 7 === e.value.node.nodeType ? w(e.value.node.target, 1) : w(e.value.node.localName || "", 1) }) }, empty: function () { return C.m(w("", 1)) } }) } function Me(a, b, c) { if (2 === b.node.nodeType) return Sd(b, c); for (; c;) { if (Sd(b, c)) return !0; if (9 === c.node.nodeType) break; c = Vb(a, c, null) } return !1 } ++ function Ne(a, b, c, d) { return d.map(function (e) { if (!B(e.type, 53)) throw Error("XPTY0004 Argument passed to fn:root() should be of the type node()"); for (e = e.value; e;) { var f = e; e = Vb(b.h, f, null) } return $b(f) }) } ++ var Oe = [{ j: [{ type: 53, g: 0 }], callFunction: Ge, localName: "name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { j: [], callFunction: Ee.bind(null, "name", Ge), localName: "name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { j: [{ type: 53, g: 3 }], callFunction: Ke, localName: "namespace-uri", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 20, g: 3 } }, { ++ j: [], callFunction: Ee.bind(null, "namespace-uri", Ke), localName: "namespace-uri", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 20, g: 3 } ++ }, { j: [{ type: 53, g: 2 }], callFunction: function (a, b, c, d) { return d.M(function (e) { if (!e.length) return C.empty(); e = Zd(b.h, e).reduceRight(function (f, g, k, l) { if (k === l.length - 1) return f.push(g), f; if (Me(b.h, g.value, f[0].value)) return f; f.unshift(g); return f }, []); return C.create(e) }) }, localName: "innermost", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 53, g: 2 } }, { ++ j: [{ type: 53, g: 2 }], callFunction: function (a, b, c, d) { ++ return d.M(function (e) { ++ if (!e.length) return C.empty(); e = Zd(b.h, e).reduce(function (f, ++ g, k) { if (0 === k) return f.push(g), f; if (Me(b.h, f[f.length - 1].value, g.value)) return f; f.push(g); return f }, []); return C.create(e) ++ }, 1) ++ }, localName: "outermost", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 53, g: 2 } ++ }, { j: [{ type: 53, g: 0 }], callFunction: Ie, localName: "has-children", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } }, { j: [], callFunction: Ee.bind(null, "has-children", Ie), localName: "has-children", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } }, { ++ j: [{ ++ type: 53, ++ g: 0 ++ }], callFunction: Je, localName: "path", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 0 } ++ }, { j: [], callFunction: Ee.bind(null, "path", Je), localName: "path", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 0 } }, { j: [{ type: 53, g: 0 }], callFunction: Fe, localName: "node-name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 23, g: 0 } }, { ++ j: [], callFunction: Ee.bind(null, "node-name", Fe), localName: "node-name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { ++ type: 23, ++ g: 0 ++ } ++ }, { j: [{ type: 53, g: 0 }], callFunction: Le, localName: "local-name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { j: [], callFunction: Ee.bind(null, "local-name", Le), localName: "local-name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } }, { j: [{ type: 53, g: 0 }], callFunction: Ne, localName: "root", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 53, g: 0 } }, { ++ j: [], callFunction: Ee.bind(null, "root", Ne), localName: "root", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 53, g: 0 } ++ }, { j: [], callFunction: Ee.bind(null, "data", He), localName: "data", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 2 } }, { j: [{ type: 59, g: 2 }], callFunction: He, localName: "data", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 2 } }]; function Pe(a, b) { var c = 0, d = a.length, e = !1, f = null; return { next: function () { if (!e) { for (; c < d;) { f || (f = b(a[c], c, a)); var g = f.next(0); f = null; if (g.value) c++; else return y(!1) } e = !0; return y(!0) } return x } } } function Qe(a) { a = a.node.nodeType; return 1 === a || 3 === a } ++ function Re(a, b) { ++ if ((B(a.type, 4) || B(a.type, 6)) && (B(b.type, 4) || B(b.type, 6))) { var c = Pd(a, 6), d = Pd(b, 6); return c.value === d.value || isNaN(a.value) && isNaN(b.value) } return (B(a.type, 4) || B(a.type, 6) || B(a.type, 3)) && (B(b.type, 4) || B(b.type, 6) || B(b.type, 3)) ? (c = Pd(a, 3), d = Pd(b, 3), c.value === d.value || isNaN(a.value) && isNaN(b.value)) : B(a.type, 23) && B(b.type, 23) ? a.value.namespaceURI === b.value.namespaceURI && a.value.localName === b.value.localName : (B(a.type, 9) || B(a.type, 7) || B(a.type, 8) || B(a.type, 11) || B(a.type, 12) || B(a.type, ++ 13) || B(a.type, 14) || B(a.type, 15)) && (B(b.type, 9) || B(b.type, 7) || B(b.type, 8) || B(b.type, 11) || B(b.type, 12) || B(b.type, 13) || B(b.type, 14) || B(b.type, 15)) ? wc(a.value, b.value) : (B(a.type, 16) || B(a.type, 17) || B(a.type, 18)) && (B(b.type, 16) || B(b.type, 17) || B(b.type, 17)) ? fc(a.value, b.value) : a.value === b.value ++ } function Se(a, b, c) { c = p([b, c].map(function (d) { return { type: 1, value: d.reduce(function (e, f) { return e += Yc(f, a).first().value }, "") } })); b = c.next().value; c = c.next().value; return y(Re(b, c)) } ++ function Te(a, b, c, d) { for (; a.value && B(a.value.type, 56);) { b.push(a.value); var e = Ub(d, a.value.value); a = c.next(0); if (e && 3 !== e.node.nodeType) break } return a } ++ function Ue(a, b, c, d, e) { var f = b.h, g = d.value, k = e.value, l = null, m = null, q = null, u, z = [], A = []; return { next: function () { for (; !u;)if (l || (l = g.next(0)), l = Te(l, z, g, f), m || (m = k.next(0)), m = Te(m, A, k, f), z.length || A.length) { var D = Se(b, z, A); z.length = 0; A.length = 0; if (!1 === D.value) return u = !0, D } else { if (l.done || m.done) return u = !0, y(l.done === m.done); q || (q = Ve(a, b, c, l.value, m.value)); D = q.next(0); q = null; if (!1 === D.value) return u = !0, D; m = l = null } return x } } } ++ function We(a, b, c, d, e) { return d.h.length !== e.h.length ? Qd(!1) : Pe(d.h, function (f) { var g = e.h.find(function (k) { return Re(k.key, f.key) }); return g ? Ue(a, b, c, f.value(), g.value()) : Qd(!1) }) } function Xe(a, b, c, d, e) { return d.P.length !== e.P.length ? Qd(!1) : Pe(d.P, function (f, g) { g = e.P[g]; return Ue(a, b, c, f(), g()) }) } ++ function Ye(a, b, c, d, e) { d = Pb(b.h, d.value); e = Pb(b.h, e.value); d = d.filter(function (f) { return Qe(f) }); e = e.filter(function (f) { return Qe(f) }); d = C.create(d.map(function (f) { return $b(f) })); e = C.create(e.map(function (f) { return $b(f) })); return Ue(a, b, c, d, e) } ++ function Ze(a, b, c, d, e) { ++ var f = Ue(a, b, c, Fe(a, b, c, C.m(d)), Fe(a, b, c, C.m(e))), g = Ye(a, b, c, d, e); d = Nb(b.h, d.value).filter(function (m) { return "http://www.w3.org/2000/xmlns/" !== m.node.namespaceURI }).sort(function (m, q) { return m.node.nodeName > q.node.nodeName ? 1 : -1 }).map(function (m) { return $b(m) }); e = Nb(b.h, e.value).filter(function (m) { return "http://www.w3.org/2000/xmlns/" !== m.node.namespaceURI }).sort(function (m, q) { return m.node.nodeName > q.node.nodeName ? 1 : -1 }).map(function (m) { return $b(m) }); var k = Ue(a, b, c, C.create(d), ++ C.create(e)), l = !1; return { next: function () { if (l) return x; var m = f.next(0); if (!m.done && !1 === m.value) return l = !0, m; m = k.next(0); if (!m.done && !1 === m.value) return l = !0, m; m = g.next(0); l = !0; return m } } ++ } function $e(a, b, c, d, e) { var f = Ue(a, b, c, Fe(a, b, c, C.m(d)), Fe(a, b, c, C.m(e))), g = !1; return { next: function () { if (g) return x; var k = f.next(0); return k.done || !1 !== k.value ? y(Re(Yc(d, b).first(), Yc(e, b).first())) : (g = !0, k) } } } ++ function Ve(a, b, c, d, e) { if (B(d.type, 46) && B(e.type, 46)) return Qd(Re(d, e)); if (B(d.type, 61) && B(e.type, 61)) return We(a, b, c, d, e); if (B(d.type, 62) && B(e.type, 62)) return Xe(a, b, c, d, e); if (B(d.type, 53) && B(e.type, 53)) { if (B(d.type, 55) && B(e.type, 55)) return Ye(a, b, c, d, e); if (B(d.type, 54) && B(e.type, 54)) return Ze(a, b, c, d, e); if (B(d.type, 47) && B(e.type, 47) || B(d.type, 57) && B(e.type, 57) || B(d.type, 58) && B(e.type, 58)) return $e(a, b, c, d, e) } return Qd(!1) }; function af(a) { return Error("XUST0001: " + (void 0 === a ? "Can not execute an updating expression in a non-updating context." : a)) } function bf(a) { return Error("XUTY0004: The attribute " + a.name + '="' + a.value + '" follows a node that is not an attribute node.') } function cf() { return Error("XUTY0005: The target of a insert expression with into must be a single element or document node.") } ++ function df() { return Error("XUTY0006: The target of a insert expression with before or after must be a single element, text, comment, or processing instruction node.") } function ff() { return Error("XUTY0008: The target of a replace expression must be a single element, attribute, text, comment, or processing instruction node.") } function gf() { return Error("XUTY0012: The target of a rename expression must be a single element, attribute, or processing instruction node.") } ++ function hf(a) { return Error("XUDY0017: The target " + a.outerHTML + " is used in more than one replace value of expression.") } function jf(a) { return Error("XUDY0021: Applying the updates will result in the XDM instance violating constraint: '" + a + "'") } function kf(a) { return Error("XUDY0023: The namespace binding " + a + " is conflicting.") } function lf(a) { return Error("XUDY0024: The namespace binding " + a + " is conflicting.") } ++ function mf() { return Error("XUDY0027: The target for an insert, replace, or rename expression expression should not be empty.") }; function H(a, b, c, d, e) { c = void 0 === c ? { C: !1, X: !1, R: "unsorted", subtree: !1 } : c; this.o = a; this.da = c.R || "unsorted"; this.subtree = !!c.subtree; this.X = !!c.X; this.C = !!c.C; this.ta = b; this.I = !1; this.ab = null; this.Yb = void 0 === d ? !1 : d; this.type = e } function I(a, b, c) { b && null !== b.N ? a.C ? (null === a.ab && (a.ab = yb(a.h(null, c).sb())), a = a.ab()) : a = a.h(b, c) : a = a.h(b, c); return a } H.prototype.B = function () { return null }; ++ H.prototype.v = function (a) { this.ta.forEach(function (b) { return b.v(a) }); if (!this.Yb && this.ta.some(function (b) { return b.I })) throw af(); }; function nf(a, b) { this.J = a; this.fa = b }; function of(a) { a && "object" === typeof a && "nodeType" in a && (a = a.ownerDocument || a, "function" === typeof a.createElementNS && "function" === typeof a.createProcessingInstruction && "function" === typeof a.createTextNode && "function" === typeof a.createComment && (this.h = a)); this.h || (this.h = null) } h = of.prototype; h.createAttributeNS = function (a, b) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createAttributeNS(a, b) }; ++ h.createCDATASection = function (a) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createCDATASection(a) }; h.createComment = function (a) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createComment(a) }; h.createDocument = function () { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.implementation.createDocument(null, null, null) }; ++ h.createElementNS = function (a, b) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createElementNS(a, b) }; h.createProcessingInstruction = function (a, b) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createProcessingInstruction(a, b) }; h.createTextNode = function (a) { if (!this.h) throw Error("Please pass a node factory if an XQuery script uses node constructors"); return this.h.createTextNode(a) }; function pf(a, b, c, d) { var e = Vb(c, a).node, f = (a = Ub(c, a)) ? a.node : null; b.forEach(function (g) { d.insertBefore(e, g.node, f) }) } function qf(a, b, c, d) { var e = Vb(c, a).node; b.forEach(function (f) { d.insertBefore(e, f.node, a.node) }) } function rf(a, b, c, d) { var e = (c = Sb(c, a)) ? c.node : null; b.forEach(function (f) { d.insertBefore(a.node, f.node, e) }) } function sf(a, b, c) { b.forEach(function (d) { c.insertBefore(a.node, d.node, null) }) } ++ function tf(a, b, c, d) { b.forEach(function (e) { var f = e.node.nodeName; if (Ob(c, a, f)) throw jf("An attribute " + f + " already exists."); d.setAttributeNS(a.node, e.node.namespaceURI, f, Qb(c, e)) }) } ++ function uf(a, b, c, d, e) { ++ d || (d = new of(a ? a.node : null)); switch (a.node.nodeType) { case 1: var f = c.getAllAttributes(a.node), g = c.getChildNodes(a.node), k = d.createElementNS(b.namespaceURI, b.Ca()); var l = { node: k, F: null }; f.forEach(function (m) { e.setAttributeNS(k, m.namespaceURI, m.nodeName, m.value) }); g.forEach(function (m) { e.insertBefore(k, m, null) }); break; case 2: b = d.createAttributeNS(b.namespaceURI, b.Ca()); b.value = Qb(c, a); l = { node: b, F: null }; break; case 7: l = { node: d.createProcessingInstruction(b.Ca(), Qb(c, a)), F: null } }if (!Vb(c, ++ a)) throw Error("Not supported: renaming detached nodes."); vf(a, [l], c, e) ++ } function wf(a, b, c, d) { c.getChildNodes(a.node).forEach(function (e) { return d.removeChild(a.node, e) }); b && d.insertBefore(a.node, b.node, null) } ++ function vf(a, b, c, d) { ++ var e = Vb(c, a), f = a.node.nodeType; if (2 === f) { if (b.some(function (l) { return 2 !== l.node.nodeType })) throw Error('Constraint "If $target is an attribute node, $replacement must consist of zero or more attribute nodes." failed.'); var g = e ? e.node : null; d.removeAttributeNS(g, a.node.namespaceURI, a.node.nodeName); b.forEach(function (l) { var m = l.node.nodeName; if (Ob(c, e, m)) throw jf("An attribute " + m + " already exists."); d.setAttributeNS(g, l.node.namespaceURI, m, Qb(c, l)) }) } if (1 === f || 3 === f || 8 === f || ++ 7 === f) { var k = (f = Ub(c, a)) ? f.node : null; d.removeChild(e.node, a.node); b.forEach(function (l) { d.insertBefore(e.node, l.node, k) }) } ++ }; function xf(a, b, c, d) { ++ yf(a, b); a.filter(function (e) { return -1 !== ["insertInto", "insertAttributes", "replaceValue", "rename"].indexOf(e.type) }).forEach(function (e) { ++ switch (e.type) { ++ case "insertInto": sf(e.target, e.content, d); break; case "insertAttributes": tf(e.target, e.content, b, d); break; case "rename": uf(e.target, e.o, b, c, d); break; case "replaceValue": var f = e.target; e = e.o; if (2 === f.node.nodeType) { var g = Vb(b, f); g ? d.setAttributeNS(g.node, f.node.namespaceURI, f.node.nodeName, e) : f.node.value = e } else d.setData(f.node, ++ e) ++ } ++ }); a.filter(function (e) { return -1 !== ["insertBefore", "insertAfter", "insertIntoAsFirst", "insertIntoAsLast"].indexOf(e.type) }).forEach(function (e) { switch (e.type) { case "insertAfter": pf(e.target, e.content, b, d); break; case "insertBefore": qf(e.target, e.content, b, d); break; case "insertIntoAsFirst": rf(e.target, e.content, b, d); break; case "insertIntoAsLast": sf(e.target, e.content, d) } }); a.filter(function (e) { return "replaceNode" === e.type }).forEach(function (e) { vf(e.target, e.o, b, d) }); a.filter(function (e) { ++ return "replaceElementContent" === ++ e.type ++ }).forEach(function (e) { wf(e.target, e.text, b, d) }); a.filter(function (e) { return "delete" === e.type }).forEach(function (e) { e = e.target; var f = Vb(b, e); (f = f ? f.node : null) && (2 === e.node.nodeType ? d.removeAttributeNS(f, e.node.namespaceURI, e.node.nodeName) : d.removeChild(f, e.node)) }); if (a.some(function (e) { return "put" === e.type })) throw Error('Not implemented: the execution for pendingUpdate "put" is not yet implemented.'); ++ } ++ function yf(a, b) { ++ function c(f) { return new zb(f.node.prefix, f.node.namespaceURI, f.node.localName) } function d(f, g) { var k = new Set; a.filter(function (l) { return l.type === f }).map(function (l) { return l.target }).forEach(function (l) { l = l ? l.node : null; k.has(l) && g(l); k.add(l) }) } d("rename", function (f) { throw Error("XUDY0015: The target " + f.outerHTML + " is used in more than one rename expression."); }); d("replaceNode", function (f) { ++ throw Error("XUDY0016: The target " + f.outerHTML + " is used in more than one replace expression."); ++ }); d("replaceValue", function (f) { throw hf(f); }); d("replaceElementContent", function (f) { throw hf(f); }); var e = new Map; a.filter(function (f) { return "replaceNode" === f.type && 2 === f.target.node.nodeType }).forEach(function (f) { var g = Vb(b, f.target); g = g ? g.node : null; var k = e.get(g); k ? k.push.apply(k, t(f.o.map(c))) : e.set(g, f.o.map(c)) }); a.filter(function (f) { return "rename" === f.type && 2 === f.target.node.nodeType }).forEach(function (f) { var g = Vb(b, f.target); if (g) { g = g.node; var k = e.get(g); k ? k.push(f.o) : e.set(g, [f.o]) } }); e.forEach(function (f) { ++ var g = ++ {}; f.forEach(function (k) { g[k.prefix] || (g[k.prefix] = k.namespaceURI); if (g[k.prefix] !== k.namespaceURI) throw lf(k.namespaceURI); }) ++ }) ++ } function zf(a) { return a.concat.apply(a, t(Aa.apply(1, arguments).filter(Boolean))) }; function Af(a, b, c, d) { H.call(this, a, b, c, !0, d); this.I = !0 } v(Af, H); Af.prototype.h = function () { throw af(); }; function Bf(a) { return a.I ? function (b, c) { return a.s(b, c) } : function (b, c) { var d = a.h(b, c); return { next: function () { var e = d.O(); return y({ fa: [], J: e }) } } } }; function Cf(a, b) { a = a.next(0); b(a.value.fa); return C.create(a.value.J) } function Df(a, b, c, d) { Af.call(this, a, b, c, d); this.I = this.ta.some(function (e) { return e.I }) } v(Df, Af); Df.prototype.h = function (a, b) { return this.A(a, b, this.ta.map(function (c) { return function (d) { return c.h(d, b) } })) }; ++ Df.prototype.s = function (a, b) { var c = [], d = this.A(a, b, this.ta.map(function (f) { return f.I ? function (g) { g = f.s(g, b); return Cf(g, function (k) { return c = zf(c, k) }) } : function (g) { return f.h(g, b) } })), e = !1; return { next: function () { if (e) return x; var f = d.O(); e = !0; return y(new nf(f, c)) } } }; Df.prototype.v = function (a) { Af.prototype.v.call(this, a); Ef(this) }; function Ef(a) { a.ta.some(function (b) { return b.I }) && (a.I = !0) }; var Ff = ["external", "attribute", "nodeName", "nodeType", "universal"], Gf = Ff.length; function Hf(a) { this.h = Ff.map(function (b) { return a[b] || 0 }); if (Object.keys(a).some(function (b) { return !Ff.includes(b) })) throw Error("Invalid specificity kind passed"); } Hf.prototype.add = function (a) { var b = this, c = Ff.reduce(function (d, e, f) { d[e] = b.h[f] + a.h[f]; return d }, Object.create(null)); return new Hf(c) }; function If(a, b) { for (var c = 0; c < Gf; ++c) { if (b.h[c] < a.h[c]) return 1; if (b.h[c] > a.h[c]) return -1 } return 0 }; function Jf() { return Sc("Expected base expression of a function call to evaluate to a sequence of single function item") } function Kf(a, b, c, d) { for (var e = [], f = 0; f < b.length; ++f)if (null === b[f]) e.push(null); else { var g = fe(a[f], b[f], c, d, !1); e.push(g) } return e } function Lf(a, b) { if (!B(a.type, 60)) throw Sc("Expected base expression to evaluate to a function item"); if (a.v !== b) throw Jf(); return a } ++ function Mf(a, b, c, d, e, f, g) { var k = 0; e = e.map(function (l) { return l ? null : f[k++](c) }); e = Kf(a.o, e, d, a.B); if (0 <= e.indexOf(null)) return Cb(a, e); b = b.apply(void 0, [c, d, g].concat(t(e))); return fe(a.s, b, d, a.B, !0) } function Nf(a, b, c) { var d = {}; Df.call(this, new Hf((d.external = 1, d)), [a].concat(b.filter(function (e) { return !!e })), { R: "unsorted", X: !1, subtree: !1, C: !1 }, c); this.la = b.length; this.S = b.map(function (e) { return null === e }); this.K = null; this.pa = a; this.Ba = b } v(Nf, Df); ++ Nf.prototype.s = function (a, b) { var c = this; if (!this.l || !this.l.I) return Df.prototype.s.call(this, a, b); var d = [], e = Mf(this.l, function (g, k, l) { return Cf(c.l.value.apply(c.l, [g, k, l].concat(t(Aa.apply(3, arguments)))), function (m) { d = zf(d, m) }) }, a, b, this.S, this.Ba.map(function (g) { return function () { return g.I ? Cf(g.s(a, b), function (k) { d = zf(d, k) }) : I(g, a, b) } }), this.K), f = !1; return { next: function () { if (f) return x; var g = e.O(); f = !0; return y({ fa: d, J: g }) } } }; ++ Nf.prototype.A = function (a, b, c) { ++ var d = this; c = p(c); var e = c.next().value, f = ja(c); if (this.l) return Mf(this.l, function (k, l, m) { return d.l.value.apply(d.l, [k, l, m].concat(t(Aa.apply(3, arguments)))) }, a, b, this.S, f, this.K); var g = e(a); return g.Z({ ++ default: function () { throw Jf(); }, m: function () { ++ return g.M(function (k) { ++ k = p(k).next().value; k = Lf(k, d.la); if (k.I) throw Error("XUDY0038: The function returned by the PrimaryExpr of a dynamic function invocation can not be an updating function"); return Mf(k, k.value, a, b, d.S, ++ f, d.K) ++ }) ++ } ++ }) ++ }; Nf.prototype.v = function (a) { this.K = Of(a); Df.prototype.v.call(this, a); if (this.pa.C) { a = I(this.pa, null, null); if (!a.wa()) throw Jf(); this.l = Lf(a.first(), this.la); this.l.I && (this.I = !0) } }; function Pf(a, b, c, d, e, f) { return ac([d, e, f], function (g) { var k = p(g); g = k.next().value; var l = k.next().value; k = k.next().value; l = l.value; k = k.value; if (l > g.P.length || 0 >= l) throw Error("FOAY0001: subarray start out of bounds."); if (0 > k) throw Error("FOAY0002: subarray length out of bounds."); if (l + k > g.P.length + 1) throw Error("FOAY0001: subarray start + length out of bounds."); return C.m(new Yb(g.P.slice(l - 1, k + l - 1))) }) } ++ function Qf(a, b, c, d, e) { return ac([d], function (f) { var g = p(f).next().value; return e.M(function (k) { k = k.map(function (z) { return z.value }).sort(function (z, A) { return A - z }).filter(function (z, A, D) { return D[A - 1] !== z }); for (var l = g.P.concat(), m = 0, q = k.length; m < q; ++m) { var u = k[m]; if (u > g.P.length || 0 >= u) throw Error("FOAY0001: subarray position out of bounds."); l.splice(u - 1, 1) } return C.m(new Yb(l)) }) }) } function Rf(a) { return B(a, 1) || B(a, 20) || B(a, 19) } ++ function Sf(a, b, c, d, e) { return 0 === d.length ? 0 !== e.length : 0 !== e.length && Ve(a, b, c, d[0], e[0]).next(0).value ? Sf(a, b, c, d.slice(1), e.slice(1)) : d[0].value !== d[0].value ? !0 : Rf(d[0].type) && 0 !== e.length && Rf(e[0].type) ? d[0].value < e[0].value : 0 === e.length ? !1 : d[0].value < e[0].value } function Tf(a, b, c, d) { d.sort(function (e, f) { return Ue(a, b, c, C.create(e), C.create(f)).next(0).value ? 0 : Sf(a, b, c, e, f) ? -1 : 1 }); return C.m(new Yb(d.map(function (e) { return function () { return C.create(e) } }))) } ++ function Uf(a, b) { return B(b.type, 62) ? b.P.reduce(function (c, d) { return d().M(function (e) { return e.reduce(Uf, c) }) }, a) : Pc([a, C.m(b)]) } ++ var Vf = [{ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "size", j: [{ type: 62, g: 3 }], i: { type: 5, g: 3 }, callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return C.m(w(e.P.length, 5)) }) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "get", j: [{ type: 62, g: 3 }, { type: 5, g: 3 }], i: { type: 59, g: 2 }, callFunction: Xb }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "put", j: [{ type: 62, g: 3 }, { type: 5, g: 3 }, { type: 59, g: 2 }], i: { ++ type: 62, ++ g: 3 ++ }, callFunction: function (a, b, c, d, e, f) { return ac([e, d], function (g) { var k = p(g); g = k.next().value; k = k.next().value; g = g.value; if (0 >= g || g > k.P.length) throw Error("FOAY0001: array position out of bounds."); k = k.P.concat(); k.splice(g - 1, 1, yb(f)); return C.m(new Yb(k)) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "append", j: [{ type: 62, g: 3 }, { type: 59, g: 2 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e) { return ac([d], function (f) { f = p(f).next().value.P.concat([yb(e)]); return C.m(new Yb(f)) }) } }, ++ { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "subarray", j: [{ type: 62, g: 3 }, { type: 5, g: 3 }, { type: 5, g: 3 }], i: { type: 62, g: 3 }, callFunction: Pf }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "subarray", j: [{ type: 62, g: 3 }, { type: 5, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e) { var f = C.m(w(d.first().value.length - e.first().value + 1, 5)); return Pf(a, b, c, d, e, f) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "remove", j: [{ type: 62, g: 3 }, { ++ type: 5, ++ g: 2 ++ }], i: { type: 62, g: 3 }, callFunction: Qf ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "insert-before", j: [{ type: 62, g: 3 }, { type: 5, g: 3 }, { type: 59, g: 2 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e, f) { return ac([d, e], function (g) { var k = p(g); g = k.next().value; k = k.next().value.value; if (k > g.P.length + 1 || 0 >= k) throw Error("FOAY0001: subarray position out of bounds."); g = g.P.concat(); g.splice(k - 1, 0, yb(f)); return C.m(new Yb(g)) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", ++ localName: "head", j: [{ type: 62, g: 3 }], i: { type: 59, g: 2 }, callFunction: function (a, b, c, d) { return Xb(a, b, c, d, C.m(w(1, 5))) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "tail", j: [{ type: 62, g: 3 }], i: { type: 59, g: 2 }, callFunction: function (a, b, c, d) { return Qf(a, b, c, d, C.m(w(1, 5))) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "reverse", j: [{ type: 62, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return C.m(new Yb(e.P.concat().reverse())) }) } }, ++ { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "join", j: [{ type: 62, g: 2 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d) { return d.M(function (e) { e = e.reduce(function (f, g) { return f.concat(g.P) }, []); return C.m(new Yb(e)) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "for-each", j: [{ type: 62, g: 3 }, { type: 60, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ f = p(f); var g = f.next().value, k = f.next().value; if (1 !== k.v) throw Sc("The callback passed into array:for-each has a wrong arity."); ++ f = g.P.map(function (l) { return yb(k.value.call(void 0, a, b, c, Kf(k.o, [l()], b, "array:for-each")[0])) }); return C.m(new Yb(f)) ++ }) ++ } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "filter", j: [{ type: 62, g: 3 }, { type: 60, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ f = p(f); var g = f.next().value, k = f.next().value; if (1 !== k.v) throw Sc("The callback passed into array:filter has a wrong arity."); var l = g.P.map(function (u) { ++ u = Kf(k.o, [u()], b, "array:filter")[0]; var z = ++ k.value; return z(a, b, c, u) ++ }), m = [], q = !1; return C.create({ next: function () { if (q) return x; for (var u = 0, z = g.P.length; u < z; ++u) { var A = l[u].ha(); m[u] = A } u = g.P.filter(function (D, F) { return m[F] }); q = !0; return y(new Yb(u)) } }) ++ }) ++ } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "fold-left", j: [{ type: 62, g: 3 }, { type: 59, g: 2 }, { type: 60, g: 3 }], i: { type: 59, g: 2 }, callFunction: function (a, b, c, d, e, f) { ++ return ac([d, f], function (g) { ++ g = p(g); var k = g.next().value, l = g.next().value; if (2 !== l.v) throw Sc("The callback passed into array:fold-left has a wrong arity."); ++ return k.P.reduce(function (m, q) { q = Kf(l.o, [q()], b, "array:fold-left")[0]; return l.value.call(void 0, a, b, c, m, q) }, e) ++ }) ++ } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "fold-right", j: [{ type: 62, g: 3 }, { type: 59, g: 2 }, { type: 60, g: 3 }], i: { type: 59, g: 2 }, callFunction: function (a, b, c, d, e, f) { ++ return ac([d, f], function (g) { ++ g = p(g); var k = g.next().value, l = g.next().value; if (2 !== l.v) throw Sc("The callback passed into array:fold-right has a wrong arity."); return k.P.reduceRight(function (m, q) { ++ q = Kf(l.o, ++ [q()], b, "array:fold-right")[0]; return l.value.call(void 0, a, b, c, m, q) ++ }, e) ++ }) ++ } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "for-each-pair", j: [{ type: 62, g: 3 }, { type: 62, g: 3 }, { type: 60, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d, e, f) { ++ return ac([d, e, f], function (g) { ++ var k = p(g); g = k.next().value; var l = k.next().value; k = k.next().value; if (2 !== k.v) throw Sc("The callback passed into array:for-each-pair has a wrong arity."); for (var m = [], q = 0, u = Math.min(g.P.length, l.P.length); q < u; ++q) { ++ var z = ++ p(Kf(k.o, [g.P[q](), l.P[q]()], b, "array:for-each-pair")), A = z.next().value; z = z.next().value; m[q] = yb(k.value.call(void 0, a, b, c, A, z)) ++ } return C.m(new Yb(m)) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "sort", j: [{ type: 62, g: 3 }], i: { type: 62, g: 3 }, callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value.P.map(function (f) { return f().O() }); return Tf(a, b, c, e) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/array", localName: "flatten", j: [{ type: 59, g: 2 }], ++ i: { type: 59, g: 2 }, callFunction: function (a, b, c, d) { return d.M(function (e) { return e.reduce(Uf, C.empty()) }) } ++ }]; function K(a, b, c, d, e) { return e.G() ? e : C.m(Pd(e.first(), a)) } ++ var Wf = [{ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "untypedAtomic", j: [{ type: 46, g: 0 }], i: { type: 19, g: 0 }, callFunction: K.bind(null, 19) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "error", j: [{ type: 46, g: 0 }], i: { type: 39, g: 0 }, callFunction: K.bind(null, 39) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "string", j: [{ type: 46, g: 0 }], i: { type: 1, g: 0 }, callFunction: K.bind(null, 1) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "boolean", j: [{ type: 46, g: 0 }], i: { ++ type: 0, ++ g: 0 ++ }, callFunction: K.bind(null, 0) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "decimal", j: [{ type: 46, g: 0 }], i: { type: 4, g: 0 }, callFunction: K.bind(null, 4) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "float", j: [{ type: 46, g: 0 }], i: { type: 6, g: 0 }, callFunction: K.bind(null, 6) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "double", j: [{ type: 46, g: 0 }], i: { type: 3, g: 0 }, callFunction: K.bind(null, 3) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "duration", j: [{ ++ type: 46, ++ g: 0 ++ }], i: { type: 18, g: 0 }, callFunction: K.bind(null, 18) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "dateTime", j: [{ type: 46, g: 0 }], i: { type: 9, g: 0 }, callFunction: K.bind(null, 9) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "dateTimeStamp", j: [{ type: 46, g: 0 }], i: { type: 10, g: 0 }, callFunction: K.bind(null, 10) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "time", j: [{ type: 46, g: 0 }], i: { type: 8, g: 0 }, callFunction: K.bind(null, 8) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "date", ++ j: [{ type: 46, g: 0 }], i: { type: 7, g: 0 }, callFunction: K.bind(null, 7) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "gYearMonth", j: [{ type: 46, g: 0 }], i: { type: 11, g: 0 }, callFunction: K.bind(null, 11) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "gYear", j: [{ type: 46, g: 0 }], i: { type: 12, g: 0 }, callFunction: K.bind(null, 12) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "gMonthDay", j: [{ type: 46, g: 0 }], i: { type: 13, g: 0 }, callFunction: K.bind(null, 13) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "gDay", j: [{ type: 46, g: 0 }], i: { type: 15, g: 0 }, callFunction: K.bind(null, 15) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "gMonth", j: [{ type: 46, g: 0 }], i: { type: 14, g: 0 }, callFunction: K.bind(null, 14) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "hexBinary", j: [{ type: 46, g: 0 }], i: { type: 22, g: 0 }, callFunction: K.bind(null, 22) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "base64Binary", j: [{ type: 46, g: 0 }], i: { type: 21, g: 0 }, callFunction: K.bind(null, 21) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "QName", j: [{ type: 46, g: 0 }], i: { type: 23, g: 0 }, callFunction: function (a, b, c, d) { ++ if (d.G()) return d; a = d.first(); if (B(a.type, 2)) throw Error("XPTY0004: The provided QName is not a string-like value."); a = Pd(a, 1).value; a = ad(a, 23); if (!bd(a, 23)) throw Error("FORG0001: The provided QName is invalid."); if (!a.includes(":")) return c = c.aa(""), C.m(w(new zb("", c, a), 23)); d = p(a.split(":")); b = d.next().value; d = d.next().value; c = c.aa(b); if (!c) throw Error("FONS0004: The value " + a + " can not be cast to a QName. Did you mean to use fn:QName?"); ++ return C.m(w(new zb(b, c, d), 23)) ++ } ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "anyURI", j: [{ type: 46, g: 0 }], i: { type: 20, g: 0 }, callFunction: K.bind(null, 20) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "normalizedString", j: [{ type: 46, g: 0 }], i: { type: 48, g: 0 }, callFunction: K.bind(null, 48) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "token", j: [{ type: 46, g: 0 }], i: { type: 52, g: 0 }, callFunction: K.bind(null, 52) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "language", ++ j: [{ type: 46, g: 0 }], i: { type: 51, g: 0 }, callFunction: K.bind(null, 51) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "NMTOKEN", j: [{ type: 46, g: 0 }], i: { type: 50, g: 0 }, callFunction: K.bind(null, 50) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "NMTOKENS", j: [{ type: 46, g: 0 }], i: { type: 49, g: 2 }, callFunction: K.bind(null, 49) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "Name", j: [{ type: 46, g: 0 }], i: { type: 25, g: 0 }, callFunction: K.bind(null, 25) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "NCName", j: [{ type: 46, g: 0 }], i: { type: 24, g: 0 }, callFunction: K.bind(null, 24) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "ID", j: [{ type: 46, g: 0 }], i: { type: 42, g: 0 }, callFunction: K.bind(null, 42) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "IDREF", j: [{ type: 46, g: 0 }], i: { type: 41, g: 0 }, callFunction: K.bind(null, 41) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "IDREFS", j: [{ type: 46, g: 0 }], i: { type: 43, g: 2 }, callFunction: K.bind(null, 43) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "ENTITY", j: [{ type: 46, g: 0 }], i: { type: 26, g: 0 }, callFunction: K.bind(null, 26) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "ENTITIES", j: [{ type: 46, g: 0 }], i: { type: 40, g: 2 }, callFunction: K.bind(null, 40) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "integer", j: [{ type: 46, g: 0 }], i: { type: 5, g: 0 }, callFunction: K.bind(null, 5) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "nonPositiveInteger", j: [{ type: 46, g: 0 }], i: { type: 27, g: 0 }, callFunction: K.bind(null, 27) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "negativeInteger", j: [{ type: 46, g: 0 }], i: { type: 28, g: 0 }, callFunction: K.bind(null, 28) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "long", j: [{ type: 46, g: 0 }], i: { type: 31, g: 0 }, callFunction: K.bind(null, 31) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "int", j: [{ type: 46, g: 0 }], i: { type: 32, g: 0 }, callFunction: K.bind(null, 32) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "short", j: [{ type: 46, g: 0 }], i: { type: 33, g: 0 }, callFunction: K.bind(null, 33) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "byte", j: [{ type: 46, g: 0 }], i: { type: 34, g: 0 }, callFunction: K.bind(null, 34) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "nonNegativeInteger", j: [{ type: 46, g: 0 }], i: { type: 30, g: 0 }, callFunction: K.bind(null, 30) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "unsignedLong", j: [{ type: 46, g: 0 }], i: { type: 36, g: 0 }, callFunction: K.bind(null, 36) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "unsignedInt", j: [{ type: 46, g: 0 }], i: { type: 35, g: 0 }, callFunction: K.bind(null, 35) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", ++ localName: "unsignedShort", j: [{ type: 46, g: 0 }], i: { type: 38, g: 0 }, callFunction: K.bind(null, 38) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "unsignedByte", j: [{ type: 46, g: 0 }], i: { type: 37, g: 0 }, callFunction: K.bind(null, 37) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "positiveInteger", j: [{ type: 46, g: 0 }], i: { type: 29, g: 0 }, callFunction: K.bind(null, 29) }, { ++ namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "yearMonthDuration", j: [{ type: 46, g: 0 }], i: { type: 16, g: 0 }, callFunction: K.bind(null, ++ 16) ++ }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "dayTimeDuration", j: [{ type: 46, g: 0 }], i: { type: 17, g: 0 }, callFunction: K.bind(null, 17) }, { namespaceURI: "http://www.w3.org/2001/XMLSchema", localName: "dateTimeStamp", j: [{ type: 46, g: 0 }], i: { type: 10, g: 0 }, callFunction: K.bind(null, 10) }]; function Xf(a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getYear(), 5)) } function Yf(a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getMonth(), 5)) } function Zf(a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getDay(), 5)) } function $f(a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getHours(), 5)) } function ag(a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getMinutes(), 5)) } function bg(a, b, c, d) { d.G() || (a = C, b = a.m, d = d.first().value, d = b.call(a, w(d.B + d.sa, 4))); return d } ++ function cg(a, b, c, d) { return d.G() ? d : (a = d.first().value.Y) ? C.m(w(a, 17)) : C.empty() } ++ var dg = [{ ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "dateTime", j: [{ type: 7, g: 0 }, { type: 8, g: 0 }], i: { type: 9, g: 0 }, callFunction: function (a, b, c, d, e) { ++ if (d.G()) return d; if (e.G()) return e; a = d.first().value; e = e.first().value; b = a.Y; c = e.Y; if (b || c) { if (!b || c) if (!b && c) b = c; else if (!fc(b, c)) throw Error("FORG0008: fn:dateTime: got a date and time value with different timezones."); } else b = null; return C.m(w(new rc(a.getYear(), a.getMonth(), a.getDay(), e.getHours(), e.getMinutes(), e.getSeconds(), e.sa, ++ b), 9)) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "year-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 5, g: 0 }, callFunction: Xf }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "month-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 5, g: 0 }, callFunction: Yf }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "day-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 5, g: 0 }, callFunction: Zf }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "hours-from-dateTime", ++ j: [{ type: 9, g: 0 }], i: { type: 5, g: 0 }, callFunction: $f ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "minutes-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 5, g: 0 }, callFunction: ag }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "seconds-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 4, g: 0 }, callFunction: bg }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "timezone-from-dateTime", j: [{ type: 9, g: 0 }], i: { type: 17, g: 0 }, callFunction: cg }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "year-from-date", j: [{ type: 7, g: 0 }], i: { type: 5, g: 0 }, callFunction: Xf ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "month-from-date", j: [{ type: 7, g: 0 }], i: { type: 5, g: 0 }, callFunction: Yf }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "day-from-date", j: [{ type: 7, g: 0 }], i: { type: 5, g: 0 }, callFunction: Zf }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "timezone-from-date", j: [{ type: 7, g: 0 }], i: { type: 17, g: 0 }, callFunction: cg }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "hours-from-time", j: [{ type: 8, g: 0 }], i: { type: 5, g: 0 }, callFunction: $f ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "minutes-from-time", j: [{ type: 8, g: 0 }], i: { type: 5, g: 0 }, callFunction: ag }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "seconds-from-time", j: [{ type: 8, g: 0 }], i: { type: 4, g: 0 }, callFunction: bg }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "timezone-from-time", j: [{ type: 8, g: 0 }], i: { type: 17, g: 0 }, callFunction: cg }]; function eg(a, b) { ++ var c = b.h, d = b.o, e = b.B; switch (a.node.nodeType) { ++ case 1: var f = d.createElementNS(a.node.namespaceURI, a.node.nodeName); c.getAllAttributes(a.node).forEach(function (g) { return e.setAttributeNS(f, g.namespaceURI, g.nodeName, g.value) }); d = p(Pb(c, a)); for (a = d.next(); !a.done; a = d.next())a = eg(a.value, b), e.insertBefore(f, a.node, null); return { node: f, F: null }; case 2: return b = d.createAttributeNS(a.node.namespaceURI, a.node.nodeName), b.value = Qb(c, a), { node: b, F: null }; case 4: return { ++ node: d.createCDATASection(Qb(c, ++ a)), F: null ++ }; case 8: return { node: d.createComment(Qb(c, a)), F: null }; case 9: d = d.createDocument(); c = p(Pb(c, a)); for (a = c.next(); !a.done; a = c.next())a = eg(a.value, b), e.insertBefore(d, a.node, null); return { node: d, F: null }; case 7: return { node: d.createProcessingInstruction(a.node.target, Qb(c, a)), F: null }; case 3: return { node: d.createTextNode(Qb(c, a)), F: null } ++ } ++ }; function fg(a, b) { ++ var c = b.B, d = b.o, e = b.h; if (Kb(a.node)) switch (a.node.nodeType) { ++ case 2: return d = d.createAttributeNS(a.node.namespaceURI, a.node.nodeName), d.value = Qb(e, a), d; case 8: return d.createComment(Qb(e, a)); case 1: var f = a.node.prefix, g = a.node.localName, k = d.createElementNS(a.node.namespaceURI, f ? f + ":" + g : g); Pb(e, a).forEach(function (l) { l = fg(l, b); c.insertBefore(k, l, null) }); Nb(e, a).forEach(function (l) { c.setAttributeNS(k, l.node.namespaceURI, l.node.nodeName, Qb(e, l)) }); k.normalize(); return k; case 7: return d.createProcessingInstruction(a.node.target, ++ Qb(e, a)); case 3: return d.createTextNode(Qb(e, a)) ++ } else return eg(a, b).node ++ } function gg(a, b, c) { var d = a; for (a = Vb(c, d); null !== a;) { if (2 === d.node.nodeType) b.push(d.node.nodeName); else { var e = Pb(c, a); b.push(e.findIndex(function (f) { return Sd(f, d) })) } d = a; a = Vb(c, d) } return d } function hg(a, b, c) { for (var d = {}; 0 < b.length;)d.$a = b.pop(), "string" === typeof d.$a ? a = Nb(c, a).find(function (e) { return function (f) { return f.node.nodeName === e.$a } }(d)) : a = Pb(c, a)[d.$a], d = { $a: d.$a }; return a.node } ++ function ig(a, b, c) { var d = a.node; if (!(Kb(d) || c || a.F)) return d; d = b.da; var e = []; if (c) return fg(a, b); a = gg(a, e, b.h); c = d.get(a.node); c || (c = { node: fg(a, b), F: null }, d.set(a.node, c)); return hg(c, e, b.h) }; function jg(a, b, c, d, e) { return d.M(function (f) { for (var g = "", k = 0; k < f.length; k++) { var l = f[k], m = b.v && B(l.type, 53) ? b.v.serializeToString(ig(l.value, b, !1)) : Zc(C.m(l), b).map(function (q) { return Pd(q, 1) }).first().value; g += "{type: " + mb[l.type] + ", value: " + m + "}\n" } void 0 !== e && (g += e.first().value); b.s.trace(g); return C.create(f) }) } ++ var kg = [{ j: [{ type: 59, g: 2 }], callFunction: jg, localName: "trace", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { j: [{ type: 59, g: 2 }, { type: 1, g: 3 }], callFunction: jg, localName: "trace", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }]; function lg(a, b, c, d, e) { a = void 0 === d || d.G() ? new zb("err", "http://www.w3.org/2005/xqt-errors", "FOER0000") : d.first().value; b = ""; void 0 === e || e.G() || (b = ": " + e.first().value); throw Error("" + a.localName + b); } ++ var mg = [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "error", j: [], i: { type: 63, g: 3 }, callFunction: lg }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "error", j: [{ type: 23, g: 0 }], i: { type: 63, g: 3 }, callFunction: lg }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "error", j: [{ type: 23, g: 0 }, { type: 1, g: 3 }], i: { type: 63, g: 3 }, callFunction: lg }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "error", j: [{ type: 23, g: 0 }, { type: 1, g: 3 }, { type: 59, g: 2 }], i: { ++ type: 63, ++ g: 3 ++ }, callFunction: function () { throw Error("Not implemented: Using an error object in error is not supported"); } ++ }]; function ng(a) { return "string" === typeof a ? a : (a = (new Gb).getChildNodes(a).find(function (b) { return 8 === b.nodeType })) ? a.data : "some expression" }; function og(a, b) { a = Error.call(this, a); this.message = a.message; "stack" in a && (this.stack = a.stack); this.position = { end: { ja: b.end.ja, line: b.end.line, offset: b.end.offset }, start: { ja: b.start.ja, line: b.start.line, offset: b.start.offset } } } v(og, Error); function pg(a, b) { ++ if (b instanceof Error) throw b; "string" !== typeof a && (a = ng(a)); var c = qg(b); a = a.replace("\r", "").split("\n"); var d = Math.floor(Math.log10(Math.min(c.end.line + 2, a.length))) + 1; a = a.reduce(function (e, f, g) { ++ var k = g + 1; if (2 < c.start.line - k || 2 < k - c.end.line) return e; g = Array(d).fill(" ", 0, Math.floor(Math.log10(k)) + 1 - d).join("") + k + ": "; e.push(g + f); if (k >= c.start.line && k <= c.end.line) { ++ var l = k < c.end.line ? f.length + g.length : c.end.ja - 1 + g.length; k = k > c.start.line ? g.length : c.start.ja - 1 + g.length; f = " ".repeat(g.length) + ++ Array.from(f.substring(0, k - g.length), function (m) { return "\t" === m ? "\t" : " " }).join("") + "^".repeat(l - k); e.push(f) ++ } return e ++ }, []); b = rg(b).join("\n"); throw new og(a.join("\n") + "\n\n" + b, c); ++ }; var sg = Object.create(null); function tg(a, b) { for (var c = [], d = 0; d < a.length + 1; ++d)c[d] = []; return function k(f, g) { if (0 === f) return g; if (0 === g) return f; if (void 0 !== c[f][g]) return c[f][g]; var l = 0; a[f - 1] !== b[g - 1] && (l = 1); l = Math.min(k(f - 1, g) + 1, k(f, g - 1) + 1, k(f - 1, g - 1) + l); return c[f][g] = l }(a.length, b.length) } ++ function ug(a) { ++ var b = sg[a] ? sg[a] : Object.keys(sg).map(function (c) { return { name: c, Ab: tg(a, c.slice(c.lastIndexOf(":") + 1)) } }).sort(function (c, d) { return c.Ab - d.Ab }).slice(0, 5).filter(function (c) { return c.Ab < a.length / 2 }).reduce(function (c, d) { return c.concat(sg[d.name]) }, []).slice(0, 5); return b.length ? b.map(function (c) { return '"Q{' + c.namespaceURI + "}" + c.localName + " (" + c.j.map(function (d) { return 4 === d ? "..." : ob(d) }).join(", ") + ')"' }).reduce(function (c, d, e, f) { return 0 === e ? c + d : c + ((e !== f.length - 1 ? ", " : " or ") + d) }, ++ "Did you mean ") + "?" : "No similar functions found." ++ } function vg(a, b, c) { var d = sg[a + ":" + b]; return d ? (d = d.find(function (e) { return e.j.some(function (f) { return 4 === f }) ? e.j.length - 1 <= c : e.j.length === c })) ? { j: d.j, arity: c, callFunction: d.callFunction, I: d.I, localName: b, namespaceURI: a, i: d.i } : null : null } function wg(a, b, c, d, e) { sg[a + ":" + b] || (sg[a + ":" + b] = []); sg[a + ":" + b].push({ j: c, arity: c.length, callFunction: e, I: !1, localName: b, namespaceURI: a, i: d }) }; var xg = {}, yg = (xg.xml = "http://www.w3.org/XML/1998/namespace", xg.xs = "http://www.w3.org/2001/XMLSchema", xg.fn = "http://www.w3.org/2005/xpath-functions", xg.map = "http://www.w3.org/2005/xpath-functions/map", xg.array = "http://www.w3.org/2005/xpath-functions/array", xg.math = "http://www.w3.org/2005/xpath-functions/math", xg.fontoxpath = "http://fontoxml.com/fontoxpath", xg.local = "http://www.w3.org/2005/xquery-local-functions", xg); function zg(a, b, c, d) { this.Ha = [Object.create(null)]; this.Ia = Object.create(null); this.s = a; this.da = Object.keys(b).reduce(function (e, f) { if (void 0 === b[f]) return e; e[f] = "Q{}" + f + "[0]"; return e }, Object.create(null)); this.o = Object.create(null); this.h = Object.create(null); this.v = c; this.l = d; this.B = [] } zg.prototype.xa = function (a, b, c) { return vg(a, b, c) }; zg.prototype.ib = function (a, b) { if (a) return null; a = this.da[b]; this.o[b] || (this.o[b] = { name: b }); return a }; ++ zg.prototype.Ua = function (a, b) { var c = this.l(a, b); if (c) this.B.push({ nc: a, arity: b, Lb: c }); else if ("" === a.prefix) { if (this.v) return { namespaceURI: this.v, localName: a.localName } } else if (b = this.aa(a.prefix, !0)) return { namespaceURI: b, localName: a.localName }; return c }; zg.prototype.aa = function (a, b) { if (void 0 !== b && !b) return null; if (yg[a]) return yg[a]; b = this.s(a); this.h[a] || (this.h[a] = { namespaceURI: b, prefix: a }); return void 0 !== b || a ? b : null }; function Ag(a) { return Error("XPTY0004: " + a) } function Bg(a, b) { a = 2 === a.node.nodeType ? a.node.nodeName + '="' + Qb(b, a) + '"' : a.node.outerHTML; return Error("XQTY0024: The node " + a + " follows a node that is not an attribute node or a namespace node.") } function Cg(a) { return Error('XQDY0044: The node name "' + a.Ca() + '" is invalid for a computed attribute constructor.') } function Dg() { return Error("XQST0045: Functions and variables may not be declared in one of the reserved namespace URIs.") } ++ function Eg() { return Error("XQST0060: Functions declared in a module or as an external function must reside in a namespace.") } function Fg() { return Error("XQST0066: A Prolog may contain at most one default function namespace declaration.") } function Gg() { return Error("XQST0070: The prefixes xml and xmlns may not be used in a namespace declaration or be bound to another namespaceURI.") } ++ function Hg(a) { return Error('XQDY0074: The value "' + a + '" of a name expressions cannot be converted to an expanded QName.') } function Ig(a) { return Error('XPST0081: The prefix "' + a + '" could not be resolved') }; function Jg(a, b) { return "Q{" + (a || "") + "}" + b } function Kg(a, b) { for (var c = a.length - 1; 0 <= c; --c)if (b in a[c]) return a[c][b] } function Lg(a) { this.o = a; this.s = this.h = 0; this.B = [Object.create(null)]; this.l = Object.create(null); this.v = null; this.Ia = a && a.Ia; this.Ha = a && a.Ha } function Of(a) { for (var b = new Lg(a.o), c = 0; c < a.h + 1; ++c)b.B = [Object.assign(Object.create(null), b.B[0], a.B[c])], b.Ha = [Object.assign(Object.create(null), b.Ha[0], a.Ha[c])], b.l = Object.assign(Object.create(null), a.l), b.Ia = a.Ia, b.v = a.v; return b } ++ function Mg(a) { a.s++; a.h++; a.B[a.h] = Object.create(null); a.Ha[a.h] = Object.create(null) } Lg.prototype.xa = function (a, b, c, d) { d = void 0 === d ? !1 : d; var e = this.l[Jg(a, b) + "~" + c]; return !e || d && e.Eb ? null === this.o ? null : this.o.xa(a, b, c, d) : e }; Lg.prototype.ib = function (a, b) { var c = Kg(this.Ha, Jg(a, b)); return c ? c : null === this.o ? null : this.o.ib(a, b) }; function Ng(a, b, c) { return (a = a.Ia[Jg(b, c)]) ? a : null } ++ function Og(a, b, c, d, e) { d = Jg(b, c) + "~" + d; if (a.l[d]) throw Error('XQST0049: The function or variable "Q{' + b + "}" + c + '" is declared more than once.'); a.l[d] = e } function Pg(a, b, c) { a.B[a.h][b] = c } function Qg(a, b, c) { b = Jg(b || "", c); return a.Ha[a.h][b] = b + "[" + a.s + "]" } function Rg(a, b, c, d) { a.Ia[Jg(b || "", c) + "[" + a.s + "]"] = d } function Sg(a) { a.B.length = a.h; a.Ha.length = a.h; a.h-- } ++ Lg.prototype.Ua = function (a, b) { var c = a.prefix, d = a.localName; return "" === c && this.v ? { localName: d, namespaceURI: this.v } : c && (c = this.aa(c, !1)) ? { localName: d, namespaceURI: c } : null === this.o ? null : this.o.Ua(a, b) }; Lg.prototype.aa = function (a, b) { var c = Kg(this.B, a || ""); return void 0 === c ? null === this.o ? void 0 : this.o.aa(a || "", void 0 === b ? !0 : b) : c }; function L(a, b) { "*" === b || Array.isArray(b) || (b = [b]); for (var c = 1; c < a.length; ++c)if (Array.isArray(a[c])) { var d = a[c]; if ("*" === b || b.includes(d[0])) return d } return null } function Tg(a) { return 2 > a.length ? "" : "object" === typeof a[1] ? a[2] || "" : a[1] || "" } function M(a, b) { if (!Array.isArray(a)) return null; a = a[1]; return "object" !== typeof a || Array.isArray(a) ? null : b in a ? a[b] : null } function N(a, b) { return b.reduce(L, a) } ++ function O(a, b) { for (var c = [], d = 1; d < a.length; ++d)if (Array.isArray(a[d])) { var e = a[d]; "*" !== b && e[0] !== b || c.push(e) } return c } function Ug(a) { return { localName: Tg(a), namespaceURI: M(a, "URI"), prefix: M(a, "prefix") } } ++ function Vg(a) { ++ function b(f) { ++ switch (f[0]) { ++ case "documentTest": return 55; case "elementTest": return 54; case "attributeTest": return 47; case "piTest": return 57; case "commentTest": return 58; case "textTest": return 56; case "anyKindTest": return 53; case "anyItemType": return 59; case "anyFunctionTest": case "functionTest": case "typedFunctionTest": return 60; case "anyMapTest": case "typedMapTest": return 61; case "anyArrayTest": case "typedArrayTest": return 62; case "atomicType": return pb([M(f, "prefix"), Tg(f)].join(":")); ++ case "parenthesizedItemType": return b(L(f, "*")); default: throw Error('Type declaration "' + L(c, "*")[0] + '" is not supported.'); ++ } ++ } var c = L(a, "typeDeclaration"); if (!c || L(c, "voidSequenceType")) return { type: 59, g: 2 }; a = { type: b(L(c, "*")), g: 3 }; var d = null, e = L(c, "occurrenceIndicator"); e && (d = Tg(e)); switch (d) { case "*": return a.g = 2, a; case "?": return a.g = 0, a; case "+": return a.g = 1, a; case "": case null: return a } ++ } ++ function P(a, b, c) { if ("object" !== typeof a[1] || Array.isArray(a[1])) { var d = {}; d[b] = c; a.splice(1, 0, d) } else a[1][b] = c }; function Wg(a) { var b = { type: 62, g: 3 }; P(a, "type", b); return b }; function Xg(a, b) { if (!b || !b.ia) return { type: 59, g: 2 }; var c = L(a, "EQName"); if (!c) return { type: 59, g: 2 }; var d = Ug(c); c = d.localName; var e = d.prefix; d = O(L(a, "arguments"), "*"); c = b.ia.Ua({ localName: c, prefix: e }, d.length); if (!c) return { type: 59, g: 2 }; b = b.ia.xa(c.namespaceURI, c.localName, d.length + 1); if (!b) return { type: 59, g: 2 }; 59 !== b.i.type && P(a, "type", b.i); return b.i }; function Q(a, b, c) { return (a << 20) + (b << 12) + (c.charCodeAt(0) << 8) + c.charCodeAt(1) } ++ var Yg = {}, Zg = (Yg[Q(2, 2, "idivOp")] = 5, Yg[Q(16, 16, "addOp")] = 16, Yg[Q(16, 16, "subtractOp")] = 16, Yg[Q(16, 16, "divOp")] = 4, Yg[Q(16, 2, "multiplyOp")] = 16, Yg[Q(16, 2, "divOp")] = 16, Yg[Q(2, 16, "multiplyOp")] = 16, Yg[Q(17, 17, "addOp")] = 17, Yg[Q(17, 17, "subtractOp")] = 17, Yg[Q(17, 17, "divOp")] = 4, Yg[Q(17, 2, "multiplyOp")] = 17, Yg[Q(17, 2, "divOp")] = 17, Yg[Q(2, 17, "multiplyOp")] = 17, Yg[Q(9, 9, "subtractOp")] = 17, Yg[Q(7, 7, "subtractOp")] = 17, Yg[Q(8, 8, "subtractOp")] = 17, Yg[Q(9, 16, "addOp")] = 9, Yg[Q(9, 16, "subtractOp")] = 9, Yg[Q(9, 17, "addOp")] = 9, Yg[Q(9, ++ 17, "subtractOp")] = 9, Yg[Q(7, 16, "addOp")] = 7, Yg[Q(7, 16, "subtractOp")] = 7, Yg[Q(7, 17, "addOp")] = 7, Yg[Q(7, 17, "subtractOp")] = 7, Yg[Q(8, 17, "addOp")] = 8, Yg[Q(8, 17, "subtractOp")] = 8, Yg[Q(9, 16, "addOp")] = 9, Yg[Q(9, 16, "subtractOp")] = 9, Yg[Q(9, 17, "addOp")] = 9, Yg[Q(9, 17, "subtractOp")] = 9, Yg[Q(7, 17, "addOp")] = 7, Yg[Q(7, 17, "subtractOp")] = 7, Yg[Q(7, 16, "addOp")] = 7, Yg[Q(7, 16, "subtractOp")] = 7, Yg[Q(8, 17, "addOp")] = 8, Yg[Q(8, 17, "subtractOp")] = 8, Yg), $g = {}, ah = ($g[Q(2, 2, "addOp")] = function (a, b) { return a + b }, $g[Q(2, 2, "subtractOp")] = function (a, ++ b) { return a - b }, $g[Q(2, 2, "multiplyOp")] = function (a, b) { return a * b }, $g[Q(2, 2, "divOp")] = function (a, b) { return a / b }, $g[Q(2, 2, "modOp")] = function (a, b) { return a % b }, $g[Q(2, 2, "idivOp")] = function (a, b) { return Math.trunc(a / b) }, $g[Q(16, 16, "addOp")] = function (a, b) { return new od(a.ga + b.ga) }, $g[Q(16, 16, "subtractOp")] = function (a, b) { return new od(a.ga - b.ga) }, $g[Q(16, 16, "divOp")] = function (a, b) { return a.ga / b.ga }, $g[Q(16, 2, "multiplyOp")] = rd, $g[Q(16, 2, "divOp")] = function (a, b) { ++ if (isNaN(b)) throw Error("FOCA0005: Cannot divide xs:yearMonthDuration by NaN"); ++ a = Math.round(a.ga / b); if (a > Number.MAX_SAFE_INTEGER || !Number.isFinite(a)) throw Error("FODT0002: Value overflow while dividing xs:yearMonthDuration"); return new od(a < Number.MIN_SAFE_INTEGER || 0 === a ? 0 : a) ++ }, $g[Q(2, 16, "multiplyOp")] = function (a, b) { return rd(b, a) }, $g[Q(17, 17, "addOp")] = function (a, b) { return new gc(a.ea + b.ea) }, $g[Q(17, 17, "subtractOp")] = function (a, b) { return new gc(a.ea - b.ea) }, $g[Q(17, 17, "divOp")] = function (a, b) { if (0 === b.ea) throw Error("FOAR0001: Division by 0"); return a.ea / b.ea }, $g[Q(17, 2, "multiplyOp")] = ++ lc, $g[Q(17, 2, "divOp")] = function (a, b) { if (isNaN(b)) throw Error("FOCA0005: Cannot divide xs:dayTimeDuration by NaN"); a = a.ea / b; if (a > Number.MAX_SAFE_INTEGER || !Number.isFinite(a)) throw Error("FODT0002: Value overflow while dividing xs:dayTimeDuration"); return new gc(a < Number.MIN_SAFE_INTEGER || Object.is(-0, a) ? 0 : a) }, $g[Q(2, 17, "multiplyOp")] = function (a, b) { return lc(b, a) }, $g[Q(9, 9, "subtractOp")] = xc, $g[Q(7, 7, "subtractOp")] = xc, $g[Q(8, 8, "subtractOp")] = xc, $g[Q(9, 16, "addOp")] = yc, $g[Q(9, 16, "subtractOp")] = zc, $g[Q(9, ++ 17, "addOp")] = yc, $g[Q(9, 17, "subtractOp")] = zc, $g[Q(7, 16, "addOp")] = yc, $g[Q(7, 16, "subtractOp")] = zc, $g[Q(7, 17, "addOp")] = yc, $g[Q(7, 17, "subtractOp")] = zc, $g[Q(8, 17, "addOp")] = yc, $g[Q(8, 17, "subtractOp")] = zc, $g[Q(9, 16, "addOp")] = yc, $g[Q(9, 16, "subtractOp")] = zc, $g[Q(9, 17, "addOp")] = yc, $g[Q(9, 17, "subtractOp")] = zc, $g[Q(7, 17, "addOp")] = yc, $g[Q(7, 17, "subtractOp")] = zc, $g[Q(7, 16, "addOp")] = yc, $g[Q(7, 16, "subtractOp")] = zc, $g[Q(8, 17, "addOp")] = yc, $g[Q(8, 17, "subtractOp")] = zc, $g); function bh(a, b) { return B(a, 5) && B(b, 5) ? 5 : B(a, 4) && B(b, 4) ? 4 : B(a, 6) && B(b, 6) ? 6 : 3 } var ch = [2, 16, 17, 9, 7, 8]; ++ function dh(a, b, c) { ++ function d(D, F) { return { U: e ? e(D) : D, V: f ? f(F) : F } } var e = null, f = null; B(b, 19) && (e = function (D) { return Pd(D, 3) }, b = 3); B(c, 19) && (f = function (D) { return Pd(D, 3) }, c = 3); var g = ch.filter(function (D) { return B(b, D) }), k = ch.filter(function (D) { return B(c, D) }); if (g.includes(2) && k.includes(2)) { var l = ah[Q(2, 2, a)], m = Zg[Q(2, 2, a)]; m || (m = bh(b, c)); "divOp" === a && 5 === m && (m = 4); return "idivOp" === a ? eh(d, l)[0] : function (D, F) { D = d(D, F); return w(l(D.U.value, D.V.value), m) } } g = p(g); for (var q = g.next(); !q.done; q = g.next()) { ++ q = ++ q.value; for (var u = {}, z = p(k), A = z.next(); !A.done; u = { nb: u.nb, qb: u.qb }, A = z.next())if (A = A.value, u.nb = ah[Q(q, A, a)], u.qb = Zg[Q(q, A, a)], u.nb && void 0 !== u.qb) return function (D) { return function (F, J) { F = d(F, J); return w(D.nb(F.U.value, F.V.value), D.qb) } }(u) ++ } ++ } ++ function fh(a, b, c) { ++ function d(u, z) { return { U: f ? f(u) : u, V: g ? g(z) : z } } var e = [2, 53, 59, 46, 47]; if (e.includes(b) || e.includes(c)) return 2; var f = null, g = null; B(b, 19) && (f = function (u) { return Pd(u, 3) }, b = 3); B(c, 19) && (g = function (u) { return Pd(u, 3) }, c = 3); var k = ch.filter(function (u) { return B(b, u) }); e = ch.filter(function (u) { return B(c, u) }); if (k.includes(2) && e.includes(2)) return e = Zg[Q(2, 2, a)], void 0 === e && (e = bh(b, c)), "divOp" === a && 5 === e && (e = 4), "idivOp" === a ? eh(d, function (u, z) { return Math.trunc(u / z) })[1] : e; k = p(k); for (var l = ++ k.next(); !l.done; l = k.next()) { l = l.value; for (var m = p(e), q = m.next(); !q.done; q = m.next())if (q = Zg[Q(l, q.value, a)], void 0 !== q) return q } ++ } ++ function eh(a, b) { return [function (c, d) { d = a(c, d); c = d.U; d = d.V; if (0 === d.value) throw Error("FOAR0001: Divisor of idiv operator cannot be (-)0"); if (Number.isNaN(c.value) || Number.isNaN(d.value) || !Number.isFinite(c.value)) throw Error("FOAR0002: One of the operands of idiv is NaN or the first operand is (-)INF"); return Number.isFinite(c.value) && !Number.isFinite(d.value) ? w(0, 5) : w(b(c.value, d.value), 5) }, 5] } var gh = Object.create(null); ++ function mh(a, b, c, d, e) { H.call(this, b.o.add(c.o), [b, c], { C: !1 }, !1, d); this.A = b; this.K = c; this.l = a; this.s = e } v(mh, H); ++ mh.prototype.h = function (a, b) { ++ var c = this; return Zc(I(this.A, a, b), b).M(function (d) { ++ return 0 === d.length ? C.empty() : Zc(I(c.K, a, b), b).M(function (e) { ++ if (0 === e.length) return C.empty(); if (1 < d.length || 1 < e.length) throw Error('XPTY0004: the operands of the "' + c.l + '" operator should be empty or singleton.'); var f = d[0]; e = e[0]; if (c.s && c.type) return C.m(c.s(f, e)); var g = f.type; var k = e.type, l = c.l, m = g + "~" + k + "~" + l, q = gh[m]; q || (q = gh[m] = dh(l, g, k)); g = q; if (!g) throw Error("XPTY0004: " + c.l + " not available for types " + mb[f.type] + ++ " and " + mb[e.type]); return C.m(g(f, e)) ++ }) ++ }) ++ }; function nh(a, b) { ++ for (var c = oh, d = !1, e = 1; e < a.length; e++)switch (a[e][0]) { ++ case "letClause": ph(b); var f = a[e], g = b, k = c, l = N(f, ["letClauseItem", "typedVariableBinding", "varName"]); l = Ug(l); f = N(f, ["letClauseItem", "letExpr"]); k = k(f[1], g); qh(g, l.localName, k); break; case "forClause": d = !0; ph(b); rh(a[e], b, c); break; case "whereClause": ph(b); g = a[e]; c(g, b); P(g, "type", { type: 0, g: 3 }); break; case "orderByClause": ph(b); break; case "returnClause": e = a[e]; g = c; c = N(e, ["*"]); b = g(c, b); P(c, "type", b); P(e, "type", b); c = b; if (!c) return { ++ type: 59, ++ g: 2 ++ }; d && (c = { type: c.type, g: 2 }); 59 !== c.type && P(a, "type", c); return c; default: c = c(a[e], b); if (!c) return { type: 59, g: 2 }; d && (c = { type: c.type, g: 2 }); 59 !== c.type && P(a, "type", c); return c ++ }if (0 < b.h) b.h--, b.o.pop(), b.v.pop(); else throw Error("Variable scope out of bound"); ++ } ++ function rh(a, b, c) { var d = Ug(N(a, ["forClauseItem", "typedVariableBinding", "varName"])); if (a = N(a, ["forClauseItem", "forExpr", "sequenceExpr"])) a = O(a, "*").map(function (e) { return c(e, b) }), a.includes(void 0) || a.includes(null) || (a = sh(a), 1 === a.length && qh(b, d.localName, a[0])) } function sh(a) { return a.filter(function (b, c, d) { return d.findIndex(function (e) { return e.type === b.type && e.g === b.g }) === c }) }; function th(a, b) { if (!b || !b.ia) return { type: 59, g: 2 }; var c = L(a, "functionName"), d = Ug(c), e = d.localName, f = d.prefix, g = d.namespaceURI; d = O(L(a, "arguments"), "*"); if (null === g) { f = b.ia.Ua({ localName: e, prefix: f }, d.length); if (!f) return { type: 59, g: 2 }; e = f.localName; g = f.namespaceURI; P(c, "URI", g); c[2] = e } b = b.ia.xa(g, e, d.length); if (!b || 63 === b.i.type) return { type: 59, g: 2 }; 59 !== b.i.type && P(a, "type", b.i); return b.i }; function uh(a) { var b = { type: 61, g: 3 }; P(a, "type", b); return b }; function vh(a, b) { if (!b || !b.ia) return { type: 59, g: 2 }; var c = L(a, "functionName"), d = Ug(c), e = d.localName, f = d.namespaceURI, g = d.prefix; d = Number(N(a, ["integerConstantExpr", "value"])[1]); if (!f) { f = b.ia.Ua({ localName: e, prefix: g }, d); if (!f) return { type: 59, g: 2 }; e = f.localName; f = f.namespaceURI; P(c, "URI", f) } b = b.ia.xa(f, e, d) || null; if (!b) return { type: 59, g: 2 }; 59 !== b.i.type && 63 !== b.i.type && P(a, "type", b.i); return b.i }; function wh(a, b) { ++ var c = O(a, "stepExpr"); if (!c) return { type: 59, g: 2 }; c = p(c); for (var d = c.next(); !d.done; d = c.next()) { ++ var e = d.value; d = b; var f = null; if (e) { ++ var g = O(e, "*"), k = ""; g = p(g); for (var l = g.next(); !l.done; l = g.next())switch (l = l.value, l[0]) { ++ case "filterExpr": f = M(N(l, ["*"]), "type"); break; case "xpathAxis": k = l[1]; b: { ++ switch (k) { ++ case "attribute": f = { type: 47, g: 2 }; break b; case "child": case "decendant": case "self": case "descendant-or-self": case "following-sibling": case "following": case "namespace": case "parent": case "ancestor": case "preceding-sibling": case "preceding": case "ancestor-or-self": f = ++ { type: 53, g: 2 }; break b ++ }f = void 0 ++ } break; case "nameTest": var m = Ug(l); if (null !== m.namespaceURI) break; if ("attribute" === k && !m.prefix) break; m = d.aa(m.prefix || ""); void 0 !== m && P(l, "URI", m); break; case "lookup": f = { type: 59, g: 2 } ++ }f && 59 !== f.type && P(e, "type", f) ++ } e = M(e, "type") ++ } e && 59 !== e.type && P(a, "type", e); return e ++ }; function xh(a) { var b = { type: 0, g: 3 }; P(a, "type", b); return b }; function yh(a, b, c) { 0 === b ? b = { type: 53, g: 2 } : 1 === b ? b = c[0] : c.includes(void 0) || c.includes(null) ? b = { type: 59, g: 2 } : (b = sh(c), b = 1 < b.length ? { type: 59, g: 2 } : { type: b[0].type, g: 2 }); b && 59 !== b.type && P(a, "type", b); return b }; function zh(a, b, c, d) { if (!b || c.includes(void 0)) return { type: 59, g: 2 }; for (var e = O(a, "typeswitchExprCaseClause"), f = 0; f < c.length; f++) { var g = L(e[f], "*"); switch (g[0]) { case "sequenceType": if (g = Ah(g, b, c[f])) return 59 !== g.type && P(a, "type", g), g; continue; case "sequenceTypeUnion": for (d = O(g, "*"), e = 0; 2 > e; e++)if (g = Ah(d[e], b, c[f])) return 59 !== g.type && P(a, "type", g), g; default: return { type: 59, g: 2 } } } 59 !== d.type && P(a, "type", d); return d } ++ function Ah(a, b, c) { var d = O(a, "*"), e = L(a, "atomicType"); if (!e) return { type: 59, g: 2 }; if (pb(M(e, "prefix") + ":" + e[2]) === b.type) if (1 === d.length) { if (3 === b.g) return c } else if (a = L(a, "occurrenceIndicator")[1], b.g === rb(a)) return c }; function Bh(a, b) { oh(a, b) } function oh(a, b) { var c = Ch.get(a[0]); if (c) return c(a, b); for (c = 1; c < a.length; c++)a[c] && oh(a[c], b) } function Dh(a, b) { var c = oh(L(a, "firstOperand")[1], b), d = oh(L(a, "secondOperand")[1], b); var e = a[0]; if (c && d) if (b = fh(e, c.type, d.type)) c = { type: b, g: c.g }, 2 !== b && 59 !== b && P(a, "type", c), a = c; else throw Error("XPTY0004: " + e + " not available for types " + ob(c) + " and " + ob(d)); else a = { type: 2, g: 3 }; return a } ++ function Eh(a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); a: { switch (a[0]) { case "orOp": b = { type: 0, g: 3 }; P(a, "type", b); a = b; break a; case "andOp": b = { type: 0, g: 3 }; P(a, "type", b); a = b; break a }a = void 0 } return a } ++ function Fh(a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); a: { switch (a[0]) { case "unionOp": b = { type: 53, g: 2 }; P(a, "type", b); a = b; break a; case "intersectOp": b = { type: 53, g: 2 }; P(a, "type", b); a = b; break a; case "exceptOp": b = { type: 53, g: 2 }; P(a, "type", b); a = b; break a }a = void 0 } return a } function Gh(a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); b = { type: 0, g: 3 }; P(a, "type", b); return b } ++ function Hh(a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); b = M(N(a, ["firstOperand", "*"]), "type"); var c = M(N(a, ["secondOperand", "*"]), "type"); b = { type: 0, g: ed(b) || ed(c) ? 0 : 3 }; P(a, "type", b); return b } function Ih(a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); b = M(N(a, ["firstOperand", "*"]), "type"); var c = M(N(a, ["secondOperand", "*"]), "type"); b = { type: 0, g: ed(b) || ed(c) ? 0 : 3 }; P(a, "type", b); return b } ++ var Ch = new Map([["unaryMinusOp", function (a, b) { b = oh(L(a, "operand")[1], b); b ? B(b.type, 2) ? (b = { type: b.type, g: b.g }, P(a, "type", b), a = b) : (b = { type: 3, g: 3 }, P(a, "type", b), a = b) : (b = { type: 2, g: 2 }, P(a, "type", b), a = b); return a }], ["unaryPlusOp", function (a, b) { b = oh(L(a, "operand")[1], b); b ? B(b.type, 2) ? (b = { type: b.type, g: b.g }, P(a, "type", b), a = b) : (b = { type: 3, g: 3 }, P(a, "type", b), a = b) : (b = { type: 2, g: 2 }, P(a, "type", b), a = b); return a }], ["addOp", Dh], ["subtractOp", Dh], ["divOp", Dh], ["idivOp", Dh], ["modOp", Dh], ["multiplyOp", Dh], ["andOp", Eh], ++ ["orOp", Eh], ["sequenceExpr", function (a, b) { var c = O(a, "*"), d = c.map(function (e) { return oh(e, b) }); return yh(a, c.length, d) }], ["unionOp", Fh], ["intersectOp", Fh], ["exceptOp", Fh], ["stringConcatenateOp", function (a, b) { oh(L(a, "firstOperand")[1], b); oh(L(a, "secondOperand")[1], b); b = { type: 1, g: 3 }; P(a, "type", b); return b }], ["rangeSequenceExpr", function (a, b) { oh(L(a, "startExpr")[1], b); oh(L(a, "endExpr")[1], b); b = { type: 5, g: 1 }; P(a, "type", b); return b }], ["equalOp", Gh], ["notEqualOp", Gh], ["lessThanOrEqualOp", Gh], ["lessThanOp", ++ Gh], ["greaterThanOrEqualOp", Gh], ["greaterThanOp", Gh], ["eqOp", Hh], ["neOp", Hh], ["ltOp", Hh], ["leOp", Hh], ["gtOp", Hh], ["geOp", Hh], ["isOp", Ih], ["nodeBeforeOp", Ih], ["nodeAfterOp", Ih], ["pathExpr", function (a, b) { var c = L(a, "rootExpr"); c && c[1] && oh(c[1], b); O(a, "stepExpr").map(function (d) { return oh(d, b) }); return wh(a, b) }], ["contextItemExpr", function () { return { type: 59, g: 2 } }], ["ifThenElseExpr", function (a, b) { ++ oh(L(L(a, "ifClause"), "*"), b); var c = oh(L(L(a, "thenClause"), "*"), b); b = oh(L(L(a, "elseClause"), "*"), b); c && b ? c.type === ++ b.type && c.g === b.g ? (59 !== c.type && P(a, "type", c), a = c) : a = { type: 59, g: 2 } : a = { type: 59, g: 2 }; return a ++ }], ["instanceOfExpr", function (a, b) { oh(L(a, "argExpr"), b); oh(L(a, "sequenceType"), b); b = { type: 0, g: 3 }; P(a, "type", b); return b }], ["integerConstantExpr", function (a) { var b = { type: 5, g: 3 }; P(a, "type", b); return b }], ["doubleConstantExpr", function (a) { var b = { type: 3, g: 3 }; P(a, "type", b); return b }], ["decimalConstantExpr", function (a) { var b = { type: 4, g: 3 }; P(a, "type", b); return b }], ["stringConstantExpr", function (a) { ++ var b = { type: 1, g: 3 }; ++ P(a, "type", b); return b ++ }], ["functionCallExpr", function (a, b) { var c = L(a, "arguments"); O(c, "*").map(function (d) { return oh(d, b) }); return th(a, b) }], ["arrowExpr", function (a, b) { oh(L(a, "argExpr")[1], b); return Xg(a, b) }], ["dynamicFunctionInvocationExpr", function (a, b) { oh(N(a, ["functionItem", "*"]), b); (a = L(a, "arguments")) && oh(a, b); return { type: 59, g: 2 } }], ["namedFunctionRef", function (a, b) { return vh(a, b) }], ["inlineFunctionExpr", function (a, b) { oh(L(a, "functionBody")[1], b); b = { type: 60, g: 3 }; P(a, "type", b); return b }], ["castExpr", ++ function (a) { var b = N(a, ["singleType", "atomicType"]); b = { type: pb(M(b, "prefix") + ":" + b[2]), g: 3 }; 59 !== b.type && P(a, "type", b); return b }], ["castableExpr", function (a) { var b = { type: 0, g: 3 }; P(a, "type", b); return b }], ["simpleMapExpr", function (a, b) { for (var c = O(a, "pathExpr"), d, e = 0; e < c.length; e++)d = oh(c[e], b); void 0 !== d && null !== d ? ((b = { type: d.type, g: 2 }, 59 !== b.type) && P(a, "type", b), a = b) : a = { type: 59, g: 2 }; return a }], ["mapConstructor", function (a, b) { ++ O(a, "mapConstructorEntry").map(function (c) { ++ return { ++ key: oh(N(c, ["mapKeyExpr", ++ "*"]), b), value: oh(N(c, ["mapValueExpr", "*"]), b) ++ } ++ }); return uh(a) ++ }], ["arrayConstructor", function (a, b) { O(L(a, "*"), "arrayElem").map(function (c) { return oh(c, b) }); return Wg(a) }], ["unaryLookup", function (a) { L(a, "NCName"); return { type: 59, g: 2 } }], ["typeswitchExpr", function (a, b) { var c = oh(L(a, "argExpr")[1], b), d = O(a, "typeswitchExprCaseClause").map(function (f) { return oh(N(f, ["resultExpr"])[1], b) }), e = oh(N(a, ["typeswitchExprDefaultClause", "resultExpr"])[1], b); return zh(a, c, d, e) }], ["quantifiedExpr", function (a, b) { ++ O(a, ++ "*").map(function (c) { return oh(c, b) }); return xh(a) ++ }], ["x:stackTrace", function (a, b) { a = O(a, "*"); return oh(a[0], b) }], ["queryBody", function (a, b) { return oh(a[1], b) }], ["flworExpr", function (a, b) { return nh(a, b) }], ["varRef", function (a, b) { var c = Ug(L(a, "name")), d; a: { for (d = b.h; 0 <= d; d--) { var e = b.o[d][c.localName]; if (e) { d = e; break a } } d = void 0 } d && 59 !== d.type && P(a, "type", d); null === c.namespaceURI && (b = b.aa(c.prefix), void 0 !== b && P(a, "URI", b)); return d }]]); function Jh(a) { this.h = 0; this.ia = a; this.o = [{}]; this.v = [{}] } function qh(a, b, c) { if (a.o[a.h][b]) throw Error("Another variable of in the scope " + a.h + " with the same name " + b + " already exists"); a.o[a.h][b] = c } function ph(a) { a.h++; a.o.push({}); a.v.push({}) } Jh.prototype.aa = function (a) { for (var b = this.h; 0 <= b; b--) { var c = this.v[b][a]; if (void 0 !== c) return c } return this.ia ? this.ia.aa(a) : void 0 }; function Kh(a, b) { var c = {}; H.call(this, new Hf((c.external = 1, c)), a, { C: a.every(function (d) { return d.C }) }, !1, b); this.l = a } v(Kh, H); Kh.prototype.h = function (a, b) { return 0 === this.l.length ? C.m(new Yb([])) : I(this.l[0], a, b).M(function (c) { return C.m(new Yb(c.map(function (d) { return yb(C.m(d)) }))) }) }; function Lh(a, b) { var c = {}; H.call(this, new Hf((c.external = 1, c)), a, { C: a.every(function (d) { return d.C }) }, !1, b); this.l = a } v(Lh, H); Lh.prototype.h = function (a, b) { return C.m(new Yb(this.l.map(function (c) { return yb(I(c, a, b)) }))) }; function Mh(a) { if (null === a) throw Rc("context is absent, it needs to be present to use axes."); if (!B(a.type, 53)) throw Error("XPTY0020: Axes can only be applied to nodes."); return a.value }; function Nh(a, b, c) { var d = b; return { next: function () { if (!d) return x; var e = d; d = Vb(a, e, c); return y($b(e)) } } } function Oh(a, b) { b = b || { Ra: !1 }; H.call(this, a.o, [a], { R: "reverse-sorted", X: !1, subtree: !1, C: !1 }); this.l = a; this.s = !!b.Ra } v(Oh, H); Oh.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); var d = this.l.B(); d = d && (d.startsWith("name-") || "type-1" === d) ? "type-1" : null; return C.create(Nh(b, this.s ? a : Vb(b, a, d), d)).filter(function (e) { return c.l.l(e) }) }; var Ph = new Map([["type-1-or-type-2", ["name", "type-1", "type-2"]], ["type-1", ["name"]], ["type-2", ["name"]]]); function Qh(a, b) { if (null === a) return b; if (null === b || a === b) return a; var c = a.startsWith("name-") ? "name" : a, d = b.startsWith("name-") ? "name" : b, e = Ph.get(c); if (void 0 !== e && e.includes(d)) return b; b = Ph.get(d); return void 0 !== b && b.includes(c) ? a : "empty" }; function Rh(a, b) { var c = {}; H.call(this, new Hf((c.attribute = 1, c)), [a], { R: "unsorted", subtree: !0, X: !0, C: !1 }); this.l = a; this.s = Qh(this.l.B(), b) } v(Rh, H); Rh.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); if (1 !== a.node.nodeType) return C.empty(); a = Nb(b, a, this.s).filter(function (d) { return "http://www.w3.org/2000/xmlns/" !== d.node.namespaceURI }).map(function (d) { return $b(d) }).filter(function (d) { return c.l.l(d) }); return C.create(a) }; Rh.prototype.B = function () { return "type-1" }; function Sh(a, b) { H.call(this, a.o, [a], { R: "sorted", subtree: !0, X: !0, C: !1 }); this.s = a; this.l = Qh(b, a.B()) } v(Sh, H); Sh.prototype.h = function (a, b) { var c = this, d = b.h, e = Mh(a.N); a = e.node.nodeType; if (1 !== a && 9 !== a) return C.empty(); var f = null, g = !1; return C.create({ next: function () { for (; !g;) { if (!f) { f = Sb(d, e, c.l); if (!f) { g = !0; continue } return y($b(f)) } if (f = Ub(d, f, c.l)) return y($b(f)); g = !0 } return x } }).filter(function (k) { return c.s.l(k) }) }; function Th(a, b, c) { var d = b.node.nodeType; if (1 !== d && 9 !== d) return { next: function () { return x } }; var e = Sb(a, b, c); return { next: function () { if (!e) return x; var f = e; e = Ub(a, e, c); return y(f) } } }; function Uh(a, b, c) { var d = [Qd(b)]; return { next: function (e) { 0 < d.length && 0 !== (e & 1) && d.shift(); if (!d.length) return x; for (e = d[0].next(0); e.done;) { d.shift(); if (!d.length) return x; e = d[0].next(0) } d.unshift(Th(a, e.value, c)); return y($b(e.value)) } } } function Vh(a, b) { b = b || { Ra: !1 }; H.call(this, a.o, [a], { C: !1, X: !1, R: "sorted", subtree: !0 }); this.l = a; this.s = !!b.Ra; this.A = (a = this.l.B()) && (a.startsWith("name-") || "type-1" === a) || "type-1-or-type-2" === a ? "type-1" : null } v(Vh, H); ++ Vh.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); a = Uh(b, a, this.A); this.s || a.next(0); return C.create(a).filter(function (d) { return c.l.l(d) }) }; function Wh(a, b, c) { var d = a.node.nodeType; if (1 !== d && 9 !== d) return a; for (d = Tb(b, a, c); null !== d;) { if (1 !== d.node.nodeType) return d; a = d; d = Tb(b, a, c) } return a } ++ function Xh(a, b, c, d) { if (void 0 === c ? 0 : c) { var e = b, f = !1; return { next: function () { if (f) return x; if (Sd(e, b)) return e = Wh(b, a, d), Sd(e, b) ? (f = !0, x) : y($b(e)); var k = e.node.nodeType, l = 9 === k || 2 === k ? null : Wb(a, e, d); if (null !== l) return e = Wh(l, a, d), y($b(e)); e = 9 === k ? null : Vb(a, e, d); return Sd(e, b) ? (f = !0, x) : y($b(e)) } } } var g = [Th(a, b, d)]; return { next: function () { if (!g.length) return x; for (var k = g[0].next(0); k.done;) { g.shift(); if (!g.length) return x; k = g[0].next(0) } g.unshift(Th(a, k.value, d)); return y($b(k.value)) } } }; function Yh(a, b, c) { for (var d = []; b && 9 !== b.node.nodeType; b = Vb(a, b, null)) { var e = Ub(a, b, c); e && d.push(e) } var f = null; return { next: function () { for (; f || d.length;) { if (!f) { f = Xh(a, d[0], !1, c); var g = y($b(d[0])), k = Ub(a, d[0], c); k ? d[0] = k : d.shift(); return g } g = f.next(0); if (g.done) f = null; else return g } return x } } } function Zh(a) { H.call(this, a.o, [a], { R: "sorted", X: !0, subtree: !1, C: !1 }); this.l = a; this.s = (a = this.l.B()) && (a.startsWith("name-") || "type-1" === a) ? "type-1" : null } v(Zh, H); ++ Zh.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); return C.create(Yh(b, a, this.s)).filter(function (d) { return c.l.l(d) }) }; function $h(a, b, c) { return { next: function () { return (b = b && Ub(a, b, c)) ? y($b(b)) : x } } } function ai(a, b) { H.call(this, a.o, [a], { R: "sorted", X: !0, subtree: !1, C: !1 }); this.l = a; this.s = Qh(this.l.B(), b) } v(ai, H); ai.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); return C.create($h(b, a, this.s)).filter(function (d) { return c.l.l(d) }) }; function bi(a, b) { H.call(this, a.o, [a], { R: "reverse-sorted", X: !0, subtree: !0, C: !1 }); this.l = a; this.s = Qh(b, this.l.B()) } v(bi, H); bi.prototype.h = function (a, b) { b = b.h; a = Mh(a.N); a = Vb(b, a, this.s); if (!a) return C.empty(); a = $b(a); return this.l.l(a) ? C.m(a) : C.empty() }; function ci(a, b, c) { for (var d = []; b && 9 !== b.node.nodeType; b = Vb(a, b, null)) { var e = Wb(a, b, c); null !== e && d.push(e) } var f = null; return { next: function () { for (; f || d.length;) { f || (f = Xh(a, d[0], !0, c)); var g = f.next(0); if (g.done) { f = null; g = Wb(a, d[0], c); var k = y($b(d[0])); null === g ? d.shift() : d[0] = g; return k } return g } return x } } } function di(a) { H.call(this, a.o, [a], { C: !1, X: !0, R: "reverse-sorted", subtree: !1 }); this.l = a; this.s = (a = this.l.B()) && (a.startsWith("name-") || "type-1" === a) ? "type-1" : null } v(di, H); ++ di.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); return C.create(ci(b, a, this.s)).filter(function (d) { return c.l.l(d) }) }; function ei(a, b, c) { return { next: function () { return (b = b && Wb(a, b, c)) ? y($b(b)) : x } } } function fi(a, b) { H.call(this, a.o, [a], { C: !1, X: !0, R: "reverse-sorted", subtree: !1 }); this.l = a; this.s = Qh(this.l.B(), b) } v(fi, H); fi.prototype.h = function (a, b) { var c = this; b = b.h; a = Mh(a.N); return C.create(ei(b, a, this.s)).filter(function (d) { return c.l.l(d) }) }; function gi(a, b) { H.call(this, a.o, [a], { R: "sorted", subtree: !0, X: !0, C: !1 }); this.l = a; this.s = Qh(this.l.B(), b) } v(gi, H); gi.prototype.h = function (a) { Mh(a.N); return this.l.l(a.N) ? C.m(a.N) : C.empty() }; gi.prototype.B = function () { return this.s }; function hi(a, b, c, d) { var e = a.o.add(b.o).add(c.o); Df.call(this, e, [a, b, c], { C: a.C && b.C && c.C, X: b.X === c.X && b.X, R: b.da === c.da ? b.da : "unsorted", subtree: b.subtree === c.subtree && b.subtree }, d); this.l = a } v(hi, Df); hi.prototype.A = function (a, b, c) { var d = null, e = c[0](a); return C.create({ next: function (f) { d || (d = (e.ha() ? c[1](a) : c[2](a)).value); return d.next(f) } }) }; hi.prototype.v = function (a) { Df.prototype.v.call(this, a); if (this.l.I) throw af(); }; function ii(a, b, c) { this.location = a; this.o = b; this.h = c } function qg(a) { return a.h instanceof Error ? a.location : qg(a.h) } function rg(a) { var b = a.h instanceof og ? ["Inner error:", a.h.message] : a.h instanceof Error ? [a.h.toString()] : rg(a.h); b.push(" at <" + a.o + ">:" + a.location.start.line + ":" + a.location.start.ja + " - " + a.location.end.line + ":" + a.location.end.ja); return b }; function ji(a, b, c) { Df.call(this, c.o, [c], { C: c.C, X: c.X, R: c.da, subtree: c.subtree }); this.l = b; this.K = { end: { ja: a.end.ja, line: a.end.line, offset: a.end.offset }, start: { ja: a.start.ja, line: a.start.line, offset: a.start.offset } } } v(ji, Df); ji.prototype.A = function (a, b, c) { var d = this; b = p(c).next().value; try { var e = b(a) } catch (f) { throw new ii(this.K, this.l, f); } return C.create({ next: function (f) { try { return e.value.next(f) } catch (g) { throw new ii(d.K, d.l, g); } } }) }; ++ ji.prototype.v = function (a) { try { Df.prototype.v.call(this, a) } catch (b) { throw new ii(this.K, this.l, b); } }; function ki(a, b, c, d) { H.call(this, a, b, c, !0); this.l = d; this.I = this.l.I } v(ki, H); function li(a, b, c, d) { var e = [], f = a.K(b, c, d, function (k) { if (a.l instanceof ki) { var l = li(a.l, b, k, d); return Cf(l, function (q) { return e = q }) } var m = null; return C.create({ next: function () { for (; ;) { if (!m) { var q = k.next(0); if (q.done) return x; q = a.l.s(q.value, d); m = Cf(q, function (u) { return e = zf(e, u) }).value } q = m.next(0); if (q.done) m = null; else return q } } }) }), g = !1; return { next: function () { if (g) return x; var k = f.O(); g = !0; return y(new nf(k, e)) } } } ++ ki.prototype.h = function (a, b) { var c = this; return this.K(a, Qd(a), b, function (d) { if (c.l instanceof ki) return mi(c.l, a, d, b); var e = null; return C.create({ next: function (f) { for (; ;) { if (!e) { var g = d.next(0); if (g.done) return x; e = I(c.l, g.value, b).value } g = e.next(f); if (g.done) e = null; else return g } } }) }) }; ki.prototype.s = function (a, b) { return li(this, a, Qd(a), b) }; ++ ki.prototype.v = function (a) { H.prototype.v.call(this, a); this.I = this.l.I; a = p(this.ta); for (var b = a.next(); !b.done; b = a.next())if (b = b.value, b !== this.l && b.I) throw af(); }; function mi(a, b, c, d) { return a.K(b, c, d, function (e) { if (a.l instanceof ki) return mi(a.l, b, e, d); var f = null; return C.create({ next: function () { for (; ;) { if (!f) { var g = e.next(0); if (g.done) return x; f = I(a.l, g.value, d).value } g = f.next(0); if (g.done) f = null; else return g } } }) }) }; function ni(a, b, c, d) { ki.call(this, b.o.add(d.o), [b, d], { C: !1 }, d); this.S = a.prefix; this.la = a.namespaceURI; this.Xb = a.localName; this.Gb = null; this.A = c; this.Ba = null; this.pa = b } v(ni, ki); ++ ni.prototype.K = function (a, b, c, d) { var e = this, f = null, g = null, k = 0; return d({ next: function () { for (var l = {}; ;) { if (!f) { var m = b.next(0); if (m.done) return x; g = m.value; k = 0; f = I(e.pa, g, c).value } l.mb = f.next(0); if (l.mb.done) f = null; else return k++, m = {}, l = (m[e.Gb] = function (q) { return function () { return C.m(q.mb.value) } }(l), m), e.Ba && (l[e.Ba] = function () { return C.m(new jb(5, k)) }), y(Nc(g, l)); l = { mb: l.mb } } } }) }; ++ ni.prototype.v = function (a) { ++ if (this.S && (this.la = a.aa(this.S), !this.la && this.S)) throw Error("XPST0081: Could not resolve namespace for prefix " + this.S + " in a for expression"); this.pa.v(a); Mg(a); this.Gb = Qg(a, this.la, this.Xb); if (this.A) { if (this.A.prefix && (this.A.namespaceURI = a.aa(this.A.prefix), !this.A.namespaceURI && this.A.prefix)) throw Error("XPST0081: Could not resolve namespace for prefix " + this.S + " in the positionalVariableBinding in a for expression"); this.Ba = Qg(a, this.A.namespaceURI, this.A.localName) } this.l.v(a); ++ Sg(a); if (this.pa.I) throw af(); this.l.I && (this.I = !0) ++ }; function oi(a, b, c) { var d = {}; H.call(this, new Hf((d.external = 1, d)), [c], { C: !1, R: "unsorted" }); this.S = a.map(function (e) { return e.name }); this.A = a.map(function (e) { return e.type }); this.s = null; this.K = b; this.l = c } v(oi, H); ++ oi.prototype.h = function (a, b) { var c = this, d = new Ab({ j: this.A, arity: this.A.length, Sa: !0, I: this.l.I, localName: "dynamic-function", namespaceURI: "", i: this.K, value: function (e, f, g) { var k = Aa.apply(3, arguments), l = Nc(Jc(a, -1, null, C.empty()), c.s.reduce(function (m, q, u) { m[q] = yb(k[u]); return m }, Object.create(null))); return I(c.l, l, b) } }); return C.m(d) }; ++ oi.prototype.v = function (a) { Mg(a); this.s = this.S.map(function (b) { return Qg(a, b.namespaceURI, b.localName) }); this.l.v(a); Sg(a); if (this.l.I) throw Error("Not implemented: inline functions can not yet be updating."); }; function pi(a, b, c) { ki.call(this, b.o.add(c.o), [b, c], { C: !1, X: c.X, R: c.da, subtree: c.subtree }, c); if (a.prefix || a.namespaceURI) throw Error("Not implemented: let expressions with namespace usage."); this.A = a.prefix; this.S = a.namespaceURI; this.Ba = a.localName; this.la = b; this.pa = null } v(pi, ki); pi.prototype.K = function (a, b, c, d) { var e = this; return d({ next: function () { var f = b.next(0); if (f.done) return x; f = f.value; var g = {}; f = Nc(f, (g[e.pa] = yb(I(e.la, f, c)), g)); return y(f) } }) }; ++ pi.prototype.v = function (a) { if (this.A && (this.S = a.aa(this.A), !this.S && this.A)) throw Error("XPST0081: Could not resolve namespace for prefix " + this.A + " using in a for expression"); this.la.v(a); Mg(a); this.pa = Qg(a, this.S, this.Ba); this.l.v(a); Sg(a); this.I = this.l.I; if (this.la.I) throw af(); }; function qi(a, b) { H.call(this, new Hf({}), [], { C: !0, R: "sorted" }, !1, b); switch (b.type) { case 5: var c = w(parseInt(a, 10), b.type); break; case 1: c = w(a, b.type); break; case 4: case 3: c = w(parseFloat(a), b.type); break; default: throw new TypeError("Type " + b + " not expected in a literal"); }this.l = function () { return C.m(c) } } v(qi, H); qi.prototype.h = function () { return this.l() }; function ri(a, b) { var c = {}; H.call(this, new Hf((c.external = 1, c)), a.reduce(function (d, e) { return d.concat(e.key, e.value) }, []), { C: !1 }, !1, b); this.l = a } v(ri, H); ri.prototype.h = function (a, b) { var c = this, d = this.l.map(function (e) { return Zc(I(e.key, a, b), b).Z({ default: function () { throw Error("XPTY0004: A key of a map should be a single atomizable value."); }, m: function (f) { return f } }) }); return ac(d, function (e) { return C.m(new dc(e.map(function (f, g) { return { key: f, value: yb(I(c.l[g].value, a, b)) } }))) }) }; function si(a, b, c) { var d = {}; H.call(this, new Hf((d.external = 1, d)), [], { C: !0 }, !1, c); this.s = b; this.A = a; this.l = null } v(si, H); si.prototype.h = function () { var a = new Ab({ j: this.l.j, I: this.l.I, arity: this.s, localName: this.l.localName, namespaceURI: this.l.namespaceURI, i: this.l.i, value: this.l.callFunction }); return C.m(a) }; ++ si.prototype.v = function (a) { ++ var b = this.A.namespaceURI, c = this.A.localName, d = this.A.prefix; if (null === b) { var e = a.Ua({ localName: c, prefix: d }, this.s); if (!e) throw Error("XPST0017: The function " + (d ? d + ":" : "") + c + " with arity " + this.s + " could not be resolved. " + ug(c)); b = e.namespaceURI; c = e.localName } this.l = a.xa(b, c, this.s) || null; if (!this.l) throw a = this.A, Error("XPST0017: Function " + ((a.namespaceURI ? "Q{" + a.namespaceURI + "}" : a.prefix ? a.prefix + ":" : "") + a.localName) + " with arity of " + this.s + " not registered. " + ug(c)); ++ H.prototype.v.call(this, a) ++ }; var ti = {}, ui = (ti[5] = 5, ti[27] = 5, ti[28] = 5, ti[31] = 5, ti[32] = 5, ti[33] = 5, ti[34] = 5, ti[30] = 5, ti[36] = 5, ti[35] = 5, ti[38] = 5, ti[37] = 5, ti[29] = 5, ti[4] = 4, ti[6] = 6, ti[3] = 3, ti); function vi(a, b, c) { H.call(this, b.o, [b], { C: !1 }, !1, c); this.s = b; this.l = a } v(vi, H); ++ vi.prototype.h = function (a, b) { var c = this; return Zc(I(this.s, a, b), b).M(function (d) { if (0 === d.length) return C.empty(); var e = d[0]; if (c.type) return d = "+" === c.l ? +e.value : -e.value, 0 === e.type && (d = Number.NaN), C.m(w(d, c.type.type)); if (1 < d.length) throw Error("XPTY0004: The operand to a unary operator must be a sequence with a length less than one"); return B(e.type, 19) ? (e = Pd(e, 3).value, C.m(w("+" === c.l ? e : -e, 3))) : B(e.type, 2) ? "+" === c.l ? C.m(e) : C.m(w(-1 * e.value, ui[e.type])) : C.m(w(Number.NaN, 3)) }) }; function wi(a, b) { H.call(this, a.reduce(function (c, d) { return c.add(d.o) }, new Hf({})), a, { C: a.every(function (c) { return c.C }) }, !1, b); this.l = a; this.s = a.reduce(function (c, d) { return Qh(c, d.B()) }, null) } v(wi, H); ++ wi.prototype.h = function (a, b) { var c = this, d = 0, e = null, f = !1, g = null; if (null !== a) { var k = a.N; null !== k && B(k.type, 53) && (g = Eb(k.value)) } return C.create({ next: function () { if (!f) { for (; d < c.l.length;) { if (!e) { var l = c.l[d]; if (null !== g && null !== l.B() && !g.includes(l.B())) return d++, f = !0, y(db); e = I(l, a, b) } if (!1 === e.ha()) return f = !0, y(db); e = null; d++ } f = !0; return y(cb) } return x } }) }; wi.prototype.B = function () { return this.s }; function xi(a, b) { var c = a.reduce(function (e, f) { return 0 < If(e, f.o) ? e : f.o }, new Hf({})); H.call(this, c, a, { C: a.every(function (e) { return e.C }) }, !1, b); var d; for (b = 0; b < a.length; ++b) { void 0 === d && (d = a[b].B()); if (null === d) break; if (d !== a[b].B()) { d = null; break } } this.s = d; this.l = a } v(xi, H); ++ xi.prototype.h = function (a, b) { var c = this, d = 0, e = null, f = !1, g = null; if (null !== a) { var k = a.N; null !== k && B(k.type, 53) && (g = Eb(k.value)) } return C.create({ next: function () { if (!f) { for (; d < c.l.length;) { if (!e) { var l = c.l[d]; if (null !== g && null !== l.B() && !g.includes(l.B())) { d++; continue } e = I(l, a, b) } if (!0 === e.ha()) return f = !0, y(cb); e = null; d++ } f = !0; return y(db) } return x } }) }; xi.prototype.B = function () { return this.s }; function yi(a, b) { var c; return C.create({ next: function (d) { for (; ;) { if (!c) { var e = a.value.next(d); if (e.done) return x; c = Yc(e.value, b) } e = c.value.next(d); if (e.done) c = null; else return e } } }) }; function zi(a, b) { if ("eqOp" === a) return function (c, d) { d = b(c, d); c = d.U; d = d.V; return c.value.namespaceURI === d.value.namespaceURI && c.value.localName === d.value.localName }; if ("neOp" === a) return function (c, d) { d = b(c, d); c = d.U; d = d.V; return c.value.namespaceURI !== d.value.namespaceURI || c.value.localName !== d.value.localName }; throw Error('XPTY0004: Only the "eq" and "ne" comparison is defined for xs:QName'); } ++ function Ai(a, b) { switch (a) { case "eqOp": return function (c, d) { c = b(c, d); return c.U.value === c.V.value }; case "neOp": return function (c, d) { c = b(c, d); return c.U.value !== c.V.value }; case "ltOp": return function (c, d) { c = b(c, d); return c.U.value < c.V.value }; case "leOp": return function (c, d) { c = b(c, d); return c.U.value <= c.V.value }; case "gtOp": return function (c, d) { c = b(c, d); return c.U.value > c.V.value }; case "geOp": return function (c, d) { c = b(c, d); return c.U.value >= c.V.value } } } ++ function Bi(a, b) { switch (a) { case "ltOp": return function (c, d) { c = b(c, d); return c.U.value.ga < c.V.value.ga }; case "leOp": return function (c, d) { d = b(c, d); c = d.U; d = d.V; return fc(c.value, d.value) || c.value.ga < d.value.ga }; case "gtOp": return function (c, d) { c = b(c, d); return c.U.value.ga > c.V.value.ga }; case "geOp": return function (c, d) { d = b(c, d); c = d.U; d = d.V; return fc(c.value, d.value) || c.value.ga > d.value.ga } } } ++ function Ci(a, b) { switch (a) { case "eqOp": return function (c, d) { c = b(c, d); return fc(c.U.value, c.V.value) }; case "ltOp": return function (c, d) { c = b(c, d); return c.U.value.ea < c.V.value.ea }; case "leOp": return function (c, d) { d = b(c, d); c = d.U; d = d.V; return fc(c.value, d.value) || c.value.ea < d.value.ea }; case "gtOp": return function (c, d) { c = b(c, d); return c.U.value.ea > c.V.value.ea }; case "geOp": return function (c, d) { d = b(c, d); c = d.U; d = d.V; return fc(c.value, d.value) || c.value.ea > d.value.ea } } } ++ function Di(a, b) { switch (a) { case "eqOp": return function (c, d) { c = b(c, d); return fc(c.U.value, c.V.value) }; case "neOp": return function (c, d) { c = b(c, d); return !fc(c.U.value, c.V.value) } } } ++ function Ei(a, b) { ++ switch (a) { ++ case "eqOp": return function (c, d, e) { c = b(c, d); return wc(c.U.value, c.V.value, Lc(e)) }; case "neOp": return function (c, d, e) { c = b(c, d); return !wc(c.U.value, c.V.value, Lc(e)) }; case "ltOp": return function (c, d, e) { c = b(c, d); e = Lc(e); return 0 > vc(c.U.value, c.V.value, e) }; case "leOp": return function (c, d, e) { d = b(c, d); c = d.U; d = d.V; var f; (f = wc(c.value, d.value, Lc(e))) || (e = Lc(e), f = 0 > vc(c.value, d.value, e)); return f }; case "gtOp": return function (c, d, e) { ++ c = b(c, d); e = Lc(e); return 0 < vc(c.U.value, c.V.value, ++ e) ++ }; case "geOp": return function (c, d, e) { d = b(c, d); c = d.U; d = d.V; var f; (f = wc(c.value, d.value, Lc(e))) || (e = Lc(e), f = 0 < vc(c.value, d.value, e)); return f } ++ } ++ } function Fi(a, b) { switch (a) { case "eqOp": return function (c, d, e) { c = b(c, d); return wc(c.U.value, c.V.value, Lc(e)) }; case "neOp": return function (c, d, e) { c = b(c, d); return !wc(c.U.value, c.V.value, Lc(e)) } } } ++ function Gi(a, b, c) { ++ function d(m, q) { return { U: g ? g(m) : m, V: k ? k(q) : q } } function e(m) { return B(b, m) && B(c, m) } function f(m) { return 0 < m.filter(function (q) { return B(b, q) }).length && 0 < m.filter(function (q) { return B(c, q) }).length } var g = null, k = null; B(b, 19) && B(c, 19) ? b = c = 1 : B(b, 19) ? (g = function (m) { return Pd(m, c) }, b = c) : B(c, 19) && (k = function (m) { return Pd(m, b) }, c = b); if (B(b, 23) && B(c, 23)) return zi(a, d); if (e(0) || f([1, 47, 61]) || f([2, 47, 61]) || e(20) || e(22) || e(21) || f([1, 20])) { var l = Ai(a, d); if (void 0 !== l) return l } if (e(16) && (l = Bi(a, ++ d), void 0 !== l) || e(17) && (l = Ci(a, d), void 0 !== l) || e(18) && (l = Di(a, d), void 0 !== l)) return l; if (e(9) || e(7) || e(8)) if (l = Ei(a, d), void 0 !== l) return l; if (e(11) || e(12) || e(13) || e(14) || e(15)) if (l = Fi(a, d), void 0 !== l) return l; throw Error("XPTY0004: " + a + " not available for " + mb[b] + " and " + mb[c]); ++ } var Hi = Object.create(null); function Ii(a, b, c) { var d = b + "~" + c + "~" + a, e = Hi[d]; e || (e = Hi[d] = Gi(a, b, c)); return e } function Ji(a, b, c) { H.call(this, b.o.add(c.o), [b, c], { C: !1 }); this.l = b; this.A = c; this.s = a } v(Ji, H); ++ Ji.prototype.h = function (a, b) { var c = this, d = I(this.l, a, b), e = I(this.A, a, b), f = yi(d, b), g = yi(e, b); return f.Z({ empty: function () { return C.empty() }, m: function () { return g.Z({ empty: function () { return C.empty() }, m: function () { var k = f.first(), l = g.first(); return Ii(c.s, k.type, l.type)(k, l, a) ? C.ba() : C.W() }, multiple: function () { throw Error("XPTY0004: Sequences to compare are not singleton."); } }) }, multiple: function () { throw Error("XPTY0004: Sequences to compare are not singleton."); } }) }; var Ki = {}, Li = (Ki.equalOp = "eqOp", Ki.notEqualOp = "neOp", Ki.lessThanOrEqualOp = "leOp", Ki.lessThanOp = "ltOp", Ki.greaterThanOrEqualOp = "geOp", Ki.greaterThanOp = "gtOp", Ki); ++ function Mi(a, b, c, d) { a = Li[a]; return c.M(function (e) { return b.filter(function (f) { for (var g = 0, k = e.length; g < k; ++g) { var l = e[g], m = void 0, q = void 0, u = f.type, z = l.type; if (B(u, 19) || B(z, 19)) B(u, 2) ? m = 3 : B(z, 2) ? q = 3 : B(u, 17) ? m = 17 : B(z, 17) ? q = 17 : B(u, 16) ? m = 16 : B(z, 16) ? q = 16 : B(u, 19) ? q = z : B(z, 19) && (m = u); q = p([q, m]); m = q.next().value; q = q.next().value; m ? f = Pd(f, m) : q && (l = Pd(l, q)); if (Ii(a, f.type, l.type)(f, l, d)) return !0 } return !1 }).Z({ default: function () { return C.ba() }, empty: function () { return C.W() } }) }) } ++ function Ni(a, b, c) { H.call(this, b.o.add(c.o), [b, c], { C: !1 }); this.l = b; this.A = c; this.s = a } v(Ni, H); Ni.prototype.h = function (a, b) { var c = this, d = I(this.l, a, b), e = I(this.A, a, b); return d.Z({ empty: function () { return C.W() }, default: function () { return e.Z({ empty: function () { return C.W() }, default: function () { var f = yi(d, b), g = yi(e, b); return Mi(c.s, f, g, a) } }) } }) }; function Oi(a, b, c, d) { if (!B(c, 53) || !B(d, 53)) throw Error("XPTY0004: Sequences to compare are not nodes"); switch (a) { case "isOp": return Pi(c, d); case "nodeBeforeOp": return b ? function (e, f) { return 0 > Yd(b, e.first(), f.first()) } : void 0; case "nodeAfterOp": return b ? function (e, f) { return 0 < Yd(b, e.first(), f.first()) } : void 0; default: throw Error("Unexpected operator"); } } ++ function Pi(a, b) { return a !== b || 47 !== a && 53 !== a && 54 !== a && 55 !== a && 56 !== a && 57 !== a && 58 !== a ? function () { return !1 } : function (c, d) { return Sd(c.first().value, d.first().value) } } function Qi(a, b, c) { H.call(this, b.o.add(c.o), [b, c], { C: !1 }); this.l = b; this.A = c; this.s = a } v(Qi, H); ++ Qi.prototype.h = function (a, b) { var c = this, d = I(this.l, a, b), e = I(this.A, a, b); return d.Z({ empty: function () { return C.empty() }, multiple: function () { throw Error("XPTY0004: Sequences to compare are not singleton"); }, m: function () { return e.Z({ empty: function () { return C.empty() }, multiple: function () { throw Error("XPTY0004: Sequences to compare are not singleton"); }, m: function () { var f = d.first(), g = e.first(); return Oi(c.s, b.h, f.type, g.type)(d, e, a) ? C.ba() : C.W() } }) } }) }; function Ri(a, b, c, d) { return c.M(function (e) { if (e.some(function (f) { return !B(f.type, 53) })) throw Error("XPTY0004: Sequences given to " + a + " should only contain nodes."); return "sorted" === d ? C.create(e) : "reverse-sorted" === d ? C.create(e.reverse()) : C.create(Zd(b, e)) }) } function Si(a, b, c, d) { H.call(this, 0 < If(b.o, c.o) ? b.o : c.o, [b, c], { C: b.C && c.C }, !1, d); this.l = a; this.s = b; this.A = c } v(Si, H); ++ Si.prototype.h = function (a, b) { ++ var c = this, d = Ri(this.l, b.h, I(this.s, a, b), this.s.da); a = Ri(this.l, b.h, I(this.A, a, b), this.A.da); var e = d.value, f = a.value, g = null, k = null, l = !1, m = !1; return C.create({ ++ next: function () { ++ if (l) return x; for (; !m;) { if (!g) { var q = e.next(0); if (q.done) return l = !0, x; g = q.value } if (!k) { q = f.next(0); if (q.done) { m = !0; break } k = q.value } if (Sd(g.value, k.value)) { if (q = y(g), k = g = null, "intersectOp" === c.l) return q } else if (0 > Yd(b.h, g, k)) { if (q = y(g), g = null, "exceptOp" === c.l) return q } else k = null } if ("exceptOp" === ++ c.l) return null !== g ? (q = y(g), g = null, q) : e.next(0); l = !0; return x ++ } ++ }) ++ }; function Ti(a, b) { Df.call(this, a.reduce(function (c, d) { return c.add(d.o) }, new Hf({})), a, { R: "unsorted", C: a.every(function (c) { return c.C }) }, b) } v(Ti, Df); Ti.prototype.A = function (a, b, c) { return c.length ? Pc(c.map(function (d) { return d(a) })) : C.empty() }; function Ui(a, b, c) { H.call(this, (new Hf({})).add(a.o), [a, b], { C: a.C && b.C }, !1, c); this.l = a; this.s = b } v(Ui, H); Ui.prototype.h = function (a, b) { var c = this, d = I(this.l, a, b), e = Ic(a, d), f = null, g = null, k = !1; return C.create({ next: function (l) { for (; !k;) { if (!f && (f = e.next(l), f.done)) return k = !0, x; g || (g = I(c.s, f.value, b)); var m = g.value.next(l); if (m.done) f = g = null; else return m } } }) }; function Vi(a, b, c) { H.call(this, a.o, [a], { C: !1 }); this.l = pb(b.prefix ? b.prefix + ":" + b.localName : b.localName); if (46 === this.l || 45 === this.l || 44 === this.l) throw Error("XPST0080: Casting to xs:anyAtomicType, xs:anySimpleType or xs:NOTATION is not permitted."); if (b.namespaceURI) throw Error("Not implemented: castable as expressions with a namespace URI."); this.A = a; this.s = c } v(Vi, H); ++ Vi.prototype.h = function (a, b) { var c = this, d = Zc(I(this.A, a, b), b); return d.Z({ empty: function () { return c.s ? C.ba() : C.W() }, m: function () { return d.map(function (e) { return Od(e, c.l).u ? cb : db }) }, multiple: function () { return C.W() } }) }; function Wi(a, b, c) { H.call(this, a.o, [a], { C: !1 }); this.l = pb(b.prefix ? b.prefix + ":" + b.localName : b.localName); if (46 === this.l || 45 === this.l || 44 === this.l) throw Error("XPST0080: Casting to xs:anyAtomicType, xs:anySimpleType or xs:NOTATION is not permitted."); if (b.namespaceURI) throw Error("Not implemented: casting expressions with a namespace URI."); this.A = a; this.s = c } v(Wi, H); ++ Wi.prototype.h = function (a, b) { var c = this, d = Zc(I(this.A, a, b), b); return d.Z({ empty: function () { if (!c.s) throw Error("XPTY0004: Sequence to cast is empty while target type is singleton."); return C.empty() }, m: function () { return d.map(function (e) { return Pd(e, c.l) }) }, multiple: function () { throw Error("XPTY0004: Sequence to cast is not singleton or empty."); } }) }; function Xi(a, b) { var c = a.value, d = null, e = !1; return C.create({ next: function () { for (; !e;) { if (!d) { var f = c.next(0); if (f.done) return e = !0, y(cb); d = b(f.value) } f = d.ha(); d = null; if (!1 === f) return e = !0, y(db) } return x } }) }; function Yi(a, b, c, d) { H.call(this, a.o, [a], { C: !1 }, !1, d); this.A = a; this.s = b; this.l = c } v(Yi, H); Yi.prototype.h = function (a, b) { var c = this, d = I(this.A, a, b); return d.Z({ empty: function () { return "?" === c.l || "*" === c.l ? C.ba() : C.W() }, multiple: function () { return "+" === c.l || "*" === c.l ? Xi(d, function (e) { var f = C.m(e); e = Jc(a, 0, e, f); return I(c.s, e, b) }) : C.W() }, m: function () { return Xi(d, function (e) { var f = C.m(e); e = Jc(a, 0, e, f); return I(c.s, e, b) }) } }) }; function Zi(a, b) { return null !== a && null !== b && B(a.type, 53) && B(b.type, 53) ? Sd(a.value, b.value) : !1 } function $i(a) { var b = a.next(0); if (b.done) return C.empty(); var c = null, d = null; return C.create({ next: function (e) { if (b.done) return x; c || (c = b.value.value); do { var f = c.next(e); if (f.done) { b = a.next(0); if (b.done) return f; c = b.value.value } } while (f.done || Zi(f.value, d)); d = f.value; return f } }) } ++ function aj(a, b) { ++ var c = []; (function () { for (var g = b.next(0), k = {}; !g.done;)k.ob = g.value.value, g = { current: k.ob.next(0), next: function (l) { return function (m) { return l.ob.next(m) } }(k) }, g.current.done || c.push(g), g = b.next(0), k = { ob: k.ob } })(); var d = null, e = !1, f = {}; return C.create((f[Symbol.iterator] = function () { return this }, f.next = function () { ++ e || (e = !0, c.every(function (z) { return B(z.current.value.type, 53) }) && c.sort(function (z, A) { return Yd(a, z.current.value, A.current.value) })); do { ++ if (!c.length) return x; var g = c.shift(); ++ var k = g.current; g.current = g.next(0); if (!B(k.value.type, 53)) return k; if (!g.current.done) { for (var l = 0, m = c.length - 1, q = 0; l <= m;) { q = Math.floor((l + m) / 2); var u = Yd(a, g.current.value, c[q].current.value); if (0 === u) { l = q; break } 0 < u ? l = q + 1 : m = q - 1 } c.splice(l, 0, g) } ++ } while (Zi(k.value, d)); d = k.value; return k ++ }, f)) ++ }; function bj(a, b) { var c = a.reduce(function (d, e) { return 0 < If(d, e.o) ? d : e.o }, new Hf({})); H.call(this, c, a, { C: a.every(function (d) { return d.C }) }, !1, b); this.l = a } v(bj, H); ++ bj.prototype.h = function (a, b) { var c = this; if (this.l.every(function (e) { return "sorted" === e.da })) { var d = 0; return aj(b.h, { next: function () { return d >= c.l.length ? x : y(I(c.l[d++], a, b)) } }).map(function (e) { if (!B(e.type, 53)) throw Error("XPTY0004: The sequences to union are not of type node()*"); return e }) } return Pc(this.l.map(function (e) { return I(e, a, b) })).M(function (e) { if (e.some(function (f) { return !B(f.type, 53) })) throw Error("XPTY0004: The sequences to union are not of type node()*"); e = Zd(b.h, e); return C.create(e) }) }; function cj(a) { ++ return a.every(function (b) { return null === b || B(b.type, 5) || B(b.type, 4) }) || null !== a.map(function (b) { return b ? $c(b.type) : null }).reduce(function (b, c) { return null === c ? b : c === b ? b : null }) ? a : a.every(function (b) { return null === b || B(b.type, 1) || B(b.type, 20) }) ? a.map(function (b) { return b ? Pd(b, 1) : null }) : a.every(function (b) { return null === b || B(b.type, 4) || B(b.type, 6) }) ? a.map(function (b) { return b ? Pd(b, 6) : b }) : a.every(function (b) { return null === b || B(b.type, 4) || B(b.type, 6) || B(b.type, 3) }) ? a.map(function (b) { ++ return b ? ++ Pd(b, 3) : b ++ }) : null ++ }; function dj(a) { return (a = a.find(function (b) { return !!b })) ? $c(a.type) : null } function ej(a, b) { var c = new Hf({}); ki.call(this, c, [b].concat(t(a.map(function (d) { return d.ca }))), { C: !1, X: !1, R: "unsorted", subtree: !1 }, b); this.A = a } v(ej, ki); ++ ej.prototype.K = function (a, b, c, d) { ++ if (this.A[1]) throw Error("More than one order spec is not supported for the order by clause."); var e = [], f = !1, g, k, l = null, m = this.A[0]; return C.create({ ++ next: function () { ++ if (!f) { ++ for (var q = b.next(0); !q.done;)e.push(q.value), q = b.next(0); q = e.map(function (ia) { return m.ca.h(ia, c) }).map(function (ia) { return Zc(ia, c) }); if (q.find(function (ia) { return !ia.G() && !ia.wa() })) throw Ag("Order by only accepts empty or singleton sequences"); g = q.map(function (ia) { return ia.first() }); g = g.map(function (ia) { ++ return null === ++ ia ? ia : B(19, ia.type) ? Pd(ia, 1) : ia ++ }); if (dj(g) && (g = cj(g), !g)) throw Ag("Could not cast values"); q = g.length; k = g.map(function (ia, Ba) { return Ba }); for (var u = 0; u < q; u++)if (u + 1 !== q) for (var z = u; 0 <= z; z--) { ++ var A = z, D = z + 1; if (D !== q) { ++ var F = g[k[A]], J = g[k[D]]; if (null !== J || null !== F) { ++ if (m.lc) { if (null === F) continue; if (null === J && null !== F) { F = p([k[D], k[A]]); k[A] = F.next().value; k[D] = F.next().value; continue } if (isNaN(J.value) && null !== F && !isNaN(F.value)) { F = p([k[D], k[A]]); k[A] = F.next().value; k[D] = F.next().value; continue } } else { ++ if (null === ++ J) continue; if (null === F && null !== J) { F = p([k[D], k[A]]); k[A] = F.next().value; k[D] = F.next().value; continue } if (isNaN(F.value) && null !== J && !isNaN(J.value)) { F = p([k[D], k[A]]); k[A] = F.next().value; k[D] = F.next().value; continue } ++ } Ii("gtOp", F.type, J.type)(F, J, a) && (F = p([k[D], k[A]]), k[A] = F.next().value, k[D] = F.next().value) ++ } ++ } ++ } var T = m.Kb ? 0 : g.length - 1; l = d({ next: function () { return m.Kb ? T >= g.length ? x : y(e[k[T++]]) : 0 > T ? x : y(e[k[T--]]) } }).value; f = !0 ++ } return l.next(0) ++ } ++ }) ++ }; function fj(a) { H.call(this, a ? a.o : new Hf({}), a ? [a] : [], { R: "sorted", subtree: !1, X: !1, C: !1 }); this.l = a } v(fj, H); fj.prototype.h = function (a, b) { if (null === a.N) throw Rc("context is absent, it needs to be present to use paths."); for (var c = b.h, d = a.N.value; 9 !== d.node.nodeType;)if (d = Vb(c, d), null === d) throw Error("XPDY0050: the root node of the context node is not a document node."); c = C.m($b(d)); return this.l ? I(this.l, Jc(a, 0, c.first(), c), b) : c }; function gj(a) { H.call(this, new Hf({}), [], { R: "sorted" }, !1, a) } v(gj, H); gj.prototype.h = function (a) { if (null === a.N) throw Rc('context is absent, it needs to be present to use the "." operator'); return C.m(a.N) }; function hj(a, b) { var c = !1, d = !1; b.forEach(function (e) { B(e.type, 53) ? c = !0 : d = !0 }); if (d && c) throw Error("XPTY0018: The path operator should either return nodes or non-nodes. Mixed sequences are not allowed."); return c ? Zd(a, b) : b } function ij(a, b) { var c = a.every(function (e) { return e.X }), d = a.every(function (e) { return e.subtree }); H.call(this, a.reduce(function (e, f) { return e.add(f.o) }, new Hf({})), a, { C: !1, X: c, R: b ? "sorted" : "unsorted", subtree: d }); this.l = a; this.s = b } v(ij, H); ++ ij.prototype.h = function (a, b) { ++ var c = this, d = !0; return this.l.reduce(function (e, f, g) { ++ var k = null === e ? Qd(a) : Ic(a, e); e = { next: function (q) { q = k.next(q); if (q.done) return x; if (null !== q.value.N && !B(q.value.N.type, 53) && 0 < g) throw Error("XPTY0019: The result of E1 in a path expression E1/E2 should not evaluate to a sequence of nodes."); return y(I(f, q.value, b)) } }; if (c.s) switch (f.da) { ++ case "reverse-sorted": var l = e; e = { next: function (q) { q = l.next(q); return q.done ? q : y(q.value.M(function (u) { return C.create(u.reverse()) })) } }; ++ case "sorted": if (f.subtree && d) { var m = $i(e); break } m = aj(b.h, e); break; case "unsorted": return $i(e).M(function (q) { return C.create(hj(b.h, q)) }) ++ } else m = $i(e); d = d && f.X; return m ++ }, null) ++ }; ij.prototype.B = function () { return this.l[0].B() }; function jj(a, b) { H.call(this, a.o.add(b.o), [a, b], { C: a.C && b.C, X: a.X, R: a.da, subtree: a.subtree }); this.s = a; this.l = b } v(jj, H); ++ jj.prototype.h = function (a, b) { ++ var c = this, d = I(this.s, a, b); if (this.l.C) { var e = I(this.l, a, b); if (e.G()) return e; var f = e.first(); if (B(f.type, 2)) { var g = f.value; if (!Number.isInteger(g)) return C.empty(); var k = d.value, l = !1; return C.create({ next: function () { if (!l) { for (var A = k.next(0); !A.done; A = k.next(0))if (1 === g--) return l = !0, A; l = !0 } return x } }) } return e.ha() ? d : C.empty() } var m = d.value, q = null, u = 0, z = null; return C.create({ ++ next: function (A) { ++ for (var D = !1; !q || !q.done;) { ++ q || (q = m.next(D ? 0 : A), D = !0); if (q.done) break; z || (z = ++ I(c.l, Jc(a, u, q.value, d), b)); var F = z.first(); F = null === F ? !1 : B(F.type, 2) ? F.value === u + 1 : z.ha(); z = null; var J = q.value; q = null; u++; if (F) return y(J) ++ } return q ++ } ++ }) ++ }; jj.prototype.B = function () { return this.s.B() }; function kj(a, b, c) { ++ c = [c]; if (B(a.type, 62)) if ("*" === b) c.push.apply(c, t(a.P.map(function (e) { return e() }))); else if (B(b.type, 5)) { var d = b.value; if (a.P.length < d || 0 >= d) throw Error("FOAY0001: Array index out of bounds"); c.push(a.P[d - 1]()) } else throw Ag("The key specifier is not an integer."); else if (B(a.type, 61)) "*" === b ? c.push.apply(c, t(a.h.map(function (e) { return e.value() }))) : (a = a.h.find(function (e) { return bc(e.key, b) })) && c.push(a.value()); else throw Ag("The provided context item is not a map or an array."); ++ return Pc(c) ++ } function lj(a, b, c, d, e) { if ("*" === b) return kj(a, b, c); b = I(b, d, e); b = yb(b)().M(function (f) { return f.reduce(function (g, k) { return kj(a, k, g) }, new ib) }); return Pc([c, b]) }; function mj(a, b) { H.call(this, a.o, [a].concat("*" === b ? [] : [b]), { C: a.C, R: a.da, subtree: a.subtree }); this.l = a; this.s = b } v(mj, H); mj.prototype.h = function (a, b) { var c = this; return I(this.l, a, b).M(function (d) { return d.reduce(function (e, f) { return lj(f, c.s, e, a, b) }, new ib) }) }; mj.prototype.B = function () { return this.l.B() }; function nj(a, b) { var c = {}; H.call(this, new Hf((c.external = 1, c)), "*" === a ? [] : [a], { C: !1 }, !1, b); this.l = a } v(nj, H); nj.prototype.h = function (a, b) { return lj(a.N, this.l, new ib, a, b) }; function oj(a, b, c, d) { var e = b.map(function (g) { return g.jb }); b = b.map(function (g) { return g.name }); var f = e.reduce(function (g, k) { return g.add(k.o) }, c.o); H.call(this, f, e.concat(c), { C: !1 }, !1, d); this.s = a; this.A = b; this.K = e; this.S = c; this.l = null } v(oj, H); ++ oj.prototype.h = function (a, b) { ++ var c = this, d = a, e = this.l.map(function (q, u) { var z = I(c.K[u], d, b).O(); u = {}; d = Nc(a, (u[q] = function () { return C.create(z) }, u)); return z }); if (e.some(function (q) { return 0 === q.length })) return "every" === this.s ? C.ba() : C.W(); var f = Array(e.length).fill(0); f[0] = -1; for (var g = !0; g;) { ++ g = !1; for (var k = 0, l = f.length; k < l; ++k) { ++ var m = e[k]; if (++f[k] > m.length - 1) f[k] = 0; else { ++ g = Object.create(null); k = {}; for (l = 0; l < f.length; k = { xb: k.xb }, l++)k.xb = e[l][f[l]], g[this.l[l]] = function (q) { return function () { return C.m(q.xb) } }(k); ++ g = Nc(a, g); g = I(this.S, g, b); if (g.ha() && "some" === this.s) return C.ba(); if (!g.ha() && "every" === this.s) return C.W(); g = !0; break ++ } ++ } ++ } return "every" === this.s ? C.ba() : C.W() ++ }; oj.prototype.v = function (a) { this.l = []; for (var b = 0, c = this.A.length; b < c; ++b) { this.K[b].v(a); Mg(a); var d = this.A[b], e = d.prefix ? a.aa(d.prefix) : null; d = Qg(a, e, d.localName); this.l[b] = d } this.S.v(a); b = 0; for (c = this.A.length; b < c; ++b)Sg(a) }; function pj(a) { H.call(this, a, [], { C: !1 }) } v(pj, H); pj.prototype.h = function (a) { return this.l(a.N) ? C.ba() : C.W() }; function qj(a) { var b = {}; pj.call(this, new Hf((b.nodeType = 1, b))); this.s = a } v(qj, pj); qj.prototype.l = function (a) { if (!B(a.type, 53)) return !1; a = a.value.node.nodeType; return 3 === this.s && 4 === a ? !0 : this.s === a }; qj.prototype.B = function () { return "type-" + this.s }; function rj(a, b) { b = void 0 === b ? { kind: null } : b; var c = a.prefix, d = a.namespaceURI; a = a.localName; var e = {}; "*" !== a && (e.nodeName = 1); e.nodeType = 1; pj.call(this, new Hf(e)); this.s = a; this.K = d; this.A = c; this.S = b.kind } v(rj, pj); ++ rj.prototype.l = function (a) { var b = B(a.type, 54), c = B(a.type, 47); if (!b && !c) return !1; a = a.value; return null !== this.S && (1 === this.S && !b || 2 === this.S && !c) ? !1 : null === this.A && "" !== this.K && "*" === this.s ? !0 : "*" === this.A ? "*" === this.s ? !0 : this.s === a.node.localName : "*" !== this.s && this.s !== a.node.localName ? !1 : (a.node.namespaceURI || null) === (("" === this.A ? b ? this.K : null : this.K) || null) }; rj.prototype.B = function () { return "*" === this.s ? null === this.S ? "type-1-or-type-2" : "type-" + this.S : "name-" + this.s }; ++ rj.prototype.v = function (a) { if (null === this.K && "*" !== this.A && (this.K = a.aa(this.A || "") || null, !this.K && this.A)) throw Error("XPST0081: The prefix " + this.A + " could not be resolved."); }; function sj(a) { var b = {}; pj.call(this, new Hf((b.nodeName = 1, b))); this.s = a } v(sj, pj); sj.prototype.l = function (a) { return B(a.type, 57) && a.value.node.target === this.s }; sj.prototype.B = function () { return "type-7" }; function tj(a) { pj.call(this, new Hf({})); this.s = a } v(tj, pj); tj.prototype.l = function (a) { return B(a.type, pb(this.s.prefix ? this.s.prefix + ":" + this.s.localName : this.s.localName)) }; function uj(a, b, c) { H.call(this, new Hf({}), [], { C: !1, R: "unsorted" }); this.A = c; this.s = b; this.K = a; this.l = null } v(uj, H); uj.prototype.h = function (a, b) { if (!a.Aa[this.l]) { if (this.S) return this.S(a, b); throw Error("XQDY0054: The variable " + this.A + " is declared but not in scope."); } return a.Aa[this.l]() }; uj.prototype.v = function (a) { null === this.s && this.K && (this.s = a.aa(this.K)); this.l = a.ib(this.s || "", this.A); if (!this.l) throw Error("XPST0008, The variable " + this.A + " is not in scope."); if (a = a.Ia[this.l]) this.S = a }; function vj(a, b) { var c = new Hf({}); ki.call(this, c, [a, b], { C: !1, X: !1, R: "unsorted", subtree: !1 }, b); this.A = a } v(vj, ki); vj.prototype.K = function (a, b, c, d) { var e = this, f = null, g = null; return d({ next: function () { for (; ;) { if (!g) { var k = b.next(0); if (k.done) return x; f = k.value; g = I(e.A, f, c) } k = g.ha(); var l = f; g = f = null; if (k) return y(l) } } }) }; function wj(a) { this.type = a }; function xj(a) { this.type = "delete"; this.target = a } v(xj, wj); xj.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b }; function yj(a, b, c) { this.type = c; this.target = a; this.content = b } v(yj, wj); yj.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b.content = this.content.map(function (c) { return ig(c, a, !0) }), b }; function zj(a, b) { yj.call(this, a, b, "insertAfter") } v(zj, yj); function Aj(a, b) { this.type = "insertAttributes"; this.target = a; this.content = b } v(Aj, wj); Aj.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b.content = this.content.map(function (c) { return ig(c, a, !0) }), b }; function Bj(a, b) { yj.call(this, a, b, "insertBefore") } v(Bj, yj); function Cj(a, b) { yj.call(this, a, b, "insertIntoAsFirst") } v(Cj, yj); function Dj(a, b) { yj.call(this, a, b, "insertIntoAsLast") } v(Dj, yj); function Ej(a, b) { yj.call(this, a, b, "insertInto") } v(Ej, yj); function Fj(a, b) { this.type = "rename"; this.target = a; this.o = b.Ca ? b : new zb(b.prefix, b.namespaceURI, b.localName) } v(Fj, wj); Fj.prototype.h = function (a) { var b = {}, c = {}; return c.type = this.type, c.target = ig(this.target, a, !1), c.newName = (b.prefix = this.o.prefix, b.namespaceURI = this.o.namespaceURI, b.localName = this.o.localName, b), c }; function Gj(a, b) { this.type = "replaceElementContent"; this.target = a; this.text = b } v(Gj, wj); Gj.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b.text = this.text ? ig(this.text, a, !0) : null, b }; function Hj(a, b) { this.type = "replaceNode"; this.target = a; this.o = b } v(Hj, wj); Hj.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b.replacement = this.o.map(function (c) { return ig(c, a, !0) }), b }; function Ij(a, b) { this.type = "replaceValue"; this.target = a; this.o = b } v(Ij, wj); Ij.prototype.h = function (a) { var b = {}; return b.type = this.type, b.target = ig(this.target, a, !1), b["string-value"] = this.o, b }; function Jj(a, b) { return new Hj(a, b) }; function Kj(a) { Af.call(this, new Hf({}), [a], { C: !1, R: "unsorted" }); this.l = a } v(Kj, Af); Kj.prototype.s = function (a, b) { var c = Bf(this.l)(a, b), d = b.h, e, f; return { next: function () { if (!e) { var g = c.next(0); if (g.value.J.some(function (k) { return !B(k.type, 53) })) throw Error("XUTY0007: The target of a delete expression must be a sequence of zero or more nodes."); e = g.value.J; f = g.value.fa } e = e.filter(function (k) { return Vb(d, k.value) }); return y({ fa: zf(e.map(function (k) { return new xj(k.value) }), f), J: [] }) } } }; function Lj(a, b, c, d, e, f) { ++ var g = b.h; a.reduce(function q(l, m) { if (B(m.type, 62)) return m.P.forEach(function (u) { return u().O().forEach(function (z) { return q(l, z) }) }), l; l.push(m); return l }, []).forEach(function (l, m, q) { ++ if (B(l.type, 47)) { if (e) throw f(l.value, g); c.push(l.value.node) } else if (B(l.type, 46) || B(l.type, 53) && 3 === l.value.node.nodeType) { ++ var u = B(l.type, 46) ? Pd(Yc(l, b).first(), 1).value : Qb(g, l.value); 0 !== m && B(q[m - 1].type, 46) && B(l.type, 46) ? (d.push({ data: " " + u, Ta: !0, nodeType: 3 }), e = !0) : u && (d.push({ ++ data: "" + u, ++ Ta: !0, nodeType: 3 ++ }), e = !0) ++ } else if (B(l.type, 55)) { var z = []; Pb(g, l.value).forEach(function (A) { return z.push($b(A)) }); e = Lj(z, b, c, d, e, f) } else if (B(l.type, 53)) d.push(l.value.node), e = !0; else { if (B(l.type, 60)) throw Tc(l.type); throw Error("Atomizing " + l.type + " is not implemented."); } ++ }); return e ++ } function Mj(a, b, c) { var d = [], e = [], f = !1; a.forEach(function (g) { f = Lj(g, b, d, e, f, c) }); return { attributes: d, Xa: e } }; function Nj(a, b, c, d, e) { var f = []; switch (a) { case 4: d.length && f.push(new Aj(b, d)); e.length && f.push(new Cj(b, e)); break; case 5: d.length && f.push(new Aj(b, d)); e.length && f.push(new Dj(b, e)); break; case 3: d.length && f.push(new Aj(b, d)); e.length && f.push(new Ej(b, e)); break; case 2: d.length && f.push(new Aj(c, d)); e.length && f.push(new Bj(b, e)); break; case 1: d.length && f.push(new Aj(c, d)), e.length && f.push(new zj(b, e)) }return f } ++ function Oj(a, b, c) { Af.call(this, new Hf({}), [a, c], { C: !1, R: "unsorted" }); this.K = a; this.l = b; this.A = c } v(Oj, Af); ++ Oj.prototype.s = function (a, b) { ++ var c = this, d = Bf(this.K)(a, b), e = Bf(this.A)(a, b), f = b.h, g, k, l, m, q, u; return { ++ next: function () { ++ if (!g) { var z = d.next(0), A = Mj([z.value.J], b, bf); g = A.attributes.map(function (D) { return { node: D, F: null } }); k = A.Xa.map(function (D) { return { node: D, F: null } }); l = z.value.fa } if (!m) { ++ z = e.next(0); if (0 === z.value.J.length) throw mf(); if (3 <= c.l) { if (1 !== z.value.J.length) throw cf(); if (!B(z.value.J[0].type, 54) && !B(z.value.J[0].type, 55)) throw cf(); } else { ++ if (1 !== z.value.J.length) throw df(); if (!(B(z.value.J[0].type, ++ 54) || B(z.value.J[0].type, 56) || B(z.value.J[0].type, 58) || B(z.value.J[0].type, 57))) throw df(); u = Vb(f, z.value.J[0].value, null); if (null === u) throw Error("XUDY0029: The target " + z.value.J[0].value.outerHTML + " for inserting a node before or after must have a parent."); ++ } m = z.value.J[0]; q = z.value.fa ++ } if (g.length) { ++ if (3 <= c.l) { if (!B(m.type, 54)) throw Error("XUTY0022: An insert expression specifies the insertion of an attribute node into a document node."); } else if (1 !== u.node.nodeType) throw Error("XUDY0030: An insert expression specifies the insertion of an attribute node before or after a child of a document node."); ++ g.reduce(function (D, F) { var J = F.node.prefix || "", T = F.node.prefix || "", ia = F.node.namespaceURI, Ba = T ? m.value.node.lookupNamespaceURI(T) : null; if (Ba && Ba !== ia) throw kf(ia); if ((T = D[T]) && ia !== T) throw lf(ia); D[J] = F.node.namespaceURI; return D }, {}) ++ } return y({ J: [], fa: zf(Nj(c.l, m.value, u ? u : null, g, k), l, q) }) ++ } ++ } ++ }; function Pj() { return Sc("Casting not supported from given type to a single xs:string or xs:untypedAtomic or any of its derived types.") } var Qj = /([A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]|[\uD800-\uDB7F][\uDC00-\uDFFF])/, Rj = new RegExp(Qj.source + (new RegExp("(" + Qj.source + "|[-.0-9\u00b7\u0300-\u036f\u203f\u2040])")).source + "*", "g"); function Sj(a) { return (a = a.match(Rj)) ? 1 === a.length : !1 } ++ function Tj(a, b) { return Zc(b, a).Z({ m: function (c) { c = c.first(); if (B(c.type, 1) || B(c.type, 19)) { if (!Sj(c.value)) throw Error('XQDY0041: The value "' + c.value + '" of a name expressions cannot be converted to a NCName.'); return C.m(c) } throw Pj(); }, default: function () { throw Pj(); } }).value } ++ function Uj(a, b, c) { return Zc(c, b).Z({ m: function (d) { d = d.first(); if (B(d.type, 23)) return C.m(d); if (B(d.type, 1) || B(d.type, 19)) { d = d.value.split(":"); if (1 === d.length) d = d[0]; else { var e = d[0]; var f = a.aa(e); d = d[1] } if (!Sj(d) || e && !Sj(e)) throw Hg(e ? e + ":" + d : d); if (e && !f) throw Hg(e + ":" + d); return C.m({ type: 23, value: new zb(e, f, d) }) } throw Pj(); }, default: function () { throw Pj(); } }).value }; function Vj(a, b) { Af.call(this, new Hf({}), [a, b], { C: !1, R: "unsorted" }); this.A = a; this.K = b; this.l = void 0 } v(Vj, Af); ++ Vj.prototype.s = function (a, b) { ++ var c = this, d = Bf(this.A)(a, b), e = Bf(this.K)(a, b); return { ++ next: function () { ++ var f = d.next(0); var g = f.value.J; if (0 === g.length) throw mf(); if (1 !== g.length) throw gf(); if (!B(g[0].type, 54) && !B(g[0].type, 47) && !B(g[0].type, 57)) throw gf(); g = g[0]; var k = e.next(0); a: { ++ var l = c.l; var m = C.create(k.value.J); switch (g.type) { ++ case 54: l = Uj(l, b, m).next(0).value.value; if ((m = g.value.node.lookupNamespaceURI(l.prefix)) && m !== l.namespaceURI) throw kf(l.namespaceURI); break a; case 47: l = Uj(l, b, m).next(0).value.value; ++ if (l.namespaceURI && (m = g.value.node.lookupNamespaceURI(l.prefix)) && m !== l.namespaceURI) throw kf(l.namespaceURI); break a; case 57: l = Tj(b, m).next(0).value.value; l = new zb("", null, l); break a ++ }l = void 0 ++ } return y({ J: [], fa: zf([new Fj(g.value, l)], f.value.fa, k.value.fa) }) ++ } ++ } ++ }; Vj.prototype.v = function (a) { this.l = Of(a); Af.prototype.v.call(this, a) }; function Wj(a, b, c) { ++ var d, e, f; return { ++ next: function () { ++ if (!d) { var g = c.next(0), k = Mj([g.value.J], a, lf); d = { attributes: k.attributes.map(function (l) { return { node: l, F: null } }), Xa: k.Xa.map(function (l) { return { node: l, F: null } }) }; e = g.value.fa } k = b.next(0); if (0 === k.value.J.length) throw mf(); if (1 !== k.value.J.length) throw ff(); if (!(B(k.value.J[0].type, 54) || B(k.value.J[0].type, 47) || B(k.value.J[0].type, 56) || B(k.value.J[0].type, 58) || B(k.value.J[0].type, 57))) throw ff(); f = Vb(a.h, k.value.J[0].value, null); if (null === f) throw Error("XUDY0009: The target " + ++ k.value.J[0].value.outerHTML + " for replacing a node must have a parent."); g = k.value.J[0]; k = k.value.fa; if (B(g.type, 47)) { if (d.Xa.length) throw Error("XUTY0011: When replacing an attribute the new value must be zero or more attribute nodes."); d.attributes.reduce(function (l, m) { var q = m.node.prefix || ""; m = m.node.namespaceURI; var u = f.node.lookupNamespaceURI(q); if (u && u !== m) throw kf(m); if ((u = l[q]) && m !== u) throw lf(m); l[q] = m; return l }, {}) } else if (d.attributes.length) throw Error("XUTY0010: When replacing an an element, text, comment, or processing instruction node the new value must be a single node."); ++ return y({ J: [], fa: zf([Jj(g.value, [].concat(d.attributes, d.Xa))], e, k) }) ++ } ++ } ++ } ++ function Xj(a, b, c) { ++ var d, e, f, g, k = !1; return { ++ next: function () { ++ if (k) return x; if (!f) { var l = c.next(0), m = Zc(C.create(l.value.J), a).map(function (q) { return Pd(q, 1) }).O().map(function (q) { return q.value }).join(" "); f = 0 === m.length ? null : { node: a.o.createTextNode(m), F: null }; g = l.value.fa } if (!d) { ++ l = b.next(0); if (0 === l.value.J.length) throw mf(); if (1 !== l.value.J.length) throw ff(); if (!(B(l.value.J[0].type, 54) || B(l.value.J[0].type, 47) || B(l.value.J[0].type, 56) || B(l.value.J[0].type, 58) || B(l.value.J[0].type, 57))) throw ff(); ++ d = l.value.J[0]; e = l.value.fa ++ } if (B(d.type, 54)) return k = !0, y({ J: [], fa: zf([new Gj(d.value, f)], g, e) }); if (B(d.type, 47) || B(d.type, 56) || B(d.type, 58) || B(d.type, 57)) { ++ l = f ? Qb(a.h, f) : ""; if (B(d.type, 58) && (l.includes("--") || l.endsWith("-"))) throw Error('XQDY0072: The content "' + l + '" for a comment node contains two adjacent hyphens or ends with a hyphen.'); if (B(d.type, 57) && l.includes("?>")) throw Error('XQDY0026: The content "' + l + '" for a processing instruction node contains "?>".'); k = !0; return y({ ++ J: [], fa: zf([new Ij(d.value, ++ l)], g, e) ++ }) ++ } ++ } ++ } ++ } function Yj(a, b, c) { Af.call(this, new Hf({}), [b, c], { C: !1, R: "unsorted" }); this.K = a; this.l = b; this.A = c } v(Yj, Af); Yj.prototype.s = function (a, b) { var c = Bf(this.l)(a, b); a = Bf(this.A)(a, b); return this.K ? Xj(b, c, a) : Wj(b, c, a) }; function Zj(a) { ++ switch (a.type) { ++ case "delete": return new xj({ node: a.target, F: null }); case "insertAfter": return new zj({ node: a.target, F: null }, a.content.map(function (b) { return { node: b, F: null } })); case "insertBefore": return new Bj({ node: a.target, F: null }, a.content.map(function (b) { return { node: b, F: null } })); case "insertInto": return new Ej({ node: a.target, F: null }, a.content.map(function (b) { return { node: b, F: null } })); case "insertIntoAsFirst": return new Cj({ node: a.target, F: null }, a.content.map(function (b) { ++ return { ++ node: b, ++ F: null ++ } ++ })); case "insertIntoAsLast": return new Dj({ node: a.target, F: null }, a.content.map(function (b) { return { node: b, F: null } })); case "insertAttributes": return new Aj({ node: a.target, F: null }, a.content.map(function (b) { return { node: b, F: null } })); case "rename": return new Fj({ node: a.target, F: null }, a.newName); case "replaceNode": return new Hj({ node: a.target, F: null }, a.replacement.map(function (b) { return { node: b, F: null } })); case "replaceValue": return new Ij({ node: a.target, F: null }, a["string-value"]); case "replaceElementContent": return new Gj({ ++ node: a.target, ++ F: null ++ }, a.text ? { node: a.text, F: null } : null); default: throw Error('Unexpected type "' + a.type + '" when parsing a transferable pending update.'); ++ } ++ }; function ak(a, b, c) { if (b.find(function (e) { return Sd(e, a) })) return !0; var d = Vb(c, a); return d ? ak(d, b, c) : !1 } function bk(a, b, c) { Af.call(this, new Hf({}), a.reduce(function (d, e) { d.push(e.jb); return d }, [b, c]), { C: !1, R: "unsorted" }); this.l = a; this.K = b; this.A = c; this.I = null } v(bk, Af); bk.prototype.h = function (a, b) { a = this.s(a, b); return Cf(a, function () { }) }; ++ bk.prototype.s = function (a, b) { ++ var c = this, d = b.h, e = b.o, f = b.B, g = [], k, l, m, q = [], u = []; return { ++ next: function () { ++ if (q.length !== c.l.length) for (var z = {}, A = q.length; A < c.l.length; z = { lb: z.lb }, A++) { ++ var D = c.l[A], F = g[A]; F || (g[A] = F = Bf(D.jb)(a, b)); F = F.next(0); if (1 !== F.value.J.length || !B(F.value.J[0].type, 53)) throw Error("XUTY0013: The source expression of a copy modify expression must return a single node."); z.lb = $b(eg(F.value.J[0].value, b)); q.push(z.lb.value); u.push(F.value.fa); F = {}; a = Nc(a, (F[D.qc] = function (J) { return function () { return C.m(J.lb) } }(z), ++ F)) ++ } m || (k || (k = Bf(c.K)(a, b)), m = k.next(0).value.fa); m.forEach(function (J) { if (J.target && !ak(J.target, q, d)) throw Error("XUDY0014: The target " + J.target.node.outerHTML + " must be a node created by the copy clause."); if ("put" === J.type) throw Error("XUDY0037: The modify expression of a copy modify expression can not contain a fn:put."); }); z = m.map(function (J) { J = J.h(b); return Zj(J) }); xf(z, d, e, f); l || (l = Bf(c.A)(a, b)); z = l.next(0); return y({ J: z.value.J, fa: zf.apply(null, [z.value.fa].concat(t(u))) }) ++ } ++ } ++ }; ++ bk.prototype.v = function (a) { Mg(a); this.l.forEach(function (b) { return b.qc = Qg(a, b.Rb.namespaceURI, b.Rb.localName) }); Af.prototype.v.call(this, a); Sg(a); this.I = this.l.some(function (b) { return b.jb.I }) || this.A.I }; function ck(a, b) { return { node: { nodeType: 2, Ta: !0, nodeName: a.Ca(), namespaceURI: a.namespaceURI, prefix: a.prefix, localName: a.localName, name: a.Ca(), value: b }, F: null } } function dk(a, b) { var c = b.wb || []; c = c.concat(a.Oa || []); H.call(this, new Hf({}), c, { C: !1, R: "unsorted" }); a.Oa ? this.s = a.Oa : this.name = new zb(a.prefix, a.namespaceURI, a.localName); this.l = b; this.A = void 0 } v(dk, H); ++ dk.prototype.h = function (a, b) { ++ var c = this, d, e, f, g = !1; return C.create({ ++ next: function () { ++ if (g) return x; if (!e) { ++ if (c.s) { if (!d) { var k = c.s.h(a, b); d = Uj(c.A, b, k) } e = d.next(0).value.value } else e = c.name; if (e) { ++ if ("xmlns" === e.prefix) throw Cg(e); if ("" === e.prefix && "xmlns" === e.localName) throw Cg(e); if ("http://www.w3.org/2000/xmlns/" === e.namespaceURI) throw Cg(e); if ("xml" === e.prefix && "http://www.w3.org/XML/1998/namespace" !== e.namespaceURI) throw Cg(e); if ("" !== e.prefix && "xml" !== e.prefix && "http://www.w3.org/XML/1998/namespace" === ++ e.namespaceURI) throw Cg(e); ++ } ++ } if (c.l.wb) return k = c.l.wb, f || (f = Pc(k.map(function (l) { return Zc(l.h(a, b), b).M(function (m) { return C.m(w(m.map(function (q) { return q.value }).join(" "), 1)) }) })).M(function (l) { return C.m($b(ck(e, l.map(function (m) { return m.value }).join("")))) }).value), f.next(0); g = !0; return y($b(ck(e, c.l.value))) ++ } ++ }) ++ }; ++ dk.prototype.v = function (a) { this.A = Of(a); if (this.name && this.name.prefix && !this.name.namespaceURI) { var b = a.aa(this.name.prefix); if (void 0 === b && this.name.prefix) throw Uc(this.name.prefix); this.name.namespaceURI = b || null } H.prototype.v.call(this, a) }; function ek(a) { H.call(this, a ? a.o : new Hf({}), a ? [a] : [], { C: !1, R: "unsorted" }); this.l = a } v(ek, H); ek.prototype.h = function (a, b) { var c = { data: "", Ta: !0, nodeType: 8 }, d = { node: c, F: null }; if (!this.l) return C.m($b(d)); a = I(this.l, a, b); return Zc(a, b).M(function (e) { e = e.map(function (f) { return Pd(f, 1).value }).join(" "); if (-1 !== e.indexOf("--\x3e")) throw Error('XQDY0072: The contents of the data of a comment may not include "--\x3e"'); c.data = e; return C.m($b(d)) }) }; function fk(a, b, c, d) { H.call(this, new Hf({}), d.concat(b).concat(a.Oa || []), { C: !1, R: "unsorted" }); a.Oa ? this.s = a.Oa : this.l = new zb(a.prefix, a.namespaceURI, a.localName); this.S = c.reduce(function (e, f) { if (f.prefix in e) throw Error("XQST0071: The namespace declaration with the prefix " + f.prefix + " has already been declared on the constructed element."); e[f.prefix || ""] = f.uri; return e }, {}); this.K = b; this.la = d; this.A = void 0 } v(fk, H); ++ fk.prototype.h = function (a, b) { ++ var c = this, d = !1, e, f, g = !1, k, l, m, q = !1; return C.create({ ++ next: function () { ++ if (q) return x; d || (e || (e = Pc(c.K.map(function (J) { return I(J, a, b) }))), f = e.O(), d = !0); if (!g) { k || (k = c.la.map(function (J) { return I(J, a, b) })); for (var u = [], z = 0; z < k.length; z++) { var A = k[z].O(); u.push(A) } l = u; g = !0 } c.s && (m || (u = c.s.h(a, b), m = Uj(c.A, b, u)), u = m.next(0), c.l = u.value.value); if ("xmlns" === c.l.prefix || "http://www.w3.org/2000/xmlns/" === c.l.namespaceURI || "xml" === c.l.prefix && "http://www.w3.org/XML/1998/namespace" !== ++ c.l.namespaceURI || c.l.prefix && "xml" !== c.l.prefix && "http://www.w3.org/XML/1998/namespace" === c.l.namespaceURI) throw Error('XQDY0096: The node name "' + c.l.Ca() + '" is invalid for a computed element constructor.'); var D = { nodeType: 1, Ta: !0, attributes: [], childNodes: [], nodeName: c.l.Ca(), namespaceURI: c.l.namespaceURI, prefix: c.l.prefix, localName: c.l.localName }; u = { node: D, F: null }; f.forEach(function (J) { D.attributes.push(J.value.node) }); z = Mj(l, b, Bg); z.attributes.forEach(function (J) { ++ if (D.attributes.find(function (T) { ++ return T.namespaceURI === ++ J.namespaceURI && T.localName === J.localName ++ })) throw Error("XQDY0025: The attribute " + J.name + " does not have an unique name in the constructed element."); D.attributes.push(J) ++ }); z.Xa.forEach(function (J) { D.childNodes.push(J) }); for (z = 0; z < D.childNodes.length; z++)if (A = D.childNodes[z], Kb(A) && 3 === A.nodeType) { var F = D.childNodes[z - 1]; F && Kb(F) && 3 === F.nodeType && (F.data += A.data, D.childNodes.splice(z, 1), z--) } q = !0; return y($b(u)) ++ } ++ }) ++ }; ++ fk.prototype.v = function (a) { ++ var b = this; Mg(a); Object.keys(this.S).forEach(function (d) { return Pg(a, d, b.S[d]) }); this.ta.forEach(function (d) { return d.v(a) }); this.K.reduce(function (d, e) { if (e.name) { e = "Q{" + (null === e.name.namespaceURI ? a.aa(e.name.prefix) : e.name.namespaceURI) + "}" + e.name.localName; if (d.includes(e)) throw Error("XQST0040: The attribute " + e + " does not have an unique name in the constructed element."); d.push(e) } return d }, []); if (this.l && null === this.l.namespaceURI) { ++ var c = a.aa(this.l.prefix); if (void 0 === ++ c && this.l.prefix) throw Uc(this.l.prefix); this.l.namespaceURI = c ++ } this.A = Of(a); Sg(a) ++ }; function gk(a) { if (/^xml$/i.test(a)) throw Error('XQDY0064: The target of a created PI may not be "' + a + '"'); } function hk(a, b) { return { node: { data: b, Ta: !0, nodeName: a, nodeType: 7, target: a }, F: null } } function ik(a, b) { var c = a.Fb ? [a.Fb].concat(b) : [b]; H.call(this, c.reduce(function (d, e) { return d.add(e.o) }, new Hf({})), c, { C: !1, R: "unsorted" }); this.l = a; this.s = b } v(ik, H); ++ ik.prototype.h = function (a, b) { var c = this, d = I(this.s, a, b); return Zc(d, b).M(function (e) { var f = e.map(function (k) { return Pd(k, 1).value }).join(" "); if (-1 !== f.indexOf("?>")) throw Error('XQDY0026: The contents of the data of a processing instruction may not include "?>"'); if (null !== c.l.Nb) return e = c.l.Nb, gk(e), C.m($b(hk(e, f))); e = I(c.l.Fb, a, b); var g = Tj(b, e); return C.create({ next: function () { var k = g.next(0); if (k.done) return k; k = k.value.value; gk(k); return y($b(hk(k, f))) } }) }) }; function jk(a) { H.call(this, a ? a.o : new Hf({}), a ? [a] : [], { C: !1, R: "unsorted" }); this.l = a } v(jk, H); jk.prototype.h = function (a, b) { if (!this.l) return C.empty(); a = I(this.l, a, b); return Zc(a, b).M(function (c) { if (0 === c.length) return C.empty(); c = { node: { data: c.map(function (d) { return Pd(d, 1).value }).join(" "), Ta: !0, nodeType: 3 }, F: null }; return C.m($b(c)) }) }; function kk(a, b, c, d) { var e = new Hf({}), f; Df.call(this, e, (f = [a].concat(t(b.map(function (g) { return g.ic })), [c])).concat.apply(f, t(b.map(function (g) { return g.Pb.map(function (k) { return k.Ob }) }))), { C: !1, X: !1, R: "unsorted", subtree: !1 }, d); this.K = a; this.l = b.length; this.S = b.map(function (g) { return g.Pb }) } v(kk, Df); ++ kk.prototype.A = function (a, b, c) { var d = this; return c[0](a).M(function (e) { for (var f = 0; f < d.l; f++)if (d.S[f].some(function (g) { switch (g.oc) { case "?": if (1 < e.length) return !1; break; case "*": break; case "+": if (1 > e.length) return !1; break; default: if (1 !== e.length) return !1 }var k = C.create(e); return e.every(function (l, m) { l = Jc(a, m, l, k); return I(g.Ob, l, b).ha() }) })) return c[f + 1](a); return c[d.l + 1](a) }) }; kk.prototype.v = function (a) { Df.prototype.v.call(this, a); if (this.K.I) throw af(); }; var lk = { $: !1, ua: !1 }, mk = { $: !0, ua: !1 }, nk = { $: !0, ua: !0 }; function ok(a) { return a.$ ? a.ua ? nk : mk : lk } ++ function R(a, b) { ++ switch (a[0]) { ++ case "andOp": var c = M(a, "type"); return new wi(pk("andOp", a, ok(b)), c); case "orOp": return c = M(a, "type"), new xi(pk("orOp", a, ok(b)), c); case "unaryPlusOp": return c = L(L(a, "operand"), "*"), a = M(a, "type"), new vi("+", R(c, b), a); case "unaryMinusOp": return c = L(L(a, "operand"), "*"), a = M(a, "type"), new vi("-", R(c, b), a); case "addOp": case "subtractOp": case "multiplyOp": case "divOp": case "idivOp": case "modOp": var d = a[0], e = R(N(a, ["firstOperand", "*"]), ok(b)); b = R(N(a, ["secondOperand", "*"]), ok(b)); ++ var f = M(a, "type"), g = M(N(a, ["firstOperand", "*"]), "type"), k = M(N(a, ["secondOperand", "*"]), "type"); g && k && M(a, "type") && (c = dh(d, g.type, k.type)); return new mh(d, e, b, f, c); case "sequenceExpr": return qk(a, b); case "unionOp": return c = M(a, "type"), new bj([R(N(a, ["firstOperand", "*"]), ok(b)), R(N(a, ["secondOperand", "*"]), ok(b))], c); case "exceptOp": case "intersectOp": return c = M(a, "type"), new Si(a[0], R(N(a, ["firstOperand", "*"]), ok(b)), R(N(a, ["secondOperand", "*"]), ok(b)), c); case "stringConcatenateOp": return rk(a, b); ++ case "rangeSequenceExpr": return sk(a, b); case "equalOp": case "notEqualOp": case "lessThanOrEqualOp": case "lessThanOp": case "greaterThanOrEqualOp": case "greaterThanOp": return tk("generalCompare", a, b); case "eqOp": case "neOp": case "ltOp": case "leOp": case "gtOp": case "geOp": return tk("valueCompare", a, b); case "isOp": case "nodeBeforeOp": case "nodeAfterOp": return tk("nodeCompare", a, b); case "pathExpr": return uk(a, b); case "contextItemExpr": return new gj(M(a, "type")); case "functionCallExpr": return vk(a, b); case "inlineFunctionExpr": return wk(a, ++ b); case "arrowExpr": return xk(a, b); case "dynamicFunctionInvocationExpr": return yk(a, b); case "namedFunctionRef": return b = L(a, "functionName"), c = M(a, "type"), a = Tg(N(a, ["integerConstantExpr", "value"])), new si(Ug(b), parseInt(a, 10), c); case "integerConstantExpr": return new qi(Tg(L(a, "value")), { type: 5, g: 3 }); case "stringConstantExpr": return new qi(Tg(L(a, "value")), { type: 1, g: 3 }); case "decimalConstantExpr": return new qi(Tg(L(a, "value")), { type: 4, g: 3 }); case "doubleConstantExpr": return new qi(Tg(L(a, "value")), { ++ type: 3, ++ g: 3 ++ }); case "varRef": return a = Ug(L(a, "name")), new uj(a.prefix, a.namespaceURI, a.localName); case "flworExpr": return zk(a, b); case "quantifiedExpr": return Ak(a, b); case "ifThenElseExpr": return c = M(a, "type"), new hi(R(L(L(a, "ifClause"), "*"), ok(b)), R(L(L(a, "thenClause"), "*"), b), R(L(L(a, "elseClause"), "*"), b), c); case "instanceOfExpr": return c = R(N(a, ["argExpr", "*"]), b), d = N(a, ["sequenceType", "*"]), e = N(a, ["sequenceType", "occurrenceIndicator"]), a = M(a, "type"), new Yi(c, R(d, ok(b)), e ? Tg(e) : "", a); case "castExpr": return b = ++ R(L(L(a, "argExpr"), "*"), ok(b)), c = L(a, "singleType"), a = Ug(L(c, "atomicType")), c = null !== L(c, "optional"), new Wi(b, a, c); case "castableExpr": return b = R(L(L(a, "argExpr"), "*"), ok(b)), c = L(a, "singleType"), a = Ug(L(c, "atomicType")), c = null !== L(c, "optional"), new Vi(b, a, c); case "simpleMapExpr": return Bk(a, b); case "mapConstructor": return Ck(a, b); case "arrayConstructor": return Dk(a, b); case "unaryLookup": return c = M(a, "type"), new nj(Ek(a, b), c); case "typeswitchExpr": return Fk(a, b); case "elementConstructor": return Gk(a, ++ b); case "attributeConstructor": return Hk(a, b); case "computedAttributeConstructor": return (c = L(a, "tagName")) ? c = Ug(c) : (c = L(a, "tagNameExpr"), c = { Oa: R(L(c, "*"), ok(b)) }), a = R(L(L(a, "valueExpr"), "*"), ok(b)), new dk(c, { wb: [a] }); case "computedCommentConstructor": if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); a = (a = L(a, "argExpr")) ? R(L(a, "*"), ok(b)) : null; return new ek(a); case "computedTextConstructor": if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); ++ a = (a = L(a, "argExpr")) ? R(L(a, "*"), ok(b)) : null; return new jk(a); case "computedElementConstructor": return Ik(a, b); case "computedPIConstructor": if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); c = L(a, "piTargetExpr"); d = L(a, "piTarget"); e = L(a, "piValueExpr"); a = M(a, "type"); return new ik({ Fb: c ? R(L(c, "*"), ok(b)) : null, Nb: d ? Tg(d) : null }, e ? R(L(e, "*"), ok(b)) : new Ti([], a)); case "CDataSection": return new qi(Tg(a), { type: 1, g: 3 }); case "deleteExpr": return a = R(N(a, ["targetExpr", ++ "*"]), b), new Kj(a); case "insertExpr": c = R(N(a, ["sourceExpr", "*"]), b); e = O(a, "*")[1]; switch (e[0]) { case "insertAfter": d = 1; break; case "insertBefore": d = 2; break; case "insertInto": d = (d = L(e, "*")) ? "insertAsFirst" === d[0] ? 4 : 5 : 3 }a = R(N(a, ["targetExpr", "*"]), b); return new Oj(c, d, a); case "renameExpr": return c = R(N(a, ["targetExpr", "*"]), b), a = R(N(a, ["newNameExpr", "*"]), b), new Vj(c, a); case "replaceExpr": return c = !!L(a, "replaceValue"), d = R(N(a, ["targetExpr", "*"]), b), a = R(N(a, ["replacementExpr", "*"]), b), new Yj(c, d, a); case "transformExpr": return Jk(a, ++ b); case "x:stackTrace": c = a[1]; for (a = a[2]; "x:stackTrace" === a[0];)a = a[2]; return new ji(c, a[0], R(a, b)); default: return Kk(a) ++ } ++ } ++ function Kk(a) { ++ switch (a[0]) { ++ case "nameTest": return new rj(Ug(a)); case "piTest": return (a = L(a, "piTarget")) ? new sj(Tg(a)) : new qj(7); case "commentTest": return new qj(8); case "textTest": return new qj(3); case "documentTest": return new qj(9); case "attributeTest": var b = (a = L(a, "attributeName")) && L(a, "star"); return !a || b ? new qj(2) : new rj(Ug(L(a, "QName")), { kind: 2 }); case "elementTest": return b = (a = L(a, "elementName")) && L(a, "star"), !a || b ? new qj(1) : new rj(Ug(L(a, "QName")), { kind: 1 }); case "anyKindTest": return new tj({ ++ prefix: "", ++ namespaceURI: null, localName: "node()" ++ }); case "anyMapTest": return new tj({ prefix: "", namespaceURI: null, localName: "map(*)" }); case "anyArrayTest": return new tj({ prefix: "", namespaceURI: null, localName: "array(*)" }); case "Wildcard": return L(a, "star") ? (b = L(a, "uri")) ? a = new rj({ localName: "*", namespaceURI: Tg(b), prefix: "" }) : (b = L(a, "NCName"), a = "star" === L(a, "*")[0] ? new rj({ localName: Tg(b), namespaceURI: null, prefix: "*" }) : new rj({ localName: "*", namespaceURI: null, prefix: Tg(b) })) : a = new rj({ ++ localName: "*", namespaceURI: null, ++ prefix: "*" ++ }), a; case "atomicType": return new tj(Ug(a)); case "anyItemType": return new tj({ prefix: "", namespaceURI: null, localName: "item()" }); default: throw Error("No selector counterpart for: " + a[0] + "."); ++ } ++ } function Dk(a, b) { var c = M(a, "type"); a = L(a, "*"); var d = O(a, "arrayElem").map(function (e) { return R(L(e, "*"), ok(b)) }); switch (a[0]) { case "curlyArray": return new Kh(d, c); case "squareArray": return new Lh(d, c); default: throw Error("Unrecognized arrayType: " + a[0]); } } ++ function Ck(a, b) { var c = M(a, "type"); return new ri(O(a, "mapConstructorEntry").map(function (d) { return { key: R(N(d, ["mapKeyExpr", "*"]), ok(b)), value: R(N(d, ["mapValueExpr", "*"]), ok(b)) } }), c) } function pk(a, b, c) { function d(f) { var g = L(L(f, "firstOperand"), "*"); f = L(L(f, "secondOperand"), "*"); g[0] === a ? d(g) : e.push(R(g, c)); f[0] === a ? d(f) : e.push(R(f, c)) } var e = []; d(b); return e } function Ek(a, b) { a = L(a, "*"); switch (a[0]) { case "NCName": return new qi(Tg(a), { type: 1, g: 3 }); case "star": return "*"; default: return R(a, ok(b)) } } ++ function tk(a, b, c) { var d = N(b, ["firstOperand", "*"]), e = N(b, ["secondOperand", "*"]); d = R(d, ok(c)); c = R(e, ok(c)); switch (a) { case "valueCompare": return new Ji(b[0], d, c); case "nodeCompare": return new Qi(b[0], d, c); case "generalCompare": return new Ni(b[0], d, c) } } ++ function Lk(a, b, c) { a = O(a, "*"); return new ej(a.filter(function (d) { return "stable" !== d[0] }).map(function (d) { var e = L(d, "orderModifier"), f = e ? L(e, "orderingKind") : null; e = e ? L(e, "emptyOrderingMode") : null; f = f ? "ascending" === Tg(f) : !0; e = e ? "empty least" === Tg(e) : !0; return { ca: R(N(d, ["orderByExpr", "*"]), b), Kb: f, lc: e } }), c) } ++ function zk(a, b) { ++ var c = O(a, "*"); a = L(c[c.length - 1], "*"); c = c.slice(0, -1); if (1 < c.length && !b.$) throw Error("XPST0003: Use of XQuery FLWOR expressions in XPath is no allowed"); return c.reduceRight(function (d, e) { ++ switch (e[0]) { ++ case "forClause": e = O(e, "*"); for (var f = e.length - 1; 0 <= f; --f) { var g = e[f], k = N(g, ["forExpr", "*"]), l = L(g, "positionalVariableBinding"); d = new ni(Ug(N(g, ["typedVariableBinding", "varName"])), R(k, ok(b)), l ? Ug(l) : null, d) } return d; case "letClause": e = O(e, "*"); for (f = e.length - 1; 0 <= f; --f)g = e[f], k = N(g, ++ ["letExpr", "*"]), d = new pi(Ug(N(g, ["typedVariableBinding", "varName"])), R(k, ok(b)), d); return d; case "whereClause": e = O(e, "*"); for (f = e.length - 1; 0 <= f; --f)d = new vj(R(e[f], b), d); return d; case "windowClause": throw Error("Not implemented: " + e[0] + " is not implemented yet."); case "groupByClause": throw Error("Not implemented: " + e[0] + " is not implemented yet."); case "orderByClause": return Lk(e, b, d); case "countClause": throw Error("Not implemented: " + e[0] + " is not implemented yet."); default: throw Error("Not implemented: " + ++ e[0] + " is not supported in a flwor expression"); ++ } ++ }, R(a, b)) ++ } function vk(a, b) { var c = L(a, "functionName"), d = O(L(a, "arguments"), "*"); a = M(a, "type"); return new Nf(new si(Ug(c), d.length, a), d.map(function (e) { return "argumentPlaceholder" === e[0] ? null : R(e, b) }), a) } ++ function xk(a, b) { var c = M(a, "type"), d = N(a, ["argExpr", "*"]); a = O(a, "*").slice(1); d = [R(d, b)]; for (var e = 0; e < a.length; e++)if ("arguments" !== a[e][0]) { if ("arguments" === a[e + 1][0]) { var f = O(a[e + 1], "*"); d = d.concat(f.map(function (g) { return "argumentPlaceholder" === g[0] ? null : R(g, b) })) } f = "EQName" === a[e][0] ? new si(Ug(a[e]), d.length, c) : R(a[e], ok(b)); d = [new Nf(f, d, c)] } return d[0] } ++ function yk(a, b) { var c = N(a, ["functionItem", "*"]), d = M(a, "type"); a = L(a, "arguments"); var e = []; a && (e = O(a, "*").map(function (f) { return "argumentPlaceholder" === f[0] ? null : R(f, b) })); return new Nf(R(c, b), e, d) } function wk(a, b) { var c = O(L(a, "paramList"), "*"), d = N(a, ["functionBody", "*"]), e = M(a, "type"); return new oi(c.map(function (f) { return { name: Ug(L(f, "varName")), type: Vg(f) } }), Vg(a), d ? R(d, b) : new Ti([], e)) } ++ function uk(a, b) { ++ var c = M(a, "type"), d = O(a, "stepExpr"), e = !1, f = d.map(function (g) { ++ var k = L(g, "xpathAxis"), l = O(g, "*"), m = [], q = null; l = p(l); for (var u = l.next(); !u.done; u = l.next())switch (u = u.value, u[0]) { case "lookup": m.push(["lookup", Ek(u, b)]); break; case "predicate": case "predicates": u = p(O(u, "*")); for (var z = u.next(); !z.done; z = u.next())z = R(z.value, ok(b)), q = Qh(q, z.B()), m.push(["predicate", z]) }if (k) switch (e = !0, g = L(g, "attributeTest anyElementTest piTest documentTest elementTest commentTest namespaceTest anyKindTest textTest anyFunctionTest typedFunctionTest schemaAttributeTest atomicType anyItemType parenthesizedItemType typedMapTest typedArrayTest nameTest Wildcard".split(" ")), ++ g = Kk(g), Tg(k)) { case "ancestor": var A = new Oh(g, { Ra: !1 }); break; case "ancestor-or-self": A = new Oh(g, { Ra: !0 }); break; case "attribute": A = new Rh(g, q); break; case "child": A = new Sh(g, q); break; case "descendant": A = new Vh(g, { Ra: !1 }); break; case "descendant-or-self": A = new Vh(g, { Ra: !0 }); break; case "parent": A = new bi(g, q); break; case "following-sibling": A = new ai(g, q); break; case "preceding-sibling": A = new fi(g, q); break; case "following": A = new Zh(g); break; case "preceding": A = new di(g); break; case "self": A = new gi(g, q) } else A = ++ N(g, ["filterExpr", "*"]), A = R(A, ok(b)); m = p(m); for (k = m.next(); !k.done; k = m.next())switch (k = k.value, k[0]) { case "lookup": A = new mj(A, k[1]); break; case "predicate": A = new jj(A, k[1]) }A.type = c; return A ++ }); a = L(a, "rootExpr"); d = e || null !== a || 1 < d.length; if (!d && 1 === f.length || !a && 1 === f.length && "sorted" === f[0].da) return f[0]; if (a && 0 === f.length) return new fj(null); f = new ij(f, d); return a ? new fj(f) : f ++ } ++ function Ak(a, b) { var c = M(a, "type"), d = Tg(L(a, "quantifier")), e = N(a, ["predicateExpr", "*"]); a = O(a, "quantifiedExprInClause").map(function (f) { var g = Ug(N(f, ["typedVariableBinding", "varName"])); f = N(f, ["sourceExpr", "*"]); return { name: g, jb: R(f, ok(b)) } }); return new oj(d, a, R(e, ok(b)), c) } function qk(a, b) { var c = O(a, "*").map(function (d) { return R(d, b) }); if (1 === c.length) return c[0]; c = M(a, "type"); return new Ti(O(a, "*").map(function (d) { return R(d, b) }), c) } ++ function Bk(a, b) { var c = M(a, "type"); return O(a, "*").reduce(function (d, e) { return null === d ? R(e, ok(b)) : new Ui(d, R(e, ok(b)), c) }, null) } function rk(a, b) { var c = M(a, "type"); a = [N(a, ["firstOperand", "*"]), N(a, ["secondOperand", "*"])]; return new Nf(new si({ localName: "concat", namespaceURI: "http://www.w3.org/2005/xpath-functions", prefix: "" }, a.length, c), a.map(function (d) { return R(d, ok(b)) }), c) } ++ function sk(a, b) { var c = M(a, "type"); a = [L(L(a, "startExpr"), "*"), L(L(a, "endExpr"), "*")]; var d = new si({ localName: "to", namespaceURI: "http://fontoxpath/operators", prefix: "" }, a.length, c); return new Nf(d, a.map(function (e) { return R(e, ok(b)) }), c) } ++ function Gk(a, b) { if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); var c = Ug(L(a, "tagName")), d = L(a, "attributeList"), e = d ? O(d, "attributeConstructor").map(function (f) { return R(f, ok(b)) }) : []; d = d ? O(d, "namespaceDeclaration").map(function (f) { var g = L(f, "prefix"); return { prefix: g ? Tg(g) : "", uri: Tg(L(f, "uri")) } }) : []; a = (a = L(a, "elementContent")) ? O(a, "*").map(function (f) { return R(f, ok(b)) }) : []; return new fk(c, e, d, a) } ++ function Hk(a, b) { if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); var c = Ug(L(a, "attributeName")), d = L(a, "attributeValue"); d = d ? Tg(d) : null; a = (a = L(a, "attributeValueExpr")) ? O(a, "*").map(function (e) { return R(e, ok(b)) }) : null; return new dk(c, { value: d, wb: a }) } function Ik(a, b) { var c = L(a, "tagName"); c ? c = Ug(c) : (c = L(a, "tagNameExpr"), c = { Oa: R(L(c, "*"), ok(b)) }); a = (a = L(a, "contentExpr")) ? O(a, "*").map(function (d) { return R(d, ok(b)) }) : []; return new fk(c, [], [], a) } ++ function Jk(a, b) { var c = O(L(a, "transformCopies"), "transformCopy").map(function (e) { var f = Ug(L(L(e, "varRef"), "name")); return { jb: R(L(L(e, "copySource"), "*"), b), Rb: new zb(f.prefix, f.namespaceURI, f.localName) } }), d = R(L(L(a, "modifyExpr"), "*"), b); a = R(L(L(a, "returnExpr"), "*"), b); return new bk(c, d, a) } ++ function Fk(a, b) { ++ if (!b.$) throw Error("XPST0003: Use of XQuery functionality is not allowed in XPath context"); var c = M(a, "type"), d = R(L(L(a, "argExpr"), "*"), b), e = O(a, "typeswitchExprCaseClause").map(function (f) { var g = 0 === O(f, "sequenceTypeUnion").length ? [L(f, "sequenceType")] : O(L(f, "sequenceTypeUnion"), "sequenceType"); return { ic: R(N(f, ["resultExpr", "*"]), b), Pb: g.map(function (k) { var l = L(k, "occurrenceIndicator"); return { oc: l ? Tg(l) : "", Ob: R(L(k, "*"), b) } }) } }); a = R(N(a, ["typeswitchExprDefaultClause", "resultExpr", ++ "*"]), b); return new kk(d, e, a, c) ++ } function Mk(a, b) { return R(a, b) }; var Nk = new Map; function Ok(a, b, c, d, e, f) { this.B = a; this.l = b; this.h = c; this.v = d; this.o = e; this.s = f } ++ function Pk(a, b, c, d, e, f, g, k) { a = Nk.get(a); if (!a) return null; b = a[b + (f ? "_DEBUG" : "")]; return b ? (b = b.find(function (l) { return l.o === g && l.B.every(function (m) { return c(m.prefix) === m.namespaceURI }) && l.l.every(function (m) { return void 0 !== d[m.name] }) && l.v.every(function (m) { return e[m.prefix] === m.namespaceURI }) && l.s.every(function (m) { var q = k(m.nc, m.arity); return q && q.namespaceURI === m.Lb.namespaceURI && q.localName === m.Lb.localName }) })) ? { ca: b.h, rc: !1 } : null : null } ++ function Qk(a, b, c, d, e, f, g) { var k = Nk.get(a); k || (k = Object.create(null), Nk.set(a, k)); a = b + (f ? "_DEBUG" : ""); (b = k[a]) || (b = k[a] = []); b.push(new Ok(Object.values(c.h), Object.values(c.o), e, Object.keys(d).map(function (l) { return { namespaceURI: d[l], prefix: l } }), g, c.B)) }; function Rk(a) { ++ var b = new Gb; if ("http://www.w3.org/2005/XQueryX" !== a.namespaceURI && "http://www.w3.org/2005/XQueryX" !== a.namespaceURI && "http://fontoxml.com/fontoxpath" !== a.namespaceURI && "http://www.w3.org/2007/xquery-update-10" !== a.namespaceURI) throw Sc("The XML structure passed as an XQueryX program was not valid XQueryX"); var c = ["stackTrace" === a.localName ? "x:stackTrace" : a.localName], d = b.getAllAttributes(a); d && 0 < d.length && c.push(Array.from(d).reduce(function (e, f) { ++ "start" === f.localName || "end" === f.localName && ++ "stackTrace" === a.localName ? e[f.localName] = JSON.parse(f.value) : "type" === f.localName ? e[f.localName] = qb(f.value) : e[f.localName] = f.value; return e ++ }, {})); b = b.getChildNodes(a); b = p(b); for (d = b.next(); !d.done; d = b.next())switch (d = d.value, d.nodeType) { case 1: c.push(Rk(d)); break; case 3: c.push(d.data) }return c ++ }; var Sk = Object.create(null); function Tk(a, b) { var c = Sk[a]; c || (c = Sk[a] = { Ma: [], Va: [], ra: null, source: b.source }); var d = c.ra || function () { }; c.Ma = c.Ma.concat(b.Ma); c.Va = c.Va.concat(b.Va); c.ra = function (e) { d(e); b.ra && b.ra(e) } } function Uk(a, b) { var c = Sk[b]; if (!c) throw Error("XQST0051: No modules found with the namespace uri " + b); c.Ma.forEach(function (d) { d.hb && Og(a, b, d.localName, d.arity, d) }); c.Va.forEach(function (d) { Qg(a, b, d.localName); Rg(a, b, d.localName, function (e, f) { return I(d.ca, e, f) }) }) } ++ function Vk() { Object.keys(Sk).forEach(function (a) { a = Sk[a]; if (a.ra) try { a.ra(a) } catch (b) { a.ra = null, pg(a.source, b) } a.ra = null }) }; function hl(a) { return a.replace(/(\x0D\x0A)|(\x0D(?!\x0A))/g, String.fromCharCode(10)) }; var S = prsc; function il(a, b) { return function (c, d) { if (b.has(d)) return b.get(d); c = a(c, d); b.set(d, c); return c } } function U(a, b) { return S.delimited(b, a, b) } function V(a, b) { return a.reverse().reduce(function (c, d) { return S.preceded(d, c) }, b) } function jl(a, b, c, d) { return S.then(S.then(a, b, function (e, f) { return [e, f] }), c, function (e, f) { var g = p(e); e = g.next().value; g = g.next().value; return d(e, g, f) }) } ++ function kl(a, b, c, d, e) { return S.then(S.then(S.then(a, b, function (f, g) { return [f, g] }), c, function (f, g) { var k = p(f); f = k.next().value; k = k.next().value; return [f, k, g] }), d, function (f, g) { var k = p(f); f = k.next().value; var l = k.next().value; k = k.next().value; return e(f, l, k, g) }) } ++ function ll(a, b, c, d, e, f) { return S.then(S.then(S.then(S.then(a, b, function (g, k) { return [g, k] }), c, function (g, k) { var l = p(g); g = l.next().value; l = l.next().value; return [g, l, k] }), d, function (g, k) { var l = p(g); g = l.next().value; var m = l.next().value; l = l.next().value; return [g, m, l, k] }), e, function (g, k) { var l = p(g); g = l.next().value; var m = l.next().value, q = l.next().value; l = l.next().value; return f(g, m, q, l, k) }) } function ml(a) { return S.map(a, function (b) { return [b] }) } ++ function nl(a, b) { return S.map(S.or(a), function () { return b }) } function ol(a) { return function (b, c) { return (b = a.exec(b.substring(c))) && 0 === b.index ? S.okWithValue(c + b[0].length, b[0]) : S.error(c, [a.source], !1) } }; var pl = S.or([S.token(" "), S.token("\t"), S.token("\r"), S.token("\n")]), ql = S.token("(:"), rl = S.token(":)"), sl = S.token("(#"), tl = S.token("#)"), ul = S.token("("), vl = S.token(")"), wl = S.token("["), xl = S.token("]"), yl = S.token("{"), zl = S.token("}"), Al = S.token("{{"), Bl = S.token("}}"), Cl = S.token("'"), Dl = S.token("''"), El = S.token('"'), Fl = S.token('""'), Gl = S.token(""), Jl = S.token(""), Ol = S.token("&#x"), ++ Pl = S.token("&#"), Ql = S.token(":*"), Rl = S.token("*:"), Sl = S.token(":="), Tl = S.token("&"), Ul = S.token(":"), Vl = S.token(";"), Wl = S.token("*"), Xl = S.token("@"), Yl = S.token("$"), Zl = S.token("#"), $l = S.token("%"), am = S.token("?"), bm = S.token("="), cm = S.followed(S.token("!"), S.not(S.peek(bm), [])), dm = S.followed(S.token("|"), S.not(S.peek(S.token("|")), [])), em = S.token("||"), fm = S.token("!="), gm = S.token("<"), hm = S.token("<<"), im = S.token("<="), jm = S.token(">"), km = S.token(">>"), lm = S.token(">="), mm = S.token(","), nm = S.token("."), ++ om = S.token(".."), pm = S.token("+"), qm = S.token("-"), rm = S.token("/"), sm = S.token("//"), tm = S.token("=>"), um = S.token("e"), vm = S.token("E"); S.token("l"); S.token("L"); S.token("m"); S.token("M"); var wm = S.token("Q"); S.token("x"); S.token("X"); ++ var xm = S.token("as"), ym = S.token("cast"), zm = S.token("castable"), Am = S.token("treat"), Bm = S.token("instance"), Cm = S.token("of"), Dm = S.token("node"), Em = S.token("nodes"), Fm = S.token("delete"), Gm = S.token("value"), Hm = S.token("function"), Im = S.token("map"), Jm = S.token("element"), Km = S.token("attribute"), Lm = S.token("schema-element"), Mm = S.token("intersect"), Nm = S.token("except"), Om = S.token("union"), Pm = S.token("to"), Qm = S.token("is"), Rm = S.token("or"), Sm = S.token("and"), Tm = S.token("div"), Um = S.token("idiv"), Vm = S.token("mod"), ++ Wm = S.token("eq"), Xm = S.token("ne"), Ym = S.token("lt"), Zm = S.token("le"), $m = S.token("gt"), an = S.token("ge"), bn = S.token("amp"), cn = S.token("quot"), dn = S.token("apos"), en = S.token("if"), fn = S.token("then"), gn = S.token("else"), hn = S.token("allowing"), jn = S.token("empty"), kn = S.token("at"), ln = S.token("in"), mn = S.token("for"), nn = S.token("let"), on = S.token("where"), pn = S.token("collation"), qn = S.token("group"), rn = S.token("by"), sn = S.token("order"), tn = S.token("stable"), un = S.token("return"), vn = S.token("array"), wn = S.token("document"), ++ xn = S.token("namespace"), yn = S.token("text"), zn = S.token("comment"), An = S.token("processing-instruction"), Bn = S.token("lax"), Cn = S.token("strict"), Dn = S.token("validate"), En = S.token("type"), Fn = S.token("declare"), Gn = S.token("default"), Hn = S.token("boundary-space"), In = S.token("strip"), Jn = S.token("preserve"), Kn = S.token("no-preserve"), Ln = S.token("inherit"), Mn = S.token("no-inherit"), Nn = S.token("greatest"), On = S.token("least"), Pn = S.token("copy-namespaces"), Qn = S.token("decimal-format"), Rn = S.token("case"), Sn = S.token("typeswitch"), ++ Tn = S.token("some"), Un = S.token("every"), Vn = S.token("satisfies"), Wn = S.token("replace"), Xn = S.token("with"), Yn = S.token("copy"), Zn = S.token("modify"), $n = S.token("first"), ao = S.token("last"), bo = S.token("before"), co = S.token("after"), eo = S.token("into"), fo = S.token("insert"), go = S.token("rename"), ho = S.token("switch"), io = S.token("variable"), jo = S.token("external"), ko = S.token("updating"), lo = S.token("import"), mo = S.token("schema"), no = S.token("module"), oo = S.token("base-uri"), po = S.token("construction"), qo = S.token("ordering"), ++ ro = S.token("ordered"), so = S.token("unordered"), to = S.token("option"), uo = S.token("context"), vo = S.token("item"), wo = S.token("xquery"), xo = S.token("version"), yo = S.token("encoding"), zo = S.token("document-node"), Ao = S.token("namespace-node"), Bo = S.token("schema-attribute"), Co = S.token("ascending"), Do = S.token("descending"), Eo = S.token("empty-sequence"), Fo = S.token("child::"), Go = S.token("descendant::"), Ho = S.token("attribute::"), Io = S.token("self::"), Jo = S.token("descendant-or-self::"), Ko = S.token("following-sibling::"), ++ Lo = S.token("following::"), Mo = S.token("parent::"), No = S.token("ancestor::"), Oo = S.token("preceding-sibling::"), Po = S.token("preceding::"), Qo = S.token("ancestor-or-self::"), Ro = S.token("decimal-separator"), So = S.token("grouping-separator"), To = S.token("infinity"), Uo = S.token("minus-sign"), Vo = S.token("NaN"), Wo = S.token("per-mille"), Xo = S.token("zero-digit"), Yo = S.token("digit"), Zo = S.token("pattern-separator"), $o = S.token("exponent-separator"), ap = S.token("schema-attribute("), bp = S.token("document-node("), cp = S.token("processing-instruction("), ++ dp = S.token("processing-instruction()"), ep = S.token("comment()"), fp = S.token("text()"), gp = S.token("namespace-node()"), hp = S.token("node()"), ip = S.token("item()"), jp = S.token("empty-sequence()"); var kp = new Map, lp = new Map, mp = S.or([ol(/[\t\n\r -\uD7FF\uE000\uFFFD]/), ol(/[\uD800-\uDBFF][\uDC00-\uDFFF]/)]), np = S.preceded(S.peek(S.not(S.or([ql, rl]), ['comment contents cannot contain "(:" or ":)"'])), mp), op = S.map(S.delimited(ql, S.star(S.or([np, function (a, b) { return op(a, b) }])), rl, !0), function (a) { return a.join("") }), pp = S.or([pl, op]), qp = S.map(S.plus(pl), function (a) { return a.join("") }), W = il(S.map(S.star(pp), function (a) { return a.join("") }), kp), X = il(S.map(S.plus(pp), function (a) { return a.join("") }), lp); var rp = S.or([ol(/[A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/), S.then(ol(/[\uD800-\uDB7F]/), ol(/[\uDC00-\uDFFF]/), function (a, b) { return a + b })]), sp = S.or([rp, ol(/[\-\.0-9\xB7\u0300-\u036F\u203F\u2040]/)]), tp = S.then(rp, S.star(sp), function (a, b) { return a + b.join("") }), up = S.map(tp, function (a) { return ["prefix", a] }), vp = S.or([rp, Ul]), wp = S.or([sp, Ul]); S.then(vp, S.star(wp), function (a, b) { return a + b.join("") }); ++ var xp = S.map(tp, function (a) { var b = {}; return [(b.prefix = "", b.URI = null, b), a] }), yp = S.then(tp, S.preceded(Ul, tp), function (a, b) { var c = {}; return [(c.prefix = a, c.URI = null, c), b] }), zp = S.or([yp, xp]), Ap = S.followed(V([wm, W, yl], S.map(S.star(ol(/[^{}]/)), function (a) { return a.join("").replace(/\s+/g, " ").trim() })), zl), Bp = S.then(Ap, tp, function (a, b) { return [a, b] }), Cp = S.or([S.map(Bp, function (a) { var b = {}; return [(b.prefix = null, b.URI = a[0], b), a[1]] }), zp]), Dp = S.or([S.map(Cp, function (a) { return ["QName"].concat(t(a)) }), S.map(Wl, ++ function () { return ["star"] })]), Ep = S.map(S.preceded(Yl, Cp), function (a) { return ["varRef", ["name"].concat(t(a))] }); var Fp = S.peek(S.or([ul, El, Cl, pp])), Gp = S.map(S.or([Fo, Go, Ho, Io, Jo, Ko, Lo]), function (a) { return a.substring(0, a.length - 2) }), Hp = S.map(S.or([Mo, No, Oo, Po, Qo]), function (a) { return a.substring(0, a.length - 2) }), Ip = jl(Tl, S.or([Ym, $m, bn, cn, dn]), Vl, function (a, b, c) { return a + b + c }), Jp = S.or([jl(Ol, ol(/[0-9a-fA-F]+/), Vl, function (a, b, c) { return a + b + c }), jl(Pl, ol(/[0-9]+/), Vl, function (a, b, c) { return a + b + c })]), Kp = nl([Fl], '"'), Lp = nl([Dl], "'"), Mp = ml(nl([ep], "commentTest")), Np = ml(nl([fp], "textTest")), Op = ml(nl([gp], "namespaceTest")), ++ Pp = ml(nl([hp], "anyKindTest")), Qp = ol(/[0-9]+/), Rp = S.then(S.or([S.then(nm, Qp, function (a, b) { return a + b }), S.then(Qp, S.optional(S.preceded(nm, ol(/[0-9]*/))), function (a, b) { return a + (null !== b ? "." + b : "") })]), jl(S.or([um, vm]), S.optional(S.or([pm, qm])), Qp, function (a, b, c) { return a + (b ? b : "") + c }), function (a, b) { return ["doubleConstantExpr", ["value", a + b]] }), Sp = S.or([S.map(S.preceded(nm, Qp), function (a) { return ["decimalConstantExpr", ["value", "." + a]] }), S.then(S.followed(Qp, nm), S.optional(Qp), function (a, b) { ++ return ["decimalConstantExpr", ++ ["value", a + "." + (null !== b ? b : "")]] ++ })]), Tp = S.map(Qp, function (a) { return ["integerConstantExpr", ["value", a]] }), Up = S.followed(S.or([Rp, Sp, Tp]), S.peek(S.not(ol(/[a-zA-Z]/), ["no alphabetical characters after numeric literal"]))), Vp = S.map(S.followed(nm, S.peek(S.not(nm, ["context item should not be followed by another ."]))), function () { return ["contextItemExpr"] }), Wp = S.or([vn, Km, zn, zo, Jm, Eo, Hm, en, vo, Im, Ao, Dm, An, Bo, Lm, ho, yn, Sn]), Xp = ml(nl([am], "argumentPlaceholder")), Yp = S.or([am, Wl, pm]), Zp = S.preceded(S.peek(S.not(ol(/[{}<&]/), ++ ["elementContentChar cannot be {, }, <, or &"])), mp), $p = S.map(S.delimited(Gl, S.star(S.preceded(S.peek(S.not(Hl, ['CDataSection content may not contain "]]\x3e"'])), mp)), Hl, !0), function (a) { return ["CDataSection", a.join("")] }), aq = S.preceded(S.peek(S.not(ol(/["{}<&]/), ['quotAttrValueContentChar cannot be ", {, }, <, or &'])), mp), bq = S.preceded(S.peek(S.not(ol(/['{}<&]/), ["aposAttrValueContentChar cannot be ', {, }, <, or &"])), mp), cq = S.map(S.star(S.or([S.preceded(S.peek(S.not(qm, [])), mp), S.map(V([qm, S.peek(S.not(qm, ++ []))], mp), function (a) { return "-" + a })])), function (a) { return a.join("") }), dq = S.map(S.delimited(Kl, cq, Ll, !0), function (a) { return ["computedCommentConstructor", ["argExpr", ["stringConstantExpr", ["value", a]]]] }), eq = S.filter(tp, function (a) { return "xml" !== a.toLowerCase() }, ['A processing instruction target cannot be "xml"']), fq = S.map(S.star(S.preceded(S.peek(S.not(Nl, [])), mp)), function (a) { return a.join("") }), gq = S.then(S.preceded(Ml, S.cut(eq)), S.cut(S.followed(S.optional(S.preceded(qp, fq)), Nl)), function (a, b) { ++ return ["computedPIConstructor", ++ ["piTarget", a], ["piValueExpr", ["stringConstantExpr", ["value", b]]]] ++ }), hq = S.map(sm, function () { return ["stepExpr", ["xpathAxis", "descendant-or-self"], ["anyKindTest"]] }), iq = S.or([Bn, Cn]), jq = S.map(S.star(S.followed(mp, S.peek(S.not(tl, ["Pragma contents should not contain '#)'"])))), function (a) { return a.join("") }), kq = S.map(S.followed(S.or([Wm, Xm, Ym, Zm, $m, an]), Fp), function (a) { return a + "Op" }), lq = S.or([S.followed(nl([Qm], "isOp"), Fp), nl([hm], "nodeBeforeOp"), nl([km], "nodeAfterOp")]), mq = S.or([nl([bm], "equalOp"), ++ nl([fm], "notEqualOp"), nl([im], "lessThanOrEqualOp"), nl([gm], "lessThanOp"), nl([lm], "greaterThanOrEqualOp"), nl([jm], "greaterThanOp")]), nq = S.map(ko, function () { return ["annotation", ["annotationName", "updating"]] }), oq = S.or([Jn, Kn]), pq = S.or([Ln, Mn]), qq = S.or([Ro, So, To, Uo, Vo, $l, Wo, Xo, Yo, Zo, $o]), rq = S.map(V([Fn, X, Hn, X], S.or([Jn, In])), function (a) { return ["boundarySpaceDecl", a] }), sq = S.map(V([Fn, X, po, X], S.or([Jn, In])), function (a) { return ["constructionDecl", a] }), tq = S.map(V([Fn, X, qo, X], S.or([ro, so])), function (a) { ++ return ["orderingModeDecl", ++ a] ++ }), uq = S.map(V([Fn, X, Gn, X, sn, X, jn, X], S.or([Nn, On])), function (a) { return ["emptyOrderDecl", a] }), vq = S.then(V([Fn, X, Pn, X], oq), V([W, mm, W], pq), function (a, b) { return ["copyNamespacesDecl", ["preserveMode", a], ["inheritMode", b]] }); function wq(a) { ++ switch (a[0]) { case "constantExpr": case "varRef": case "contextItemExpr": case "functionCallExpr": case "sequenceExpr": case "elementConstructor": case "computedElementConstructor": case "computedAttributeConstructor": case "computedDocumentConstructor": case "computedTextConstructor": case "computedCommentConstructor": case "computedNamespaceConstructor": case "computedPIConstructor": case "orderedExpr": case "unorderedExpr": case "namedFunctionRef": case "inlineFunctionExpr": case "dynamicFunctionInvocationExpr": case "mapConstructor": case "arrayConstructor": case "stringConstructor": case "unaryLookup": return a }return ["sequenceExpr", ++ a] ++ } function xq(a) { if (!(1 <= a && 55295 >= a || 57344 <= a && 65533 >= a || 65536 <= a && 1114111 >= a)) throw Error("XQST0090: The character reference " + a + " (" + a.toString(16) + ") does not reference a valid codePoint."); } ++ function yq(a) { return a.replace(/(&[^;]+);/g, function (b) { if (/^&#x/.test(b)) return b = parseInt(b.slice(3, -1), 16), xq(b), String.fromCodePoint(b); if (/^&#/.test(b)) return b = parseInt(b.slice(2, -1), 10), xq(b), String.fromCodePoint(b); switch (b) { case "<": return "<"; case ">": return ">"; case "&": return "&"; case """: return String.fromCharCode(34); case "'": return String.fromCharCode(39) }throw Error('XPST0003: Unknown character reference: "' + b + '"'); }) } ++ function zq(a, b, c) { ++ if (!a.length) return []; for (var d = [a[0]], e = 1; e < a.length; ++e) { var f = d[d.length - 1]; "string" === typeof f && "string" === typeof a[e] ? d[d.length - 1] = f + a[e] : d.push(a[e]) } if ("string" === typeof d[0] && 0 === d.length) return []; d = d.reduce(function (g, k, l) { if ("string" !== typeof k) g.push(k); else if (c && /^\s*$/.test(k)) { var m = d[l + 1]; m && "CDataSection" === m[0] ? g.push(yq(k)) : (l = d[l - 1]) && "CDataSection" === l[0] && g.push(yq(k)) } else g.push(yq(k)); return g }, []); if (!d.length) return d; if (1 < d.length || b) for (a = 0; a < d.length; a++)"string" === ++ typeof d[a] && (d[a] = ["stringConstantExpr", ["value", d[a]]]); return d ++ } function Aq(a) { return a[0].prefix ? a[0].prefix + ":" + a[1] : a[1] }; var Bq = S.then(Cp, S.optional(am), function (a, b) { return null !== b ? ["singleType", ["atomicType"].concat(t(a)), ["optional"]] : ["singleType", ["atomicType"].concat(t(a))] }), Cq = S.map(Cp, function (a) { return ["atomicType"].concat(t(a)) }); var Dq = new Map; ++ function Eq(a) { ++ function b(n, r) { return r.reduce(function (G, ea) { return [ea[0], ["firstOperand", G], ["secondOperand", ea[1]]] }, n) } function c(n, r, G) { return S.then(n, S.star(S.then(U(r, W), S.cut(n), function (ea, oa) { return [ea, oa] })), G) } function d(n, r, G, ea) { G = void 0 === G ? "firstOperand" : G; ea = void 0 === ea ? "secondOperand" : ea; return S.then(n, S.optional(S.then(U(r, W), S.cut(n), function (oa, Ja) { return [oa, Ja] })), function (oa, Ja) { return null === Ja ? oa : [Ja[0], [G, oa], [ea, Ja[1]]] }) } function e(n) { ++ return a.vb ? function (r, G) { ++ r = n(r, ++ G); if (!r.success) return r; var ea = m.has(G) ? m.get(G) : { offset: G, line: -1, ja: -1 }, oa = m.has(r.offset) ? m.get(r.offset) : { offset: r.offset, line: -1, ja: -1 }; m.set(G, ea); m.set(r.offset, oa); return S.okWithValue(r.offset, ["x:stackTrace", { start: ea, end: oa }, r.value]) ++ } : n ++ } function f(n, r) { return Wk(n, r) } function g(n, r) { return hh(n, r) } function k(n, r) { return e(S.or([Yr, Zr, $r, as, bs, cs, ds, es, fs, gs, hs]))(n, r) } function l(n, r) { ++ return c(k, mm, function (G, ea) { return 0 === ea.length ? G : ["sequenceExpr", G].concat(t(ea.map(function (oa) { return oa[1] }))) })(n, ++ r) ++ } var m = new Map, q = S.preceded(wl, S.followed(U(l, W), xl)), u = S.map(a.Ya ? S.or([U(S.star(S.or([Ip, Jp, Kp, ol(/[^"&]/)])), El), U(S.star(S.or([Ip, Jp, Lp, ol(/[^'&]/)])), Cl)]) : S.or([U(S.star(S.or([Kp, ol(/[^"]/)])), El), U(S.star(S.or([Lp, ol(/[^']/)])), Cl)]), function (n) { return n.join("") }), z = S.or([S.map(V([Jm, W], S.delimited(S.followed(ul, W), S.then(Dp, V([W, mm, W], Cp), function (n, r) { return [["elementName", n], ["typeName"].concat(t(r))] }), S.preceded(W, vl))), function (n) { ++ var r = p(n); n = r.next().value; r = r.next().value; return ["elementTest", ++ n, r] ++ }), S.map(V([Jm, W], S.delimited(ul, Dp, vl)), function (n) { return ["elementTest", ["elementName", n]] }), S.map(V([Jm, W], S.delimited(ul, W, vl)), function () { return ["elementTest"] })]), A = S.or([S.map(Cp, function (n) { return ["QName"].concat(t(n)) }), S.map(Wl, function () { return ["star"] })]), D = S.or([S.map(V([Km, W], S.delimited(S.followed(ul, W), S.then(A, V([W, mm, W], Cp), function (n, r) { return [["attributeName", n], ["typeName"].concat(t(r))] }), S.preceded(W, vl))), function (n) { ++ var r = p(n); n = r.next().value; r = r.next().value; return ["attributeTest", ++ n, r] ++ }), S.map(V([Km, W], S.delimited(ul, A, vl)), function (n) { return ["attributeTest", ["attributeName", n]] }), S.map(V([Km, W], S.delimited(ul, W, vl)), function () { return ["attributeTest"] })]), F = S.map(V([Lm, W, ul], S.followed(Cp, vl)), function (n) { return ["schemaElementTest"].concat(t(n)) }), J = S.map(S.delimited(ap, U(Cp, W), vl), function (n) { return ["schemaAttributeTest"].concat(t(n)) }), T = S.map(S.preceded(bp, S.followed(U(S.optional(S.or([z, F])), W), vl)), function (n) { return ["documentTest"].concat(t(n ? [n] : [])) }), ia = S.or([S.map(S.preceded(cp, ++ S.followed(U(S.or([tp, u]), W), vl)), function (n) { return ["piTest", ["piTarget", n]] }), ml(nl([dp], "piTest"))]), Ba = S.or([T, z, D, F, J, ia, Mp, Np, Op, Pp]), Ra = S.or([S.map(S.preceded(Rl, tp), function (n) { return ["Wildcard", ["star"], ["NCName", n]] }), ml(nl([Wl], "Wildcard")), S.map(S.followed(Ap, Wl), function (n) { return ["Wildcard", ["uri", n], ["star"]] }), S.map(S.followed(tp, Ql), function (n) { return ["Wildcard", ["NCName", n], ["star"]] })]), kb = S.or([Ra, S.map(Cp, function (n) { return ["nameTest"].concat(t(n)) })]), Rb = S.or([Ba, kb]), Gd = S.then(S.optional(Xl), ++ Rb, function (n, r) { return null !== n || "attributeTest" === r[0] || "schemaAttributeTest" === r[0] ? ["stepExpr", ["xpathAxis", "attribute"], r] : ["stepExpr", ["xpathAxis", "child"], r] }), is = S.or([S.then(Gp, Rb, function (n, r) { return ["stepExpr", ["xpathAxis", n], r] }), Gd]), js = S.map(om, function () { return ["stepExpr", ["xpathAxis", "parent"], ["anyKindTest"]] }), ks = S.or([S.then(Hp, Rb, function (n, r) { return ["stepExpr", ["xpathAxis", n], r] }), js]), ls = S.map(S.star(S.preceded(W, q)), function (n) { ++ return 0 < n.length ? ["predicates"].concat(t(n)) : ++ void 0 ++ }), ms = S.then(S.or([ks, is]), ls, function (n, r) { return void 0 === r ? n : n.concat([r]) }), ih = S.or([Up, S.map(u, function (n) { return ["stringConstantExpr", ["value", a.Ya ? yq(n) : n]] })]), jh = S.or([S.delimited(ul, U(l, W), vl), S.map(S.delimited(ul, W, vl), function () { return ["sequenceExpr"] })]), Xk = S.or([k, Xp]), ef = S.map(S.delimited(ul, U(S.optional(S.then(Xk, S.star(S.preceded(U(mm, W), Xk)), function (n, r) { return [n].concat(t(r)) })), W), vl), function (n) { return null !== n ? n : [] }), ns = S.preceded(S.not(jl(Wp, W, ul, function () { }), ["cannot use reseved keyword for function names"]), ++ S.then(Cp, S.preceded(W, ef), function (n, r) { return ["functionCallExpr", ["functionName"].concat(t(n)), null !== r ? ["arguments"].concat(t(r)) : ["arguments"]] })), os = S.then(Cp, S.preceded(Zl, Tp), function (n, r) { return ["namedFunctionRef", ["functionName"].concat(t(n)), r] }), lb = S.delimited(yl, U(S.optional(l), W), zl), Yk = S.map(lb, function (n) { return n ? n : ["sequenceExpr"] }), Bb = S.or([S.map(jp, function () { return [["voidSequenceType"]] }), S.then(f, S.optional(S.preceded(W, Yp)), function (n, r) { ++ return [n].concat(t(null !== r ? [["occurrenceIndicator", ++ r]] : [])) ++ })]), kh = S.then(V([$l, W], Cp), S.optional(S.followed(S.then(V([ul, W], ih), S.star(V([mm, W], ih)), function (n, r) { return n.concat(r) }), vl)), function (n, r) { return ["annotation", ["annotationName"].concat(t(n))].concat(t(r ? ["arguments", r] : [])) }), ps = S.map(V([Hm, W, ul, W, Wl, W], vl), function () { return ["anyFunctionTest"] }), qs = S.then(V([Hm, W, ul, W], S.optional(c(Bb, mm, function (n, r) { return n.concat.apply(n, r.map(function (G) { return G[1] })) }))), V([W, vl, X, xm, X], Bb), function (n, r) { ++ return ["typedFunctionTest", ["paramTypeList", ++ ["sequenceType"].concat(t(n ? n : []))], ["sequenceType"].concat(t(r))] ++ }), rs = S.then(S.star(kh), S.or([ps, qs]), function (n, r) { return [r[0]].concat(t(n), t(r.slice(1))) }), ss = S.map(V([Im, W, ul, W, Wl, W], vl), function () { return ["anyMapTest"] }), ts = S.then(V([Im, W, ul, W], Cq), V([W, mm], S.followed(U(Bb, W), vl)), function (n, r) { return ["typedMapTest", n, ["sequenceType"].concat(t(r))] }), us = S.or([ss, ts]), vs = S.map(V([vn, W, ul, W, Wl, W], vl), function () { return ["anyArrayTest"] }), ws = S.map(V([vn, W, ul], S.followed(U(Bb, W), vl)), function (n) { ++ return ["typedArrayTest", ++ ["sequenceType"].concat(t(n))] ++ }), xs = S.or([vs, ws]), ys = S.map(S.delimited(ul, U(f, W), vl), function (n) { return ["parenthesizedItemType", n] }), Wk = S.or([Ba, ml(nl([ip], "anyItemType")), rs, us, xs, Cq, ys]), Vc = S.map(V([xm, X], Bb), function (n) { return ["typeDeclaration"].concat(t(n)) }), zs = S.then(S.preceded(Yl, Cp), S.optional(S.preceded(X, Vc)), function (n, r) { return ["param", ["varName"].concat(t(n))].concat(t(r ? [r] : [])) }), Zk = c(zs, mm, function (n, r) { return [n].concat(t(r.map(function (G) { return G[1] }))) }), As = kl(S.star(kh), V([W, Hm, ++ W, ul, W], S.optional(Zk)), V([W, vl, W], S.optional(S.map(V([xm, W], S.followed(Bb, W)), function (n) { return ["typeDeclaration"].concat(t(n)) }))), Yk, function (n, r, G, ea) { return ["inlineFunctionExpr"].concat(t(n), [["paramList"].concat(t(r ? r : []))], t(G ? [G] : []), [["functionBody", ea]]) }), Bs = S.or([os, As]), Cs = S.map(k, function (n) { return ["mapKeyExpr", n] }), Ds = S.map(k, function (n) { return ["mapValueExpr", n] }), Es = S.then(Cs, S.preceded(U(Ul, W), Ds), function (n, r) { return ["mapConstructorEntry", n, r] }), Fs = S.preceded(Im, S.delimited(U(yl, ++ W), S.map(S.optional(c(Es, U(mm, W), function (n, r) { return [n].concat(t(r.map(function (G) { return G[1] }))) })), function (n) { return n ? ["mapConstructor"].concat(t(n)) : ["mapConstructor"] }), S.preceded(W, zl))), Gs = S.map(S.delimited(wl, U(S.optional(c(k, mm, function (n, r) { return [n].concat(t(r.map(function (G) { return G[1] }))).map(function (G) { return ["arrayElem", G] }) })), W), xl), function (n) { return ["squareArray"].concat(t(null !== n ? n : [])) }), Hs = S.map(S.preceded(vn, S.preceded(W, lb)), function (n) { ++ return ["curlyArray"].concat(t(null !== ++ n ? [["arrayElem", n]] : [])) ++ }), Is = S.map(S.or([Gs, Hs]), function (n) { return ["arrayConstructor", n] }), $k = S.or([tp, Tp, jh, Wl]), Js = S.map(S.preceded(am, S.preceded(W, $k)), function (n) { return "*" === n ? ["unaryLookup", ["star"]] : "string" === typeof n ? ["unaryLookup", ["NCName", n]] : ["unaryLookup", n] }), lh = S.or([Ip, Jp, nl([Al], "{"), nl([Bl], "}"), S.map(lb, function (n) { return n || ["sequenceExpr"] })]), Ks = S.or([$p, function (n, r) { return al(n, r) }, lh, Zp]), Ls = S.or([S.map(aq, function (n) { return n.replace(/[\x20\x0D\x0A\x09]/g, " ") }), lh]), ++ Ms = S.or([S.map(bq, function (n) { return n.replace(/[\x20\x0D\x0A\x09]/g, " ") }), lh]), Ns = S.map(S.or([U(S.star(S.or([Kp, Ls])), El), U(S.star(S.or([Lp, Ms])), Cl)]), function (n) { return zq(n, !1, !1) }), Os = S.then(zp, S.preceded(U(bm, S.optional(qp)), Ns), function (n, r) { ++ if ("" === n[0].prefix && "xmlns" === n[1]) { if (r.length && "string" !== typeof r[0]) throw Error("XQST0022: A namespace declaration may not contain enclosed expressions"); return ["namespaceDeclaration", r.length ? ["uri", r[0]] : ["uri"]] } if ("xmlns" === n[0].prefix) { ++ if (r.length && ++ "string" !== typeof r[0]) throw Error("XQST0022: The namespace declaration for 'xmlns:" + n[1] + "' may not contain enclosed expressions"); return ["namespaceDeclaration", ["prefix", n[1]], r.length ? ["uri", r[0]] : ["uri"]] ++ } return ["attributeConstructor", ["attributeName"].concat(n), 0 === r.length ? ["attributeValue"] : 1 === r.length && "string" === typeof r[0] ? ["attributeValue", r[0]] : ["attributeValueExpr"].concat(r)] ++ }), Ps = S.map(S.star(S.preceded(qp, S.optional(Os))), function (n) { return n.filter(Boolean) }), Qs = jl(S.preceded(gm, ++ zp), Ps, S.or([S.map(Il, function () { return null }), S.then(S.preceded(jm, S.star(Ks)), V([W, Jl], S.followed(zp, S.then(S.optional(qp), jm, function () { return null }))), function (n, r) { return [zq(n, !0, !0), r] })]), function (n, r, G) { ++ var ea = G; if (G && G.length) { ea = Aq(n); var oa = Aq(G[1]); if (ea !== oa) throw Error('XQST0118: The start and the end tag of an element constructor must be equal. "' + ea + '" does not match "' + oa + '"'); ea = G[0] } return ["elementConstructor", ["tagName"].concat(t(n))].concat(t(r.length ? [["attributeList"].concat(t(r))] : ++ []), t(ea && ea.length ? [["elementContent"].concat(t(ea))] : [])) ++ }), al = S.or([Qs, dq, gq]), Rs = S.map(V([wn, W], lb), function (n) { return ["computedDocumentConstructor"].concat(t(n ? [["argExpr", n]] : [])) }), Ss = S.map(lb, function (n) { return n ? [["contentExpr", n]] : [] }), Ts = S.then(V([Jm, W], S.or([S.map(Cp, function (n) { return ["tagName"].concat(t(n)) }), S.map(S.delimited(yl, U(l, W), zl), function (n) { return ["tagNameExpr", n] })])), S.preceded(W, Ss), function (n, r) { return ["computedElementConstructor", n].concat(t(r)) }), Us = S.then(S.preceded(Km, ++ S.or([S.map(V([Fp, W], Cp), function (n) { return ["tagName"].concat(t(n)) }), S.map(S.preceded(W, S.delimited(yl, U(l, W), zl)), function (n) { return ["tagNameExpr", n] })])), S.preceded(W, lb), function (n, r) { return ["computedAttributeConstructor", n, ["valueExpr", r ? r : ["sequenceExpr"]]] }), Vs = S.map(lb, function (n) { return n ? [["prefixExpr", n]] : [] }), Ws = S.map(lb, function (n) { return n ? [["URIExpr", n]] : [] }), Xs = S.then(V([xn, W], S.or([up, Vs])), S.preceded(W, Ws), function (n, r) { return ["computedNamespaceConstructor"].concat(t(n), t(r)) }), ++ Ys = S.map(V([yn, W], lb), function (n) { return ["computedTextConstructor"].concat(t(n ? [["argExpr", n]] : [])) }), Zs = S.map(V([zn, W], lb), function (n) { return ["computedCommentConstructor"].concat(t(n ? [["argExpr", n]] : [])) }), $s = V([An, W], S.then(S.or([S.map(tp, function (n) { return ["piTarget", n] }), S.map(S.delimited(yl, U(l, W), zl), function (n) { return ["piTargetExpr", n] })]), S.preceded(W, lb), function (n, r) { return ["computedPIConstructor", n].concat(t(r ? [["piValueExpr", r]] : [])) })), at = S.or([Rs, Ts, Us, Xs, Ys, Zs, $s]), bt = S.or([al, at]), ++ bl = S.or([ih, Ep, jh, Vp, ns, bt, Bs, Fs, Is, Js]), cl = S.map(V([am, W], $k), function (n) { return "*" === n ? ["lookup", ["star"]] : "string" === typeof n ? ["lookup", ["NCName", n]] : ["lookup", n] }), dt = S.then(S.map(bl, function (n) { return wq(n) }), S.star(S.or([S.map(S.preceded(W, q), function (n) { return ["predicate", n] }), S.map(S.preceded(W, ef), function (n) { return ["argumentList", n] }), S.preceded(W, cl)])), function (n, r) { ++ function G() { ++ dl && 1 === Ja.length ? Wc.push(["predicate", Ja[0]]) : 0 !== Ja.length && Wc.push(["predicates"].concat(t(Ja))); Ja.length = ++ 0 ++ } function ea(ct) { G(); 0 !== Wc.length ? ("sequenceExpr" === oa[0][0] && 2 < oa[0].length && (oa = [["sequenceExpr"].concat(t(oa))]), oa = [["filterExpr"].concat(t(oa))].concat(t(Wc)), Wc.length = 0) : ct && (oa = [["filterExpr"].concat(t(oa))]) } var oa = [n], Ja = [], Wc = [], dl = !1; n = p(r); for (r = n.next(); !r.done; r = n.next())switch (r = r.value, r[0]) { ++ case "predicate": Ja.push(r[1]); break; case "lookup": dl = !0; G(); Wc.push(r); break; case "argumentList": ea(!1); 1 < oa.length && (oa = [["sequenceExpr", ["pathExpr", ["stepExpr"].concat(t(oa))]]]); oa = [["dynamicFunctionInvocationExpr", ++ ["functionItem"].concat(t(oa))].concat(t(r[1].length ? [["arguments"].concat(t(r[1]))] : []))]; break; default: throw Error("unreachable"); ++ }ea(!0); return oa ++ }), Xc = S.or([S.map(dt, function (n) { return ["stepExpr"].concat(t(n)) }), ms]), et = S.followed(bl, S.peek(S.not(S.preceded(W, S.or([q, ef, cl])), ["primary expression not followed by predicate, argumentList, or lookup"]))), ft = S.or([jl(Xc, S.preceded(W, hq), S.preceded(W, g), function (n, r, G) { return ["pathExpr", n, r].concat(t(G)) }), S.then(Xc, S.preceded(U(rm, W), g), function (n, ++ r) { return ["pathExpr", n].concat(t(r)) }), et, S.map(Xc, function (n) { return ["pathExpr", n] })]), hh = S.or([jl(Xc, S.preceded(W, hq), S.preceded(W, g), function (n, r, G) { return [n, r].concat(t(G)) }), S.then(Xc, S.preceded(U(rm, W), g), function (n, r) { return [n].concat(t(r)) }), S.map(Xc, function (n) { return [n] })]), gt = S.or([S.map(V([rm, W], hh), function (n) { return ["pathExpr", ["rootExpr"]].concat(t(n)) }), S.then(hq, S.preceded(W, hh), function (n, r) { return ["pathExpr", ["rootExpr"], n].concat(t(r)) }), S.map(S.followed(rm, S.not(S.preceded(W, ++ a.Ya ? ol(/[*= b + c) return x; k = e.next(k); d++; return k } }, f) } function Xq(a) { return a.map(function (b) { return B(b.type, 19) ? Pd(b, 3) : b }) } ++ function Yq(a) { a = Xq(a); if (a.some(function (b) { return Number.isNaN(b.value) })) return [w(NaN, 3)]; a = cj(a); if (!a) throw Error("FORG0006: Incompatible types to be converted to a common type"); return a } ++ function Zq(a, b, c, d, e, f) { return ac([e, f], function (g) { var k = p(g); g = k.next().value; k = k.next().value; if (Infinity === g.value) return C.empty(); if (-Infinity === g.value) return k && Infinity === k.value ? C.empty() : d; if (k) { if (isNaN(k.value)) return C.empty(); Infinity === k.value && (k = null) } return isNaN(g.value) ? C.empty() : Wq(d, Math.round(g.value), k ? Math.round(k.value) : null) }) } ++ function $q(a, b, c, d, e) { if (d.G()) return e; a = Xq(d.O()); a = cj(a); if (!a) throw Error("FORG0006: Incompatible types to be converted to a common type"); if (!a.every(function (f) { return B(f.type, 2) })) throw Error("FORG0006: items passed to fn:sum are not all numeric."); b = a.reduce(function (f, g) { return f + g.value }, 0); return a.every(function (f) { return B(f.type, 5) }) ? C.m(w(b, 5)) : a.every(function (f) { return B(f.type, 3) }) ? C.m(w(b, 3)) : a.every(function (f) { return B(f.type, 4) }) ? C.m(w(b, 4)) : C.m(w(b, 6)) }; var ar = [].concat(Vf, [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "boolean", j: [{ type: 59, g: 2 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d) { return d.ha() ? C.ba() : C.W() } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "true", j: [], i: { type: 0, g: 3 }, callFunction: function () { return C.ba() } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "not", j: [{ type: 59, g: 2 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d) { return !1 === d.ha() ? C.ba() : C.W() } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "false", j: [], i: { type: 0, g: 3 }, callFunction: function () { return C.W() } ++ }], [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "last", j: [], i: { type: 5, g: 3 }, callFunction: function (a) { if (null === a.N) throw Rc("The fn:last() function depends on dynamic context, which is absent."); var b = !1; return C.create({ next: function () { if (b) return x; var c = a.Da.Qa(); b = !0; return y(w(c, 5)) } }, 1) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "position", j: [], i: { type: 5, g: 3 }, callFunction: function (a) { ++ if (null === ++ a.N) throw Rc("The fn:position() function depends on dynamic context, which is absent."); return C.m(w(a.Ka + 1, 5)) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "current-dateTime", j: [], i: { type: 10, g: 3 }, callFunction: function (a) { return C.m(w(Kc(a), 10)) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "current-date", j: [], i: { type: 7, g: 3 }, callFunction: function (a) { return C.m(w(tc(Kc(a), 7), 7)) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "current-time", ++ j: [], i: { type: 8, g: 3 }, callFunction: function (a) { return C.m(w(tc(Kc(a), 8), 8)) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "implicit-timezone", j: [], i: { type: 17, g: 3 }, callFunction: function (a) { return C.m(w(Lc(a), 17)) } }], Wf, dg, kg, [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "years-from-duration", j: [{ type: 18, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.gb(), 5)) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "months-from-duration", ++ j: [{ type: 18, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.cb(), 5)) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "days-from-duration", j: [{ type: 18, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.bb(), 5)) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "hours-from-duration", j: [{ type: 18, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d) { ++ return d.G() ? d : C.m(w(d.first().value.getHours(), ++ 5)) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "minutes-from-duration", j: [{ type: 18, g: 0 }], i: { type: 5, g: 0 }, callFunction: function (a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getMinutes(), 5)) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "seconds-from-duration", j: [{ type: 18, g: 0 }], i: { type: 4, g: 0 }, callFunction: function (a, b, c, d) { return d.G() ? d : C.m(w(d.first().value.getSeconds(), 4)) } }], mg, [{ ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "id", j: [{ ++ type: 1, ++ g: 2 ++ }, { type: 53, g: 3 }], i: { type: 54, g: 2 }, callFunction: Sq ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "id", j: [{ type: 1, g: 2 }], i: { type: 54, g: 2 }, callFunction: function (a, b, c, d) { return Sq(a, b, c, d, C.m(a.N)) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "idref", j: [{ type: 1, g: 2 }, { type: 53, g: 3 }], i: { type: 53, g: 2 }, callFunction: Tq }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "idref", j: [{ type: 1, g: 2 }], i: { type: 53, g: 2 }, callFunction: function (a, b, c, d) { ++ return Tq(a, ++ b, c, d, C.m(a.N)) ++ } ++ }], [{ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "parse-json", j: [{ type: 1, g: 3 }], i: { type: 59, g: 0 }, callFunction: function (a, b, c, d) { try { var e = JSON.parse(d.first().value) } catch (f) { throw Error("FOJS0001: parsed JSON string contains illegal JSON."); } return Uq(e) } }], [{ ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "contains", j: [{ type: 61, g: 3 }, { type: 46, g: 3 }], i: { type: 0, g: 3 }, callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ f = p(f); var g = f.next().value, ++ k = f.next().value; return g.h.some(function (l) { return bc(l.key, k) }) ? C.ba() : C.W() ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "entry", j: [{ type: 46, g: 3 }, { type: 59, g: 2 }], i: { type: 61, g: 3 }, callFunction: function (a, b, c, d, e) { return d.map(function (f) { return new dc([{ key: f, value: yb(e) }]) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "for-each", j: [{ type: 61, g: 3 }, { type: 59, g: 2 }], i: { type: 59, g: 2 }, callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ f = p(f); ++ var g = f.next().value, k = f.next().value; return Pc(g.h.map(function (l) { return k.value.call(void 0, a, b, c, C.m(l.key), l.value()) })) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "get", j: [{ type: 61, g: 3 }, { type: 46, g: 3 }], i: { type: 59, g: 2 }, callFunction: cc }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "keys", j: [{ type: 61, g: 3 }], i: { type: 46, g: 2 }, callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return C.create(e.h.map(function (f) { return f.key })) }) } }, ++ { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "merge", j: [{ type: 61, g: 2 }, { type: 61, g: 3 }], i: { type: 61, g: 3 }, callFunction: Vq }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "merge", j: [{ type: 61, g: 2 }], i: { type: 61, g: 3 }, callFunction: function (a, b, c, d) { return Vq(a, b, c, d, C.m(new dc([{ key: w("duplicates", 1), value: function () { return C.m(w("use-first", 1)) } }]))) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "put", j: [{ type: 61, g: 3 }, { type: 46, g: 3 }, { ++ type: 59, ++ g: 2 ++ }], i: { type: 61, g: 3 }, callFunction: function (a, b, c, d, e, f) { return ac([d, e], function (g) { g = p(g); var k = g.next().value, l = g.next().value; g = k.h.concat(); k = g.findIndex(function (m) { return bc(m.key, l) }); 0 <= k ? g.splice(k, 1, { key: l, value: yb(f) }) : g.push({ key: l, value: yb(f) }); return C.m(new dc(g)) }) } ++ }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "remove", j: [{ type: 61, g: 3 }, { type: 46, g: 2 }], i: { type: 61, g: 3 }, callFunction: function (a, b, c, d, e) { ++ return ac([d], function (f) { ++ var g = p(f).next().value.h.concat(); ++ return e.M(function (k) { k.forEach(function (l) { var m = g.findIndex(function (q) { return bc(q.key, l) }); 0 <= m && g.splice(m, 1) }); return C.m(new dc(g)) }) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/map", localName: "size", j: [{ type: 61, g: 3 }], i: { type: 5, g: 3 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(e.h.length, 5) }) } }], [{ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "pi", j: [], i: { type: 3, g: 3 }, callFunction: function () { return C.m(w(Math.PI, 3)) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", ++ localName: "exp", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.pow(Math.E, e.value), 3) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "exp10", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.pow(10, e.value), 3) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "log", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { ++ return d.map(function (e) { ++ return w(Math.log(e.value), ++ 3) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "log10", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.log10(e.value), 3) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "pow", j: [{ type: 3, g: 0 }, { type: 2, g: 3 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d, e) { ++ return e.M(function (f) { ++ var g = p(f).next().value; return d.map(function (k) { ++ return 1 !== Math.abs(k.value) || Number.isFinite(g.value) ? w(Math.pow(k.value, ++ g.value), 3) : w(1, 3) ++ }) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "sqrt", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.sqrt(e.value), 3) }) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "sin", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.sin(e.value), 3) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "cos", j: [{ ++ type: 3, ++ g: 0 ++ }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.cos(e.value), 3) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "tan", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.tan(e.value), 3) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "asin", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { ++ return d.map(function (e) { ++ return w(Math.asin(e.value), ++ 3) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "acos", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.acos(e.value), 3) }) } }, { namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "atan", j: [{ type: 3, g: 0 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(Math.atan(e.value), 3) }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions/math", localName: "atan2", j: [{ type: 3, g: 0 }, ++ { type: 3, g: 3 }], i: { type: 3, g: 0 }, callFunction: function (a, b, c, d, e) { return e.M(function (f) { var g = p(f).next().value; return d.map(function (k) { return w(Math.atan2(k.value, g.value), 3) }) }) } ++ }], Oe, re, [{ namespaceURI: "http://fontoxpath/operators", localName: "to", j: [{ type: 5, g: 0 }, { type: 5, g: 0 }], i: { type: 5, g: 2 }, callFunction: function (a, b, c, d, e) { a = d.first(); e = e.first(); if (null === a || null === e) return C.empty(); var f = a.value; e = e.value; return f > e ? C.empty() : C.create({ next: function () { return y(w(f++, 5)) } }, e - f + 1) } }], [{ ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ localName: "QName", j: [{ type: 1, g: 0 }, { type: 1, g: 3 }], i: { type: 23, g: 3 }, callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ var g = p(f); f = g.next().value; g = g.next().value.value; if (!bd(g, 23)) throw Error("FOCA0002: The provided QName is invalid."); f = f ? f.value || null : null; if (null === f && g.includes(":")) throw Error("FOCA0002: The URI of a QName may not be empty if a prefix is provided."); if (d.G()) return C.m(w(new zb("", null, g), 23)); if (!g.includes(":")) return C.m(w(new zb("", f, g), 23)); var k = p(g.split(":")); ++ g = k.next().value; k = k.next().value; return C.m(w(new zb(g, f, k), 23)) ++ }) ++ } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "prefix-from-QName", j: [{ type: 23, g: 0 }], i: { type: 24, g: 0 }, callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; if (null === e) return C.empty(); e = e.value; return e.prefix ? C.m(w(e.prefix, 24)) : C.empty() }) } }, { ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "local-name-from-QName", j: [{ type: 23, g: 0 }], i: { type: 24, g: 0 }, callFunction: function (a, ++ b, c, d) { return d.map(function (e) { return w(e.value.localName, 24) }) } ++ }, { namespaceURI: "http://www.w3.org/2005/xpath-functions", localName: "namespace-uri-from-QName", j: [{ type: 23, g: 0 }], i: { type: 20, g: 0 }, callFunction: function (a, b, c, d) { return d.map(function (e) { return w(e.value.namespaceURI || "", 20) }) } }], [{ ++ j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return d.Z({ empty: function () { return C.ba() }, multiple: function () { return C.W() }, m: function () { return C.W() } }) }, localName: "empty", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 0, g: 3 } ++ }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return d.Z({ empty: function () { return C.W() }, multiple: function () { return C.ba() }, m: function () { return C.ba() } }) }, localName: "exists", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return Wq(d, 1, 1) }, localName: "head", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 0 } }, { ++ j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return Wq(d, 2, null) }, localName: "tail", ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } ++ }, { j: [{ type: 59, g: 2 }, { type: 5, g: 3 }, { type: 59, g: 2 }], callFunction: function (a, b, c, d, e, f) { if (d.G()) return f; if (f.G()) return d; a = d.O(); e = e.first().value - 1; 0 > e ? e = 0 : e > a.length && (e = a.length); b = a.slice(e); return C.create(a.slice(0, e).concat(f.O(), b)) }, localName: "insert-before", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { ++ j: [{ type: 59, g: 2 }, { type: 5, g: 3 }], callFunction: function (a, b, c, d, e) { ++ a = e.first().value; d = d.O(); if (!d.length || ++ 1 > a || a > d.length) return C.create(d); d.splice(a - 1, 1); return C.create(d) ++ }, localName: "remove", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } ++ }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return d.M(function (e) { return C.create(e.reverse()) }) }, localName: "reverse", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { ++ j: [{ type: 59, g: 2 }, { type: 3, g: 3 }], callFunction: function (a, b, c, d, e) { return Zq(a, b, c, d, e, C.empty()) }, localName: "subsequence", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 59, g: 2 } ++ }, { j: [{ type: 59, g: 2 }, { type: 3, g: 3 }, { type: 3, g: 3 }], callFunction: Zq, localName: "subsequence", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { return d }, localName: "unordered", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { ++ j: [{ type: 46, g: 2 }], callFunction: function (a, b, c, d) { var e = Zc(d, b).O(); return C.create(e).filter(function (f, g) { return e.slice(0, g).every(function (k) { return !Re(f, k) }) }) }, localName: "distinct-values", ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 2 } ++ }, { j: [{ type: 46, g: 2 }, { type: 1, g: 3 }], callFunction: function () { throw Error("FOCH0002: No collations are supported"); }, localName: "distinct-values", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 2 } }, { ++ j: [{ type: 46, g: 2 }, { type: 46, g: 3 }], callFunction: function (a, b, c, d, e) { ++ return e.M(function (f) { ++ var g = p(f).next().value; return Zc(d, b).map(function (k, l) { return Ii("eqOp", k.type, g.type)(k, g, a) ? w(l + 1, 5) : w(-1, 5) }).filter(function (k) { ++ return -1 !== ++ k.value ++ }) ++ }) ++ }, localName: "index-of", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 5, g: 2 } ++ }, { j: [{ type: 46, g: 2 }, { type: 46, g: 3 }, { type: 1, g: 3 }], callFunction: function () { throw Error("FOCH0002: No collations are supported"); }, localName: "index-of", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 5, g: 2 } }, { ++ j: [{ type: 59, g: 2 }, { type: 59, g: 2 }], callFunction: function (a, b, c, d, e) { ++ var f = !1, g = Ue(a, b, c, d, e); return C.create({ ++ next: function () { ++ if (f) return x; var k = g.next(0); if (k.done) return k; f = !0; return y(w(k.value, ++ 0)) ++ } ++ }) ++ }, localName: "deep-equal", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } ++ }, { j: [{ type: 59, g: 2 }, { type: 59, g: 2 }, { type: 1, g: 3 }], callFunction: function () { throw Error("FOCH0002: No collations are supported"); }, localName: "deep-equal", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 0, g: 3 } }, { ++ j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { var e = !1; return C.create({ next: function () { if (e) return x; var f = d.Qa(); e = !0; return y(w(f, 5)) } }) }, localName: "count", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 5, g: 3 } ++ }, { ++ j: [{ type: 46, g: 2 }], callFunction: function (a, b, c, d) { if (d.G()) return d; a = Xq(d.O()); a = cj(a); if (!a) throw Error("FORG0006: Incompatible types to be converted to a common type"); if (!a.every(function (e) { return B(e.type, 2) })) throw Error("FORG0006: items passed to fn:avg are not all numeric."); b = a.reduce(function (e, f) { return e + f.value }, 0) / a.length; return a.every(function (e) { return B(e.type, 5) || B(e.type, 3) }) ? C.m(w(b, 3)) : a.every(function (e) { return B(e.type, 4) }) ? C.m(w(b, 4)) : C.m(w(b, 6)) }, localName: "avg", ++ namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } ++ }, { j: [{ type: 46, g: 2 }], callFunction: function (a, b, c, d) { if (d.G()) return d; a = Yq(d.O()); return C.m(a.reduce(function (e, f) { return e.value < f.value ? f : e })) }, localName: "max", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } }, { j: [{ type: 46, g: 2 }, { type: 1, g: 3 }], callFunction: function () { throw Error("FOCH0002: No collations are supported"); }, localName: "max", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } }, ++ { j: [{ type: 46, g: 2 }], callFunction: function (a, b, c, d) { if (d.G()) return d; a = Yq(d.O()); return C.m(a.reduce(function (e, f) { return e.value > f.value ? f : e })) }, localName: "min", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } }, { j: [{ type: 46, g: 2 }, { type: 1, g: 3 }], callFunction: function () { throw Error("FOCH0002: No collations are supported"); }, localName: "min", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } }, { ++ j: [{ type: 46, g: 2 }], callFunction: function (a, b, c, d) { ++ return $q(a, b, c, d, C.m(w(0, ++ 5))) ++ }, localName: "sum", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 3 } ++ }, { j: [{ type: 46, g: 2 }, { type: 46, g: 0 }], callFunction: $q, localName: "sum", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 46, g: 0 } }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { if (!d.G() && !d.wa()) throw Error("FORG0003: The argument passed to fn:zero-or-one contained more than one item."); return d }, localName: "zero-or-one", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 0 } }, { ++ j: [{ ++ type: 59, ++ g: 2 ++ }], callFunction: function (a, b, c, d) { if (d.G()) throw Error("FORG0004: The argument passed to fn:one-or-more was empty."); return d }, localName: "one-or-more", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 1 } ++ }, { j: [{ type: 59, g: 2 }], callFunction: function (a, b, c, d) { if (!d.wa()) throw Error("FORG0005: The argument passed to fn:exactly-one is empty or contained more than one item."); return d }, localName: "exactly-one", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 3 } }, { ++ j: [{ ++ type: 59, ++ g: 2 ++ }, { type: 60, g: 3 }], callFunction: function (a, b, c, d, e) { if (d.G()) return d; var f = e.first(), g = f.o; if (1 !== g.length) throw Error("XPTY0004: signature of function passed to fn:filter is incompatible."); return d.filter(function (k) { k = fe(g[0], C.m(k), b, "fn:filter", !1); k = f.value.call(void 0, a, b, c, k); if (!k.wa() || !B(k.first().type, 0)) throw Error("XPTY0004: signature of function passed to fn:filter is incompatible."); return k.first().value }) }, localName: "filter", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 59, g: 2 } ++ }, { ++ j: [{ type: 59, g: 2 }, { type: 60, g: 3 }], callFunction: function (a, b, c, d, e) { if (d.G()) return d; var f = e.first(), g = f.o; if (1 !== g.length) throw Error("XPTY0004: signature of function passed to fn:for-each is incompatible."); var k = d.value, l; return C.create({ next: function (m) { for (; ;) { if (!l) { var q = k.next(0); if (q.done) return q; q = fe(g[0], C.m(q.value), b, "fn:for-each", !1); l = f.value.call(void 0, a, b, c, q).value } q = l.next(m); if (!q.done) return q; l = null } } }) }, localName: "for-each", namespaceURI: "http://www.w3.org/2005/xpath-functions", ++ i: { type: 59, g: 2 } ++ }, { j: [{ type: 59, g: 2 }, { type: 59, g: 2 }, { type: 60, g: 3 }], callFunction: function (a, b, c, d, e, f) { if (d.G()) return d; var g = f.first(), k = g.o; if (2 !== k.length) throw Error("XPTY0004: signature of function passed to fn:fold-left is incompatible."); return d.M(function (l) { return l.reduce(function (m, q) { m = fe(k[0], m, b, "fn:fold-left", !1); q = fe(k[1], C.m(q), b, "fn:fold-left", !1); return g.value.call(void 0, a, b, c, m, q) }, e) }) }, localName: "fold-left", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, ++ { j: [{ type: 59, g: 2 }, { type: 59, g: 2 }, { type: 60, g: 3 }], callFunction: function (a, b, c, d, e, f) { if (d.G()) return d; var g = f.first(), k = g.o; if (2 !== k.length) throw Error("XPTY0004: signature of function passed to fn:fold-right is incompatible."); return d.M(function (l) { return l.reduceRight(function (m, q) { m = fe(k[0], m, b, "fn:fold-right", !1); q = fe(k[1], C.m(q), b, "fn:fold-right", !1); return g.value.call(void 0, a, b, c, q, m) }, e) }) }, localName: "fold-right", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 59, g: 2 } }, { ++ j: [{ ++ type: 59, ++ g: 2 ++ }], callFunction: function (a, b, c, d) { if (!b.v) throw Error("serialize() called but no xmlSerializer set in execution parameters."); a = d.O(); if (!a.every(function (e) { return B(e.type, 53) })) throw Error("Expected argument to fn:serialize to resolve to a sequence of Nodes."); return C.m(w(a.map(function (e) { return b.v.serializeToString(ig(e.value, b, !1)) }).join(""), 1)) }, localName: "serialize", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 1, g: 3 } ++ }], De, [{ ++ j: [{ type: 59, g: 3 }, { type: 61, g: 3 }], callFunction: function (a, ++ b, c, d, e) { var f, g; return C.create({ next: function () { if (!f) { var k = Qq(d, e, c, b); f = k.sc; g = k.pc } try { return f.next(0) } catch (l) { pg(g.value, l) } } }) }, localName: "evaluate", namespaceURI: "http://fontoxml.com/fontoxpath", i: { type: 59, g: 2 } ++ }, { j: [], callFunction: function () { return C.m(w("undefined" === typeof VERSION ? "devbuild" : VERSION, 1)) }, localName: "version", namespaceURI: "http://fontoxml.com/fontoxpath", i: { type: 1, g: 3 } }], [{ ++ j: [{ type: 23, g: 3 }, { type: 5, g: 3 }], callFunction: function (a, b, c, d, e) { ++ return ac([d, e], function (f) { ++ var g = ++ p(f); f = g.next().value; g = g.next().value; var k = c.xa(f.value.namespaceURI, f.value.localName, g.value); if (null === k) return C.empty(); f = new Ab({ j: k.j, arity: g.value, localName: f.value.localName, namespaceURI: f.value.namespaceURI, i: k.i, value: k.callFunction }); return C.m(f) ++ }) ++ }, localName: "function-lookup", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { g: 0, type: 60 } ++ }, { ++ j: [{ type: 60, g: 3 }], callFunction: function (a, b, c, d) { ++ return ac([d], function (e) { ++ e = p(e).next().value; return e.Sa() ? C.empty() : C.m(w(new zb("", e.l, ++ e.B), 23)) ++ }) ++ }, localName: "function-name", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 23, g: 0 } ++ }, { j: [{ type: 60, g: 3 }], callFunction: function (a, b, c, d) { return ac([d], function (e) { e = p(e).next().value; return C.m(w(e.v, 5)) }) }, localName: "function-arity", namespaceURI: "http://www.w3.org/2005/xpath-functions", i: { type: 5, g: 3 } }]); function br(a) { this.h = a } h = br.prototype; h.createAttributeNS = function (a, b) { return this.h.createAttributeNS(a, b) }; h.createCDATASection = function (a) { return this.h.createCDATASection(a) }; h.createComment = function (a) { return this.h.createComment(a) }; h.createDocument = function () { return this.h.createDocument() }; h.createElementNS = function (a, b) { return this.h.createElementNS(a, b) }; h.createProcessingInstruction = function (a, b) { return this.h.createProcessingInstruction(a, b) }; h.createTextNode = function (a) { return this.h.createTextNode(a) }; var cr = Symbol("IS_XPATH_VALUE_SYMBOL"); function dr(a) { return function (b, c) { b = Dc(new Mb(null === c ? new Gb : c), b, qb(a)); c = {}; return c[cr] = !0, c.Hb = b, c } }; ar.forEach(function (a) { wg(a.namespaceURI, a.localName, a.j, a.i, a.callFunction) }); function er(a) { return a && "object" === typeof a && "lookupNamespaceURI" in a ? function (b) { return a.lookupNamespaceURI(b || null) } : function () { return null } } function fr(a) { return function (b) { var c = b.localName; return b.prefix ? null : { namespaceURI: a, localName: c } } } ++ function gr(a, b, c, d, e, f) { ++ if (null === d || void 0 === d) d = d || {}; if (e) { var g = e.logger || { trace: console.log.bind(console) }; var k = e.documentWriter; var l = e.moduleImports; var m = e.namespaceResolver; var q = e.functionNameResolver; var u = e.nodesFactory; var z = e.xmlSerializer } else g = { trace: console.log.bind(console) }, l = {}, z = k = u = m = null, q = void 0; var A = new Mb(null === c ? new Gb : c); c = l || Object.create(null); l = void 0 === e.defaultFunctionNamespaceURI ? "http://www.w3.org/2005/xpath-functions" : e.defaultFunctionNamespaceURI; var D = Pq(a, ++ f, m || er(b), d, c, l, q || fr(l)); a = b ? Ec(A, b) : C.empty(); b = !u && f.$ ? new of(b) : new br(u); k = k ? new Jb(k) : Ib; u = Object.keys(d).reduce(function (T, ia) { var Ba = d[ia]; T["Q{}" + ia + "[0]"] = Ba && "object" === typeof Ba && cr in Ba ? function () { return C.create(Ba.Hb) } : function () { return Ec(A, d[ia]) }; return T }, Object.create(null)); m = {}; q = p(Object.keys(D.ia.Ia)); for (c = q.next(); !c.done; m = { Za: m.Za }, c = q.next())m.Za = c.value, u[m.Za] || (u[m.Za] = function (T) { return function () { return (0, D.ia.Ia[T.Za])(F, J) } }(m)); var F = new Hc({ ++ N: a.first(), Ka: 0, ++ Da: a, Aa: u ++ }); var J = new Oc(f.debug, f.La, A, b, k, e.currentContext, new Map, g, z); return { Bb: F, Cb: J, ca: D.ca } ++ }; function hr(a, b) { ++ var c = {}, d = 0, e = !1, f = null; return { ++ next: function () { ++ if (e) return x; for (var g = {}; d < a.h.length;) { ++ a: { ++ var k = a.h[d].key.value; if (!f) { ++ g.pb = a.h[d]; var l = g.pb.value().Z({ default: function (m) { return m }, multiple: function (m) { return function () { throw Error("Serialization error: The value of an entry in a map is expected to be a single item or an empty sequence. Use arrays when putting multiple values in a map. The value of the key " + m.pb.key.value + " holds multiple items"); } }(g) }).first(); if (null === ++ l) { c[k] = null; d++; break a } f = ir(l, b) ++ } l = f.next(0); f = null; c[k] = l.value; d++ ++ } g = { pb: g.pb } ++ } e = !0; return y(c) ++ } ++ } ++ } ++ function jr(a, b) { var c = [], d = 0, e = !1, f = null; return { next: function () { if (e) return x; for (; d < a.P.length;) { if (!f) { var g = a.P[d]().Z({ default: function (k) { return k }, multiple: function () { throw Error("Serialization error: The value of an entry in an array is expected to be a single item or an empty sequence. Use nested arrays when putting multiple values in an array."); } }).first(); if (null === g) { c[d++] = null; continue } f = ir(g, b) } g = f.next(0); f = null; c[d++] = g.value } e = !0; return y(c) } } } ++ function ir(a, b) { if (B(a.type, 61)) return hr(a, b); if (B(a.type, 62)) return jr(a, b); if (B(a.type, 23)) { var c = a.value; return { next: function () { return y("Q{" + (c.namespaceURI || "") + "}" + c.localName) } } } switch (a.type) { case 7: case 8: case 9: case 11: case 12: case 13: case 14: case 15: var d = a.value; return { next: function () { return y(uc(d)) } }; case 47: case 53: case 54: case 55: case 56: case 57: case 58: var e = a.value; return { next: function () { return y(ig(e, b, !1)) } }; default: return { next: function () { return y(a.value) } } } }; var kr = { ANY: 0, NUMBER: 1, STRING: 2, BOOLEAN: 3, NODES: 7, FIRST_NODE: 9, STRINGS: 10, MAP: 11, ARRAY: 12, NUMBERS: 13, ALL_RESULTS: 14, ASYNC_ITERATOR: 99 }; kr[kr.ANY] = "ANY"; kr[kr.NUMBER] = "NUMBER"; kr[kr.STRING] = "STRING"; kr[kr.BOOLEAN] = "BOOLEAN"; kr[kr.NODES] = "NODES"; kr[kr.FIRST_NODE] = "FIRST_NODE"; kr[kr.STRINGS] = "STRINGS"; kr[kr.MAP] = "MAP"; kr[kr.ARRAY] = "ARRAY"; kr[kr.NUMBERS] = "NUMBERS"; kr[kr.ALL_RESULTS] = "ALL_RESULTS"; kr[kr.ASYNC_ITERATOR] = "ASYNC_ITERATOR"; ++ function lr(a, b, c, d) { ++ switch (c) { ++ case 3: return b.ha(); case 2: return b = Zc(b, d).O(), b.length ? b.map(function (l) { return Pd(l, 1).value }).join(" ") : ""; case 10: return b = Zc(b, d).O(), b.length ? b.map(function (l) { return l.value + "" }) : []; case 1: return b = b.first(), null !== b && B(b.type, 2) ? b.value : NaN; case 9: b = b.first(); if (null === b) return null; if (!B(b.type, 53)) throw Error("Expected XPath " + ng(a) + " to resolve to Node. Got " + mb[b.type]); return ig(b.value, d, !1); case 7: b = b.O(); if (!b.every(function (l) { return B(l.type, 53) })) throw Error("Expected XPath " + ++ ng(a) + " to resolve to a sequence of Nodes."); return b.map(function (l) { return ig(l.value, d, !1) }); case 11: b = b.O(); if (1 !== b.length) throw Error("Expected XPath " + ng(a) + " to resolve to a single map."); b = b[0]; if (!B(b.type, 61)) throw Error("Expected XPath " + ng(a) + " to resolve to a map"); return hr(b, d).next(0).value; case 12: b = b.O(); if (1 !== b.length) throw Error("Expected XPath " + ng(a) + " to resolve to a single array."); b = b[0]; if (!B(b.type, 62)) throw Error("Expected XPath " + ng(a) + " to resolve to an array"); return jr(b, ++ d).next(0).value; case 13: return b.O().map(function (l) { if (!B(l.type, 2)) throw Error("Expected XPath " + ng(a) + " to resolve to numbers"); return l.value }); case 99: var e = b.value, f = null, g = !1, k = function () { for (; !g;) { if (!f) { var l = e.next(0); if (l.done) { g = !0; break } f = ir(l.value, d) } l = f.next(0); f = null; return l } return Promise.resolve({ done: !0, value: null }) }; "asyncIterator" in Symbol ? (b = {}, b = (b[Symbol.asyncIterator] = function () { return this }, b.next = function () { ++ return (new Promise(function (l) { return l(k()) })).catch(function (l) { ++ pg(a, ++ l) ++ }) ++ }, b)) : b = { next: function () { return new Promise(function (l) { return l(k()) }) } }; return b; case 14: return b.O().map(function (l) { return ir(l, d).next(0).value }); default: return b = b.O(), b.every(function (l) { return B(l.type, 53) && !B(l.type, 47) }) ? (b = b.map(function (l) { return ig(l.value, d, !1) }), 1 === b.length ? b[0] : b) : 1 === b.length ? (b = b[0], B(b.type, 62) ? jr(b, d).next(0).value : B(b.type, 61) ? hr(b, d).next(0).value : Yc(b, d).first().value) : Zc(C.create(b), d).O().map(function (l) { return l.value }) ++ } ++ }; var mr = !1, nr = null, or = { ++ getPerformanceSummary: function () { var a = nr.getEntriesByType("measure").filter(function (b) { return b.name.startsWith("XPath: ") }); return Array.from(a.reduce(function (b, c) { var d = c.name.substring(7); b.has(d) ? (d = b.get(d), d.times += 1, d.totalDuration += c.duration) : b.set(d, { xpath: d, times: 1, totalDuration: c.duration, average: 0 }); return b }, new Map).values()).map(function (b) { b.average = b.totalDuration / b.times; return b }).sort(function (b, c) { return c.totalDuration - b.totalDuration }) }, setPerformanceImplementation: function (a) { ++ nr = ++ a ++ }, startProfiling: function () { if (null === nr) throw Error("Performance API object must be set using `profiler.setPerformanceImplementation` before starting to profile"); nr.clearMarks(); nr.clearMeasures(); mr = !0 }, stopProfiling: function () { mr = !1 } ++ }, pr = 0; var qr = { XPATH_3_1_LANGUAGE: "XPath3.1", XQUERY_3_1_LANGUAGE: "XQuery3.1", XQUERY_UPDATE_3_1_LANGUAGE: "XQueryUpdate3.1" }; function rr(a, b, c, d, e, f) { ++ e = e || 0; if (!a || "string" !== typeof a && !("nodeType" in a)) throw new TypeError("Failed to execute 'evaluateXPath': xpathExpression must be a string or an element depicting an XQueryX DOM tree."); f = f || {}; try { var g = gr(a, b, c || null, d || {}, f, { ua: "XQueryUpdate3.1" === f.language, $: "XQuery3.1" === f.language || "XQueryUpdate3.1" === f.language, debug: !!f.debug, La: !!f.disableCache }); var k = g.Bb; var l = g.Cb; var m = g.ca } catch (z) { pg(a, z) } if (m.I) throw Error("XUST0001: Updating expressions should be evaluated as updating expressions"); ++ if (3 === e && b && "object" === typeof b && "nodeType" in b && (c = m.B(), b = Fb(b), null !== c && !b.includes(c))) return !1; try { b = a; mr && ("string" !== typeof b && (b = ng(b)), nr.mark(b + (0 === pr ? "" : "@" + pr)), pr++); var q = I(m, k, l), u = lr(a, q, e, l); e = a; mr && ("string" !== typeof e && (e = ng(e)), pr--, k = e + (0 === pr ? "" : "@" + pr), nr.measure("XPath: " + e, k), nr.clearMarks(k)); return u } catch (z) { pg(a, z) } ++ } Object.assign(rr, { tc: 14, ANY_TYPE: 0, Tb: 12, Ub: 99, BOOLEAN_TYPE: 3, Wb: 9, Zb: 11, ac: 7, bc: 13, NUMBER_TYPE: 1, cc: 10, STRING_TYPE: 2, uc: "XPath3.1", vc: "XQuery3.1", fc: "XQueryUpdate3.1" }); ++ var sr = {}; Object.assign(rr, (sr.ALL_RESULTS_TYPE = 14, sr.ANY_TYPE = 0, sr.ARRAY_TYPE = 12, sr.ASYNC_ITERATOR_TYPE = 99, sr.BOOLEAN_TYPE = 3, sr.FIRST_NODE_TYPE = 9, sr.MAP_TYPE = 11, sr.NODES_TYPE = 7, sr.NUMBERS_TYPE = 13, sr.NUMBER_TYPE = 1, sr.STRINGS_TYPE = 10, sr.STRING_TYPE = 2, sr.XPATH_3_1_LANGUAGE = "XPath3.1", sr.XQUERY_3_1_LANGUAGE = "XQuery3.1", sr.XQUERY_UPDATE_3_1_LANGUAGE = "XQueryUpdate3.1", sr)); function tr(a, b, c, d, e) { return rr(a, b, c, d, rr.Ub, e) }; function ur(a, b, c, d) { var e = {}; return e.pendingUpdateList = a.fa.map(function (f) { return f.h(d) }), e.xdmValue = lr(b, C.create(a.J), c, d), e }; function vr(a, b, c, d, e) { ++ var f, g, k, l, m, q, u, z, A, D; return za(new ya(new ua(function (F) { ++ switch (F.h) { ++ case 1: e = e || {}; Vk(); try { l = gr(a, b, c || null, d || {}, e || {}, { ua: !0, $: !0, debug: !!e.debug, La: !!e.disableCache }), f = l.Bb, g = l.Cb, k = l.ca } catch (T) { pg(a, T) } if (k.I) { F.h = 2; break } m = []; q = tr(a, b, c, d, Object.assign(Object.assign({}, e), { language: "XQueryUpdate3.1" })); var J = q.next(); F.h = 3; return { value: J }; case 3: u = F.l; case 4: if (u.done) { F.h = 6; break } m.push(u.value); J = q.next(); F.h = 7; return { value: J }; case 7: u = F.l; F.h = 4; break; case 6: return z = ++ {}, F.return(Promise.resolve((z.pendingUpdateList = [], z.xdmValue = m, z))); case 2: try { D = k.s(f, g), A = D.next(0) } catch (T) { pg(a, T) } return F.return(ur(A.value, a, e.returnType, g)) ++ } ++ }))) ++ }; function wr(a, b, c, d, e) { e = e || {}; Vk(); try { var f = gr(a, b, c || null, d || {}, e || {}, { ua: !0, $: !0, debug: !!e.debug, La: !!e.disableCache }); var g = f.Bb; var k = f.Cb; var l = f.ca } catch (q) { pg(a, q) } if (!l.I) return g = {}, k = {}, k.pendingUpdateList = [], k.xdmValue = rr(a, b, c, d, e.i, Object.assign(Object.assign({}, e), (g.language = rr.fc, g))), k; try { var m = l.s(g, k).next(0) } catch (q) { pg(a, q) } return ur(m.value, a, e.returnType, k) }; function xr(a, b, c, d, e) { return rr(a, b, c, d, rr.Tb, e) }; function yr(a, b, c, d, e) { return rr(a, b, c, d, rr.BOOLEAN_TYPE, e) }; function zr(a, b, c, d, e) { return rr(a, b, c, d, rr.Wb, e) }; function Ar(a, b, c, d, e) { return rr(a, b, c, d, rr.Zb, e) }; function Br(a, b, c, d, e) { return rr(a, b, c, d, rr.ac, e) }; function Cr(a, b, c, d, e) { return rr(a, b, c, d, rr.NUMBER_TYPE, e) }; function Dr(a, b, c, d, e) { return rr(a, b, c, d, rr.bc, e) }; function Er(a, b, c, d, e) { return rr(a, b, c, d, rr.STRING_TYPE, e) }; function Fr(a, b, c, d, e) { return rr(a, b, c, d, rr.cc, e) }; function Gr(a, b, c, d) { b = new Mb(b ? b : new Gb); d = d ? new Jb(d) : Ib; c = c ? c = new br(c) : null; a = a.map(Zj); xf(a, b, c, d) }; function Y(a, b, c) { return { code: a, va: b, H: c, isAstAccepted: !0 } } function Hr(a) { return { isAstAccepted: !1, reason: a } }; function Z(a, b) { return a.isAstAccepted ? b(a) : a } function Ir(a, b) { return a.isAstAccepted ? b(a) : [a, null] } ++ function Jr(a, b, c) { return Z(a, function (d) { switch (d.va.type) { case 0: return d; case 1: return Z(Kr(c, d, "nodes"), function (e) { return Z(Kr(c, b, "contextItem"), function (f) { return Y("(function () {\n\t\t\t\t\t\t\tconst { done, value } = " + e.code + "(" + f.code + ").next();\n\t\t\t\t\t\t\treturn done ? null : value;\n\t\t\t\t\t\t})()", { type: 0 }, [].concat(t(e.H), t(f.H))) }) }); default: throw Error("invalid generated code type to convert to value: " + d.va.type); } }) } ++ function Lr(a, b, c, d) { a = Jr(a, c, d); return b && 0 === b.type && 3 === b.g ? a : Z(a, function (e) { return Y("!!" + e.code, { type: 0 }, e.H) }) } function Mr(a, b, c) { return b ? a.isAstAccepted && 0 !== a.va.type ? Hr("Atomization only implemented for single value") : B(b.type, 1) ? a : B(b.type, 47) ? Z(Kr(c, a, "attr"), function (d) { return Y("(" + d.code + " ? domFacade.getData(" + d.code + ") : null)", { type: 0 }, d.H) }) : Hr("Atomization only implemented for string and attribute") : Hr("Can not atomize value if type was not annotated") } ++ function Nr(a, b, c, d) { a = Jr(a, c, d); d = Mr(a, b, d); return ed(b) ? Z(d, function (e) { return Y(e.code + " ?? ''", { type: 0 }, e.H) }) : d } ++ function Or(a, b, c) { return Z(Kr(c, a, "node"), function (d) { return 1 === d.va.type ? d : b && !B(b.type, 53) ? Hr("Can not evaluate to node if expression does not result in nodes") : Y("(function () {\n\t\t\t\tif (" + d.code + " !== null && !" + d.code + ".nodeType) {\n\t\t\t\t\tthrow new Error('XPDY0050: The result of the expression was not a node');\n\t\t\t\t}\n\t\t\t\treturn " + d.code + ";\n\t\t\t})()", { type: 0 }, d.H) }) } ++ function Pr(a, b, c, d) { return Z(a, function (e) { switch (e.va.type) { case 1: return Z(Kr(d, e, "nodes"), function (f) { return Z(Kr(d, c, "contextItem"), function (g) { return Y("Array.from(" + f.code + "(" + g.code + "))", { type: 0 }, [].concat(t(f.H), t(g.H))) }) }); case 0: return Z(Kr(d, Or(e, b, d), "node"), function (f) { return Y("(" + f.code + " === null ? [] : [" + f.code + "])", { type: 0 }, f.H) }); default: return Hr("Unsupported code type to evaluate to nodes") } }) } ++ function Qr(a, b) { return Z(a, function (c) { return Z(b, function (d) { if (0 !== c.va.type || 0 !== d.va.type) throw Error("can only use emitAnd with value expressions"); return Y(c.code + " && " + d.code, { type: 0 }, [].concat(t(c.H), t(d.H))) }) }) }; function Rr(a, b, c, d) { return (a = N(a, [b, "*"])) ? d.h(a, c, d) : [Hr(b + " expression not found"), null] }; var Sr = {}, Tr = (Sr.equalOp = "eqOp", Sr.notEqualOp = "neOp", Sr.lessThanOrEqualOp = "leOp", Sr.lessThanOp = "ltOp", Sr.greaterThanOrEqualOp = "geOp", Sr.greaterThanOp = "gtOp", Sr), Ur = {}, Vr = (Ur.eqOp = "eqOp", Ur.neOp = "neOp", Ur.leOp = "geOp", Ur.ltOp = "gtOp", Ur.geOp = "leOp", Ur.gtOp = "ltOp", Ur); ++ function Wr(a, b, c, d) { ++ var e = M(N(a, ["firstOperand", "*"]), "type"), f = M(N(a, ["secondOperand", "*"]), "type"); if (!e || !f) return Hr("Can not generate code for value compare without both types"); var g = [47, 1]; if (!g.includes(e.type) || !g.includes(f.type)) return Hr("Unsupported types in compare: [" + mb[e.type] + ", " + mb[f.type] + "]"); g = new Map([["eqOp", "==="], ["neOp", "!=="]]); if (!g.has(b)) return Hr(b + " not yet implemented"); var k = g.get(b); b = p(Rr(a, "firstOperand", c, d)); g = b.next().value; b.next(); b = Jr(g, c, d); b = Mr(b, e, ++ d); return Z(Kr(d, b, "first"), function (l) { var m = p(Rr(a, "secondOperand", c, d)), q = m.next().value; m.next(); m = Jr(q, c, d); m = Mr(m, f, d); return Z(Kr(d, m, "second"), function (u) { var z = []; ed(e) && z.push(l.code + " === null"); ed(f) && z.push(u.code + " === null"); return Y("(" + (z.length ? z.join(" || ") + " ? null : " : "") + l.code + " " + k + " " + u.code + ")", { type: 0 }, [].concat(t(l.H), t(u.H))) }) }) ++ } ++ function Xr(a, b, c, d, e, f) { ++ var g = M(N(a, [b, "*"]), "type"), k = M(N(a, [c, "*"]), "type"); if (!g || !k) return Hr("Can not generate code for general compare without both types"); var l = [47, 1]; if (!l.includes(g.type) || !l.includes(k.type)) return Hr("Unsupported types in compare: [" + mb[g.type] + ", " + mb[k.type] + "]"); l = new Map([["eqOp", "==="], ["neOp", "!=="]]); if (!l.has(d)) return Hr(d + " not yet implemented"); var m = l.get(d); b = p(Rr(a, b, e, f)); d = b.next().value; b.next(); b = Jr(d, e, f); g = Mr(b, g, f); return Z(Kr(f, g, "single"), function (q) { ++ var u = ++ p(Rr(a, c, e, f)), z = u.next().value; u.next(); return Z(Kr(f, z, "multiple"), function (A) { ++ if (1 !== A.va.type) return Hr("can only generate general compare for a single value and a generator"); var D = vu(f, wu(f, "n")), F = Mr(D, k, f); return Z(e, function (J) { ++ return Z(F, function (T) { ++ return Y("(function () {\n\t\t\t\t\t\t\t\t\tfor (const " + D.code + " of " + A.code + "(" + J.code + ")) {\n\t\t\t\t\t\t\t\t\t\t" + T.H.join("\n") + "\n\t\t\t\t\t\t\t\t\t\tif (" + T.code + " " + m + " " + q.code + ") {\n\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t})()", ++ { type: 0 }, [].concat(t(q.H), t(D.H), t(J.H), t(A.H))) ++ }) ++ }) ++ }) ++ }) ++ }; function xu(a) { return JSON.stringify(a).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029") }; var Du = { "false#0": yu, "local-name#0": zu, "local-name#1": zu, "name#0": Au, "name#1": Au, "not#1": Bu, "true#0": Cu }, Eu = {}, Fu = (Eu["http://fontoxml.com/fontoxpath"] = ["version#0"], Eu[""] = ["root#1", "path#1"], Eu); ++ function Gu(a, b, c, d) { var e = p(d.h(a, c, d)), f = e.next().value; e.next(); a = M(a, "type"); if (b ? 2 === b.g || 1 === b.g : 1) return Hr("Not supported: sequence arguments with multiple items"); if (B(b.type, 53)) return b = Jr(f, c, d), Or(b, a, d); switch (b.type) { case 59: return Jr(f, c, d); case 0: return Lr(f, a, c, d); case 1: return Nr(f, a, c, d) }return Hr("Argument types not supported: " + (a ? mb[a.type] : "unknown") + " -> " + mb[b.type]) } ++ function Hu(a, b, c, d) { if (a.length !== b.length || b.some(function (k) { return 4 === k })) return Hr("Not supported: variadic function or mismatch in argument count"); if (0 === a.length) return Y("", { type: 0 }, []); var e = p(a); a = e.next().value; var f = ja(e); b = p(b); e = b.next().value; var g = ja(b); a = Kr(d, Gu(a, e, c, d), "arg"); return 0 === f.length ? a : Z(a, function (k) { var l = Hu(f, g, c, d); return Z(l, function (m) { return Y(k.code + ", " + m.code, { type: 0 }, [].concat(t(k.H), t(m.H))) }) }) } ++ function Iu(a, b) { return Z(a, function (c) { return (b ? 2 === b.g || 1 === b.g : 1) || ![0, 1].includes(b.type) && !B(b.type, 53) ? Hr("Function return type " + mb[b.type] + " not supported") : c }) } ++ function Ju(a, b, c) { ++ var d = Ug(L(a, "functionName")), e = d.localName, f = d.namespaceURI; d = O(L(a, "arguments"), "*"); var g = d.length, k = e + "#" + g, l = f === c.B; if (l) { var m = Du[k]; if (void 0 !== m) return m(a, b, c) } if ((a = Fu[l ? "" : f]) && !a.includes(k)) return Hr("Not supported: built-in function not on allow list: " + k); a = vg(f, e, g); if (!a) return Hr("Unknown function / arity: " + k); if (a.I) return Hr("Not supported: updating functions"); b = Hu(d, a.j, b, c); b = Z(b, function (q) { ++ return Y("runtimeLib.callFunction(domFacade, " + xu(f) + ", " + xu(e) + ++ ", [" + q.code + "], options)", { type: 0 }, q.H) ++ }); return Iu(b, a.i) ++ } function Ku(a, b) { return Z(Kr(b, a, "contextItem"), function (c) { return Y(c.code, { type: 0 }, [].concat(t(c.H), ["if (" + c.code + " === undefined || " + c.code + " === null) {\n\t\t\t\t\tthrow errXPDY0002('The function which was called depends on dynamic context, which is absent.');\n\t\t\t\t}"])) }) } ++ function Lu(a, b, c, d) { if ((a = N(a, ["arguments", "*"])) && "contextItemExpr" !== a[0]) { var e = M(a, "type"); if (!e || !B(e.type, 53)) return Hr("name function only implemented if arg is a node"); a = p(c.h(a, b, c)); e = a.next().value; a.next(); a = e } else a = Ku(b, c); b = Jr(a, b, c); return Z(Kr(c, b, "arg"), function (f) { return Y("(" + f.code + " ? " + d(f.code) + " : '')", { type: 0 }, f.H) }) } ++ function Au(a, b, c) { return Lu(a, b, c, function (d) { return "(((" + d + ".prefix || '').length !== 0 ? " + d + ".prefix + ':' : '')\n\t\t+ (" + d + ".localName || " + d + ".target || ''))" }) } function zu(a, b, c) { return Lu(a, b, c, function (d) { return "(" + d + ".localName || " + d + ".target || '')" }) } function Bu(a, b, c) { var d = N(a, ["arguments", "*"]); a = M(d, "type"); d = p(c.h(d, b, c)); var e = d.next().value; d.next(); b = Lr(e, a, b, c); return Z(b, function (f) { return Y("!" + f.code, { type: 0 }, f.H) }) } function yu() { return Y("false", { type: 0 }, []) } ++ function Cu() { return Y("true", { type: 0 }, []) }; function Mu(a, b, c, d) { var e = p(Rr(a, "firstOperand", c, d)), f = e.next().value; e = e.next().value; var g = M(N(a, ["firstOperand", "*"]), "type"); f = Lr(f, g, c, d); g = p(Rr(a, "secondOperand", c, d)); var k = g.next().value; g = g.next().value; f = Z(f, function (l) { var m = M(N(a, ["secondOperand", "*"]), "type"); m = Lr(k, m, c, d); return Z(m, function (q) { return Y("(" + l.code + " " + b + " " + q.code + ")", { type: 0 }, [].concat(t(l.H), t(q.H))) }) }); e = "&&" === b ? Qh(e, g) : e === g ? e : null; return [f, e] }; function Nu(a, b, c) { return Z(a, function (d) { return Z(b, function (e) { return Z(c, function (f) { return Y("for (" + d.code + ") {\n\t\t\t\t\t\t" + e.H.join("\n") + "\n\t\t\t\t\t\tif (!(" + e.code + ")) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t" + f.H.join("\n") + "\n\t\t\t\t\t\t" + f.code + "\n\t\t\t\t\t}", { type: 2 }, d.H) }) }) }) } ++ function Ou(a, b, c, d, e) { var f = b ? ', "' + b + '"' : ""; b = Z(d, function (g) { return Z(e, function (k) { return Y("let " + g.code + " = domFacade.getFirstChild(" + k.code + f + ");\n\t\t\t\t\t\t\t" + g.code + ";\n\t\t\t\t\t\t\t" + g.code + " = domFacade.getNextSibling(" + g.code + f + ")", { type: 2 }, [].concat(t(g.H), t(k.H))) }) }); return Nu(b, a, c) } ++ function Pu(a, b, c, d, e) { var f = Qh(b, "type-2"), g = Z(e, function (k) { return Y("(" + k.code + " && " + k.code + ".nodeType === /*ELEMENT_NODE*/ 1 ? domFacade.getAllAttributes(" + k.code + (f ? ', "' + f + '"' : "") + ") : [])", { type: 0 }, k.H) }); b = Z(d, function (k) { return Z(g, function (l) { return Y("const " + k.code + " of " + l.code, { type: 2 }, [].concat(t(k.H), t(l.H))) }) }); return Nu(b, a, c) } function Qu(a, b, c, d, e) { var f = b ? ', "' + b + '"' : ""; b = Z(e, function (g) { return Y("domFacade.getParentNode(" + g.code + f + ")", { type: 0 }, g.H) }); return Ru(d, b, a, c) } ++ function Ru(a, b, c, d) { var e = Qr(a, c); return Z(a, function (f) { return Z(b, function (g) { return Z(e, function (k) { return Z(d, function (l) { return Y("const " + f.code + " = " + g.code + ";\n\t\t\t\t\t\t" + k.H.join("\n") + "\n\t\t\t\t\t\tif (" + k.code + ") {\n\t\t\t\t\t\t\t" + l.H.join("\n") + "\n\t\t\t\t\t\t\t" + l.code + "\n\t\t\t\t\t\t}", { type: 2 }, [].concat(t(f.H), t(g.H))) }) }) }) }) } ++ function Su(a, b, c, d, e, f) { a = Tg(a); switch (a) { case "attribute": return [Pu(b, c, d, e, f), "type-1"]; case "child": return [Ou(b, c, d, e, f), null]; case "parent": return [Qu(b, c, d, e, f), null]; case "self": return [Ru(e, f, b, d), c]; default: return [Hr("Unsupported: the " + a + " axis"), null] } }; var Tu = { dc: "textTest", Vb: "elementTest", $b: "nameTest", ec: "Wildcard", Sb: "anyKindTest" }, Uu = Object.values(Tu); function Vu(a) { return [Z(a, function (b) { return Y(b.code + ".nodeType === /*TEXT_NODE*/ 3", { type: 0 }, []) }), "type-3"] } function Wu(a, b) { if (null === a.namespaceURI && "*" !== a.prefix) { b = b.aa(a.prefix || "") || null; if (!b && a.prefix) throw Error("XPST0081: The prefix " + a.prefix + " could not be resolved."); a.namespaceURI = b } } ++ function Xu(a, b, c, d) { ++ Wu(a, d); var e = a.prefix, f = a.namespaceURI, g = a.localName; return Ir(c, function (k) { ++ var l = b ? Y(k.code + ".nodeType\n\t\t\t\t\t\t&& (" + k.code + ".nodeType === /*ELEMENT_NODE*/ 1\n\t\t\t\t\t\t|| " + k.code + ".nodeType === /*ATTRIBUTE_NODE*/ 2)", { type: 0 }, []) : Y(k.code + ".nodeType\n\t\t\t\t\t\t&& " + k.code + ".nodeType === /*ELEMENT_NODE*/ 1", { type: 0 }, []); if ("*" === e) return "*" === g ? [l, b ? "type-1-or-type-2" : "type-1"] : [Qr(l, Y(k.code + ".localName === " + xu(g), { type: 0 }, [])), "name-" + g]; l = "*" === g ? l : Qr(l, Y(k.code + ++ ".localName === " + xu(g), { type: 0 }, [])); var m = Y(xu(f), { type: 0 }, []); m = "" === e && b ? Z(m, function (q) { return Y(k.code + ".nodeType === /*ELEMENT_NODE*/ 1 ? " + q.code + " : null", { type: 0 }, q.H) }) : m; m = Z(m, function (q) { return Y("(" + k.code + ".namespaceURI || null) === ((" + q.code + ") || null)", { type: 0 }, q.H) }); return [Qr(l, m), "name-" + g] ++ }) ++ } ++ function Yu(a, b, c) { var d = (a = L(a, "elementName")) && L(a, "star"); if (null === a || d) return [Z(b, function (e) { return Y(e.code + ".nodeType === /*ELEMENT_NODE*/ 1", { type: 0 }, []) }), "type-1"]; a = Ug(L(a, "QName")); return Xu(a, !1, b, c) } function Zu(a) { return [Z(a, function (b) { return Y("!!" + b.code + ".nodeType", { type: 0 }, []) }), null] } ++ function $u(a, b, c, d) { ++ var e = a[0]; switch (e) { ++ case Tu.Vb: return Yu(a, c, d); case Tu.dc: return Vu(c); case Tu.$b: return Xu(Ug(a), b, c, d); case Tu.ec: return L(a, "star") ? (e = L(a, "uri"), null !== e ? a = Xu({ localName: "*", namespaceURI: Tg(e), prefix: "" }, b, c, d) : (e = L(a, "NCName"), a = "star" === L(a, "*")[0] ? Xu({ localName: Tg(e), namespaceURI: null, prefix: "*" }, b, c, d) : Xu({ localName: "*", namespaceURI: null, prefix: Tg(e) }, b, c, d))) : a = Xu({ localName: "*", namespaceURI: null, prefix: "*" }, b, c, d), a; case Tu.Sb: return Zu(c); default: return [Hr("Test not implemented: '" + ++ e), null] ++ } ++ }; function av(a, b, c) { var d = p(c.h(a, b, c)), e = d.next().value; d = d.next().value; return [Lr(e, M(a, "type"), b, c), d] } ++ function bv(a, b, c) { ++ a = a ? O(a, "*") : []; var d = p(a.reduce(function (e, f) { e = p(e); var g = e.next().value, k = e.next().value; if (!g) return av(f, b, c); var l = k; return Ir(g, function (m) { var q = p(av(f, b, c)), u = q.next().value; q = q.next().value; l = Qh(k, q); return [Z(u, function (z) { return Y(m.code + " && " + z.code, { type: 0 }, [].concat(t(m.H), t(z.H))) }), l] }) }, [null, null])); a = d.next().value; d = d.next().value; return [a ? Z(a, function (e) { ++ return Y("(function () {\n\t\t\t\t\t\t\t" + e.H.join("\n") + "\n\t\t\t\t\t\t\treturn " + e.code + ";\n\t\t\t\t\t\t})()", ++ { type: 0 }, []) ++ }) : null, d] ++ } ++ function cv(a, b, c, d) { ++ if (0 === a.length) return [Z(c, function (q) { return Y("yield " + q.code + ";", { type: 2 }, q.H) }), null]; a = p(a); var e = a.next().value, f = ja(a); if (0 < O(e, "lookup").length) return [Hr("Unsupported: lookups"), null]; var g = vu(d, wu(d, "contextItem")); a = L(e, "predicates"); a = p(bv(a, g, d)); var k = a.next().value, l = a.next().value; if (a = L(e, "xpathAxis")) { ++ e = L(e, Uu); if (!e) return [Hr("Unsupported test in step"), null]; var m = Tg(a); b = "attribute" === m || "self" === m && b; m = p($u(e, b, g, d)); e = m.next().value; m = m.next().value; e = null === ++ k ? e : Qr(e, k); l = Qh(m, l); b = p(cv(f, b, g, d)); m = b.next().value; b.next(); return Su(a, e, l, m, g, c) ++ } a = N(e, ["filterExpr", "*"]); if (!a) return [Hr("Unsupported: unknown step type"), null]; l = p(d.h(a, c, d)); a = l.next().value; l = l.next().value; return [Z(a, function (q) { ++ var u = 0 === f.length ? Y("", { type: 2 }, []) : Y("if (" + g.code + " !== null && !" + g.code + ".nodeType) {\n\t\t\t\t\t\t\t\t\tthrow new Error('XPTY0019: The result of E1 in a path expression E1/E2 should evaluate to a sequence of nodes.');\n\t\t\t\t\t\t\t\t}", { type: 2 }, []), z = ++ p(cv(f, !0, g, d)), A = z.next().value; z.next(); z = null === k ? A : Z(k, function (D) { return Z(A, function (F) { return Y("if (" + D.code + ") {\n\t\t\t\t\t\t\t\t\t" + F.H.join("\n") + "\n\t\t\t\t\t\t\t\t\t" + F.code + "\n\t\t\t\t\t\t\t\t}", { type: 2 }, D.H) }) }); return Z(z, function (D) { ++ switch (q.va.type) { ++ case 1: return Z(c, function (F) { return Y("for (const " + g.code + " of " + q.code + "(" + F.code + ")) {\n\t\t\t\t\t\t\t\t\t" + D.H.join("\n") + "\n\t\t\t\t\t\t\t\t\t" + D.code + "\n\t\t\t\t\t\t\t\t}", { type: 2 }, [].concat(t(g.H), t(q.H), t(u.H))) }); case 0: return Y("const " + ++ g.code + " = " + q.code + ";\n\t\t\t\t\t\t\t" + u.code + "\n\t\t\t\t\t\t\tif (" + g.code + " !== null) {\n\t\t\t\t\t\t\t\t" + D.H.join("\n") + "\n\t\t\t\t\t\t\t\t" + D.code + "\n\t\t\t\t\t\t\t}", { type: 2 }, [].concat(t(g.H), t(q.H), t(u.H))); default: return Hr("Unsupported generated code type for filterExpr") ++ } ++ }) ++ }), l] ++ } ++ function dv(a) { return Z(a, function (b) { return Y("(function () {\n\t\t\t\tlet n = " + b.code + ";\n\t\t\t\twhile (n.nodeType !== /*DOCUMENT_NODE*/9) {\n\t\t\t\t\tn = domFacade.getParentNode(n);\n\t\t\t\t\tif (n === null) {\n\t\t\t\t\t\tthrow new Error('XPDY0050: the root node of the context node is not a document node.');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn n;\n\t\t\t})()", { type: 0 }, b.H) }) } ++ function ev(a, b, c) { return Ir(b, function (d) { if (0 < O(a, "lookup").length) return [Hr("Unsupported: lookups"), null]; var e = L(a, "predicates"), f = p(bv(e, d, c)); e = f.next().value; f = f.next().value; var g = L(a, Uu); if (!g) return [Hr("Unsupported test in step"), null]; var k = p($u(g, !0, d, c)); g = k.next().value; k = k.next().value; e = null === e ? g : Qr(g, e); f = Qh(k, f); return [Z(e, function (l) { return Y("((" + l.code + ") ? " + d.code + " : null)", { type: 0 }, [].concat(t(d.H), t(l.H))) }), f] }) } ++ function fv(a, b, c) { var d = O(a, "stepExpr"); if (1 === d.length) { var e = L(d[0], "xpathAxis"); if (e && "self" === Tg(e)) return ev(d[0], b, c) } var f = vu(c, wu(c, "contextItem")); b = (a = L(a, "rootExpr")) ? Kr(c, dv(f), "root") : f; d = p(cv(d, !a, b, c)); c = d.next().value; d = d.next().value; return [Z(c, function (g) { return Y("(function* (" + f.code + ") {\n\t\t\t" + g.H.join("\n") + "\n\t\t\t" + g.code + "\n\t\t})", { type: 1 }, []) }), d] }; function gv(a, b, c) { ++ var d = a[0]; switch (d) { ++ case "contextItemExpr": return [b, null]; case "pathExpr": return fv(a, b, c); case "andOp": return Mu(a, "&&", b, c); case "orOp": return Mu(a, "||", b, c); case "stringConstantExpr": return a = L(a, "value")[1] || "", a = xu(a), [Y(a, { type: 0 }, []), null]; case "equalOp": case "notEqualOp": case "lessThanOrEqualOp": case "lessThanOp": case "greaterThanOrEqualOp": case "greaterThanOp": case "eqOp": case "neOp": case "ltOp": case "leOp": case "gtOp": case "geOp": case "isOp": case "nodeBeforeOp": case "nodeAfterOp": a: switch (d) { ++ case "eqOp": case "neOp": case "ltOp": case "leOp": case "gtOp": case "geOp": case "isOp": a = ++ Wr(a, d, b, c); break a; case "equalOp": case "notEqualOp": case "lessThanOrEqualOp": case "lessThanOp": case "greaterThanOrEqualOp": case "greaterThanOp": var e = M(N(a, ["firstOperand", "*"]), "type"), f = M(N(a, ["secondOperand", "*"]), "type"); a = e && f ? 3 === e.g && 3 === f.g ? Wr(a, Tr[d], b, c) : 3 === e.g ? Xr(a, "firstOperand", "secondOperand", Tr[d], b, c) : 3 === f.g ? Xr(a, "secondOperand", "firstOperand", Vr[Tr[d]], b, c) : Hr("General comparison for sequences is not implemented") : Hr("types of compare are not known"); break a; default: a = Hr("Unsupported compare type: " + ++ d) ++ }return [a, null]; case "functionCallExpr": return [Ju(a, b, c), null]; default: return [Hr("Unsupported: the base expression '" + d + "'."), null] ++ } ++ }; function hv(a, b) { this.o = new Map; this.v = new Map; this.aa = a; this.B = b; this.h = gv } function Kr(a, b, c) { return Z(b, function (d) { var e = a.o.get(d); e || (e = wu(a, c), e = Y(e, d.va, [].concat(t(d.H), ["const " + e + " = " + d.code + ";"])), a.o.set(d, e), a.o.set(e, e)); return e }) } function vu(a, b) { b = Y(b, { type: 0 }, []); a.o.set(b, b); return b } function wu(a, b) { b = void 0 === b ? "v" : b; var c = a.v.get(b) || 0; a.v.set(b, c + 1); return b + c }; function iv(a) { var b = O(a, "*"); if ("pathExpr" === a[0]) return !0; a = p(b); for (b = a.next(); !b.done; b = a.next())if (iv(b.value)) return !0; return !1 }; function jv(a, b, c) { ++ c = c || {}; b = b || 0; if ("string" === typeof a) { a = hl(a); var d = { $: "XQuery3.1" === c.language || "XQueryUpdate3.1" === c.language, debug: !1 }; try { var e = Jq(a, d) } catch (k) { pg(a, k) } } else e = Rk(a); a = L(e, "mainModule"); if (!a) return Hr("Unsupported: XQuery Library modules are not supported."); if (L(a, "prolog")) return Hr("Unsupported: XQuery Prologs are not supported."); d = void 0 === c.defaultFunctionNamespaceURI ? "http://www.w3.org/2005/xpath-functions" : c.defaultFunctionNamespaceURI; a = new hv(c.namespaceResolver || ++ er(null), d); c = new Jh(new Lg(new zg(a.aa, {}, d, c.functionNameResolver || fr("http://www.w3.org/2005/xpath-functions")))); oh(e, c); if (c = L(e, "mainModule")) if (L(c, "prolog")) a = Hr("Unsupported: XQuery."); else { ++ var f = N(c, ["queryBody", "*"]); c = vu(a, "contextItem"); var g = p(a.h(f, c, a)); d = g.next().value; g.next(); b: switch (f = M(f, "type"), b) { case 9: b = Jr(d, c, a); a = Or(b, f, a); break b; case 7: a = Pr(d, f, c, a); break b; case 3: a = Lr(d, f, c, a); break b; case 2: a = Nr(d, f, c, a); break b; default: a = Hr("Unsupported: the return type '" + b + "'.") }a.isAstAccepted && ++ (a = "\n\t\t" + a.H.join("\n") + "\n\t\treturn " + a.code + ";", b = "\n\treturn (contextItem, domFacade, runtimeLib, options) => {\n\t\tconst {\n\t\t\terrXPDY0002,\n\t\t} = runtimeLib;", iv(e) && (b += '\n\t\tif (!contextItem) {\n\t\t\tthrow errXPDY0002("Context is needed to evaluate the given path expression.");\n\t\t}\n\n\t\tif (!contextItem.nodeType) {\n\t\t\tthrow new Error("Context item must be subtype of node().");\n\t\t}\n\t\t'), a = { code: b + (a + "}\n//# sourceURL=generated.js"), isAstAccepted: !0 }) ++ } else a = Hr("Unsupported: Can not execute a library module."); ++ return a ++ }; function kv(a, b, c) { var d = a.stack; d && (d.includes(a.message) && (d = d.substr(d.indexOf(a.message) + a.message.length).trim()), d = d.split("\n"), d.splice(10), d = d.map(function (e) { return e.startsWith(" ") || e.startsWith("\t") ? e : " " + e }), d = d.join("\n")); a = Error.call(this, "Custom XPath function Q{" + c + "}" + b + " raised:\n" + a.message + "\n" + d); this.message = a.message; "stack" in a && (this.stack = a.stack) } v(kv, Error); ++ function lv(a, b, c) { return 0 === b.g ? a.G() ? null : ir(a.first(), c).next(0).value : 2 === b.g || 1 === b.g ? a.O().map(function (d) { if (B(d.type, 47)) throw Error("Cannot pass attribute nodes to custom functions"); return ir(d, c).next(0).value }) : ir(a.first(), c).next(0).value } ++ function mv(a) { if ("object" === typeof a) return a; a = a.split(":"); if (2 !== a.length) throw Error("Do not register custom functions in the default function namespace"); var b = p(a); a = b.next().value; b = b.next().value; var c = yg[a]; if (!c) { c = "generated_namespace_uri_" + a; if (yg[a]) throw Error("Prefix already registered: Do not register the same prefix twice."); yg[a] = c } return { localName: b, namespaceURI: c } } ++ function nv(a, b, c, d) { a = mv(a); var e = a.namespaceURI, f = a.localName; if (!e) throw Eg(); var g = b.map(function (l) { return qb(l) }), k = qb(c); wg(e, f, g, k, function (l, m, q) { var u = Array.from(arguments); u.splice(0, 3); u = u.map(function (D, F) { return lv(D, g[F], m) }); var z = {}; z = (z.currentContext = m.l, z.domFacade = m.h.h, z); try { var A = d.apply(void 0, [z].concat(t(u))) } catch (D) { throw new kv(D, f, e); } return A && "object" === typeof A && Object.getOwnPropertySymbols(A).includes(cr) ? C.create(A.Hb) : Ec(m.h, A, k) }) }; var ov = { callFunction: function (a, b, c, d, e) { var f = vg(b, c, d.length); if (!f) throw Error("function not found for codegen function call"); b = new Hc({ N: null, Ka: 0, Da: C.empty(), Aa: {} }); var g = new Mb(a); a = new Oc(!1, !1, g, null, null, e ? e.currentContext : null, null); d = f.callFunction.apply(f, [b, a, null].concat(t(d.map(function (k, l) { return Ec(g, k, f.j[l]) })))); return lv(d, { type: 59, g: 0 }, a) }, errXPDY0002: Rc }; function pv(a, b, c, d) { c = c ? c : new Gb; return a()(null !== b && void 0 !== b ? b : null, c, ov, d) }; var qv = {}, rv = (qv["http://www.w3.org/2005/XQueryX"] = "xqx", qv["http://www.w3.org/2007/xquery-update-10"] = "xquf", qv["http://fontoxml.com/fontoxpath"] = "x", qv); ++ function sv(a, b) { ++ switch (a) { ++ case "copySource": case "insertAfter": case "insertAsFirst": case "insertAsLast": case "insertBefore": case "insertInto": case "modifyExpr": case "newNameExpr": case "replacementExpr": case "replaceValue": case "returnExpr": case "sourceExpr": case "targetExpr": case "transformCopies": case "transformCopy": return { localName: a, ub: b || "http://www.w3.org/2005/XQueryX" }; case "deleteExpr": case "insertExpr": case "renameExpr": case "replaceExpr": case "transformExpr": return { localName: a, ub: "http://www.w3.org/2007/xquery-update-10" }; ++ case "x:stackTrace": return { localName: "stackTrace", ub: "http://fontoxml.com/fontoxpath" }; default: return { localName: a, ub: "http://www.w3.org/2005/XQueryX" } ++ } ++ } ++ function tv(a, b, c, d, e) { ++ if ("string" === typeof c) return 0 === c.length ? null : b.createTextNode(c); if (!Array.isArray(c)) throw new TypeError("JsonML element should be an array or string"); d = sv(c[0], d); var f = d.localName; d = d.ub; var g = b.createElementNS(d, rv[d] + ":" + f), k = c[1], l = 1; if ("object" === typeof k && !Array.isArray(k)) { ++ if (null !== k) { ++ l = p(Object.keys(k)); for (var m = l.next(); !m.done; m = l.next()) { ++ m = m.value; var q = k[m]; null !== q && ("type" === m ? void 0 !== q && a.setAttributeNS(g, d, "fontoxpath:" + m, ob(q)) : ("start" !== m && "end" !== ++ m || "stackTrace" !== f || (q = JSON.stringify(q)), e && "prefix" === m && "" === q || a.setAttributeNS(g, d, rv[d] + ":" + m, q))) ++ } ++ } l = 2 ++ } f = l; for (k = c.length; f < k; ++f)l = tv(a, b, c[f], d, e), null !== l && a.insertBefore(g, l, null); return g ++ } ++ function uv(a, b, c, d) { ++ d = void 0 === d ? Ib : d; a = hl(a); try { var e = Jq(a, { $: "XQuery3.1" === b.language || "XQueryUpdate3.1" === b.language, debug: b.debug }) } catch (m) { pg(a, m) } var f = new zg(b.namespaceResolver || function () { return null }, {}, void 0 === b.defaultFunctionNamespaceURI ? "http://www.w3.org/2005/xpath-functions" : b.defaultFunctionNamespaceURI, b.functionNameResolver || function () { return null }); f = new Lg(f); var g = L(e, ["mainModule", "libraryModule"]), k = L(g, "moduleDecl"); if (k) { ++ var l = Tg(L(k, "prefix")); k = Tg(L(k, "uri")); Pg(f, ++ l, k) ++ } (g = L(g, "prolog")) && Mq(g, f, !1, a); !1 !== b.annotateAst && Bh(e, new Jh(f)); f = new Gb; b = tv(d, c, e, null, !1 === b.wc); d.insertBefore(b, c.createComment(a), f.getFirstChild(b)); return b ++ }; function vv(a) { return Promise.resolve(a) }; function wv(a, b) { ++ b = void 0 === b ? { debug: !1 } : b; b = Jq(a, { $: !0, debug: b.debug }); Bh(b, new Jh(void 0)); b = L(b, "libraryModule"); if (!b) throw Error("XQuery module must be declared in a library module."); var c = L(b, "moduleDecl"), d = L(c, "uri"), e = Tg(d); c = L(c, "prefix"); d = Tg(c); c = new Lg(new zg(function () { return null }, Object.create(null), "http://www.w3.org/2005/xpath-functions", fr("http://www.w3.org/2005/xpath-functions"))); Pg(c, d, e); b = L(b, "prolog"); if (null !== b) { ++ try { var f = Mq(b, c, !0, a) } catch (g) { pg(a, g) } f.Ma.forEach(function (g) { ++ if (e !== ++ g.namespaceURI) throw Error("XQST0048: Functions and variables declared in a module must reside in the module target namespace."); ++ }); Tk(e, f) ++ } else Tk(e, { Ma: [], Va: [], ra: null, source: a }); return e ++ }; var xv = new Map; function yv(a) { var b; a: { if (b = Nk.get(a)) for (var c = p(Object.keys(b)), d = c.next(); !d.done; d = c.next())if (d = d.value, b[d] && b[d].length) { b = b[d][0].h; break a } b = null } if (b) return b; if (xv.has(a)) return xv.get(a); b = "string" === typeof a ? Jq(a, { $: !1 }) : Rk(a); b = N(b, ["mainModule", "queryBody", "*"]); if (null === b) throw Error("Library modules do not have a specificity"); b = Mk(b, { ua: !1, $: !1 }); xv.set(a, b); return b } function zv(a) { return yv(a).B() } function Av(a, b) { return If(yv(a).o, yv(b).o) } var Bv = new Gb; ++ "undefined" !== typeof fontoxpathGlobal && (fontoxpathGlobal.compareSpecificity = Av, fontoxpathGlobal.compileXPathToJavaScript = jv, fontoxpathGlobal.domFacade = Bv, fontoxpathGlobal.evaluateXPath = rr, fontoxpathGlobal.evaluateXPathToArray = xr, fontoxpathGlobal.evaluateXPathToAsyncIterator = tr, fontoxpathGlobal.evaluateXPathToBoolean = yr, fontoxpathGlobal.evaluateXPathToFirstNode = zr, fontoxpathGlobal.evaluateXPathToMap = Ar, fontoxpathGlobal.evaluateXPathToNodes = Br, fontoxpathGlobal.evaluateXPathToNumber = Cr, fontoxpathGlobal.evaluateXPathToNumbers = ++ Dr, fontoxpathGlobal.evaluateXPathToString = Er, fontoxpathGlobal.evaluateXPathToStrings = Fr, fontoxpathGlobal.evaluateUpdatingExpression = vr, fontoxpathGlobal.evaluateUpdatingExpressionSync = wr, fontoxpathGlobal.executeJavaScriptCompiledXPath = pv, fontoxpathGlobal.executePendingUpdateList = Gr, fontoxpathGlobal.getBucketForSelector = zv, fontoxpathGlobal.getBucketsForNode = Fb, fontoxpathGlobal.precompileXPath = vv, fontoxpathGlobal.registerXQueryModule = wv, fontoxpathGlobal.registerCustomXPathFunction = nv, fontoxpathGlobal.parseScript = ++ uv, fontoxpathGlobal.profiler = or, fontoxpathGlobal.createTypedValueFactory = dr, fontoxpathGlobal.finalizeModuleRegistration = Vk, fontoxpathGlobal.Language = qr, fontoxpathGlobal.ReturnType = kr); ++ return fontoxpathGlobal; ++ })(xspattern, prsc); ++ }); + - std::string version(""); - - // Parse date or Date from response headers. -- if (headers && headers->GetDateValue(&date)) { -- version = utils::ConvertBaseTimeToABPFilterVersionFormat(date); -+ if (headers) { -+ if (auto date = headers->GetDateValue()) { -+ version = utils::ConvertBaseTimeToABPFilterVersionFormat(date.value()); -+ } - } - - std::move(std::get(*ongoing_ping_)) -@@ -191,11 +192,11 @@ void SubscriptionDownloaderImpl::OnDownloadFinished( - persistent_metadata_->IncrementDownloadErrorCount(subscription_url); - if (std::get(download_it->second) == - RetryPolicy::RetryUntilSucceeded) { -- DLOG(WARNING) << "[eyeo] Failed to retrieve content for " -+ LOG(WARNING) << "[eyeo] Failed to retrieve content for " - << subscription_url << ", will retry"; - std::get(download_it->second)->Retry(); - } else { -- DLOG(WARNING) << "[eyeo] Failed to retrieve content for " -+ LOG(WARNING) << "[eyeo] Failed to retrieve content for " - << subscription_url << ", will abort"; - std::move(std::get(download_it->second)) - .Run(nullptr); -@@ -212,8 +213,10 @@ void SubscriptionDownloaderImpl::OnDownloadFinished( - TRACE_ID_LOCAL(GenerateTraceId(subscription_url)), "url", - subscription_url.spec()); - -+ bool allow_privileged_filter = -+ persistent_metadata_->AllowPrivilegedFilters(subscription_url); - conversion_executor_->ConvertFilterListFile( -- subscription_url, downloaded_file, -+ subscription_url, downloaded_file, allow_privileged_filter, - base::BindOnce(&SubscriptionDownloaderImpl::OnConversionFinished, - weak_ptr_factory_.GetWeakPtr(), subscription_url)); - } -@@ -227,14 +230,14 @@ void SubscriptionDownloaderImpl::OnConversionFinished( - 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 " -+ LOG(WARNING) << "[eyeo] Conversion result discarded, subscription download " - "was cancelled."; - return; - } - - if (absl::holds_alternative>( - converter_result)) { -- VLOG(1) << "[eyeo] Finished converting " << subscription_url -+ LOG(WARNING) << "[eyeo] Finished converting " << subscription_url - << " successfully"; - std::move(std::get(download_it->second)) - .Run(std::move( -@@ -277,7 +280,7 @@ void SubscriptionDownloaderImpl::AbortWithWarning( - if (ongoing_download_it == ongoing_downloads_.end()) { - return; - } -- DLOG(WARNING) << "[eyeo] " << warning << " Aborting download of " -+ LOG(WARNING) << "[eyeo] " << warning << " Aborting download of " - << ongoing_download_it->first; - std::move(std::get(ongoing_download_it->second)) - .Run(nullptr); -diff --git a/components/adblock/core/subscription/subscription_persistent_metadata.h b/components/adblock/core/subscription/subscription_persistent_metadata.h ---- a/components/adblock/core/subscription/subscription_persistent_metadata.h -+++ b/components/adblock/core/subscription/subscription_persistent_metadata.h -@@ -74,6 +74,7 @@ class SubscriptionPersistentMetadata : public KeyedService { - // Returns the number of successful downloads of this subscription in the - // past. - virtual int GetDownloadSuccessCount(const GURL& subscription_url) const = 0; -+ virtual bool AllowPrivilegedFilters(const GURL& subscription_url) = 0; - // Returns number of consecutive download errors. - virtual int GetDownloadErrorCount(const GURL& subscription_url) const = 0; - -diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc ---- a/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc -+++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc -@@ -21,6 +21,7 @@ - #include "base/time/time.h" - #include "base/values.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" - - namespace adblock { -@@ -145,6 +146,12 @@ void SubscriptionPersistentMetadataImpl::UpdatePrefs() { - prefs_->SetDict(common::prefs::kSubscriptionMetadata, std::move(dict)); - } - -+bool SubscriptionPersistentMetadataImpl::AllowPrivilegedFilters( -+ const GURL& subscription_url) { -+ return prefs_ && prefs_->GetBoolean(common::prefs::kAllowPrivilegedFilters) -+ && config::AllowPrivilegedFilters(subscription_url); +} ++/**! End hide-if-matches-xpath3 dependency !**/ +diff --git a/components/adblock/core/resources/snippets/dist/isolated-first-all.source.jst b/components/adblock/core/resources/snippets/dist/isolated-first-all.source.jst +new file mode 100644 +--- /dev/null ++++ b/components/adblock/core/resources/snippets/dist/isolated-first-all.source.jst +@@ -0,0 +1,5256 @@ ++/*! ++ * snippets v2.0.0 ++ * https://gitlab.com/eyeo/anti-cv/snippets/-/blob/v2.0.0/dist/isolated-first-all.source.jst?ref_type=heads ++ */ ++(e, ...t) => { ++/*! ++ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * @eyeo/snippets is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * @eyeo/snippets is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with @eyeo/snippets. If not, see . ++ */ ++ ((environment, ...filters) => { ++ /*! ++ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * @eyeo/snippets is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * @eyeo/snippets is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with @eyeo/snippets. If not, see . ++ */ ++ const $$1 = Proxy; + - void SubscriptionPersistentMetadataImpl::LoadFromPrefs() { - const base::Value& dict = - prefs_->GetValue(common::prefs::kSubscriptionMetadata); -diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.h b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h ---- a/components/adblock/core/subscription/subscription_persistent_metadata_impl.h -+++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h -@@ -43,6 +43,7 @@ class SubscriptionPersistentMetadataImpl 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; -+ bool AllowPrivilegedFilters(const GURL& subscription_url) final; - int GetDownloadErrorCount(const GURL& subscription_url) const final; - - void RemoveMetadata(const GURL& subscription_url) final; -diff --git a/components/adblock/core/subscription/subscription_persistent_storage_impl.cc b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc ---- a/components/adblock/core/subscription/subscription_persistent_storage_impl.cc -+++ b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc -@@ -98,7 +98,7 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( - const base::FilePath& storage_dir, - SubscriptionValidator::IsSignatureValidThreadSafeCallback - is_signature_valid) { -- DLOG(INFO) << "[eyeo] Reading subscriptions from directory"; -+ LOG(INFO) << "[eyeo] Reading subscriptions from directory " << storage_dir; - TRACE_EVENT0("eyeo", "ReadSubscriptionsFromDirectory"); - // Does nothing if directory already exists: - base::CreateDirectory(storage_dir); -@@ -116,6 +116,8 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( - if (!base::ReadFileToString(flatbuffer_path, &contents)) { - // File could not be read. - base::DeleteFile(flatbuffer_path); -+ LOG(INFO) << "[eyeo] Deleting " << flatbuffer_path.BaseName().AsUTF8Unsafe() -+ << "reason: File could not be read"; - continue; - } - TRACE_EVENT_END1("eyeo", "ReadFileToString", "path", -@@ -123,6 +125,8 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( - TRACE_EVENT_BEGIN0("eyeo", "VerifySubscriptionBuffer"); - if (!is_signature_valid.Run(InMemoryFlatbufferData(std::move(contents)), - flatbuffer_path)) { -+ LOG(INFO) << "[eyeo] Deleting " << flatbuffer_path.BaseName().AsUTF8Unsafe() -+ << "reason: This is not a valid subscription file"; - // This is not a valid subscription file, remove it. - base::DeleteFile(flatbuffer_path); - continue; -@@ -130,13 +134,16 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( - TRACE_EVENT_END0("eyeo", "VerifySubscriptionBuffer"); - auto buffer = std::make_unique(flatbuffer_path); - if (!buffer->data()) { -+ LOG(INFO) << "[eyeo] Could not create mapped memory region to file content for " -+ << flatbuffer_path.BaseName().AsUTF8Unsafe(); - // Could not create mapped memory region to file content. - // TODO(mpawlowski) revert to in-memory buffer? - continue; - } -+ LOG(INFO) << "[eyeo] Loaded " << flatbuffer_path.BaseName().AsUTF8Unsafe(); - result.emplace_back(std::move(buffer), std::move(flatbuffer_path)); - } -- DLOG(INFO) << "[eyeo] Finished reading and validating subscriptions. Loaded " -+ LOG(INFO) << "[eyeo] Finished reading and validating subscriptions. Loaded " - << result.size() << " subscriptions."; - return result; - } -diff --git a/components/adblock/core/subscription/subscription_service.h b/components/adblock/core/subscription/subscription_service.h ---- a/components/adblock/core/subscription/subscription_service.h -+++ b/components/adblock/core/subscription/subscription_service.h -@@ -38,6 +38,15 @@ namespace adblock { - // FilteringConfigurations. - class SubscriptionService : public KeyedService { - public: -+ virtual void StartUpdate() = 0; -+ virtual raw_ptr GetMetadata() = 0; -+ virtual raw_ptr GetMetadataFor( -+ raw_ptr configuration) = 0; -+ virtual void SetPrivilegedFiltersEnabled(bool enabled) = 0; -+ virtual bool IsPrivilegedFiltersEnabled() = 0; -+ virtual std::vector> GetCustomSubscriptions( -+ FilteringConfiguration* configuration) const = 0; -+ - using Snapshot = std::vector>; - class SubscriptionObserver : public base::CheckedObserver { - public: -diff --git a/components/adblock/core/subscription/subscription_service_impl.cc b/components/adblock/core/subscription/subscription_service_impl.cc ---- a/components/adblock/core/subscription/subscription_service_impl.cc -+++ b/components/adblock/core/subscription/subscription_service_impl.cc -@@ -35,9 +35,23 @@ - #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/subscription_config.h" - - namespace adblock { - -+namespace { -+ -+bool IsKnownSubscription( -+ const std::vector& known_subscriptions, -+ const GURL& url) { -+ return std::ranges::any_of(known_subscriptions, -+ [&](const auto& known_subscription) { -+ return known_subscription.url == url; -+ }); -+} ++ const {apply: a, bind: b, call: c} = Function; ++ const apply$2 = c.bind(a); ++ const bind = c.bind(b); ++ const call = c.bind(c); + -+} ++ const callerHandler = { ++ get(target, name) { ++ return bind(c, target[name]); ++ } ++ }; ++ const caller = target => new $$1(target, callerHandler); + - class EmptySubscription : public Subscription { - public: - EmptySubscription(const GURL& url) : url_(url) {} -@@ -132,6 +146,68 @@ void SubscriptionServiceImpl::UninstallFilteringConfiguration( - maintainers_.erase(it); - } - -+void SubscriptionServiceImpl::StartUpdate() { -+ for (auto& entry : maintainers_) { -+ if (!entry.second) { -+ continue; // Configuration is disabled ++ const handler$2 = { ++ get(target, name) { ++ return bind(target[name], target); + } -+ entry.second->StartUpdate(); -+ } -+} ++ }; ++ const bound = target => new $$1(target, handler$2); + -+bool SubscriptionServiceImpl::IsPrivilegedFiltersEnabled() { -+ auto* adblock_filtering_configuration = GetAdblockFilteringConfiguration(); -+ return adblock_filtering_configuration->IsPrivilegedFiltersEnabled(); -+} ++ const { ++ assign: assign$1, ++ defineProperties: defineProperties$1, ++ freeze: freeze$1, ++ getOwnPropertyDescriptor: getOwnPropertyDescriptor$2, ++ getOwnPropertyDescriptors: getOwnPropertyDescriptors$1, ++ getPrototypeOf ++ } = bound(Object); + -+void SubscriptionServiceImpl::SetPrivilegedFiltersEnabled(bool enabled) { -+ auto* adblock_filtering_configuration = GetAdblockFilteringConfiguration(); -+ adblock_filtering_configuration->SetPrivilegedFiltersEnabled(enabled); -+ auto known_subscriptions = adblock::config::GetKnownSubscriptions(); -+ for (const auto& cur : known_subscriptions) { -+ if (config::AllowPrivilegedFilters(cur.url)) { -+ if (enabled) -+ adblock_filtering_configuration->AddFilterList(cur.url); -+ else -+ adblock_filtering_configuration->RemoveFilterList(cur.url); -+ } -+ } -+ StartUpdate(); -+} ++ const {hasOwnProperty} = caller({}); + -+std::vector> -+SubscriptionServiceImpl::GetCustomSubscriptions( -+ FilteringConfiguration* configuration) const { -+ std::vector> selected = -+ GetCurrentSubscriptions(configuration); ++ const {species} = Symbol; + -+ auto known_subscriptions = adblock::config::GetKnownSubscriptions(); -+ selected.erase(std::remove_if(selected.begin(), selected.end(), -+ [&known_subscriptions](const auto& subscription) { -+ return IsKnownSubscription( -+ known_subscriptions, -+ subscription->GetSourceUrl()); -+ }), -+ selected.end()); -+ return selected; -+} ++ const handler$1 = { ++ get(target, name) { ++ const Native = target[name]; ++ class Secure extends Native {} + -+raw_ptr SubscriptionServiceImpl::GetMetadata() { -+ return GetMetadataFor(GetAdblockFilteringConfiguration()); -+} ++ const proto = getOwnPropertyDescriptors$1(Native.prototype); ++ delete proto.constructor; ++ freeze$1(defineProperties$1(Secure.prototype, proto)); + -+raw_ptr SubscriptionServiceImpl::GetMetadataFor( -+ raw_ptr configuration) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ auto it = std::ranges::find_if(maintainers_, [&](const auto& entry) { -+ return entry.first.get() == configuration; -+ }); -+ if (it != maintainers_.end() && it->second) { -+ return it->second->GetMetadata(); -+ } -+ return nullptr; -+} ++ const statics = getOwnPropertyDescriptors$1(Native); ++ delete statics.length; ++ delete statics.prototype; ++ statics[species] = {value: Secure}; ++ return freeze$1(defineProperties$1(Secure, statics)); ++ } ++ }; + - std::vector - SubscriptionServiceImpl::GetInstalledFilteringConfigurations() { - std::vector result; -@@ -177,7 +253,7 @@ void SubscriptionServiceImpl::OnEnabledStateChanged( - }); - DCHECK(it != maintainers_.end()) << "Received OnEnabledStateChanged from " - "unregistered FilteringConfiguration"; -- VLOG(1) << "[eyeo] FilteringConfiguration " << config->GetName() -+ LOG(INFO) << "[eyeo] FilteringConfiguration " << config->GetName() - << (config->IsEnabled() ? " enabled" : " disabled"); - if (config->IsEnabled()) { - // Enable the configuration by creating a new -diff --git a/components/adblock/core/subscription/subscription_service_impl.h b/components/adblock/core/subscription/subscription_service_impl.h ---- a/components/adblock/core/subscription/subscription_service_impl.h -+++ b/components/adblock/core/subscription/subscription_service_impl.h -@@ -43,6 +43,15 @@ namespace adblock { - class SubscriptionServiceImpl final : public SubscriptionService, - public FilteringConfiguration::Observer { - public: -+ void StartUpdate() override; -+ raw_ptr GetMetadata() override; -+ raw_ptr GetMetadataFor( -+ raw_ptr configuration) final; -+ void SetPrivilegedFiltersEnabled(bool enabled) override; -+ bool IsPrivilegedFiltersEnabled() override; -+ std::vector> GetCustomSubscriptions( -+ FilteringConfiguration* configuration) const override; ++ const secure = target => new $$1(target, handler$1); + - // Used to notify this about updates to installed subscriptions. - using SubscriptionUpdatedCallback = - base::RepeatingCallback; -diff --git a/components/adblock/core/subscription/subscription_updater_impl.cc b/components/adblock/core/subscription/subscription_updater_impl.cc ---- a/components/adblock/core/subscription/subscription_updater_impl.cc -+++ b/components/adblock/core/subscription/subscription_updater_impl.cc -@@ -38,7 +38,7 @@ void SubscriptionUpdaterImpl::StartSchedule( - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(!timer_.IsRunning()); - run_update_check_ = std::move(run_update_check); -- VLOG(1) << "[eyeo] Starting update schedule, first check scheduled for " -+ LOG(INFO) << "[eyeo] Starting update schedule, first check scheduled for " - << base::Time::Now() + initial_delay_; - timer_.Start(FROM_HERE, initial_delay_, - base::BindOnce(&SubscriptionUpdaterImpl::RunUpdateCheck, -@@ -47,14 +47,14 @@ void SubscriptionUpdaterImpl::StartSchedule( - - void SubscriptionUpdaterImpl::StopSchedule() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -- VLOG(1) << "[eyeo] Stopping update schedule"; -+ LOG(INFO) << "[eyeo] Stopping update schedule"; - timer_.Stop(); - } - - void SubscriptionUpdaterImpl::RunUpdateCheck() { -- VLOG(1) << "[eyeo] Running subscription update check"; -+ LOG(INFO) << "[eyeo] Running subscription update check"; - run_update_check_.Run(); -- VLOG(1) -+ LOG(INFO) - << "[eyeo] Subscription update check completed, next one scheduled for " - << base::Time::Now() + check_interval_; - timer_.Start(FROM_HERE, check_interval_, -diff --git a/components/adblock/core/subscription/subscription_validator_impl.cc b/components/adblock/core/subscription/subscription_validator_impl.cc ---- a/components/adblock/core/subscription/subscription_validator_impl.cc -+++ b/components/adblock/core/subscription/subscription_validator_impl.cc -@@ -63,11 +63,11 @@ bool IsSignatureValidInternal( - const auto* expected_hash = initial_subscription_signatures.FindString( - path.BaseName().AsUTF8Unsafe()); - if (!expected_hash) { -- DLOG(WARNING) << "[eyeo] " << path << " has no matching signature in prefs"; -+ LOG(WARNING) << "[eyeo] " << path.BaseName().AsUTF8Unsafe() << " has no matching signature in prefs"; - return false; - } - if (*expected_hash != ComputeSubscriptionHash(data)) { -- DLOG(WARNING) << "[eyeo] " << path << " has invalid signature in prefs"; -+ LOG(WARNING) << "[eyeo] " << path.BaseName().AsUTF8Unsafe() << " has invalid signature in prefs"; - return false; - } - return true; -diff --git a/components/blocked_content/popup_blocker.cc b/components/blocked_content/popup_blocker.cc ---- a/components/blocked_content/popup_blocker.cc -+++ b/components/blocked_content/popup_blocker.cc -@@ -20,6 +20,11 @@ - #include "third_party/blink/public/mojom/frame/frame.mojom-shared.h" - - namespace blocked_content { ++ const libEnvironment = typeof environment !== "undefined" ? environment : ++ {}; + -+CROMITE_FEATURE(kStrictPopupBlocker, -+ "StrictPopupBlocker", -+ base::FEATURE_DISABLED_BY_DEFAULT); -+ - namespace { - - content::Page& GetSourcePageForPopup( -@@ -91,7 +96,9 @@ PopupBlockType ShouldBlockPopup(content::WebContents* web_contents, - GetSourcePageForPopup(open_url_params, web_contents))) { - return PopupBlockType::kAbusive; - } -- return PopupBlockType::kNotBlocked; -+ return base::FeatureList::IsEnabled(kStrictPopupBlocker) -+ ? PopupBlockType::kAbusive -+ : PopupBlockType::kNotBlocked; - } - - } // namespace -diff --git a/components/blocked_content/popup_blocker.h b/components/blocked_content/popup_blocker.h ---- a/components/blocked_content/popup_blocker.h -+++ b/components/blocked_content/popup_blocker.h -@@ -5,6 +5,7 @@ - #ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_ - #define COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_ - -+#include "base/feature_list.h" - #include "components/content_settings/core/browser/host_content_settings_map.h" - #include "third_party/blink/public/mojom/window_features/window_features.mojom-forward.h" - #include "ui/base/window_open_disposition.h" -@@ -18,6 +19,8 @@ struct OpenURLParams; - } // namespace content - - namespace blocked_content { -+BASE_DECLARE_FEATURE(kStrictPopupBlocker); -+ - class PopupNavigationDelegate; - - // Classifies what caused a popup to be blocked. -diff --git a/components/content_settings/core/browser/bromite_content_settings/ads.inc b/components/content_settings/core/browser/bromite_content_settings/ads.inc -new file mode 100644 ---- /dev/null -+++ b/components/content_settings/core/browser/bromite_content_settings/ads.inc -@@ -0,0 +1,3 @@ -+ content_settings::WebsiteSettingsRegistry::GetInstance() -+ ->GetMutable(ContentSettingsType::ADS) -+ ->set_show_into_info_page(); -diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn ---- a/components/resources/BUILD.gn -+++ b/components/resources/BUILD.gn -@@ -100,7 +100,6 @@ grit("components_resources") { - - deps += [ - "//components/resources/adblocking:copy_snippets_lib", -- "//components/resources/adblocking:make_all_preloaded_subscriptions", - ] - } - -diff --git a/components/resources/adblock_resources.grdp b/components/resources/adblock_resources.grdp ---- a/components/resources/adblock_resources.grdp -+++ b/components/resources/adblock_resources.grdp -@@ -21,7 +21,4 @@ - - - -- -- -- - -diff --git a/components/resources/adblocking/.gitignore b/components/resources/adblocking/.gitignore ---- a/components/resources/adblocking/.gitignore -+++ b/components/resources/adblocking/.gitignore -@@ -1 +1 @@ --snippets -+#snippets -diff --git a/components/resources/adblocking/BUILD.gn b/components/resources/adblocking/BUILD.gn ---- a/components/resources/adblocking/BUILD.gn -+++ b/components/resources/adblocking/BUILD.gn -@@ -18,7 +18,7 @@ import("//build/compiled_action.gni") - - # Converts text-format filter lists into flatbuffers using a standalone - # converter tool. --template("make_preloaded_subscription") { -+template("make_preloaded_subscription_NO") { - compiled_action(target_name) { - tool = "//components/adblock/core/converter:adblock_flatbuffer_converter" - inputs = [ invoker.input ] -@@ -31,34 +31,6 @@ template("make_preloaded_subscription") { - } - } - --# 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/resources/adblocking/easylist.txt.gz" -- url = "https://easylist-downloads.adblockplus.org/easylist.txt" -- output = "${target_gen_dir}/easylist.fb" --} -- --make_preloaded_subscription("make_exceptionrules") { -- input = "//components/resources/adblocking/exceptionrules.txt.gz" -- url = "https://easylist-downloads.adblockplus.org/exceptionrules.txt" -- output = "${target_gen_dir}/exceptionrules.fb" --} -- --make_preloaded_subscription("make_anticv") { -- input = "//components/resources/adblocking/anticv.txt.gz" -- url = "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt" -- output = "${target_gen_dir}/anticv.fb" --} -- --group("make_all_preloaded_subscriptions") { -- deps = [ -- ":make_anticv", -- ":make_easylist", -- ":make_exceptionrules", -- ] --} -- - action("prepare_snippets_deps") { - script = "//tools/eyeo/snippets_deps.py" - inputs = ["//components/resources/adblocking/snippets/dist"] -@@ -72,11 +44,6 @@ copy("copy_snippets_lib") { - ":prepare_snippets_deps", - ] - -- if (is_debug) { -- sources = [ "//components/resources/adblocking/snippets/dist/isolated-first-xpath3.source.jst" ] -- } else { -- sources = -- [ "//components/resources/adblocking/snippets/dist/isolated-first-xpath3.jst" ] -- } -+ sources = [ "//components/resources/adblocking/snippets/dist/isolated-first.source.jst" ] - outputs = [ "${target_gen_dir}/snippets.jst" ] - } -diff --git a/components/resources/adblocking/elemhide_for_selector.jst b/components/resources/adblocking/elemhide_for_selector.jst ---- a/components/resources/adblocking/elemhide_for_selector.jst -+++ b/components/resources/adblocking/elemhide_for_selector.jst -@@ -43,7 +43,7 @@ if (typeof(elemhideForSelector) !== typeof(Function)) - } - else - { -- console.debug("Nothing found for selector " + selector + ", retrying elemhide in 100 millis"); -+ //console.debug("Nothing found for selector " + selector + ", retrying elemhide in 100 millis"); - setTimeout(elemhideForSelector, 100, url, selector, attempt + 1); - } - } -diff --git a/components/resources/adblocking/elemhideemu.jst b/components/resources/adblocking/elemhideemu.jst ---- a/components/resources/adblocking/elemhideemu.jst -+++ b/components/resources/adblocking/elemhideemu.jst -@@ -1,3 +1,4 @@ -+(function() { - /* - * This file is part of eyeo Chromium SDK, - * Copyright (C) 2006-present eyeo GmbH -@@ -1434,3 +1435,4 @@ let elemHideEmulation = new ElemHideEmulation( - ); - - elemHideEmulation.apply(elemHidingEmulatedPatterns); -+})() -diff --git a/components/resources/adblocking/snippets/dist/isolated-first.jst b/components/resources/adblocking/snippets/dist/isolated-first.jst -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/snippets/dist/isolated-first.jst -@@ -0,0 +1,66 @@ -+(e, ...t) => { -+/*! -+ * snippets v1.4.0 -+ * https://gitlab.com/eyeo/anti-cv/snippets/-/blob/23006aca188eff00b56e3bd9e12591aae7adedf2/dist/isolated-first.jst -+ * -+ -+ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * @eyeo/snippets is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * @eyeo/snippets is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with @eyeo/snippets. If not, see . -+ */ -+ ((environment, ...filters) => { -+const e=Proxy,{apply:t,bind:n,call:o}=Function,r=o.bind(t),i=o.bind(n),s=o.bind(o),c={get:(e,t)=>i(o,e[t])},a=t=>new e(t,c),l={get:(e,t)=>i(e[t],e)},u=t=>new e(t,l),{assign:d,defineProperties:f,freeze:h,getOwnPropertyDescriptor:p,getOwnPropertyDescriptors:g,getPrototypeOf:m}=u(Object),{hasOwnProperty:w}=a({}),{species:b}=Symbol,y={get(e,t){const n=e[t];class o extends n{}const r=g(n.prototype);delete r.constructor,h(f(o.prototype,r));const i=g(n);return delete i.length,delete i.prototype,i[b]={value:o},h(f(o,i))}},v=t=>new e(t,y),S="undefined"!=typeof environment?environment:{};"undefined"==typeof globalThis&&(window.globalThis=window);const{apply:k,ownKeys:x}=u(Reflect),E="world"in S,M=E&&"ISOLATED"===S.world,C=E&&"MAIN"===S.world,T="object"==typeof chrome&&!!chrome.runtime,O="object"==typeof browser&&!!browser.runtime,P=!C&&(M||T||O),W=e=>P?e:L(e,$(e)),{create:L,defineProperties:D,defineProperty:N,freeze:A,getOwnPropertyDescriptor:R,getOwnPropertyDescriptors:$}=u(Object),I=u(globalThis),V=P?globalThis:v(globalThis),{Map:H,RegExp:j,Set:B,WeakMap:F,WeakSet:q}=V,_=(e,t,n=null)=>{const o=x(t);for(const r of x(e)){if(o.includes(r))continue;const i=R(e,r);if(n&&"value"in i){const{value:e}=i;"function"==typeof e&&(i.value=n(e))}N(t,r,i)}},z=e=>{const t=V[e];class n extends t{}const{toString:o,valueOf:r}=t.prototype;D(n.prototype,{toString:{value:o},valueOf:{value:r}});const i=e.toLowerCase(),s=e=>function(){const t=k(e,this,arguments);return typeof t===i?new n(t):t};return _(t,n,s),_(t.prototype,n.prototype,s),n},X=A({frozen:new F,hidden:new q,iframePropertiesToAbort:{read:new B,write:new B},abortedIframes:new F}),U=new j("^[A-Z]");var G=new Proxy(new H([["chrome",P&&(T&&chrome||O&&browser)||void 0],["isExtensionContext",P],["variables",X],["console",W(console)],["document",globalThis.document],["performance",W(performance)],["JSON",W(JSON)],["Map",H],["Math",W(Math)],["Number",P?Number:z("Number")],["RegExp",j],["Set",B],["String",P?String:z("String")],["WeakMap",F],["WeakSet",q],["MouseEvent",MouseEvent]]),{get(e,t){if(e.has(t))return e.get(t);let n=globalThis[t];return"function"==typeof n&&(n=(U.test(t)?V:I)[t]),e.set(t,n),n},has:(e,t)=>e.has(t)});const J={WeakSet:WeakSet,WeakMap:WeakMap,WeakValue:class{has(){return!1}set(){}}},{apply:K}=Reflect;const{Map:Q,WeakMap:Y,WeakSet:Z,setTimeout:ee}=G;let te=!0,ne=e=>{e.clear(),te=!te};var oe=function(e){const{WeakSet:t,WeakMap:n,WeakValue:o}=this||J,r=new t,i=new n,s=new o;return function(t){if(r.has(t))return t;if(i.has(t))return i.get(t);if(s.has(t))return s.get(t);const n=K(e,this,arguments);return r.add(n),n!==t&&("object"==typeof t&&t?i:s).set(t,n),n}}.bind({WeakMap:Y,WeakSet:Z,WeakValue:class extends Q{set(e,t){return te&&(te=!te,ee(ne,0,this)),super.set(e,t)}}});const{concat:re,includes:ie,join:se,reduce:ce,unshift:ae}=a([]),le=v(globalThis),{Map:ue,WeakMap:de}=le,fe=new ue,he=t=>{const n=(e=>{const t=[];let n=e;for(;n;){if(fe.has(n))ae(t,fe.get(n));else{const e=g(n);fe.set(n,e),ae(t,e)}n=m(n)}return ae(t,{}),r(d,null,t)})("function"==typeof t?t.prototype:t),o={get(e,t){if(t in n){const{value:o,get:r}=n[t];if(r)return s(r,e);if("function"==typeof o)return i(o,e)}return e[t]},set(e,t,o){if(t in n){const{set:r}=n[t];if(r)return s(r,e,o),!0}return e[t]=o,!0}};return t=>new e(t,o)},{isExtensionContext:pe,Array:ge,Number:me,String:we,Object:be}=G,{isArray:ye}=ge,{getOwnPropertyDescriptor:ve,setPrototypeOf:Se}=be,{toString:ke}=be.prototype,{slice:xe}=we.prototype,{get:Ee}=ve(Node.prototype,"nodeType"),Me=pe?{}:{Attr:he(Attr),CanvasRenderingContext2D:he(CanvasRenderingContext2D),CSSStyleDeclaration:he(CSSStyleDeclaration),Document:he(Document),Element:he(Element),HTMLCanvasElement:he(HTMLCanvasElement),HTMLElement:he(HTMLElement),HTMLImageElement:he(HTMLImageElement),HTMLScriptElement:he(HTMLScriptElement),MutationRecord:he(MutationRecord),Node:he(Node),ShadowRoot:he(ShadowRoot),get CSS2Properties(){return Me.CSSStyleDeclaration}},Ce=(e,t)=>{if("Element"!==t&&t in Me)return Me[t](e);if(ye(e))return Se(e,ge.prototype);const n=(e=>s(xe,s(ke,e),8,-1))(e);if(n in Me)return Me[n](e);if(n in G)return Se(e,G[n].prototype);if("nodeType"in e)switch(s(Ee,e)){case 1:if(!(t in Me))throw new Error("unknown hint "+t);return Me[t](e);case 2:return Me.Attr(e);case 3:return Me.Node(e);case 9:return Me.Document(e)}throw new Error("unknown brand "+n)};var Te=pe?e=>e===window||e===globalThis?G:e:oe(((e,t="Element")=>{if(e===window||e===globalThis)return G;switch(typeof e){case"object":return e&&Ce(e,t);case"string":return new we(e);case"number":return new me(e);default:throw new Error("unsupported value")}}));let Oe=!1;function Pe(){return Oe}let{console:We,document:Le,getComputedStyle:De,isExtensionContext:Ne,variables:Ae,Array:Re,MutationObserver:$e,Object:Ie,XPathEvaluator:Ve,XPathExpression:He,XPathResult:je}=Te(window);const{querySelectorAll:Be}=Le,Fe=Be&&i(Be,Le);function qe(e,t=!1){try{const n=navigator.userAgent.includes("Firefox")?e.openOrClosedShadowRoot:browser.dom.openOrClosedShadowRoot(e);return null===n&&Pe()&&!t&&We.log("Shadow root not found or not added in element yet",e),n}catch(n){return Pe()&&!t&&We.log("Error while accessing shadow root",e,n),null}}function _e(e,t=!1){return Ue(e,Fe.bind(Le),Le,t)}function ze(e,t,n,o){const r=t.getAttribute("xlink:href")||t.getAttribute("href");if(r){const s=Fe(r)[0];if(!s&&Pe())return We.log("No elements found matching",r),!1;if(!(i=e)||0===i.length||i.every((e=>""===e.trim()))){const e=o.length>0?o:[];return n.push({element:s,rootParents:[...e,t]}),!1}const c=s.querySelectorAll.bind(s);return{nextBoundElement:s,nestedSelectorsString:e.join("^^"),next$$:c}}var i}function Xe(e,t){const n=qe(t);if(n){const{querySelectorAll:o}=n,r=o&&i(o,n).bind(n);return{nextBoundElement:t,nestedSelectorsString:":host "+e.join("^^"),next$$:r}}return!1}function Ue(e,t,n,o,r=[]){if(e.includes("^^")){const[i,s,...c]=e.split("^^");let a,l;switch(s){case"svg":l=ze;break;case"sh":l=Xe;break;default:return Pe()&&We.log(s," is not supported. Supported commands are: \n^^sh^^\n^^svg^^"),[]}a=""===i.trim()?[n]:t(i);const u=[];for(const e of a){const t=l(c,e,u,r);if(!t)continue;const{next$$:n,nestedSelectorsString:i,nextBoundElement:s}=t,a=Ue(i,n,s,o,[...r,e]);a&&u.push(...a)}return u}const i=t(e);return o?[...i].map((e=>({element:e,rootParents:r.length>0?r:[]}))):i}function Ge(e,t,n=[]){if(t.includes("^^svg^^")&&(t=t.split("^^svg^^")[0]),t.includes("^^sh^^")){const o=t.split("^^sh^^"),r=o.length-1;if(t=`:host ${o[r]}`,r===n.length)return e.closest(t);return n[r].closest(t)}return n[0]?n[0].closest(t):e.closest(t)}const{assign:Je,setPrototypeOf:Ke}=Ie;class Qe extends He{evaluate(...e){return Ke(r(super.evaluate,this,e),je.prototype)}}class Ye extends Ve{createExpression(...e){return Ke(r(super.createExpression,this,e),Qe.prototype)}}function Ze(e){if(Ae.hidden.has(e))return!1;!function(e){Ne&&"function"==typeof checkElement&&checkElement(e)}(e),Ae.hidden.add(e);let{style:t}=Te(e),n=Te(t,"CSSStyleDeclaration"),o=Te([]),{debugCSSProperties:r}=S;for(let[e,t]of r||[["display","none"]])n.setProperty(e,t,"important"),o.push([e,n.getPropertyValue(e)]);return new $e((()=>{for(let[e,t]of o){let o=n.getPropertyValue(e),r=n.getPropertyPriority(e);o==t&&"important"==r||n.setProperty(e,t,"important")}})).observe(e,{attributes:!0,attributeFilter:["style"]}),!0}function et(e){let t=e;if(t.startsWith("xpath(")&&t.endsWith(")")){let e=t.slice(6,-1),n=(new Ye).createExpression(e,null),o=je.ORDERED_NODE_SNAPSHOT_TYPE;return e=>{if(!e)return;let t=n.evaluate(Le,o,null),{snapshotLength:r}=t;for(let n=0;n_e(e).forEach(t)}function tt(e,t,n,o){let r;null==n&&(n=t);const i=()=>{for(const{element:i,rootParents:s}of _e(n,!0)){const n=Ge(Te(i),t,s);n&&e(i,n,s)&&(r(),Ze(n)&&"function"==typeof o&&o(n))}};return Je(new $e(i),{race(e){r=e,this.observe(Le,{childList:!0,characterData:!0,subtree:!0}),i()}})}function nt(e,t,n,o){let r=Te(t,"CSSStyleDeclaration");if("none"==r.getPropertyValue("display"))return!1;let i=r.getPropertyValue("visibility");if("hidden"==i||"collapse"==i)return!1;if(!n||e==n)return!0;let s=Te(e).parentElement;if(!s){if(!o||!o.length)return!0;s=o[o.length-1],o=o.slice(0,-1)}return nt(s,De(s),n,o)}function ot(e){let t=De(e),{cssText:n}=t;if(n)return n;for(let e of t)n+=`${e}: ${t[e]}; `;return Te(n).trim()}let{Math:rt,RegExp:it}=Te(window);function st(e){let{length:t}=e;if(t>1&&"/"===e[0]){let n="/"===e[t-1];if(n||t>2&&Te(e).endsWith("/i")){let t=[Te(e).slice(1,n?-1:-2)];return n||t.push("i"),new it(...t)}}return new it(Te(e).replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))}const{console:ct}=Te(window),at=()=>{};function lt(...e){if(Pe()){const t=["%c DEBUG","font-weight: bold;"],n=e.indexOf("error"),o=e.indexOf("warn"),r=e.indexOf("success"),i=e.indexOf("info");-1!==n?(t[0]+=" - ERROR",t[1]+="color: red; border:2px solid red",Te(e).splice(n,1)):-1!==o?(t[0]+=" - WARNING",t[1]+="color: orange; border:2px solid orange ",Te(e).splice(o,1)):-1!==r?(t[0]+=" - SUCCESS",t[1]+="color: green; border:2px solid green",Te(e).splice(r,1)):-1!==i&&(t[1]+="color: black;",Te(e).splice(i,1)),Te(e).unshift(...t)}ct.log(...e)}function ut(e){return i(Pe()?lt:at,null,e)}let{Array:dt,Error:ft,Map:ht,parseInt:pt}=Te(window),gt=null,mt=null;function wt(e,t){if(null===gt)return at;let n=gt,{participants:o}=n;return o.set(r,t),r;function r(){if(n.winners<1)return;if(ut("race")("success",`${e} won the race`),n===gt)mt.push(r);else if(o.delete(r),--n.winners<1){for(let e of o.values())e();o.clear()}}}const bt={get(e,t){const n=e;for(;!w(e,t);)e=m(e);const{get:o,set:i}=p(e,t);return function(){return arguments.length?r(i,n,arguments):s(o,n)}}};var yt;function vt(e,t,n){var o,r;n?"load"===n?(e("info","Waiting until window.load"),window.addEventListener("load",(()=>{e("info","Window.load fired."),t()}))):"loading"===n||"interactive"===n||"complete"===n?(e("info","Waiting document state until :",n),document.addEventListener("readystatechange",(()=>{e("info","Document state changed:",document.readyState),document.readyState===n&&t()}))):(e("info","Waiting until ",n," event is triggered on document"),(o=document,r=n,new Promise((e=>{const t=()=>{o.removeEventListener(r,t),e()};o.addEventListener(r,t)}))).then((()=>{e("info",n," is triggered on document, starting the snippet"),t()})).catch((t=>{e("error","There was an error while waiting for the event.",t)}))):t()}Te(window),yt=window,new e(yt,bt),Te(/^\d+$/);let{MutationObserver:St,WeakSet:kt,getComputedStyle:xt}=Te(window);let{clearTimeout:Et,fetch:Mt,getComputedStyle:Ct,setTimeout:Tt,Map:Ot,MutationObserver:Pt,Uint8Array:Wt}=Te(window);let Lt=new Ot;function Dt(e,{as:t="arrayBuffer",cleanup:n=6e4}={}){let o=t+":"+e,r=Lt.get(o)||{remove:()=>Lt.delete(o),result:null,timer:0};return Et(r.timer),r.timer=Tt(r.remove,n),r.result||(r.result=Mt(e).then((e=>e[t]())).catch(r.remove),Lt.set(o,r)),r.result}const{parseFloat:Nt,Math:At,MutationObserver:Rt,WeakSet:$t}=Te(window),{min:It}=At,Vt=(e,t)=>{const n=e.length+1,o=t.length+1,r=[[0]];let i=0,s=0;for(;++i"{mark(){},end(){}}"};function Yt(e,t=10){return Qt}let{MutationObserver:Zt,WeakSet:en}=Te(window);const{ELEMENT_NODE:tn}=Node;let{MutationObserver:nn,WeakSet:on}=Te(window);const{ELEMENT_NODE:rn}=Node;let{parseInt:sn,setTimeout:cn,Error:an,MouseEvent:ln,MutationObserver:un,WeakSet:dn}=Te(window);const fn=["auxclick","click","dblclick","gotpointercapture","lostpointercapture","mouseenter","mousedown","mouseleave","mousemove","mouseout","mouseover","mouseup","pointerdown","pointerenter","pointermove","pointerover","pointerout","pointerup","pointercancel","pointerleave"];let{isNaN:hn,MutationObserver:pn,parseInt:gn,parseFloat:mn,setTimeout:wn}=Te(window);const bn={log:lt,race:function(e,t="1"){switch(e){case"start":gt={winners:pt(t,10)||1,participants:new ht},mt=new dt;break;case"end":case"finish":case"stop":gt=null;for(let e of mt)e();mt=null;break;default:throw new ft(`Invalid action: ${e}`)}},debug:function(){Oe=!0},"hide-if-matches-xpath":function(e,t){const{mark:n,end:o}=Yt(),r=ut("hide-if-matches-xpath"),i=t=>{const i=et(`xpath(${e})`),s=new en,c=()=>{n(),i((t=>{if(s.has(t))return!1;s.add(t),l(),Te(t).nodeType===tn?Ze(t):Te(t).textContent="",r("success","Matched: ",t," for selector: ",e)})),o()},a=new Zt(c),l=wt("hide-if-matches-xpath",(()=>a.disconnect()));a.observe(t,{characterData:!0,childList:!0,subtree:!0}),c()};if(t){let e,n=0;const o=et(`xpath(${t})`),r=()=>{o((e=>{i(e),n++})),n>0&&e.disconnect()};e=new Zt(r),e.observe(document,{characterData:!0,childList:!0,subtree:!0}),r()}else i(document)},"hide-if-matches-computed-xpath":function(e,t,n,o){const{mark:r,end:i}=Yt(),s=ut("hide-if-matches-computed-xpath");if(!t||!e)return void s("error","No query or searchQuery provided.");const c=t=>{const n=(t=>e.replace("{{}}",t))(t);s("info","Starting hiding elements that match query: ",n);const o=et(`xpath(${n})`),c=new on,a=()=>{r(),o((t=>{if(c.has(t))return!1;c.add(t),u(),Te(t).nodeType===rn?Ze(t):Te(t).textContent="",s("success","Matched: ",t," for selector: ",e)})),i()},l=new nn(a),u=wt("hide-if-matches-computed-xpath",(()=>l.disconnect()));l.observe(document,{characterData:!0,childList:!0,subtree:!0}),a()},a=st(n);vt(s,(()=>{if(t){s("info","Started searching for: ",t);const e=new on;let n;const o=et(`xpath(${t})`),r=()=>{o((t=>{if(e.has(t))return!1;if(e.add(t),s("info","Found node: ",t),t.innerHTML){s("info","Searching in: ",t.innerHTML);const e=t.innerHTML.match(a);if(e&&e.length){let t="";t=e[1]?e[1]:e[0],s("info","Matched search query: ",t),c(t)}}}))};n=new nn(r),n.observe(document,{characterData:!0,childList:!0,subtree:!0}),r()}}),o)},"hide-if-contains":function(e,t="*",n=null){const o=ut("hide-if-contains");let r=st(e);const i=tt((e=>r.test(Te(e).textContent)),t,n,(e=>{o("success","Matched: ",e," for selector: ",t,n)}));i.race(wt("hide-if-contains",(()=>{i.disconnect()})))},"hide-if-contains-similar-text":function(e,t,n=null,o=0,r=0){const i=new $t,s=ut("hide-if-contains-similar-text"),c=Te(e),{length:a}=c,l=a+Nt(o)||0,u=Te([...c]).sort(),d=Nt(r)||1/0;null==n&&(n=t),s("Looking for similar text: "+c);const f=()=>{for(const{element:e,rootParents:r}of _e(n,!0)){if(i.has(e))continue;i.add(e);const{innerText:n}=Te(e),a=It(d,n.length-l+1);for(let i=0;ih.disconnect()));h.observe(document,{childList:!0,characterData:!0,subtree:!0}),f()},"hide-if-contains-visible-text":function(e,t,n=null,...o){let r=Te([]);const i=new jt([["-snippet-box-margin","2"],["-disable-bg-color-check","false"],["-check-is-contained","false"]]);for(let e of o){e=Te(e);let t=e.indexOf(":");if(t<0)continue;let n=e.slice(0,t).trim().toString(),o=e.slice(t+1).trim().toString();n&&o&&(i.has(n)?i.set(n,o):r.push([n,o]))}let s=Te([["opacity","0"],["font-size","0px"],["color","rgba(0, 0, 0, 0)"]]),c=new jt(s.concat(r));function a(e,t,{bgColorCheck:n=!0}={}){t||(t=Ht(e)),t=Te(t);for(const[e,n]of c){if(st(n).test(t.getPropertyValue(e)))return!1}let o=t.getPropertyValue("color");return!n||t.getPropertyValue("background-color")!=o}function l(e,t,{bgColorCheck:n=!0}={}){let o=Ht(e,t);if(!nt(e,o)||!a(e,o,{bgColorCheck:n}))return"";let{content:r}=Te(o);if(r&&"none"!==r){let t=Te([]);return r=Te(r).trim().replace(/(["'])(?:(?=(\\?))\2.)*?\1/g,(e=>""+(t.push(Te(e).slice(1,-1))-1))),r=r.replace(/\s*attr\(\s*([^\s,)]+)[^)]*?\)\s*/g,((t,n)=>Te(e).getAttribute(n)||"")),r.replace(/\x01(\d+)/g,((e,n)=>t[n]))}return""}function u(e,t,{boxMargin:n=2}={}){const o=Te(e).getBoundingClientRect(),r=Te(t).getBoundingClientRect(),i=r.left-n,s=r.right+n,c=r.top-n,a=r.bottom+n;return i<=o.left&&o.left<=s&&c<=o.top&&o.top<=a&&c<=o.bottom&&o.bottom<=a&&i<=o.right&&o.right<=s}function d(e,t,n,o,r,i,{boxMargin:s=2,bgColorCheck:c,checkIsContained:f}={}){let h=!n;if(h&&(n=Ht(e)),!nt(e,n,h&&t,i))return"";o||"hidden"!==Te(n).getPropertyValue("overflow-x")&&"hidden"!==Te(n).getPropertyValue("overflow-y")||(o=e);let p=l(e,":before",{bgColorCheck:c});for(let t of function(e,t=!0){const n=qe(e,t);return n?n.childNodes:Te(e).childNodes}(Te(e)))switch(Te(t).nodeType){case qt:p+=d(t,e,Ht(t),o,r,i,{boxMargin:s,bgColorCheck:c,checkIsContained:f});break;case _t:if(o)u(e,o,{boxMargin:s})&&a(e,n,{bgColorCheck:c})&&(p+=Te(t).nodeValue);else if(a(e,n,{bgColorCheck:c})){if(f&&!u(e,r,{boxMargin:s}))continue;p+=Te(t).nodeValue}}return p+l(e,":after",{bgColorCheck:c})}const f=i.get("-snippet-box-margin"),h=Ft(f)||0,p=!("true"===i.get("-disable-bg-color-check")),g="true"===i.get("-check-is-contained");let m=st(e),w=new Bt;const b=tt(((e,t,n)=>{if(w.has(e))return!1;w.add(e);let o=d(e,t,null,null,e,n,{boxMargin:h,bgColorCheck:p,checkIsContained:g}),r=m.test(o);return Pe()&&o.length&<(r?"success":"info",r,m,o),r}),t,n);b.race(wt("hide-if-contains-visible-text",(()=>{b.disconnect()})))},"hide-if-contains-and-matches-style":function(e,t="*",n=null,o=null,r=null,i,s=null,c=null){const a=ut("hide-if-contains-and-matches-style"),l=new kt,u=Pe()&&new kt;null==n&&(n=t);const d=st(e),f=o?st(o):null,h=r?st(r):null;vt(a,(()=>{const e=()=>{if(!(s&&window.innerWidthc))for(const{element:e,rootParents:o}of _e(n,!0))if(!l.has(e)&&d.test(Te(e).textContent))if(!h||h.test(ot(e))){const n=Ge(Te(e),t,o);if(!n)continue;if(!f||f.test(ot(n)))r(),Ze(n),l.add(e),a("success","Matched: ",n,"which contains: ",e," for params: ",...arguments);else{if(!u||u.has(n))continue;a("info","In this element the searchStyle matched but style didn't:\n",n,xt(n),...arguments),u.add(n)}}else{if(!u||u.has(e))continue;a("info","In this element the searchStyle didn't match:\n",e,xt(e),...arguments),u.add(e)}},o=new St(e),r=wt("hide-if-contains-and-matches-style",(()=>o.disconnect()));o.observe(document,{childList:!0,characterData:!0,subtree:!0}),e()}),i)},"hide-if-has-and-matches-style":function(e,t="*",n=null,o=null,r=null,i=null,s=null,c=null){const a=ut("hide-if-has-and-matches-style"),l=new Xt,u=Pe()&&new Xt;null==n&&(n=t);const d=o?st(o):null,f=r?st(r):null;vt(a,(()=>{const o=()=>{if(!(s&&window.innerWidthc))for(const{element:o,rootParents:r}of _e(n,!0))if(!l.has(o))if(!Te(o).querySelector(e)||f&&!f.test(ot(o))){if(!u||u.has(o))continue;a("info","In this element the searchStyle didn't match:\n",o,Ut(o),...arguments),u.add(o)}else{const e=Ge(Te(o),t,r);if(!e||d&&!d.test(ot(e))){if(!u||u.has(e))continue;a("info","In this element the searchStyle matchedbut style didn't:\n",e,Ut(e),...arguments),u.add(e)}else i(),Ze(e),l.add(o),a("success","Matched: ",e,"which contains: ",o," for params: ",...arguments)}},r=new zt(o),i=wt("hide-if-has-and-matches-style",(()=>r.disconnect()));r.observe(document,{childList:!0,subtree:!0}),o()}),i)},"hide-if-labelled-by":function(e,t,n=null){let o=null==n,r=st(e),i=new Kt,s=()=>{for(const{element:e,rootParents:s}of _e(t,!0)){let t=o?e:Ge(Te(e),n,s);if(!t||!nt(e,Gt(e),t))continue;let c=Te(e).getAttribute("aria-labelledby"),l=()=>{i.has(t)||r.test(Te(e).getAttribute("aria-label")||"")&&(a(),i.add(t),Ze(t))};if(c)for(let e of Te(c).split(/\s+/)){let n=Te(document).getElementById(e);n?!i.has(n)&&r.test(n.innerText)&&(a(),i.add(n),Ze(t)):l()}else l()}},c=new Jt(s),a=wt("hide-if-labelled-by",(()=>c.disconnect()));c.observe(document,{characterData:!0,childList:!0,subtree:!0}),s()},"hide-if-contains-image":function(e,t,n){null==n&&(n=t);let o=st(e);const r=ut("hide-if-contains-image");let i=()=>{for(const{element:e,rootParents:i}of _e(n,!0)){let n=Ct(e),s=Te(n["background-image"]).match(/^url\("(.*)"\)$/);s&&Dt(s[1]).then((n=>{if(o.test(new Wt(n).reduce(((e,t)=>e+function(e,t=2){let n=Te(e).toString(16);n.lengths.disconnect()));s.observe(document,{childList:!0,subtree:!0}),i()},"simulate-mouse-event":function(...e){const t=ut("simulate-mouse-event");if(e.length<1)throw new an("[simulate-mouse-event snippet]: No selector provided.");e.length>7&&(e=e.slice(0,7));const n=Te([]);function o(){return n.forEach((e=>{if(!e.found){(function(e){let t=e;if(t.startsWith("xpath(")&&t.endsWith(")")){let t=et(e);return()=>{let e=Te([]);return t((t=>e.push(t))),e}}return()=>Re.from(_e(e))})(e.selector)().length>0&&(e.found=!0)}})),n.every((e=>e.found))}function r(e,n,o){e&&n&&("click"===n&&e.click?(e.click(),t("success","Clicked on this node:\n",e,"\nwith a delay of",o,"ms")):(e.dispatchEvent(new ln(n,{bubbles:!0,cancelable:!0})),t("success","A",n,"event was dispatched with a delay of",o,"ms on this node:\n",e)))}Te(e).forEach((e=>{const o=function(e){if(!e)return null;const n={selector:"",continue:!1,trigger:!1,event:"click",delay:"500",clicked:!1,found:!1},o=e.split("$");let r=[];o.length>=2&&(r=o[1].toLowerCase().split(",")),[n.selector]=o;for(const e of r)if("trigger"===e)n.trigger=!0;else if("continue"===e)n.continue=!0;else if(e.startsWith("event")){const t=e.toLowerCase().split("=");t[1]?n.event=t[1]:n.event="click"}else if(e.startsWith("delay")){const t=e.toLowerCase().split("=");t[1]?n.delay=t[1]:n.delay="500"}return fn.includes(n.event)||t("warn",n.event," might be misspelled, check for typos.\n","These are the supported events:",fn),n}(e);n.push(o)}));let i=!1;const[s]=n.slice(-1);s.trigger=!0;let c=new dn;function a(){if(i||(i=o()),i)for(const e of n){const t=et(e.selector),n=sn(e.delay,10);e.trigger&&t((t=>{c.has(t)||(c.add(t),e.continue?setInterval((()=>{r(t,e.event,e.delay)}),n):cn((()=>{r(t,e.event,e.delay)}),n))}))}}new un(a).observe(document,{childList:!0,subtree:!0}),a()},"skip-video":function(e,t,...n){const o=new Map([["-max-attempts","10"],["-retry-ms","10"],["-run-once","false"],["-wait-until",""],["-skip-to","-0.1"],["-stop-on-video-end","false"],["-start-from","0"],["-mute-video-when-skipping","true"]]);for(let e of n){e=Te(e);let t=e.indexOf(":");if(t<0)continue;let n=e.slice(0,t).trim().toString(),r=e.slice(t+1).trim().toString();n&&r&&o.has(n)&&o.set(n,r)}const r=o.get("-max-attempts"),i=gn(r||10,10),s=o.get("-retry-ms"),c=gn(s||10,10),a="true"===o.get("-run-once"),l=o.get("-skip-to"),u=mn(l||-.1),d=o.get("-start-from"),f=gn(d||0,10),h=o.get("-wait-until"),p="true"===o.get("-stop-on-video-end"),g=!("false"===o.get("-mute-video-when-skipping")),m=ut("skip-video"),w=et(`xpath(${t})`);let b=!1;vt(m,(()=>{const n=(s=0)=>{b&&a?o&&o.disconnect():w((o=>{m("info","Matched: ",o," for selector: ",t),m("info","Running video skipping logic.");const a=_e(e)[0];for(;hn(a.duration)&&s{const e=s+1;m("info","Running video skipping logic. Attempt: ",e),n(e)}),c);const l=a.duration-a.currentTime<.5;!(!hn(a.duration)&&a.duration>0&&a.currentTimeo.disconnect()));o.observe(document,{characterData:!0,childList:!0,subtree:!0}),n()}),h)}}; -+const snippets=bn; -+let context; -+for (const [name, ...args] of filters) { -+if (snippets.hasOwnProperty(name)) { -+try { context = snippets[name].apply(context, args); } -+catch (error) { console.error(error); } -+} -+} -+context = void 0; -+})(e, ...t); -+ -+const callback = (environment, ...filters) => { -+const e=Proxy,{apply:t,bind:n,call:r}=Function,o=r.bind(t),i=r.bind(n),s=r.bind(r),a={get:(e,t)=>i(r,e[t])},c=t=>new e(t,a),l=(t,n)=>new e(t,{apply:(e,t,r)=>o(n,t,r)}),u={get:(e,t)=>i(e[t],e)},f=t=>new e(t,u),{assign:p,defineProperties:d,freeze:h,getOwnPropertyDescriptor:w,getOwnPropertyDescriptors:g,getPrototypeOf:y}=f(Object),{hasOwnProperty:m}=c({}),{species:b}=Symbol,v={get(e,t){const n=e[t];class r extends n{}const o=g(n.prototype);delete o.constructor,h(d(r.prototype,o));const i=g(n);return delete i.length,delete i.prototype,i[b]={value:r},h(d(r,i))}},S=t=>new e(t,v),E="undefined"!=typeof environment?environment:{};"undefined"==typeof globalThis&&(window.globalThis=window);const{apply:x,ownKeys:M}=f(Reflect),T="world"in E,O=T&&"ISOLATED"===E.world,j=T&&"MAIN"===E.world,P="object"==typeof chrome&&!!chrome.runtime,N="object"==typeof browser&&!!browser.runtime,k=!j&&(O||P||N),C=e=>k?e:$(e,D(e)),{create:$,defineProperties:L,defineProperty:A,freeze:W,getOwnPropertyDescriptor:R,getOwnPropertyDescriptors:D}=f(Object),H=f(globalThis),z=k?globalThis:S(globalThis),{Map:I,RegExp:F,Set:B,WeakMap:J,WeakSet:V}=z,U=(e,t,n=null)=>{const r=M(t);for(const o of M(e)){if(r.includes(o))continue;const i=R(e,o);if(n&&"value"in i){const{value:e}=i;"function"==typeof e&&(i.value=n(e))}A(t,o,i)}},q=e=>{const t=z[e];class n extends t{}const{toString:r,valueOf:o}=t.prototype;L(n.prototype,{toString:{value:r},valueOf:{value:o}});const i=e.toLowerCase(),s=e=>function(){const t=x(e,this,arguments);return typeof t===i?new n(t):t};return U(t,n,s),U(t.prototype,n.prototype,s),n},_=W({frozen:new J,hidden:new V,iframePropertiesToAbort:{read:new B,write:new B},abortedIframes:new J}),G=new F("^[A-Z]");var X=new Proxy(new I([["chrome",k&&(P&&chrome||N&&browser)||void 0],["isExtensionContext",k],["variables",_],["console",C(console)],["document",globalThis.document],["performance",C(performance)],["JSON",C(JSON)],["Map",I],["Math",C(Math)],["Number",k?Number:q("Number")],["RegExp",F],["Set",B],["String",k?String:q("String")],["WeakMap",J],["WeakSet",V],["MouseEvent",MouseEvent]]),{get(e,t){if(e.has(t))return e.get(t);let n=globalThis[t];return"function"==typeof n&&(n=(G.test(t)?z:H)[t]),e.set(t,n),n},has:(e,t)=>e.has(t)});const K={WeakSet:WeakSet,WeakMap:WeakMap,WeakValue:class{has(){return!1}set(){}}},{apply:Y}=Reflect;const{Map:Z,WeakMap:Q,WeakSet:ee,setTimeout:te}=X;let ne=!0,re=e=>{e.clear(),ne=!ne};var oe=function(e){const{WeakSet:t,WeakMap:n,WeakValue:r}=this||K,o=new t,i=new n,s=new r;return function(t){if(o.has(t))return t;if(i.has(t))return i.get(t);if(s.has(t))return s.get(t);const n=Y(e,this,arguments);return o.add(n),n!==t&&("object"==typeof t&&t?i:s).set(t,n),n}}.bind({WeakMap:Q,WeakSet:ee,WeakValue:class extends Z{set(e,t){return ne&&(ne=!ne,te(re,0,this)),super.set(e,t)}}});const{concat:ie,includes:se,join:ae,reduce:ce,unshift:le}=c([]),ue=S(globalThis),{Map:fe,WeakMap:pe}=ue,de=new fe,he=t=>{const n=(e=>{const t=[];let n=e;for(;n;){if(de.has(n))le(t,de.get(n));else{const e=g(n);de.set(n,e),le(t,e)}n=y(n)}return le(t,{}),o(p,null,t)})("function"==typeof t?t.prototype:t),r={get(e,t){if(t in n){const{value:r,get:o}=n[t];if(o)return s(o,e);if("function"==typeof r)return i(r,e)}return e[t]},set(e,t,r){if(t in n){const{set:o}=n[t];if(o)return s(o,e,r),!0}return e[t]=r,!0}};return t=>new e(t,r)},{isExtensionContext:we,Array:ge,Number:ye,String:me,Object:be}=X,{isArray:ve}=ge,{getOwnPropertyDescriptor:Se,setPrototypeOf:Ee}=be,{toString:xe}=be.prototype,{slice:Me}=me.prototype,{get:Te}=Se(Node.prototype,"nodeType"),Oe=we?{}:{Attr:he(Attr),CanvasRenderingContext2D:he(CanvasRenderingContext2D),CSSStyleDeclaration:he(CSSStyleDeclaration),Document:he(Document),Element:he(Element),HTMLCanvasElement:he(HTMLCanvasElement),HTMLElement:he(HTMLElement),HTMLImageElement:he(HTMLImageElement),HTMLScriptElement:he(HTMLScriptElement),MutationRecord:he(MutationRecord),Node:he(Node),ShadowRoot:he(ShadowRoot),get CSS2Properties(){return Oe.CSSStyleDeclaration}},je=(e,t)=>{if("Element"!==t&&t in Oe)return Oe[t](e);if(ve(e))return Ee(e,ge.prototype);const n=(e=>s(Me,s(xe,e),8,-1))(e);if(n in Oe)return Oe[n](e);if(n in X)return Ee(e,X[n].prototype);if("nodeType"in e)switch(s(Te,e)){case 1:if(!(t in Oe))throw new Error("unknown hint "+t);return Oe[t](e);case 2:return Oe.Attr(e);case 3:return Oe.Node(e);case 9:return Oe.Document(e)}throw new Error("unknown brand "+n)};var Pe=we?e=>e===window||e===globalThis?X:e:oe(((e,t="Element")=>{if(e===window||e===globalThis)return X;switch(typeof e){case"object":return e&&je(e,t);case"string":return new me(e);case"number":return new ye(e);default:throw new Error("unsupported value")}}));const Ne={get(e,t){const n=e;for(;!m(e,t);)e=y(e);const{get:r,set:i}=w(e,t);return function(){return arguments.length?o(i,n,arguments):s(r,n)}}},ke=t=>new e(t,Ne);let Ce=!1;function $e(){return Ce}const{console:Le}=Pe(window),Ae=()=>{};function We(...e){if($e()){const t=["%c DEBUG","font-weight: bold;"],n=e.indexOf("error"),r=e.indexOf("warn"),o=e.indexOf("success"),i=e.indexOf("info");-1!==n?(t[0]+=" - ERROR",t[1]+="color: red; border:2px solid red",Pe(e).splice(n,1)):-1!==r?(t[0]+=" - WARNING",t[1]+="color: orange; border:2px solid orange ",Pe(e).splice(r,1)):-1!==o?(t[0]+=" - SUCCESS",t[1]+="color: green; border:2px solid green",Pe(e).splice(o,1)):-1!==i&&(t[1]+="color: black;",Pe(e).splice(i,1)),Pe(e).unshift(...t)}Le.log(...e)}function Re(e){return i($e()?We:Ae,null,e)}let{Math:De,RegExp:He}=Pe(window);function ze(e){let{length:t}=e;if(t>1&&"/"===e[0]){let n="/"===e[t-1];if(n||t>2&&Pe(e).endsWith("/i")){let t=[Pe(e).slice(1,n?-1:-2)];return n||t.push("i"),new He(...t)}}return new He(Pe(e).replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))}function Ie(){return Pe(De.floor(2116316160*De.random()+60466176)).toString(36)}let{parseFloat:Fe,variables:Be,Array:Je,Error:Ve,Map:Ue,Object:qe,ReferenceError:_e,Set:Ge,WeakMap:Xe}=Pe(window),{onerror:Ke}=ke(window),Ye=Node.prototype,Ze=Element.prototype,Qe=null;function et(e,t,n,r=!0){let o=Pe(t),i=o.indexOf(".");if(-1==i){let o=qe.getOwnPropertyDescriptor(e,t);if(o&&!o.configurable)return;let i=qe.assign({},n,{configurable:r});if(!o&&!i.get&&i.set){let n=e[t];i.get=()=>n}return void qe.defineProperty(e,t,i)}let s=o.slice(0,i).toString();t=o.slice(i+1).toString();let a=e[s];!a||"object"!=typeof a&&"function"!=typeof a||et(a,t,n);let c=qe.getOwnPropertyDescriptor(e,s);if(c&&!c.configurable)return;Qe||(Qe=new Xe),Qe.has(e)||Qe.set(e,new Ue);let l=Qe.get(e);if(l.has(s))return void l.get(s).set(t,n);let u=new Ue([[t,n]]);l.set(s,u),qe.defineProperty(e,s,{get:()=>a,set(e){if(a=e,a&&("object"==typeof a||"function"==typeof a))for(let[e,t]of u)et(a,e,t)},configurable:r})}function tt(e){let t=Ke();Ke(((...n)=>{let r=n.length&&n[0];return!("string"!=typeof r||!Pe(r).includes(e))||("function"==typeof t?o(t,this,n):void 0)}))}function nt(e,t,n,r=!0){let o=Re(e);if(!n)return void o("error","no property to abort on read");let i=Ie();o("info",`aborting on ${n} access`),et(t,n,{get:function(){throw o("success",`${n} access aborted`),new _e(i)},set(){}},r),tt(i)}function rt(e,t,n,r=!0){let o=Re(e);if(!n)return void o("error","no property to abort on write");let i=Ie();o("info",`aborting when setting ${n}`),et(t,n,{set:function(){throw o("success",`setting ${n} aborted`),new _e(i)}},r),tt(i)}function ot(e,t=!1,n=!1){let r=Be.abortedIframes,i=Be.iframePropertiesToAbort;for(let o of Je.from(window.frames))if(r.has(o))for(let i of e)t&&r.get(o).read.add(i),n&&r.get(o).write.add(i);for(let r of e)t&&i.read.add(r),n&&i.write.add(r);function a(){for(let e of Je.from(window.frames)){r.has(e)||r.set(e,{read:new Ge(i.read),write:new Ge(i.write)});let t=r.get(e).read;if(t.size>0){let n=Je.from(t);t.clear();for(let t of n)nt("abort-on-iframe-property-read",e,t)}let n=r.get(e).write;if(n.size>0){let t=Je.from(n);n.clear();for(let n of t)rt("abort-on-iframe-property-write",e,n)}}}a(),r.has(document)||(r.set(document,!0),function(e){let t;function n(e,t){for(let n of t){et(e,n,r(e,n))}}function r(t,n){let r=t[n];return{get:()=>function(...t){let n;return n=o(r,this,t),e&&e(),n}}}function i(t,n){let r=qe.getOwnPropertyDescriptor(t,n),{set:o}=r||{};return{set(t){let n;return n=s(o,this,t),e&&e(),n}}}n(Ye,["appendChild","insertBefore","replaceChild"]),n(Ze,["append","prepend","replaceWith","after","before","insertAdjacentElement","insertAdjacentHTML"]),t=i(Ze,"innerHTML"),et(Ze,"innerHTML",t),t=i(Ze,"outerHTML"),et(Ze,"outerHTML",t)}(a))}let{Object:it}=window;function st(e,t){if(!(e instanceof it))return;let n=e,r=Pe(t).split(".");if(0===r.length)return;for(let e=0;e{};case"trueFunc":return()=>!0;case"falseFunc":return()=>!1;case"emptyArray":return[];case"emptyObj":return{};case"undefined":return;case"":return e;default:if(at.test(e))return Fe(e);throw new Ve(`[override-property-read snippet]: Value "${e}" is not valid.`)}}let{HTMLScriptElement:lt,Object:ut,ReferenceError:ft}=Pe(window),pt=ut.getPrototypeOf(lt);let{Error:dt,URL:ht}=Pe(window),{cookie:wt}=ke(document);let{console:gt,document:yt,getComputedStyle:mt,isExtensionContext:bt,variables:vt,Array:St,MutationObserver:Et,Object:xt,XPathEvaluator:Mt,XPathExpression:Tt,XPathResult:Ot}=Pe(window);const{querySelectorAll:jt}=yt,Pt=jt&&i(jt,yt);function Nt(e,t=!1){return $t(e,Pt.bind(yt),yt,t)}function kt(e,t,n,r){const o=t.getAttribute("xlink:href")||t.getAttribute("href");if(o){const s=Pt(o)[0];if(!s&&$e())return gt.log("No elements found matching",o),!1;if(!(i=e)||0===i.length||i.every((e=>""===e.trim()))){const e=r.length>0?r:[];return n.push({element:s,rootParents:[...e,t]}),!1}const a=s.querySelectorAll.bind(s);return{nextBoundElement:s,nestedSelectorsString:e.join("^^"),next$$:a}}var i}function Ct(e,t){const n=function(e,t=!1){try{const n=navigator.userAgent.includes("Firefox")?e.openOrClosedShadowRoot:browser.dom.openOrClosedShadowRoot(e);return null===n&&$e()&&!t&>.log("Shadow root not found or not added in element yet",e),n}catch(n){return $e()&&!t&>.log("Error while accessing shadow root",e,n),null}}(t);if(n){const{querySelectorAll:r}=n,o=r&&i(r,n).bind(n);return{nextBoundElement:t,nestedSelectorsString:":host "+e.join("^^"),next$$:o}}return!1}function $t(e,t,n,r,o=[]){if(e.includes("^^")){const[i,s,...a]=e.split("^^");let c,l;switch(s){case"svg":l=kt;break;case"sh":l=Ct;break;default:return $e()&>.log(s," is not supported. Supported commands are: \n^^sh^^\n^^svg^^"),[]}c=""===i.trim()?[n]:t(i);const u=[];for(const e of c){const t=l(a,e,u,o);if(!t)continue;const{next$$:n,nestedSelectorsString:i,nextBoundElement:s}=t,c=$t(i,n,s,r,[...o,e]);c&&u.push(...c)}return u}const i=t(e);return r?[...i].map((e=>({element:e,rootParents:o.length>0?o:[]}))):i}const{assign:Lt,setPrototypeOf:At}=xt;class Wt extends Tt{evaluate(...e){return At(o(super.evaluate,this,e),Ot.prototype)}}class Rt extends Mt{createExpression(...e){return At(o(super.createExpression,this,e),Wt.prototype)}}function Dt(e){if(vt.hidden.has(e))return!1;!function(e){bt&&"function"==typeof checkElement&&checkElement(e)}(e),vt.hidden.add(e);let{style:t}=Pe(e),n=Pe(t,"CSSStyleDeclaration"),r=Pe([]),{debugCSSProperties:o}=E;for(let[e,t]of o||[["display","none"]])n.setProperty(e,t,"important"),r.push([e,n.getPropertyValue(e)]);return new Et((()=>{for(let[e,t]of r){let r=n.getPropertyValue(e),o=n.getPropertyPriority(e);r==t&&"important"==o||n.setProperty(e,t,"important")}})).observe(e,{attributes:!0,attributeFilter:["style"]}),!0}function Ht(e){let t=e;if(t.startsWith("xpath(")&&t.endsWith(")")){let t=function(e){let t=e;if(t.startsWith("xpath(")&&t.endsWith(")")){let e=t.slice(6,-1),n=(new Rt).createExpression(e,null),r=Ot.ORDERED_NODE_SNAPSHOT_TYPE;return e=>{if(!e)return;let t=n.evaluate(yt,r,null),{snapshotLength:o}=t;for(let n=0;nNt(e).forEach(t)}(e);return()=>{let e=Pe([]);return t((t=>e.push(t))),e}}return()=>St.from(Nt(e))}let{ELEMENT_NODE:zt,TEXT_NODE:It,prototype:Ft}=Node,{prototype:Bt}=Element,{prototype:Jt}=HTMLElement,{console:Vt,variables:Ut,DOMParser:qt,Error:_t,MutationObserver:Gt,Object:Xt,ReferenceError:Kt}=Pe(window),{getOwnPropertyDescriptor:Yt}=Xt;Pe(window);const{Map:Zt,MutationObserver:Qt,Object:en,Set:tn,WeakSet:nn}=Pe(window);let rn=Element.prototype,{attachShadow:on}=rn,sn=new nn,an=new Zt,cn=null;const{Error:ln,JSON:un,Map:fn,Response:pn,Object:dn}=Pe(window);let hn=null;let{Error:wn,JSON:gn,Map:yn,Object:mn,Response:bn}=Pe(window),vn=null;let{Error:Sn}=Pe(window);let{Error:En,Map:xn,Object:Mn,console:Tn}=Pe(window),{toString:On}=Function.prototype,jn=EventTarget.prototype,{addEventListener:Pn}=jn,Nn=null;let kn,{URL:Cn,fetch:$n}=Pe(window),{delete:Ln,has:An}=c(URLSearchParams.prototype);const Wn={"abort-current-inline-script":function(e,t=null){const n=Re("abort-current-inline-script"),r=t?ze(t):null,o=Ie(),i=Pe(document).currentScript;let a=window;const c=Pe(e).split("."),l=Pe(c).pop();for(let e of Pe(c))if(a=a[e],!a||"object"!=typeof a&&"function"!=typeof a)return void n("warn",c," is not found");const{get:u,set:f}=ut.getOwnPropertyDescriptor(a,l)||{};let p=a[l];void 0===p&&n("warn","The property",l,"doesn't exist yet. Check typos.");const d=()=>{const e=Pe(document).currentScript;if(e instanceof pt&&""==Pe(e,"HTMLScriptElement").src&&e!=i&&(!r||r.test(Pe(e).textContent)))throw n("success",c," is aborted \n",e),new ft(o)};et(a,l,{get(){return d(),u?s(u,this):p},set(e){d(),f?s(f,this,e):p=e}}),tt(o)},"abort-on-iframe-property-read":function(...e){ot(e,!0,!1)},"abort-on-iframe-property-write":function(...e){ot(e,!1,!0)},"abort-on-property-read":function(e,t){nt("abort-on-property-read",window,e,!("false"===t))},"abort-on-property-write":function(e,t){rt("abort-on-property-write",window,e,!("false"===t))},"cookie-remover":function(e,t=!1){if(!e)throw new dt("[cookie-remover snippet]: No cookie to remove.");let n=Re("cookie-remover"),r=ze(e);if(!Pe(/^http|^about/).test(location.protocol))return void n("warn","Snippet only works for http or https and about.");function o(){return Pe(wt()).split(";").filter((e=>r.test(Pe(e).split("=")[0])))}const i=()=>{n("info","Parsing cookies for matches");for(const e of Pe(o())){let t=Pe(location.hostname);!t&&Pe(location.ancestorOrigins)&&Pe(location.ancestorOrigins[0])&&(t=new ht(Pe(location.ancestorOrigins[0])).hostname);const r=Pe(e).split("=")[0],o="expires=Thu, 01 Jan 1970 00:00:00 GMT",i="path=/",s=t.split(".");for(let e=s.length;e>0;e--){const t=s.slice(s.length-e).join(".");wt(`${Pe(r).trim()}=;${o};${i};domain=${t}`),wt(`${Pe(r).trim()}=;${o};${i};domain=.${t}`),n("success",`Set expiration date on ${r}`)}}};if(i(),t){let e=o();setInterval((()=>{let t=o();if(t!==e)try{i()}finally{e=t}}),1e3)}},debug:function(){Ce=!0},"freeze-element":function(e,t="",...n){let r,i,a=!1,c=!1,l=Pe(n).filter((e=>!h(e))),u=Pe(n).filter((e=>h(e))).map(ze),f=Ie(),p=Ht(e);!function(){let n=Pe(t).split("+");1===n.length&&""===n[0]&&(n=[]);for(let t of n)switch(t){case"subtree":a=!0;break;case"abort":c=!0;break;default:throw new _t("[freeze] Unknown option passed to the snippet. [selector]: "+e+" [option]: "+t)}}();let d={selector:e,shouldAbort:c,rid:f,exceptionSelectors:l,regexExceptions:u,changeId:0};function h(e){return e.length>=2&&"/"==e[0]&&"/"==e[e.length-1]}function w(){i=p(),g(i,!1)}function g(e,t=!0){for(let n of e)Ut.frozen.has(n)||(Ut.frozen.set(n,d),!t&&a&&new Gt((e=>{for(let t of Pe(e))g(Pe(t,"MutationRecord").addedNodes)})).observe(n,{childList:!0,subtree:!0}),a&&Pe(n).nodeType===zt&&g(Pe(n).childNodes))}function y(e,...t){We(`[freeze][${e}] `,...t)}function m(e,t,n,r){let o=r.selector,i=r.changeId,s="string"==typeof e,a=r.shouldAbort?"aborting":"watching";switch(Vt.groupCollapsed(`[freeze][${i}] ${a}: ${o}`),n){case"appendChild":case"append":case"prepend":case"insertBefore":case"replaceChild":case"insertAdjacentElement":case"insertAdjacentHTML":case"insertAdjacentText":case"innerHTML":case"outerHTML":y(i,s?"text: ":"node: ",e),y(i,"added to node: ",t);break;case"replaceWith":case"after":case"before":y(i,s?"text: ":"node: ",e),y(i,"added to node: ",Pe(t).parentNode);break;case"textContent":case"innerText":case"nodeValue":y(i,"content of node: ",t),y(i,"changed to: ",e)}y(i,`using the function "${n}"`),Vt.groupEnd(),r.changeId++}function b(e,t){if(t)for(let n of t)if(n.test(e))return!0;return!1}function v(e){throw new Kt(e)}function S(e,t,n,r){let o=new qt,{body:i}=Pe(o.parseFromString(e,"text/html")),s=E(Pe(i).childNodes,t,n,r);return Pe(s).map((e=>{switch(Pe(e).nodeType){case zt:return Pe(e).outerHTML;case It:return Pe(e).textContent;default:return""}})).join("")}function E(e,t,n,r){let o=Pe([]);for(let i of e)x(i,t,n,r)&&o.push(i);return o}function x(e,t,n,r){let o=r.shouldAbort,i=r.regexExceptions,s=r.exceptionSelectors,a=r.rid;if("string"==typeof e){let s=e;return!!b(s,i)||($e()&&m(s,t,n,r),o&&v(a),$e())}let c=e;switch(Pe(c).nodeType){case zt:return!!function(e,t){if(t){let n=Pe(e);for(let e of t)if(n.matches(e))return!0}return!1}(c,s)||(o&&($e()&&m(c,t,n,r),v(a)),!!$e()&&(Dt(c),m(c,t,n,r),!0));case It:return!!b(Pe(c).textContent,i)||($e()&&m(c,t,n,r),o&&v(a),!1);default:return!0}}function M(e,t,n,r){let i=Yt(e,t)||{},a=i.get&&s(i.get,e)||i.value;if(a)return{get:()=>function(...e){if(n(this)){let n=r(this);if(n){let r=e[0];if(!x(r,this,t,n))return r}}return o(a,this,e)}}}function T(e,t,n,r){let i=Yt(e,t)||{},a=i.get&&s(i.get,e)||i.value;if(a)return{get:()=>function(...e){if(!n(this))return o(a,this,e);let i=r(this);if(!i)return o(a,this,e);let s=E(e,this,t,i);return s.length>0?o(a,this,s):void 0}}}function O(e,t,n,r){let i=Yt(e,t)||{},a=i.get&&s(i.get,e)||i.value;if(a)return{get:()=>function(...e){let[i,c]=e,l="afterbegin"===i||"beforeend"===i;if(n(this,l)){let e=r(this,l);if(e){let n,r=l?this:Pe(this).parentNode;switch(t){case"insertAdjacentElement":if(!x(c,r,t,e))return c;break;case"insertAdjacentHTML":return n=S(c,r,t,e),n?s(a,this,i,n):void 0;case"insertAdjacentText":if(!x(c,r,t,e))return}}}return o(a,this,e)}}}function j(e,t,n,r){let o=Yt(e,t)||{},{set:i}=o;if(i)return{set(e){if(!n(this))return s(i,this,e);let o=r(this);if(!o)return s(i,this,e);let a=S(e,this,t,o);return a?s(i,this,a):void 0}}}function P(e,t,n,r){let o=Yt(e,t)||{},{set:i}=o;if(i)return{set(e){if(!n(this))return s(i,this,e);let o=r(this);return o?x(e,this,t,o)?s(i,this,e):void 0:s(i,this,e)}}}Ut.frozen.has(document)||(Ut.frozen.set(document,!0),function(){let e;function t(e){return e&&Ut.frozen.has(e)}function n(e){try{return e&&(Ut.frozen.has(e)||Ut.frozen.has(Pe(e).parentNode))}catch(e){return!1}}function r(e,t){try{return e&&(Ut.frozen.has(e)&&t||Ut.frozen.has(Pe(e).parentNode)&&!t)}catch(e){return!1}}function o(e){return Ut.frozen.get(e)}function i(e){try{if(Ut.frozen.has(e))return Ut.frozen.get(e);let t=Pe(e).parentNode;return Ut.frozen.get(t)}catch(e){}}function s(e,t){try{if(Ut.frozen.has(e)&&t)return Ut.frozen.get(e);let n=Pe(e).parentNode;return Ut.frozen.get(n)}catch(e){}}e=M(Ft,"appendChild",t,o),et(Ft,"appendChild",e),e=M(Ft,"insertBefore",t,o),et(Ft,"insertBefore",e),e=M(Ft,"replaceChild",t,o),et(Ft,"replaceChild",e),e=T(Bt,"append",t,o),et(Bt,"append",e),e=T(Bt,"prepend",t,o),et(Bt,"prepend",e),e=T(Bt,"replaceWith",n,i),et(Bt,"replaceWith",e),e=T(Bt,"after",n,i),et(Bt,"after",e),e=T(Bt,"before",n,i),et(Bt,"before",e),e=O(Bt,"insertAdjacentElement",r,s),et(Bt,"insertAdjacentElement",e),e=O(Bt,"insertAdjacentHTML",r,s),et(Bt,"insertAdjacentHTML",e),e=O(Bt,"insertAdjacentText",r,s),et(Bt,"insertAdjacentText",e),e=j(Bt,"innerHTML",t,o),et(Bt,"innerHTML",e),e=j(Bt,"outerHTML",n,i),et(Bt,"outerHTML",e),e=P(Ft,"textContent",t,o),et(Ft,"textContent",e),e=P(Jt,"innerText",t,o),et(Jt,"innerText",e),e=P(Ft,"nodeValue",t,o),et(Ft,"nodeValue",e)}()),r=new Gt(w),r.observe(document,{childList:!0,subtree:!0}),w()},"hide-if-shadow-contains":function(e,t="*"){let n=`${e}\\${t}`;an.has(n)||an.set(n,[ze(e),t,Ae]);const r=Re("hide-if-shadow-contain");cn||(cn=new Qt((e=>{let t=new tn;for(let{target:n}of Pe(e)){let e=Pe(n).parentNode;for(;e;)[n,e]=[e,Pe(n).parentNode];if(!sn.has(n)&&!t.has(n)){t.add(n);for(let[e,t,o]of an.values())if(e.test(Pe(n).textContent)){let e=Pe(n.host).closest(t);e&&(o(),Pe(n).appendChild(document.createElement("style")).textContent=":host {display: none !important}",Dt(e),sn.add(n),r("success","Hiding: ",e," for params: ",...arguments))}}}})),en.defineProperty(rn,"attachShadow",{value:l(on,(function(){let e=o(on,this,arguments);return r("info","attachShadow is called for: ",e),cn.observe(e,{childList:!0,characterData:!0,subtree:!0}),e}))}))},"json-override":function(e,t,n="",r=""){if(!e)throw new ln("[json-override snippet]: Missing paths to override.");if(void 0===t)throw new ln("[json-override snippet]: No value to override with.");if(!hn){let i=Re("json-override");function s(e,t){for(let{prune:n,needle:r,filter:o,value:s}of hn.values())if(!o||o.test(t)){if(Pe(r).some((t=>!st(e,t))))return e;for(let t of n){let n=st(e,t);void 0!==n&&(i("success",`Found ${t} replaced it with ${s}`),n[0][n[1]]=ct(s))}}return e}let{parse:a}=un;hn=new fn,dn.defineProperty(window.JSON,"parse",{value:l(a,(function(e){return s(o(a,this,arguments),e)}))}),i("info","Wrapped JSON.parse for override");let{json:c}=pn.prototype;dn.defineProperty(window.Response.prototype,"json",{value:l(c,(function(e){return o(c,this,arguments).then((t=>s(t,e)))}))}),i("info","Wrapped Response.json for override")}hn.set(e,{prune:Pe(e).split(/ +/),needle:n.length?Pe(n).split(/ +/):[],filter:r?ze(r):null,value:t})},"json-prune":function(e,t=""){if(!e)throw new wn("Missing paths to prune");if(!vn){let n=Re("json-prune");function r(e){for(let{prune:t,needle:r}of vn.values()){if(Pe(r).some((t=>!st(e,t))))return e;for(let r of t){let t=st(e,r);void 0!==t&&(n("success",`Found ${r} and deleted`),delete t[0][t[1]])}}return e}let{parse:i}=gn;vn=new yn,mn.defineProperty(window.JSON,"parse",{value:l(i,(function(){return r(o(i,this,arguments))}))}),n("info","Wrapped JSON.parse for prune");let{json:s}=bn.prototype;mn.defineProperty(window.Response.prototype,"json",{value:l(s,(function(){return o(s,this,arguments).then((e=>r(e)))}))}),n("info","Wrapped Response.json for prune")}vn.set(e,{prune:Pe(e).split(/ +/),needle:t.length?Pe(t).split(/ +/):[]})},"override-property-read":function(e,t,n){if(!e)throw new Sn("[override-property-read snippet]: No property to override.");if(void 0===t)throw new Sn("[override-property-read snippet]: No value to override with.");let r=Re("override-property-read"),o=ct(t);r("info",`Overriding ${e}.`);const i=!("false"===n);et(window,e,{get:()=>(r("success",`${e} override done.`),o),set(){}},i)},"prevent-listener":function(e,t,n){if(!e)throw new En("[prevent-listener snippet]: No event type.");if(!Nn){Nn=new xn;let e=Re("[prevent]");Mn.defineProperty(jn,"addEventListener",{value:l(Pn,(function(t,n){for(let{evt:r,handlers:o,selectors:i}of Nn.values()){if(!r.test(t))continue;let a=this instanceof Element;for(let c=0;c{let[t]=e;if("string"==typeof t){let r=new Cn(t);for(let[o,i]of kn)i&&!i.test(t)||An(r.searchParams,o)&&(n("success",`${o} has been stripped from url ${t}`),Ln(r.searchParams,o),e[0]=r.href)}return o($n,self,e)}))),kn.set(e,t&&ze(t))},trace:function(...e){o(We,null,e)}}; -+const snippets=Wn; -+let context; -+for (const [name, ...args] of filters) { -+if (snippets.hasOwnProperty(name)) { -+try { context = snippets[name].apply(context, args); } -+catch (error) { console.error(error); } -+} -+} -+context = void 0; -+}; -+const graph = new Map([["abort-current-inline-script",null],["abort-on-iframe-property-read",null],["abort-on-iframe-property-write",null],["abort-on-property-read",null],["abort-on-property-write",null],["cookie-remover",null],["debug",null],["freeze-element",null],["hide-if-shadow-contains",null],["json-override",null],["json-prune",null],["override-property-read",null],["prevent-listener",null],["strip-fetch-query-parameter",null],["trace",null]]); -+callback.get = snippet => graph.get(snippet); -+callback.has = snippet => graph.has(snippet); -+ -+ if (t.every(([name]) => !callback.has(name))) return; -+ const append = () => { -+ URL.revokeObjectURL( -+ Object.assign( -+ document.documentElement.appendChild(document.createElement("script")), -+ {async: false, src: URL.createObjectURL(new Blob([ -+ "(" + callback + ")(..." + JSON.stringify([e, ...t]) + ")" -+ ]))} -+ ).src -+ ); -+ }; -+ try { append(); } -+ catch (_) { -+ document.addEventListener("readystatechange", append, {once:true}); -+ } -+} -diff --git a/components/resources/adblocking/snippets/dist/isolated-first.source.jst b/components/resources/adblocking/snippets/dist/isolated-first.source.jst -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/snippets/dist/isolated-first.source.jst -@@ -0,0 +1,4793 @@ -+(e, ...t) => { -+/*! -+ * snippets v1.5.0 -+ * https://gitlab.com/eyeo/anti-cv/snippets/-/blob/2beff11ad5e1a8e7460519e3bb45829f5a19a1ba/dist/isolated-first-all.source.jst -+ * -+ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * @eyeo/snippets is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * @eyeo/snippets is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with @eyeo/snippets. If not, see . -+ */ -+ ((environment, ...filters) => { -+ /*! -+ * This file is part of eyeo's Anti-Circumvention Snippets module (@eyeo/snippets), -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * @eyeo/snippets is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * @eyeo/snippets is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with @eyeo/snippets. If not, see . -+ */ -+ const $$1 = Proxy; -+ -+ const {apply: a, bind: b, call: c} = Function; -+ const apply$2 = c.bind(a); -+ const bind = c.bind(b); -+ const call = c.bind(c); -+ -+ const callerHandler = { -+ get(target, name) { -+ return bind(c, target[name]); -+ } -+ }; -+ const caller = target => new $$1(target, callerHandler); -+ -+ const handler$2 = { -+ get(target, name) { -+ return bind(target[name], target); -+ } -+ }; -+ const bound = target => new $$1(target, handler$2); -+ -+ const { -+ assign: assign$1, -+ defineProperties: defineProperties$1, -+ freeze: freeze$1, -+ getOwnPropertyDescriptor: getOwnPropertyDescriptor$2, -+ getOwnPropertyDescriptors: getOwnPropertyDescriptors$1, -+ getPrototypeOf -+ } = bound(Object); -+ -+ const {hasOwnProperty} = caller({}); -+ -+ const {species} = Symbol; -+ -+ const handler$1 = { -+ get(target, name) { -+ const Native = target[name]; -+ class Secure extends Native {} -+ -+ const proto = getOwnPropertyDescriptors$1(Native.prototype); -+ delete proto.constructor; -+ freeze$1(defineProperties$1(Secure.prototype, proto)); -+ -+ const statics = getOwnPropertyDescriptors$1(Native); -+ delete statics.length; -+ delete statics.prototype; -+ statics[species] = {value: Secure}; -+ return freeze$1(defineProperties$1(Secure, statics)); -+ } -+ }; -+ -+ const secure = target => new $$1(target, handler$1); -+ -+ const libEnvironment = typeof environment !== "undefined" ? environment : -+ {}; -+ -+ if (typeof globalThis === "undefined") -+ window.globalThis = window; ++ if (typeof globalThis === "undefined") ++ window.globalThis = window; + + const {apply: apply$1, ownKeys} = bound(Reflect); + @@ -5928,7 +6382,6 @@ new file mode 100644 + + ["console", copyIfExtension(console)], + ["document", globalThis.document], -+ ["performance", copyIfExtension(performance)], + ["JSON", copyIfExtension(JSON)], + ["Map", Map$6], + ["Math", copyIfExtension(Math)], @@ -5987,7 +6440,7 @@ new file mode 100644 + }; + } + -+ const {Map: Map$5, WeakMap: WeakMap$2, WeakSet: WeakSet$9, setTimeout: setTimeout$4} = env; ++ const {Map: Map$5, WeakMap: WeakMap$2, WeakSet: WeakSet$9, setTimeout: setTimeout$3} = env; + + let cleanup = true; + let cleanUpCallback = map => { @@ -6003,7 +6456,7 @@ new file mode 100644 + set(key, value) { + if (cleanup) { + cleanup = !cleanup; -+ setTimeout$4(cleanUpCallback, 0, this); ++ setTimeout$3(cleanUpCallback, 0, this); + } + return super.set(key, value); + } @@ -6063,13 +6516,13 @@ new file mode 100644 + + const { + isExtensionContext: isExtensionContext$1, -+ Array: Array$4, ++ Array: Array$3, + Number: Number$1, + String: String$1, + Object: Object$2 + } = env; + -+ const {isArray} = Array$4; ++ const {isArray} = Array$3; + const {getOwnPropertyDescriptor, setPrototypeOf: setPrototypeOf$1} = Object$2; + + const {toString} = Object$2.prototype; @@ -6102,7 +6555,7 @@ new file mode 100644 + return chained[hint](value); + + if (isArray(value)) -+ return setPrototypeOf$1(value, Array$4.prototype); ++ return setPrototypeOf$1(value, Array$3.prototype); + + const brand = getBrand(value); + if (brand in chained) @@ -6163,10 +6616,10 @@ new file mode 100644 + let { + console: console$2, + document: document$1, -+ getComputedStyle: getComputedStyle$6, ++ getComputedStyle: getComputedStyle$5, + isExtensionContext, + variables, -+ Array: Array$3, ++ Array: Array$2, + MutationObserver: MutationObserver$a, + Object: Object$1, + XPathEvaluator, @@ -6441,7 +6894,7 @@ new file mode 100644 + return elements; + }; + } -+ return () => Array$3.from($$(selector)); ++ return () => Array$2.from($$(selector)); + } + + function hideIfMatches(match, selector, searchSelector, onHideCallback) { @@ -6498,12 +6951,12 @@ new file mode 100644 + } + + return isVisible( -+ parent, getComputedStyle$6(parent), closest, shadowRootParents ++ parent, getComputedStyle$5(parent), closest, shadowRootParents + ); + } + + function getComputedCSSText(element) { -+ let style = getComputedStyle$6(element); ++ let style = getComputedStyle$5(element); + let {cssText} = style; + + if (cssText) @@ -6515,7 +6968,7 @@ new file mode 100644 + return $(cssText).trim(); + } + -+ let {Array: Array$2, Math: Math$3, RegExp} = $(window); ++ let {Array: Array$1, Math: Math$4, RegExp} = $(window); + + function regexEscape(string) { + return $(string).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); @@ -6540,7 +6993,58 @@ new file mode 100644 + } + + function formatArguments(args) { -+ return $(Array$2.from(args)).map(arg => `'${arg}'`).join(" "); ++ return $(Array$1.from(args)).map(arg => `'${arg}'`).join(" "); ++ } ++ ++ let {Math: Math$3, setInterval: setInterval$1, performance} = $(window); ++ ++ const noopProfile = { ++ mark() {}, ++ end() {}, ++ toString() { ++ return "{mark(){},end(){}}"; ++ } ++ }; ++ ++ let inactive = true; ++ ++ function setProfile() { ++ inactive = false; ++ } ++ ++ function profile(id, rate = 10) { ++ if (inactive) ++ return noopProfile; ++ function processSamples() { ++ let samples = $([]); ++ ++ for (let {name, duration} of performance.getEntriesByType("measure")) ++ samples.push({name, duration}); ++ ++ if (samples.length) ++ performance.clearMeasures(); ++ } ++ ++ if (!profile[id]) { ++ profile[id] = setInterval$1(processSamples, ++ Math$3.round(60000 / Math$3.min(60, rate))); ++ } ++ ++ return { ++ mark() { ++ performance.mark(id); ++ }, ++ end(clear = false) { ++ const measureObj = performance.measure(id, id); ++ console.log("PROFILER:", measureObj); ++ performance.clearMarks(id); ++ if (clear) { ++ clearInterval(profile[id]); ++ delete profile[id]; ++ processSamples(); ++ } ++ } ++ }; + } + + const {console: console$1} = $(window); @@ -6548,6 +7052,7 @@ new file mode 100644 + const noop = () => {}; + + function log(...args) { ++ let {mark, end} = profile("log"); + if (debug()) { + const logArgs = ["%c DEBUG", "font-weight: bold;"]; + @@ -6578,14 +7083,16 @@ new file mode 100644 + + $(args).unshift(...logArgs); + } ++ mark(); + console$1.log(...args); ++ end(); + } + + function getDebugger(name) { + return bind(debug() ? log : noop, null, name); + } + -+ let {Array: Array$1, Error: Error$2, Map: Map$3, parseInt: parseInt$2} = $(window); ++ let {Array, Error: Error$2, Map: Map$3, parseInt: parseInt$2} = $(window); + + let stack = null; + let won = null; @@ -6597,7 +7104,7 @@ new file mode 100644 + winners: parseInt$2(winners, 10) || 1, + participants: new Map$3() + }; -+ won = new Array$1(); ++ won = new Array(); + break; + case "end": + case "finish": @@ -6649,12 +7156,15 @@ new file mode 100644 + function hideIfContains(search, selector = "*", searchSelector = null) { + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-contains"); ++ const {mark, end} = profile("hide-if-contains"); + const onHideCallback = node => { ++ mark(); + debugLog("success", + "Matched: ", + node, + "\nFILTER: hide-if-contains", + formattedArguments); ++ end(); + }; + let re = toRegExp(search); + @@ -6754,7 +7264,7 @@ new file mode 100644 + } + } + -+ let {MutationObserver: MutationObserver$9, WeakSet: WeakSet$8, getComputedStyle: getComputedStyle$5} = $(window); ++ let {MutationObserver: MutationObserver$9, WeakSet: WeakSet$8, getComputedStyle: getComputedStyle$4} = $(window); + + function hideIfContainsAndMatchesStyle(search, + selector = "*", @@ -6767,6 +7277,7 @@ new file mode 100644 + ) { + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-contains-and-matches-style"); ++ const {mark, end} = profile("hide-if-contains-and-matches-style"); + const hiddenMap = new WeakSet$8(); + const logMap = debug() && new WeakSet$8(); + if (searchSelector == null) @@ -6778,6 +7289,7 @@ new file mode 100644 + const searchStyleRegExp = searchStyle ? toRegExp(searchStyle) : null; + const mainLogic = () => { + const callback = () => { ++ mark(); + if ((windowWidthMin && window.innerWidth < windowWidthMin) || + (windowWidthMax && window.innerWidth > windowWidthMax) + ) @@ -6810,7 +7322,7 @@ new file mode 100644 + "In this element the searchStyle matched " + + "but style didn't:\n", + closest, -+ getComputedStyle$5(closest), ++ getComputedStyle$4(closest), + formattedArguments); + logMap.add(closest); + } @@ -6821,12 +7333,13 @@ new file mode 100644 + debugLog("info", + "In this element the searchStyle didn't match:\n", + element, -+ getComputedStyle$5(element), ++ getComputedStyle$4(element), + formattedArguments); + logMap.add(element); + } + } + } ++ end(); + }; + + const mo = new MutationObserver$9(callback); @@ -6841,10 +7354,10 @@ new file mode 100644 + } + + let { -+ clearTimeout: clearTimeout$1, ++ clearTimeout, + fetch, -+ getComputedStyle: getComputedStyle$4, -+ setTimeout: setTimeout$3, ++ getComputedStyle: getComputedStyle$3, ++ setTimeout: setTimeout$2, + Map: Map$2, + MutationObserver: MutationObserver$8, + Uint8Array @@ -6858,10 +7371,12 @@ new file mode 100644 + + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-contains-image"); ++ const {mark, end} = profile("hide-if-contains-image"); + + let callback = () => { ++ mark(); + for (const {element, rootParents} of $$(searchSelector, true)) { -+ let style = getComputedStyle$4(element); ++ let style = getComputedStyle$3(element); + let match = $(style["background-image"]).match(/^url\("(.*)"\)$/); + if (match) { + fetchContent(match[1]).then(content => { @@ -6880,6 +7395,7 @@ new file mode 100644 + }); + } + } ++ end(); + }; + + let mo = new MutationObserver$8(callback); @@ -6901,8 +7417,8 @@ new file mode 100644 + result: null, + timer: 0 + }; -+ clearTimeout$1(details.timer); -+ details.timer = setTimeout$3(details.remove, cleanup); ++ clearTimeout(details.timer); ++ details.timer = setTimeout$2(details.remove, cleanup); + if (!details.result) { + details.result = fetch(url).then(res => res[as]()).catch(details.remove); + fetchContentMap.set(uid, details); @@ -6960,6 +7476,7 @@ new file mode 100644 + const visitedNodes = new WeakSet$7(); + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-contains-similar-text"); ++ const {mark, end} = profile("hide-if-contains-similar-text"); + const $search = $(search); + const {length} = $search; + const chars = length + parseFloat$2(ignoreChars) || 0; @@ -6972,6 +7489,7 @@ new file mode 100644 + debugLog("info", "Looking for similar text: " + $search); + + const callback = () => { ++ mark(); + for (const {element, rootParents} of $$(searchSelector, true)) { + if (visitedNodes.has(element)) + continue; @@ -6997,6 +7515,7 @@ new file mode 100644 + } + } + } ++ end(); + }; + + let mo = new MutationObserver$7(callback); @@ -7008,13 +7527,14 @@ new file mode 100644 + callback(); + } + -+ let {getComputedStyle: getComputedStyle$3, Map: Map$1, WeakSet: WeakSet$6, parseFloat: parseFloat$1, DOMMatrix, Math: Math$1} = $(window); ++ let {getComputedStyle: getComputedStyle$2, Map: Map$1, WeakSet: WeakSet$6, parseFloat: parseFloat$1, DOMMatrix, Math: Math$1} = $(window); + + const {ELEMENT_NODE: ELEMENT_NODE$3, TEXT_NODE} = Node; + + function hideIfContainsVisibleText(search, selector, + searchSelector = null, + ...attributes) { ++ const {mark, end} = profile("hide-if-contains-visible-text"); + const formattedArguments = formatArguments(arguments); + let entries = $([]); + const optionalParameters = new Map$1([ @@ -7052,7 +7572,7 @@ new file mode 100644 + + function isTextVisible(element, style, {bgColorCheck = true} = {}) { + if (!style) -+ style = getComputedStyle$3(element); ++ style = getComputedStyle$2(element); + + style = $(style); + @@ -7070,7 +7590,7 @@ new file mode 100644 + } + + function getTransformMatrix(element, pseudo = null) { -+ const style = getComputedStyle$3(element, pseudo); ++ const style = getComputedStyle$2(element, pseudo); + let transform = style.transform; + + if (transform === "none") @@ -7080,7 +7600,7 @@ new file mode 100644 + + function getPseudoContent(element, pseudo, parentMatrix, + {bgColorCheck = true, translateThresh = 2} = {}) { -+ let style = getComputedStyle$3(element, pseudo); ++ let style = getComputedStyle$2(element, pseudo); + + if (!isVisible(element, style) || + !isTextVisible(element, style, {bgColorCheck})) @@ -7158,7 +7678,7 @@ new file mode 100644 + } = {}) { + let checkClosest = !style; + if (checkClosest) -+ style = getComputedStyle$3(element); ++ style = getComputedStyle$2(element); + + if (!isVisible(element, style, checkClosest && closest, shadowRootParents)) + return ""; @@ -7186,7 +7706,7 @@ new file mode 100644 + case ELEMENT_NODE$3: + text += getVisibleContent(node, + element, -+ getComputedStyle$3(node), ++ getComputedStyle$2(node), + parentOverflowNode, + originalElement, + shadowRootParents, @@ -7239,6 +7759,7 @@ new file mode 100644 + + const mo = hideIfMatches( + (element, closest, rootParents) => { ++ mark(); + if (seen.has(element)) + return false; + @@ -7258,7 +7779,7 @@ new file mode 100644 + log("success", result, re, text, "\nFILTER: hide-if-contains-visible-text", formattedArguments) : + log("info", result, re, text); + } -+ ++ end(); + return result; + }, + selector, @@ -7272,7 +7793,7 @@ new file mode 100644 + )); + } + -+ let {MutationObserver: MutationObserver$6, WeakSet: WeakSet$5, getComputedStyle: getComputedStyle$2} = $(window); ++ let {MutationObserver: MutationObserver$6, WeakSet: WeakSet$5, getComputedStyle: getComputedStyle$1} = $(window); + + function hideIfHasAndMatchesStyle(search, + selector = "*", @@ -7285,6 +7806,7 @@ new file mode 100644 + ) { + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-has-and-matches-style"); ++ const {mark, end} = profile("hide-if-has-and-matches-style"); + const hiddenMap = new WeakSet$5(); + const logMap = debug() && new WeakSet$5(); + if (searchSelector == null) @@ -7294,6 +7816,7 @@ new file mode 100644 + const searchStyleRegExp = searchStyle ? toRegExp(searchStyle) : null; + const mainLogic = () => { + const callback = () => { ++ mark(); + if ((windowWidthMin && window.innerWidth < windowWidthMin) || + (windowWidthMax && window.innerWidth > windowWidthMax) + ) @@ -7325,7 +7848,7 @@ new file mode 100644 + "In this element the searchStyle matched" + + "but style didn't:\n", + closest, -+ getComputedStyle$2(closest), ++ getComputedStyle$1(closest), + ...arguments); + logMap.add(closest); + } @@ -7336,11 +7859,12 @@ new file mode 100644 + debugLog("info", + "In this element the searchStyle didn't match:\n", + element, -+ getComputedStyle$2(element), ++ getComputedStyle$1(element), + ...arguments); + logMap.add(element); + } + } ++ end(); + }; + + const mo = new MutationObserver$6(callback); @@ -7354,9 +7878,10 @@ new file mode 100644 + waitUntilEvent(debugLog, mainLogic, waitUntil); + } + -+ let {getComputedStyle: getComputedStyle$1, MutationObserver: MutationObserver$5, WeakSet: WeakSet$4} = $(window); ++ let {getComputedStyle, MutationObserver: MutationObserver$5, WeakSet: WeakSet$4} = $(window); + + function hideIfLabelledBy(search, selector, searchSelector = null) { ++ const {mark, end} = profile("hide-if-labelled-by"); + let sameSelector = searchSelector == null; + + let searchRegExp = toRegExp(search); @@ -7364,12 +7889,13 @@ new file mode 100644 + let matched = new WeakSet$4(); + + let callback = () => { ++ mark(); + for (const {element, rootParents} of $$(selector, true)) { + let closest = sameSelector ? + element : + $closest($(element), searchSelector, rootParents); + if (!closest || -+ !isVisible(element, getComputedStyle$1(element), closest)) ++ !isVisible(element, getComputedStyle(element), closest)) + continue; + + let attr = $(element).getAttribute("aria-labelledby"); @@ -7405,6 +7931,7 @@ new file mode 100644 + fallback(); + } + } ++ end(); + }; + + let mo = new MutationObserver$5(callback); @@ -7416,39 +7943,23 @@ new file mode 100644 + callback(); + } + -+ $(window); -+ -+ const noopProfile = { -+ mark() {}, -+ end() {}, -+ toString() { -+ return "{mark(){},end(){}}"; -+ } -+ }; -+ -+ function profile(id, rate = 10) { -+ return noopProfile; -+ } -+ -+ let {MutationObserver: MutationObserver$4, WeakSet: WeakSet$3} = $(window); ++ let {MutationObserver: MutationObserver$4, WeakSet: WeakSet$3} = $(window); + + const {ELEMENT_NODE: ELEMENT_NODE$2} = Node; + -+ function hideIfMatchesXPath(query, scopeQuery) { -+ const {mark, end} = profile(); ++ function hideIfMatchesXPath(query, scopeQuery, waitUntil) { ++ const {mark, end} = profile("hide-if-matches-xpath"); + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-matches-xpath"); + -+ const startHidingMutationObserver = scopeNode => { -+ const queryAndApply = initQueryAndApply(`xpath(${query})`); -+ const seenMap = new WeakSet$3(); -+ const callback = () => { -+ mark(); -+ queryAndApply(node => { -+ if (seenMap.has(node)) -+ return false; ++ const mainLogic = () => { ++ const startHidingMutationObserver = scopeNode => { ++ const queryAndApply = initQueryAndApply(`xpath(${query})`); ++ const seenMap = new WeakSet$3(); ++ const hideNode = node => { + seenMap.add(node); + win(); ++ + if ($(node).nodeType === ELEMENT_NODE$2) + hideElement(node); + else @@ -7458,43 +7969,70 @@ new file mode 100644 + node, + "\nFILTER: hide-if-matches-xpath", + formattedArguments); -+ }); -+ end(); -+ }; -+ const mo = new MutationObserver$4(callback); -+ const win = raceWinner( -+ "hide-if-matches-xpath", -+ () => mo.disconnect() -+ ); -+ mo.observe( -+ scopeNode, {characterData: true, childList: true, subtree: true}); -+ callback(); -+ }; ++ }; + -+ if (scopeQuery) { ++ const callback = () => { ++ mark(); ++ queryAndApply(node => { ++ if (seenMap.has(node)) ++ return false; + -+ let count = 0; -+ let scopeMutationObserver; -+ const scopeQueryAndApply = initQueryAndApply(`xpath(${scopeQuery})`); -+ const findMutationScopeNodes = () => { -+ scopeQueryAndApply(scopeNode => { ++ if (scopeQuery) { ++ const scopeQueryAndApply = initQueryAndApply(`xpath(${scopeQuery})`); ++ scopeQueryAndApply(matchingScopeNode => { ++ if (matchingScopeNode.contains(node)) { + -+ startHidingMutationObserver(scopeNode); -+ count++; -+ }); -+ if (count > 0) -+ scopeMutationObserver.disconnect(); ++ hideNode(node); ++ } ++ else { ++ ++ return false; ++ } ++ }); ++ } ++ else { ++ hideNode(node); ++ } ++ }); ++ end(); ++ }; ++ const mo = new MutationObserver$4(callback); ++ const win = raceWinner( ++ "hide-if-matches-xpath", ++ () => mo.disconnect() ++ ); ++ mo.observe( ++ scopeNode, {characterData: true, childList: true, subtree: true}); ++ callback(); + }; -+ scopeMutationObserver = new MutationObserver$4(findMutationScopeNodes); -+ scopeMutationObserver.observe( -+ document, {characterData: true, childList: true, subtree: true} -+ ); -+ findMutationScopeNodes(); -+ } -+ else { + -+ startHidingMutationObserver(document); -+ } ++ if (scopeQuery) { ++ ++ let count = 0; ++ let scopeMutationObserver; ++ const scopeQueryAndApply = initQueryAndApply(`xpath(${scopeQuery})`); ++ const findMutationScopeNodes = () => { ++ scopeQueryAndApply(scopeNode => { ++ ++ startHidingMutationObserver(scopeNode); ++ count++; ++ }); ++ if (count > 0) ++ scopeMutationObserver.disconnect(); ++ }; ++ scopeMutationObserver = new MutationObserver$4(findMutationScopeNodes); ++ scopeMutationObserver.observe( ++ document, {characterData: true, childList: true, subtree: true} ++ ); ++ findMutationScopeNodes(); ++ } ++ else { ++ ++ startHidingMutationObserver(document); ++ } ++ }; ++ ++ waitUntilEvent(debugLog, mainLogic, waitUntil); + } + + let {MutationObserver: MutationObserver$3, WeakSet: WeakSet$2} = $(window); @@ -7503,7 +8041,7 @@ new file mode 100644 + + function hideIfMatchesComputedXPath(query, searchQuery, searchRegex, + waitUntil) { -+ const {mark, end} = profile(); ++ const {mark, end} = profile("hide-if-matches-computed-xpath"); + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("hide-if-matches-computed-xpath"); + @@ -7592,7 +8130,7 @@ new file mode 100644 + + let { + parseInt: parseInt$1, -+ setTimeout: setTimeout$2, ++ setTimeout: setTimeout$1, + Error: Error$1, + MouseEvent: MouseEvent$1, + MutationObserver: MutationObserver$2, @@ -7609,9 +8147,11 @@ new file mode 100644 + function simulateMouseEvent(...selectors) { + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("simulate-mouse-event"); ++ const {mark, end} = profile("simulate-mouse-event"); + const MAX_ARGS = 7; + if (selectors.length < 1) + throw new Error$1("[simulate-mouse-event snippet]: No selector provided."); ++ + if (selectors.length > MAX_ARGS) { + + selectors = selectors.slice(0, MAX_ARGS); @@ -7724,6 +8264,7 @@ new file mode 100644 + findNodesAndDispatchEvents(); + + function findNodesAndDispatchEvents() { ++ mark(); + + if (!allFound) + allFound = checkIfAllSelectorsFound(); @@ -7741,7 +8282,7 @@ new file mode 100644 + }, delayInMiliseconds); + } + else { -+ setTimeout$2(() => { ++ setTimeout$1(() => { + triggerEvent(node, parsedRule.event, parsedRule.delay); + }, delayInMiliseconds); + } @@ -7750,10 +8291,11 @@ new file mode 100644 + } + } + } ++ end(); + } + } + -+ let {isNaN, MutationObserver: MutationObserver$1, parseInt, parseFloat, setTimeout: setTimeout$1} = $(window); ++ let {isNaN, MutationObserver: MutationObserver$1, parseInt, parseFloat, setTimeout} = $(window); + + function skipVideo(playerSelector, xpathCondition, ...attributes) { + const formattedArguments = formatArguments(arguments); @@ -7805,10 +8347,12 @@ new file mode 100644 + const muteVideo = !(muteVideoStr === "false"); + + const debugLog = getDebugger("skip-video"); ++ const {mark, end} = profile("skip-video"); + const queryAndApply = initQueryAndApply(`xpath(${xpathCondition})`); + let skippedOnce = false; + + const mainLogic = () => { ++ mark(); + const seenMap = new WeakSet(); + const callback = (retryCounter = 0) => { + if (skippedOnce && runOnceFlag) { @@ -7823,45 +8367,49 @@ new file mode 100644 + debugLog("info", "Matched:", node, " for selector: ", xpathCondition); + debugLog("info", "Running video skipping logic."); + } -+ const video = $$(playerSelector)[0]; -+ while (isNaN(video.duration) && retryCounter < maxAttemptsNum) { -+ setTimeout$1(() => { ++ const videos = $$(playerSelector); ++ let foundValidVideo = false; ++ for (const video of videos) { ++ if (!video || isNaN(video.duration) || isNaN(video.currentTime)) ++ continue; ++ foundValidVideo = true; ++ const videoNearEnd = (video.duration - video.currentTime) < 0.5; ++ if ((video.duration > 0) && (video.currentTime < video.duration) && ++ !(stopOnVideoEndFlag && videoNearEnd)) { ++ if (muteVideo) { ++ video.muted = true; ++ if (!nodeAlreadySeen) ++ debugLog("success", "Muted video..."); ++ } ++ if (startFrom <= video.currentTime * 1000) { ++ ++ skipToNum <= 0 ? ++ video.currentTime = video.duration + skipToNum : ++ video.currentTime += skipToNum; ++ if (lastSkippedVideoDuration !== video.duration) { ++ debugLog("success", ++ "Skipped video, currentTime: ", ++ video.currentTime, ++ "s.", ++ "\nFILTER: skip-video", ++ formattedArguments); ++ seenMap.add(node); ++ lastSkippedVideoDuration = video.duration; ++ } ++ video.paused && video.play(); ++ skippedOnce = true; ++ win(); ++ } ++ } ++ } ++ if (!foundValidVideo && retryCounter < maxAttemptsNum) { ++ setTimeout(() => { + const attempt = retryCounter + 1; + debugLog("info", + "Running video skipping logic. Attempt: ", + attempt); + callback(attempt); + }, retryMsNum); -+ return; -+ } -+ const videoNearEnd = (video.duration - video.currentTime) < 0.5; -+ if (!isNaN(video.duration) && (video.duration > 0) && -+ (video.currentTime < video.duration) && -+ !(stopOnVideoEndFlag && videoNearEnd)) { -+ if (muteVideo) { -+ video.muted = true; -+ if (!nodeAlreadySeen) -+ debugLog("success", "Muted video..."); -+ } -+ if (startFrom <= video.currentTime * 1000) { -+ -+ skipToNum <= 0 ? -+ video.currentTime = video.duration + skipToNum : -+ video.currentTime += skipToNum; -+ if (lastSkippedVideoDuration !== video.duration) { -+ debugLog("success", -+ "Skipped video, currentTime: ", -+ video.currentTime, -+ "s.", -+ "\nFILTER: skip-video", -+ formattedArguments); -+ seenMap.add(node); -+ lastSkippedVideoDuration = video.duration; -+ } -+ video.paused && video.play(); -+ skippedOnce = true; -+ win(); -+ } + } + }); + }; @@ -7873,6 +8421,7 @@ new file mode 100644 + mo.observe( + document, {characterData: true, childList: true, subtree: true}); + callback(); ++ end(); + }; + + waitUntilEvent(debugLog, mainLogic, waitUntil); @@ -7882,6 +8431,7 @@ new file mode 100644 + log, + race, + "debug": setDebug, ++ "profile": setProfile, + "hide-if-matches-xpath": hideIfMatchesXPath, + "hide-if-matches-computed-xpath": hideIfMatchesComputedXPath, + "hide-if-contains": hideIfContains, @@ -7900,7 +8450,7 @@ new file mode 100644 + const {ELEMENT_NODE} = Node; + + function hideIfMatchesXPath3(query, scopeQuery) { -+ let {mark, end} = profile(); ++ let {mark, end} = profile("hide-if-matches-xpath3"); + + const namespaceResolver = prefix => { + switch (prefix) { @@ -7979,10 +8529,7 @@ new file mode 100644 + } + } + -+ const DEFAULT_GRAPH_CUTOFF=500;async function domToGraph(e,t,r=false){return new Promise(((o,i)=>{if(!e||!t)return i();let l=e.config;let n=l.cutoff||e.topology.graphml.nodes||DEFAULT_GRAPH_CUTOFF;l=l.filter((e=>e.include));for(let e of l)e.groupName=Object.keys(e)[2];let s=(e,t,r,o)=>{if(r==="attributes"&&typeof e.attributes[o]!=="undefined")t.attributes[o]=e.attributes[o].value;else if(r==="style"&&e.style[o])t.attributes.style[o]=e.style[o];else if(r==="css")t.cssSelectors=getComputedStyle(e).cssText||"";};let a=e=>{if(r&&!e.clientWidth&&!e.clientHeight)return;n-=1;if(n<0)return;let t={tag:e.tagName,width:e.clientWidth,height:e.clientHeight,attributes:{style:{}},children:[]};for(let r of l){for(let o of r[r.groupName].features){for(let[i,l]of Object.entries(o)){if("names"in l){for(let o of l.names){for(let i of o.split("^"))s(e,t,r.groupName,i);}}else {s(e,t,r.groupName,i);}}}}if(e.children){for(let r of e.children){let e=a(r);if(e)t.children.push(e);}}return t};let f=a(t);o(f);}))}function parseArgs(e){if(!e||!Array.isArray(e)||!e.length)return {};let t=[];let r={debug:false,frameonly:false,failsafe:false,denoise:false,silent:false,model:"",selector:"",subselector:""};for(let o of e){if(o&&o in r)r[o]=true;else t.push(o);}if(t.length<2)return {};r.model=t[0];t.splice(0,1);if(t.length>2||t.some((e=>e&&e.startsWith('"'))))t=t.join(" ").split('"').map((e=>e.trim())).filter((e=>e));r.selector=t[0]||"";r.subselector=t[1]||"";return r}function resolveSelectors(e,t){let r=[document];e=$(e).split("..");let o=[];$(e).forEach(((e,t)=>{if(t){r=$(r).reduce(((e,t)=>t&&$(t).parentElement?e.concat($(t).parentElement):e),$([]));}r=$(r).reduce(((t,r)=>{if(!e){t.push(r);}else {try{t=t.concat(...$(r).querySelectorAll(e));}catch(e){}}return t}),$([]));}));for(let e of r){let r=[e];if(t){try{r=$(e).querySelectorAll(t);}catch(e){}}o.push([e,r]);}return o}let{WeakSet:WeakSet$,MutationObserver:MutationObserver$}=(typeof $!=="undefined"?$:e=>e)(window);class Observer{constructor(){this.digestedElements=new WeakSet$;this.selector="";this.subselector="";}observe(e,t,r){this.selector=e;this.subselector=t;this.callback=r;this.elementObserver=new MutationObserver$(this.digest.bind(this));this.elementObserver.observe(document,{childList:true,subtree:true});this.digest();}stop(){if(this.elementObserver)this.elementObserver.disconnect();}digest(){let e=resolveSelectors(this.selector,this.subselector).filter((([e])=>!this.digestedElements.has(e)));this.callback(e);for(let[t]of e)this.digestedElements.add(t);}}let mode=false;function print$1(e,t=false,...r){if(mode){console.log("%cMLDBG ♥ %c| %s%c |","color:cyan",t?"color:red":"color:inherit",e,"color:inherit",...r);}}function toggle(e){mode=!!e;}let data={nodeCount:0,organicCount:0,adCount:0,aaCount:0,times:[]};function set(e=false,t=false,r){if(mode){if(!data.nodeCount){$(document).head.insertAdjacentHTML("beforeend",`\n \n `);}}data.nodeCount++;if(t)data.aaCount++;else if(e)data.adCount++;else data.organicCount++;if(r)data.times.push(r);}function print(){if(mode){$(document).body.style.setProperty("--dbg-ad",`"${data.adCount}"`);$(document).body.style.setProperty("--dbg-nad",`"${data.organicCount}"`);$(document).body.style.setProperty("--dbg-aa",`"${data.aaCount}"`);$(document).body.style.setProperty("--dbg-t",`"${data.times.reduce(((e,t,r)=>!r||r%3?e+=t+"ms ":e+="\\A\\9\\9 "+t+"ms "),"")}"`);}}const MESSAGE_PREFIX="ML:";const MESSAGE_PREPARE_SUFFIX="prepare";const MESSAGE_INFERENCE_SUFFIX="inference";const errors={UNKNOWN_REQUEST:1,MISSING_REQUEST_DATA:2,UNKNOWN_MODEL:3,MISSING_INFERENCE_DATA:4,INFERENCE_FAILED:5,MODEL_INSTANTIATION_FAILED:6,MISSING_ENVIRONMENTAL_SUPPORT:7};const IN_FRAME=window.self!==window.top;const SERVICE_WORKER_TIMEOUT=1e4;let{Map:Map$}=(typeof $!=="undefined"?$:e=>e)(window);let modelConfigs=new Map$;let globallyAllowlisted=false;function hideIfClassifies(...e){let{debug:t,frameonly:r,failsafe:o,denoise:i,silent:l,model:n,selector:s,subselector:a}=parseArgs(e||[]);toggle(t);if(typeof chrome==="undefined"||!chrome.runtime||!chrome.runtime.sendMessage)return print$1("environmental support",false);if(!n||!s)return print$1("Invalid filter",true);if(r&&!IN_FRAME)return;if(!IN_FRAME)print$1(`Filter hit for ${n}: ${s} ${a}`);let f=new Observer;let d=raceWinner("hide-if-graph-matches",(()=>f.stop()));let c=()=>{if(modelConfigs.has(n))return modelConfigs.get(n);print$1(`Preparing worker with model ${n}`);let e=new Promise(((e,t)=>{let r=setTimeout((()=>{t(`Worker preparation with ${n} failed: service worker timeout`);}),SERVICE_WORKER_TIMEOUT);p({type:MESSAGE_PREFIX+MESSAGE_PREPARE_SUFFIX,debug:mode,model:n},(o=>{clearTimeout(r);if(o&&"config"in o){print$1(`Received config for ${n}`,false,"config:",o.config);o.config.cutoff=o.cutoff;e(o.config);}else {t(`Worker preparation with ${n} failed`);}}));}));e.catch((e=>{}));modelConfigs.set(n,e);return e};let u=(e,t)=>{if(o&&data.nodeCount>=10&&data.adCount/data.nodeCount>=1){print$1("Ad to non-ad ratio is 100%/0%. Stopping inference.",true);return f.stop()}print$1(`Requesting inference with ${n}`,false,"graph:",e);p({type:MESSAGE_PREFIX+MESSAGE_INFERENCE_SUFFIX,debug:mode,model:n,graph:e},(r=>{if(r&&"prediction"in r&&typeof r.prediction==="boolean"){print$1(`Received ${r.prediction} inference results with ${n}`,false,"graph:",e);if(r.allowlisted&&!globallyAllowlisted)globallyAllowlisted=true;set(r.prediction,r.allowlisted,r.digestTime-r.startTime);if(globallyAllowlisted&&!mode)return f.stop();if(r.prediction&&!mode&&!l){d();hideElement(t);}else if(mode){if(r.allowlisted)$(t).style.border="3px solid #00ffff";else if(r.prediction)$(t).style.border="3px solid #ff0000";else if(!r.prediction)$(t).style.border="3px solid #00ff00";print();}}else {print$1(`Inference with ${n} failed`,true,"graph:",e,"response:",r);if("error"in r&&(r.error===errors.INFERENCE_FAILED||r.error===errors.MISSING_ENVIRONMENTAL_SUPPORT))f.stop();}}));};let p=(e,t)=>{if(!globallyAllowlisted)chrome.runtime.sendMessage(e,t);else if(mode)t({prediction:false,allowlisted:true});};let h=e=>{if((!e||!e.length)&&IN_FRAME)return c();c().then((t=>{if(!t)return Promise.reject(`Config file for ${n} corrupted`);for(let[r,o]of e){for(let e of o){print$1(`Preparing inference with ${n}`,false,"target:",r);domToGraph({config:t},e,i).then((r=>i&&!r?domToGraph({config:t},e,false):r)).then((e=>u(e,r))).catch((e=>print$1(`domToGraph failed with error "${e}"`,true,"target:",r)));}}})).catch((e=>{print$1(e,true);f.stop();}));};f.observe(s,a,h);} -+ + snippets["hide-if-matches-xpath3"] = hideIfMatchesXPath3; -+ snippets["hide-if-classifies"] = hideIfClassifies; + let context; + for (const [name, ...args] of filters) { + if (snippets.hasOwnProperty(name)) { @@ -8097,7 +8644,7 @@ new file mode 100644 + + const invokes = bound(globalThis); + const classes = isExtensionContext$2 ? globalThis : secure(globalThis); -+ const {Map: Map$a, RegExp: RegExp$3, Set: Set$3, WeakMap: WeakMap$5, WeakSet: WeakSet$4} = classes; ++ const {Map: Map$b, RegExp: RegExp$3, Set: Set$3, WeakMap: WeakMap$5, WeakSet: WeakSet$4} = classes; + + const augment = (source, target, method = null) => { + const known = ownKeys(target); @@ -8145,7 +8692,7 @@ new file mode 100644 + + const startsCapitalized = new RegExp$3("^[A-Z]"); + -+ var env = new Proxy(new Map$a([ ++ var env = new Proxy(new Map$b([ + + ["chrome", ( + isExtensionContext$2 && ( @@ -8158,9 +8705,8 @@ new file mode 100644 + + ["console", copyIfExtension(console)], + ["document", globalThis.document], -+ ["performance", copyIfExtension(performance)], + ["JSON", copyIfExtension(JSON)], -+ ["Map", Map$a], ++ ["Map", Map$b], + ["Math", copyIfExtension(Math)], + ["Number", isExtensionContext$2 ? Number : primitive("Number")], + ["RegExp", RegExp$3], @@ -8217,7 +8763,7 @@ new file mode 100644 + }; + } + -+ const {Map: Map$9, WeakMap: WeakMap$4, WeakSet: WeakSet$3, setTimeout} = env; ++ const {Map: Map$a, WeakMap: WeakMap$4, WeakSet: WeakSet$3, setTimeout} = env; + + let cleanup = true; + let cleanUpCallback = map => { @@ -8229,7 +8775,7 @@ new file mode 100644 + WeakMap: WeakMap$4, + WeakSet: WeakSet$3, + -+ WeakValue: class extends Map$9 { ++ WeakValue: class extends Map$a { + set(key, value) { + if (cleanup) { + cleanup = !cleanup; @@ -8242,9 +8788,9 @@ new file mode 100644 + + const {concat, includes, join, reduce, unshift} = caller([]); + -+ const {Map: Map$8, WeakMap: WeakMap$3} = secure(globalThis); ++ const {Map: Map$9, WeakMap: WeakMap$3} = secure(globalThis); + -+ const map = new Map$8; ++ const map = new Map$9; + const descriptors = target => { + const chain = []; + let current = target; @@ -8293,16 +8839,16 @@ new file mode 100644 + + const { + isExtensionContext: isExtensionContext$1, -+ Array: Array$4, ++ Array: Array$6, + Number: Number$1, + String: String$1, -+ Object: Object$b ++ Object: Object$c + } = env; + -+ const {isArray} = Array$4; -+ const {getOwnPropertyDescriptor: getOwnPropertyDescriptor$1, setPrototypeOf: setPrototypeOf$1} = Object$b; ++ const {isArray} = Array$6; ++ const {getOwnPropertyDescriptor: getOwnPropertyDescriptor$1, setPrototypeOf: setPrototypeOf$1} = Object$c; + -+ const {toString: toString$1} = Object$b.prototype; ++ const {toString: toString$1} = Object$c.prototype; + const {slice} = String$1.prototype; + const getBrand = value => call(slice, call(toString$1, value), 8, -1); + @@ -8332,7 +8878,7 @@ new file mode 100644 + return chained[hint](value); + + if (isArray(value)) -+ return setPrototypeOf$1(value, Array$4.prototype); ++ return setPrototypeOf$1(value, Array$6.prototype); + + const brand = getBrand(value); + if (brand in chained) @@ -8396,6 +8942,57 @@ new file mode 100644 + + const accessor = target => new $$1(target, handler); + ++ let {Math: Math$2, setInterval: setInterval$1, performance} = $(window); ++ ++ const noopProfile = { ++ mark() {}, ++ end() {}, ++ toString() { ++ return "{mark(){},end(){}}"; ++ } ++ }; ++ ++ let inactive = true; ++ ++ function setProfile() { ++ inactive = false; ++ } ++ ++ function profile(id, rate = 10) { ++ if (inactive) ++ return noopProfile; ++ function processSamples() { ++ let samples = $([]); ++ ++ for (let {name, duration} of performance.getEntriesByType("measure")) ++ samples.push({name, duration}); ++ ++ if (samples.length) ++ performance.clearMeasures(); ++ } ++ ++ if (!profile[id]) { ++ profile[id] = setInterval$1(processSamples, ++ Math$2.round(60000 / Math$2.min(60, rate))); ++ } ++ ++ return { ++ mark() { ++ performance.mark(id); ++ }, ++ end(clear = false) { ++ const measureObj = performance.measure(id, id); ++ console.log("PROFILER:", measureObj); ++ performance.clearMarks(id); ++ if (clear) { ++ clearInterval(profile[id]); ++ delete profile[id]; ++ processSamples(); ++ } ++ } ++ }; ++ } ++ + let debugging = false; + + function debug() { @@ -8411,6 +9008,7 @@ new file mode 100644 + const noop = () => {}; + + function log(...args) { ++ let {mark, end} = profile("log"); + if (debug()) { + const logArgs = ["%c DEBUG", "font-weight: bold;"]; + @@ -8441,14 +9039,16 @@ new file mode 100644 + + $(args).unshift(...logArgs); + } ++ mark(); + console$4.log(...args); ++ end(); + } + + function getDebugger(name) { + return bind(debug() ? log : noop, null, name); + } + -+ let {Array: Array$3, Math: Math$1, RegExp: RegExp$2} = $(window); ++ let {Array: Array$5, Math: Math$1, RegExp: RegExp$2} = $(window); + + function regexEscape(string) { + return $(string).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); @@ -8478,16 +9078,16 @@ new file mode 100644 + } + + function formatArguments(args) { -+ return $(Array$3.from(args)).map(arg => `'${arg}'`).join(" "); ++ return $(Array$5.from(args)).map(arg => `'${arg}'`).join(" "); + } + + let { + parseFloat, + variables: variables$2, -+ Array: Array$2, -+ Error: Error$7, -+ Map: Map$7, -+ Object: Object$a, ++ Array: Array$4, ++ Error: Error$8, ++ Map: Map$8, ++ Object: Object$b, + ReferenceError: ReferenceError$2, + Set: Set$2, + WeakMap: WeakMap$2 @@ -8506,11 +9106,11 @@ new file mode 100644 + let dotIndex = $property.indexOf("."); + if (dotIndex == -1) { + -+ let currentDescriptor = Object$a.getOwnPropertyDescriptor(object, property); ++ let currentDescriptor = Object$b.getOwnPropertyDescriptor(object, property); + if (currentDescriptor && !currentDescriptor.configurable) + return; + -+ let newDescriptor = Object$a.assign({}, descriptor, { ++ let newDescriptor = Object$b.assign({}, descriptor, { + configurable: setConfigurable + }); + @@ -8519,7 +9119,7 @@ new file mode 100644 + newDescriptor.get = () => propertyValue; + } + -+ Object$a.defineProperty(object, property, newDescriptor); ++ Object$b.defineProperty(object, property, newDescriptor); + return; + } + @@ -8529,7 +9129,7 @@ new file mode 100644 + if (value && (typeof value == "object" || typeof value == "function")) + wrapPropertyAccess(value, property, descriptor); + -+ let currentDescriptor = Object$a.getOwnPropertyDescriptor(object, name); ++ let currentDescriptor = Object$b.getOwnPropertyDescriptor(object, name); + if (currentDescriptor && !currentDescriptor.configurable) + return; + @@ -8537,7 +9137,7 @@ new file mode 100644 + propertyAccessors = new WeakMap$2(); + + if (!propertyAccessors.has(object)) -+ propertyAccessors.set(object, new Map$7()); ++ propertyAccessors.set(object, new Map$8()); + + let properties = propertyAccessors.get(object); + if (properties.has(name)) { @@ -8545,9 +9145,9 @@ new file mode 100644 + return; + } + -+ let toBeWrapped = new Map$7([[property, descriptor]]); ++ let toBeWrapped = new Map$8([[property, descriptor]]); + properties.set(name, toBeWrapped); -+ Object$a.defineProperty(object, name, { ++ Object$b.defineProperty(object, name, { + get: () => value, + set(newValue) { + value = newValue; @@ -8632,7 +9232,7 @@ new file mode 100644 + + const formattedPropertiesToLog = formatArguments(properties); + -+ for (let frame of Array$2.from(window.frames)) { ++ for (let frame of Array$4.from(window.frames)) { + if (abortedIframes.has(frame)) { + for (let property of properties) { + if (abortRead) @@ -8661,7 +9261,7 @@ new file mode 100644 + } + + function queryAndProxyIframe() { -+ for (let frame of Array$2.from(window.frames)) { ++ for (let frame of Array$4.from(window.frames)) { + + if (!abortedIframes.has(frame)) { + abortedIframes.set(frame, { @@ -8672,7 +9272,7 @@ new file mode 100644 + + let readProps = abortedIframes.get(frame).read; + if (readProps.size > 0) { -+ let props = Array$2.from(readProps); ++ let props = Array$4.from(readProps); + readProps.clear(); + for (let {property, formattedProperties} of props) { + abortOnRead("abort-on-iframe-property-read", @@ -8684,7 +9284,7 @@ new file mode 100644 + + let writeProps = abortedIframes.get(frame).write; + if (writeProps.size > 0) { -+ let props = Array$2.from(writeProps); ++ let props = Array$4.from(writeProps); + writeProps.clear(); + for (let {property, formattedProperties} of props) { + abortOnWrite("abort-on-iframe-property-write", @@ -8733,7 +9333,7 @@ new file mode 100644 + } + + function getInnerHTMLDescriptor(target, property) { -+ let desc = Object$a.getOwnPropertyDescriptor(target, property); ++ let desc = Object$b.getOwnPropertyDescriptor(target, property); + let {set: prevSetter} = desc || {}; + return { + set(val) { @@ -8803,17 +9403,18 @@ new file mode 100644 + if (decimals.test(value)) + return parseFloat(value); + -+ throw new Error$7("[override-property-read snippet]: " + ++ throw new Error$8("[override-property-read snippet]: " + + `Value "${value}" is not valid.`); + } + } + -+ let {HTMLScriptElement: HTMLScriptElement$1, Object: Object$9, ReferenceError: ReferenceError$1} = $(window); -+ let Script = Object$9.getPrototypeOf(HTMLScriptElement$1); ++ let {HTMLScriptElement: HTMLScriptElement$1, Object: Object$a, ReferenceError: ReferenceError$1} = $(window); ++ let Script = Object$a.getPrototypeOf(HTMLScriptElement$1); + + function abortCurrentInlineScript(api, search = null) { + const formattedArguments = formatArguments(arguments); + const debugLog = getDebugger("abort-current-inline-script"); ++ const {mark, end} = profile("abort-current-inline-script"); + const re = search ? toRegExp(search) : null; + + const rid = randomId(); @@ -8833,7 +9434,7 @@ new file mode 100644 + } + + const {get: prevGetter, set: prevSetter} = -+ Object$9.getOwnPropertyDescriptor(object, name) || {}; ++ Object$a.getOwnPropertyDescriptor(object, name) || {}; + + let currentValue = object[name]; + if (typeof currentValue === "undefined") @@ -8874,40 +9475,133 @@ new file mode 100644 + } + }; + -+ wrapPropertyAccess(object, name, descriptor, formattedArguments); ++ mark(); ++ wrapPropertyAccess(object, ++ name, ++ descriptor); ++ end(); + + overrideOnError(rid); + } + + function abortOnIframePropertyRead(...properties) { ++ const {mark, end} = profile("abort-on-iframe-property-read"); ++ mark(); + abortOnIframe(properties, true, false); ++ end(); + } + + function abortOnIframePropertyWrite(...properties) { ++ const {mark, end} = profile("abort-on-iframe-property-write"); ++ mark(); + abortOnIframe(properties, false, true); ++ end(); + } + + function abortOnPropertyRead(property, setConfigurable) { + const configurableFlag = !(setConfigurable === "false"); + const formattedArguments = formatArguments(arguments); ++ const {mark, end} = profile("abort-on-property-read"); ++ mark(); + abortOnRead("abort-on-property-read", + window, + property, + formattedArguments, + configurableFlag); ++ end(); + } + + function abortOnPropertyWrite(property, setConfigurable) { + const formattedArguments = formatArguments(arguments); ++ const {mark, end} = profile("abort-on-property-write"); + const configurableFlag = !(setConfigurable === "false"); ++ mark(); + abortOnWrite("abort-on-property-write", + window, + property, + formattedArguments, + configurableFlag); ++ end(); ++ } ++ ++ const {Error: Error$7, Object: Object$9, Array: Array$3, Map: Map$7} = $(window); ++ ++ let arrayValues = null; ++ ++ function arrayOverride(method, needle, returnValue = "false") { ++ if (!method) ++ throw new Error$7("[array-override snippet]: Missing method to override."); ++ ++ if (!needle) ++ throw new Error$7("[array-override snippet]: Missing needle."); ++ ++ if (!arrayValues) ++ arrayValues = new Map$7(); ++ ++ let debugLog = getDebugger("array-override"); ++ const {mark, end} = profile("array-override"); ++ const formattedArgsToLog = formatArguments(arguments); ++ ++ if (method === "push" && !arrayValues.has("push")) { ++ mark(); ++ const {push} = Array$3.prototype; ++ arrayValues.set("push", $([])); ++ ++ Object$9.defineProperty(window.Array.prototype, "push", { ++ value: proxy(push, function(val) { ++ ++ if (!(typeof val === "string" || typeof val === "number")) ++ return apply$2(push, this, arguments); ++ ++ const valStr = val.toString(); ++ const overrideVals = arrayValues.get("push"); ++ for (const {needleRegex} of overrideVals) { ++ if (valStr.match && valStr.match(needleRegex)) { ++ debugLog("success", `Array.push is ignored for needle: ${needleRegex}\nFILTER: array-override ${formattedArgsToLog}`); ++ return; ++ } ++ } ++ return apply$2(push, this, arguments); ++ }) ++ }); ++ debugLog("info", "Wrapped Array.prototype.push"); ++ end(); ++ } ++ ++ else if (method === "includes" && !arrayValues.has("includes")) { ++ mark(); ++ const {includes} = Array$3.prototype; ++ arrayValues.set("includes", $([])); ++ ++ Object$9.defineProperty(window.Array.prototype, "includes", { ++ value: proxy(includes, function(val) { ++ ++ if (!(typeof val === "string" || typeof val === "number")) ++ return apply$2(includes, this, arguments); ++ ++ const valStr = val.toString(); ++ const overrideVals = arrayValues.get("includes"); ++ for (const {needleRegex, retVal} of overrideVals) { ++ if (valStr.match && valStr.match(needleRegex)) { ++ debugLog("success", `Array.includes returned ${retVal} for ${needleRegex}\nFILTER: array-override ${formattedArgsToLog}`); ++ return retVal; ++ } ++ } ++ return apply$2(includes, this, arguments); ++ }) ++ }); ++ debugLog("info", "Wrapped Array.prototype.includes"); ++ end(); ++ } ++ ++ const needleRegex = toRegExp(needle); ++ const overrideVals = arrayValues.get(method); ++ const retVal = returnValue === "true"; ++ overrideVals.push({needleRegex, retVal}); ++ arrayValues.set(method, overrideVals); + } + -+ let {Error: Error$6, URL: URL$1} = $(window); ++ let {Error: Error$6, URL: URL$2} = $(window); + let {cookie: documentCookies} = accessor(document); + + function cookieRemover(cookie, autoRemoveCookie = false) { @@ -8916,6 +9610,7 @@ new file mode 100644 + + const formattedArguments = formatArguments(arguments); + let debugLog = getDebugger("cookie-remover"); ++ const {mark, end} = profile("cookie-remover"); + let re = toRegExp(cookie); + + if (!$(/^http|^about/).test(location.protocol)) { @@ -8930,12 +9625,13 @@ new file mode 100644 + + const mainLogic = () => { + debugLog("info", "Parsing cookies for matches"); ++ mark(); + for (const pair of $(getCookieMatches())) { + let $hostname = $(location.hostname); + + if (!$hostname && + $(location.ancestorOrigins) && $(location.ancestorOrigins[0])) -+ $hostname = new URL$1($(location.ancestorOrigins[0])).hostname; ++ $hostname = new URL$2($(location.ancestorOrigins[0])).hostname; + const name = $(pair).split("=")[0]; + const expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT"; + const path = "path=/"; @@ -8950,6 +9646,7 @@ new file mode 100644 + debugLog("success", `Set expiration date on ${name}`, "\nFILTER: cookie-remover", formattedArguments); + } + } ++ end(); + }; + + mainLogic(); @@ -8977,7 +9674,7 @@ new file mode 100644 + getComputedStyle, + isExtensionContext, + variables: variables$1, -+ Array: Array$1, ++ Array: Array$2, + MutationObserver: MutationObserver$3, + Object: Object$8, + XPathEvaluator, @@ -9221,7 +9918,7 @@ new file mode 100644 + return elements; + }; + } -+ return () => Array$1.from($$(selector)); ++ return () => Array$2.from($$(selector)); + } + + let {ELEMENT_NODE, TEXT_NODE, prototype: NodeProto} = Node; @@ -9770,6 +10467,7 @@ new file mode 100644 + function hideIfCanvasContains(search, selector = "canvas") { + const debugLog = getDebugger("hide-if-canvas-contains"); + const formattedArgsToLog = formatArguments(arguments); ++ const {mark, end} = profile("hide-if-canvas-contains"); + + if (!search) { + debugLog("error", "The parameter 'search' is required"); @@ -9777,6 +10475,7 @@ new file mode 100644 + } + + if (!canvasRules) { ++ mark(); + const CanvasProto = CanvasRenderingContext2D$1.prototype; + debugLog("info", "CanvasRenderingContext2D proxied"); + @@ -9801,7 +10500,6 @@ new file mode 100644 + } + } + } -+ + return apply$2(originalFunction, this, [text, ...args]); + }) + }); @@ -9821,6 +10519,7 @@ new file mode 100644 + }); + + mo.observe(document$1, {childList: true, subtree: true}); ++ end(); + } + + const searchRegex = toRegExp(search); @@ -9873,9 +10572,11 @@ new file mode 100644 + } + + const debugLog = getDebugger("hide-if-shadow-contains"); ++ const {mark, end} = profile("hide-if-shadow-contains"); + + if (!observer) { + observer = new MutationObserver(records => { ++ mark(); + let visited = new Set(); + for (let {target} of $(records)) { + @@ -9908,6 +10609,7 @@ new file mode 100644 + closest, + `\nFILTER: hide-if-shadow-contains ${formattedArgs}`); + } ++ end(); + } + } + } @@ -9932,7 +10634,7 @@ new file mode 100644 + } + } + -+ const {Error: Error$4, JSON: JSON$2, Map: Map$4, Response: Response$2, Object: Object$4} = $(window); ++ const {Array: Array$1, Error: Error$4, JSON: JSON$2, Map: Map$4, Object: Object$4, Response: Response$2} = $(window); + + let paths$1 = null; + @@ -9946,6 +10648,8 @@ new file mode 100644 + + if (!paths$1) { + let debugLog = getDebugger("json-override"); ++ const {mark, end} = profile("json-override"); ++ mark(); + + function overrideObject(obj, str) { + @@ -9957,16 +10661,78 @@ new file mode 100644 + return obj; + + for (let path of prune) { -+ let details = findOwner(obj, path); -+ if (typeof details != "undefined") { -+ debugLog("success", `Found ${path} replaced it with ${val}`, `\nFILTER: json-override ${formattedArgs}`); -+ details[0][details[1]] = overrideValue(val); -+ } ++ if (path.includes("{}") || path.includes("[]")) ++ overridePathWithPlaceholders(obj, path, val, formattedArgs); ++ else ++ overridePathSimple(obj, path, val, formattedArgs); + } + } + return obj; + } + ++ function overridePathWithPlaceholders(obj, path, newValue, formattedArgs) { ++ let pathParts = $(path).split("."); ++ let currentObj = obj; ++ ++ for (let i = 0; i < pathParts.length; i++) { ++ let part = pathParts[i]; ++ ++ if (part === "[]") { ++ ++ if (Array$1.isArray(currentObj)) { ++ debugLog("info", `Iterating over array at: ${part}`); ++ $(currentObj).forEach(item => { ++ if (item !== null && typeof item !== "undefined") { ++ overridePathWithPlaceholders(item, ++ pathParts.slice(i + 1).join("."), ++ newValue, ++ formattedArgs); ++ } ++ }); ++ } ++ return; ++ } ++ else if (part === "{}") { ++ ++ if (currentObj && typeof currentObj === "object") { ++ debugLog("info", `Iterating over object at: ${part}`); ++ Object$4.keys(currentObj).forEach(key => { ++ let nextItem = currentObj[key]; ++ if (nextItem !== null && typeof nextItem !== "undefined") { ++ overridePathWithPlaceholders(nextItem, ++ pathParts.slice(i + 1).join("."), ++ newValue, ++ formattedArgs); ++ } ++ }); ++ } ++ return; ++ } ++ else if (currentObj && typeof currentObj === "object" && ++ hasOwnProperty(currentObj, part)) { ++ ++ if (i === pathParts.length - 1) { ++ debugLog("success", `Found ${path}, replaced it with ${newValue}`, `\nFILTER: json-override ${formattedArgs}`); ++ currentObj[part] = overrideValue(newValue); ++ } ++ else { ++ currentObj = currentObj[part]; ++ } ++ } ++ else { ++ return; ++ } ++ } ++ } ++ ++ function overridePathSimple(obj, path, newValue, formattedArgs) { ++ let details = findOwner(obj, path); ++ if (typeof details != "undefined") { ++ debugLog("success", `Found ${path}, replaced it with ${newValue}`, `\nFILTER: json-override ${formattedArgs}`); ++ details[0][details[1]] = overrideValue(newValue); ++ } ++ } ++ + let {parse} = JSON$2; + paths$1 = new Map$4(); + @@ -9986,6 +10752,7 @@ new file mode 100644 + }) + }); + debugLog("info", "Wrapped Response.json for override"); ++ end(); + } + + const formattedArgsToLog = formatArguments(arguments); @@ -9999,20 +10766,30 @@ new file mode 100644 + }); + } + -+ let {Array, Error: Error$3, JSON: JSON$1, Map: Map$3, Object: Object$3, Response: Response$1} = $(window); ++ let {Array, Error: Error$3, JSON: JSON$1, Map: Map$3, Object: Object$3, Response: Response$1, URL: URL$1} = $(window); + + let paths = null; + -+ function jsonPrune(rawPrunePaths, rawNeedlePaths = "") { ++ function jsonPrune(rawPrunePaths, ++ rawNeedlePaths = "", ++ rawNeedleStack = "") { + if (!rawPrunePaths) + throw new Error$3("Missing paths to prune"); + + if (!paths) { + let debugLog = getDebugger("json-prune"); ++ const {mark, end} = profile("json-prune"); ++ mark(); + + function pruneObject(obj) { -+ for (let {prune, needle, formattedArgs} of paths.values()) { -+ if ($(needle).some(path => !findOwner(obj, path))) ++ for (let {prune, needle, stackNeedle, formattedArgs} of paths.values()) { ++ ++ if ($(needle).length > 0 && ++ $(needle).some(path => !findOwner(obj, path))) ++ return obj; ++ ++ if ($(stackNeedle) && ++ $(stackNeedle).length > 0 && !matchesStackTrace(stackNeedle)) + return obj; + + for (let path of prune) { @@ -10076,6 +10853,60 @@ new file mode 100644 + } + } + ++ function matchesStackTrace(stackNeedle) { ++ if (!stackNeedle) ++ return false; ++ ++ const token = randomId(); ++ const error = new Error$3(token); ++ ++ const locHref = new URL$1(self.location.href); ++ locHref.hash = ""; ++ ++ const lineRegex = /(.*?@)?(\S+)(:\d+):\d+\)?$/; ++ const lines = []; ++ for (let line of error.stack.split(/[\n\r]+/)) { ++ if ($(line).includes(token)) ++ continue; ++ ++ line = $(line).trim(); ++ const match = $(lineRegex).exec(line); ++ if (match === null) ++ continue; ++ ++ let url = match[2]; ++ if ($(url).startsWith("(")) ++ url = $(url).slice(1); ++ ++ if (url === locHref.href) ++ url = "inlineScript"; ++ else if ($(url).startsWith("")) ++ url = "injectedScript"; ++ ++ let functionName = match[1] ? ++ $(match[1]).slice(0, -1) : ++ $(line).slice(0, $(match).index).trim(); ++ ++ if ($(functionName).startsWith("at")) ++ functionName = $(functionName).slice(2).trim(); ++ ++ let linePosition = match[3]; ++ $(lines).push(" " + `${functionName} ${url}${linePosition}:1`.trim()); ++ } ++ ++ lines[0] = `stackDepth:${lines.length - 1}`; ++ const normalizedStack = $(lines).join("\n"); ++ ++ for (let needle of stackNeedle) { ++ const regex = toRegExp(needle); ++ if (regex.test(normalizedStack)) { ++ debugLog("info", `Found needle in stack trace: ${needle}`); ++ return true; ++ } ++ debugLog("info", `Needle ${needle} not found in stack trace: ${normalizedStack}`); ++ } ++ } ++ + let {parse} = JSON$1; + paths = new Map$3(); + @@ -10095,6 +10926,7 @@ new file mode 100644 + }) + }); + debugLog("info", "Wrapped Response.json for prune"); ++ end(); + } + + const formattedArgs = formatArguments(arguments); @@ -10102,7 +10934,8 @@ new file mode 100644 + paths.set(rawPrunePaths, { + formattedArgs, + prune: $(rawPrunePaths).split(/ +/), -+ needle: rawNeedlePaths.length ? $(rawNeedlePaths).split(/ +/) : [] ++ needle: rawNeedlePaths.length ? $(rawNeedlePaths).split(/ +/) : [], ++ stackNeedle: rawNeedleStack.length ? $(rawNeedleStack).split(/ +/) : [] + }); + } + @@ -10120,6 +10953,7 @@ new file mode 100644 + + const formattedArguments = formatArguments(arguments); + let debugLog = getDebugger("override-property-read"); ++ const {mark, end} = profile("override-property-read"); + + let cValue = overrideValue(value); + @@ -10130,10 +10964,13 @@ new file mode 100644 + + debugLog("info", `Overriding ${property}.`); + ++ const configurableFlag = !(setConfigurable === "false"); ++ mark(); + wrapPropertyAccess(window, + property, + {get: newGetter, set() {}}, -+ formattedArguments); ++ configurableFlag); ++ end(); + } + + let {Error: Error$1, Map: Map$2, Object: Object$2, console: console$1} = $(window); @@ -10152,9 +10989,11 @@ new file mode 100644 + events = new Map$2(); + + let debugLog = getDebugger("[prevent]"); ++ const {mark, end} = profile("prevent-listener"); + + Object$2.defineProperty(EventTargetProto, "addEventListener", { + value: proxy(addEventListener, function(type, listener) { ++ mark(); + for (let {evt, handlers, selectors} of events.values()) { + + if (!evt.test(type)) @@ -10221,6 +11060,7 @@ new file mode 100644 + return; + } + } ++ end(); + return apply$2(addEventListener, this, arguments); + }) + }); @@ -10301,6 +11141,7 @@ new file mode 100644 + function replaceFetchResponse(search, replacement = "", needle = null) { + const formattedArgsToLog = formatArguments(arguments); + const debugLog = getDebugger("replace-fetch-response"); ++ const {mark, end} = profile("replace-fetch-response"); + if (!search) { + debugLog("error", "The parameter 'search' is required"); + return; @@ -10308,6 +11149,7 @@ new file mode 100644 + + if (!fetchRules) { + const mainLogic = origResponse => { ++ mark(); + const clonedResponse = $(origResponse).clone(); + return clonedResponse.text().then(origText => { + let replacedText = $(origText); @@ -10319,7 +11161,7 @@ new file mode 100644 + if (needleRegex.test(replacedText)) { + if (debug()) { + console.groupCollapsed(`DEBUG [replace-fetch-response] success: '${thisNeedle}' found in fetch response`); -+ debugLog("success", `${replacedText}`); ++ debugLog("info", `${replacedText}`); + console.groupEnd(); + } + } @@ -10333,19 +11175,19 @@ new file mode 100644 + } + } + replacedText = replacedText.replace(thisSearch, thisReplacement); -+ if (debug() && replacedText !== origText) { ++ if (debug() && replacedText.toString() !== origText.toString()) { + console.groupCollapsed(`DEBUG [replace-fetch-response] success: '${thisSearch}' replaced with '${thisReplacement}' in fetch response`, -+ `\nFILTER: replace-xhr-response ${formattedArgs}` ++ `\nFILTER: replace-fetch-response ${formattedArgs}` + ); + debugLog("success", `${replacedText}`); + console.groupEnd(); + } + } + -+ if (replacedText === origText) ++ if (replacedText.toString() === origText.toString()) + return origResponse; + -+ const replacedResponse = new Response(replacedText, { ++ const replacedResponse = new Response(replacedText.toString(), { + status: origResponse.status, + statusText: origResponse.statusText, + headers: origResponse.headers @@ -10356,6 +11198,7 @@ new file mode 100644 + type: {value: origResponse.type}, + url: {value: origResponse.url} + }); ++ end(); + return replacedResponse; + }); + }; @@ -10379,6 +11222,7 @@ new file mode 100644 + function replaceXhrResponse(search, replacement = "", needle = null) { + const formattedArgsToLog = formatArguments(arguments); + const debugLog = getDebugger("replace-xhr-response"); ++ const {mark, end} = profile("replace-xhr-response"); + + if (!search) { + debugLog("error", "The parameter 'pattern' is required"); @@ -10397,11 +11241,16 @@ new file mode 100644 + xhrInFlightRequests.set(originalXhr, xhrData); + return super.open(method, url, ...args); + } ++ ++ send(...args) { ++ return super.send(...args); ++ } + get response() { + const innerResponse = super.response; + const xhrData = xhrInFlightRequests.get(this); + if (typeof xhrData === "undefined") + return innerResponse; ++ mark(); + + const responseLength = typeof innerResponse === "string" ? + innerResponse.length : void 0; @@ -10425,7 +11274,7 @@ new file mode 100644 + if (needleRegex.test(replacedText)) { + if (debug()) { + console.groupCollapsed(`DEBUG [replace-xhr-response] success: '${thisNeedle}' found in XHR response`); -+ debugLog("success", replacedText); ++ debugLog("info", replacedText); + console.groupEnd(); + } + } @@ -10440,106 +11289,1011 @@ new file mode 100644 + } + replacedText = + $(replacedText).replace(thisSearch, thisReplacement).toString(); -+ if (debug() && innerResponse !== replacedText) { ++ if (debug() && innerResponse.toString() !== replacedText.toString()) { + console.groupCollapsed(`DEBUG [replace-xhr-response] success: '${thisSearch}' replaced with '${thisReplacement}' in XHR response`, + `\nFILTER: replace-xhr-response ${formattedArgs}`); + debugLog("success", replacedText); + console.groupEnd(); + } + } -+ -+ return (xhrData.response = replacedText); ++ end(); ++ return (xhrData.response = replacedText.toString()); + } + get responseText() { + const response = this.response; + if (typeof response !== "string") + return super.responseText; + -+ return response; -+ } -+ }; -+ } ++ return response; ++ } ++ }; ++ } ++ ++ const regex = toRegExp(search); ++ ++ const globalisedRegEx = new RegExp(regex, "g"); ++ xhrRules.set(globalisedRegEx, ++ {replacement, needle, formattedArgs: formattedArgsToLog}); ++ } ++ ++ let {delete: deleteParam, has: hasParam} = caller(URLSearchParams.prototype); ++ ++ let parameters; ++ ++ function stripFetchQueryParameter(name, urlPattern = null) { ++ const formattedArgs = formatArguments(arguments); ++ const debugLog = getDebugger("strip-fetch-query-parameter"); ++ const {mark, end} = profile("strip-fetch-query-parameter"); ++ ++ const stripFunction = url => { ++ mark(); ++ for (let [key, value] of parameters.entries()) { ++ const {reg, args} = value; ++ if (!reg || reg.test(url)) { ++ if (hasParam(url.searchParams, key)) { ++ debugLog("success", `${key} has been stripped from url ${url}`, `\nFILTER: strip-fetch-query-parameter ${args}`); ++ deleteParam(url.searchParams, key); ++ } ++ } ++ } ++ end(); ++ }; ++ ++ if (!parameters) { ++ parameters = new Map(); ++ addPreFetchCallback(stripFunction); ++ } ++ ++ parameters.set(name, ++ {reg: urlPattern && toRegExp(urlPattern), ++ args: formattedArgs}); ++ } ++ ++ function trace(...args) { ++ ++ apply$2(log, null, args); ++ } ++ ++ const snippets = { ++ "abort-current-inline-script": abortCurrentInlineScript, ++ "abort-on-iframe-property-read": abortOnIframePropertyRead, ++ "abort-on-iframe-property-write": abortOnIframePropertyWrite, ++ "abort-on-property-read": abortOnPropertyRead, ++ "abort-on-property-write": abortOnPropertyWrite, ++ "array-override": arrayOverride, ++ "cookie-remover": cookieRemover, ++ "profile": setProfile, ++ "debug": setDebug, ++ "freeze-element": freezeElement, ++ "hide-if-canvas-contains": hideIfCanvasContains, ++ "hide-if-shadow-contains": hideIfShadowContains, ++ "json-override": jsonOverride, ++ "json-prune": jsonPrune, ++ "override-property-read": overridePropertyRead, ++ "prevent-listener": preventListener, ++ "replace-fetch-response": replaceFetchResponse, ++ "replace-xhr-response": replaceXhrResponse, ++ "strip-fetch-query-parameter": stripFetchQueryParameter, ++ "trace": trace ++ }; ++ let context; ++ for (const [name, ...args] of filters) { ++ if (snippets.hasOwnProperty(name)) { ++ try { context = snippets[name].apply(context, args); } ++ catch (error) { console.error(error); } ++ } ++ } ++ context = void 0; ++}; ++const graph = new Map([["abort-current-inline-script",null],["abort-on-iframe-property-read",null],["abort-on-iframe-property-write",null],["abort-on-property-read",null],["abort-on-property-write",null],["array-override",null],["cookie-remover",null],["profile",null],["debug",null],["freeze-element",null],["hide-if-canvas-contains",null],["hide-if-shadow-contains",null],["json-override",null],["json-prune",null],["override-property-read",null],["prevent-listener",null],["replace-fetch-response",null],["replace-xhr-response",null],["strip-fetch-query-parameter",null],["trace",null]]); ++callback.get = snippet => graph.get(snippet); ++callback.has = snippet => graph.has(snippet); ++ ++ if (t.every(([name]) => !callback.has(name))) return; ++ const isTrustedTypesSupported = typeof trustedTypes !== 'undefined'; ++ ++ let policy; ++ if (isTrustedTypesSupported) { ++ try { ++ const id = Math.floor(Math.random() * 2116316160 + 60466176).toString(36); ++ policy = trustedTypes.createPolicy(id, { ++ createScript: (code) => code, ++ createScriptURL: (url) => url ++ }); ++ } catch (_) {} ++ } ++ ++ const appendWithTrustedTypes = () => { ++ const scriptContent = policy.createScript("(" + callback + ")(..." + JSON.stringify([e, ...t]) + ")"); ++ const blob = new Blob([scriptContent], { type: 'application/javascript' }); ++ const url = URL.createObjectURL(blob); ++ ++ const script = document.createElement('script'); ++ script.async = false; ++ script.src = policy.createScriptURL(url); ++ ++ document.documentElement.appendChild(script); ++ ++ URL.revokeObjectURL(url); ++ }; ++ ++ const appendOriginal = () => { ++ URL.revokeObjectURL( ++ Object.assign( ++ document.documentElement.appendChild(document.createElement("script")), ++ {async: false, src: URL.createObjectURL(new Blob([ ++ "(" + callback + ")(..." + JSON.stringify([e, ...t]) + ")" ++ ]))} ++ ).src ++ ); ++ }; ++ ++ const evalScript = () => { ++ const scriptContent = "(" + callback + ")(..." + JSON.stringify([e, ...t]) + ")"; ++ ++ if (policy) { ++ const trustedScript = policy.createScript(scriptContent); ++ eval(trustedScript); ++ } else { ++ eval(scriptContent); ++ } ++ }; ++ ++ const appendScript = () => { ++ if (policy) { ++ appendWithTrustedTypes(); ++ } else { ++ appendOriginal(); ++ } ++ }; ++ ++ const useEval = !(t.every(([name]) => name !== "use-eval")); ++ const executeScript = useEval ? evalScript : appendScript; ++ ++ try { ++ executeScript(); } ++ catch (_) { ++ document.addEventListener("readystatechange", executeScript, {once:true}); ++ } ++} +diff --git a/components/adblock/core/sitekey_storage_impl.cc b/components/adblock/core/sitekey_storage_impl.cc +--- a/components/adblock/core/sitekey_storage_impl.cc ++++ b/components/adblock/core/sitekey_storage_impl.cc +@@ -32,16 +32,6 @@ namespace { + + SiteKey GetSitekeyHeader( + const scoped_refptr& 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 {}; + } + +@@ -55,6 +45,8 @@ void SitekeyStorageImpl::ProcessResponseHeaders( + const GURL& request_url, + const scoped_refptr& headers, + const std::string& user_agent) { ++ // remove Acceptable Ads site key processing ++ if ((true)) return; + if (user_agent.empty()) { + LOG(WARNING) << "[eyeo] No user agent info"; + return; +@@ -71,6 +63,7 @@ void SitekeyStorageImpl::ProcessResponseHeaders( + + absl::optional> + SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& urls) const { ++ if ((true)) return {}; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + for (const auto& url : urls) { + auto elem = url_to_sitekey_map_.find(url); +@@ -84,6 +77,8 @@ SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& urls) const { + void SitekeyStorageImpl::ProcessSiteKey(const GURL& request_url, + const SiteKey& site_key, + const std::string& user_agent) { ++ // remove Acceptable Ads site key processing ++ if ((true)) return; // simple caution, never invoked being private + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!site_key.value().empty()); + auto site_key_pair = FindSiteKeyForAnyUrl({request_url}); +@@ -134,6 +129,8 @@ bool SitekeyStorageImpl::IsSitekeySignatureValid( + const std::string& public_key_b64, + const std::string& signature_b64, + const std::string& data) const { ++ // remove Acceptable Ads site key ++ if ((true)) return false; // simple caution, never invoked being private + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::string signature; + if (!base::Base64Decode(signature_b64, &signature, +diff --git a/components/adblock/core/subscription/conversion_executors.h b/components/adblock/core/subscription/conversion_executors.h +--- a/components/adblock/core/subscription/conversion_executors.h ++++ b/components/adblock/core/subscription/conversion_executors.h +@@ -40,6 +40,7 @@ class ConversionExecutors { + virtual void ConvertFilterListFile( + const GURL& subscription_url, + const base::FilePath& path, ++ bool allow_privileged_filter, + base::OnceCallback result_callback) const = 0; + + virtual ~ConversionExecutors() = default; +diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer.h b/components/adblock/core/subscription/filtering_configuration_maintainer.h +--- a/components/adblock/core/subscription/filtering_configuration_maintainer.h ++++ b/components/adblock/core/subscription/filtering_configuration_maintainer.h +@@ -24,6 +24,7 @@ + #include "base/memory/scoped_refptr.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" + + namespace adblock { + +@@ -41,6 +42,9 @@ class FilteringConfigurationMaintainer { + virtual std::unique_ptr GetSubscriptionCollection() + const = 0; + ++ virtual void StartUpdate() = 0; ++ virtual raw_ptr GetMetadata() = 0; ++ + // Allows inspecting what Subscriptions are currently in use. This includes + // ongoing downloads, preloaded subscriptions and installed subscriptions. + virtual std::vector> GetCurrentSubscriptions() +diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc +--- a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc ++++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.cc +@@ -273,9 +273,22 @@ void FilteringConfigurationMaintainerImpl::RemoveDuplicateSubscriptions() { + unique_subscriptions.end()); + } + ++void FilteringConfigurationMaintainerImpl::StartUpdate() { ++ LOG(INFO) << "[eyeo] Running forced update"; ++ for (auto& subscription : current_state_) { ++ const auto& url = subscription->GetSourceUrl(); ++ DownloadAndInstallSubscription(url); ++ } ++} ++ ++raw_ptr ++ FilteringConfigurationMaintainerImpl::GetMetadata() { ++ return persistent_metadata_; ++} ++ + void FilteringConfigurationMaintainerImpl::RunUpdateCheck() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +- VLOG(1) << "[eyeo] Running update check"; ++ LOG(INFO) << "[eyeo] Running update check"; + + // Run recommended subscription update check first so + // we don't update lists that would get removed. +@@ -313,7 +326,6 @@ void FilteringConfigurationMaintainerImpl::RunUpdateCheck() { + AcceptableAdsUrl(); + }) && + persistent_metadata_->IsExpired(AcceptableAdsUrl())) { +- PingAcceptableAds(); + } + } + +@@ -405,15 +417,6 @@ void FilteringConfigurationMaintainerImpl::SubscriptionAddedToStorage( + 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()) { +@@ -426,20 +429,20 @@ void FilteringConfigurationMaintainerImpl::OnHeadRequestDone( + + void FilteringConfigurationMaintainerImpl::UninstallSubscription( + const GURL& subscription_url) { +- DVLOG(1) << "[eyeo] Removing subscription " << subscription_url; ++ LOG(INFO) << "[eyeo] Removing subscription " << subscription_url; + if (!UninstallSubscriptionInternal(subscription_url)) { +- VLOG(1) << "[eyeo] Nothing to remove, subscription not installed " ++ LOG(INFO) << "[eyeo] Nothing to remove, subscription not installed " + << subscription_url; + return; + } +- if (subscription_url != AcceptableAdsUrl()) { ++ if ((true) || 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; ++ LOG(INFO) << "[eyeo] Removed subscription " << subscription_url; + } + + bool FilteringConfigurationMaintainerImpl::UninstallSubscriptionInternal( +@@ -473,6 +476,8 @@ void FilteringConfigurationMaintainerImpl::SetCustomFilters() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + std::vector filters = configuration_->GetCustomFilters(); ++ std::vector temp_filters = configuration_->GetTemporaryCustomFilters(); ++ filters.insert(filters.end(), temp_filters.begin(), temp_filters.end()); + std::ranges::transform(configuration_->GetAllowedDomains(), + std::back_inserter(filters), + &CreateDomainAllowlistingFilter); +diff --git a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h +--- a/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h ++++ b/components/adblock/core/subscription/filtering_configuration_maintainer_impl.h +@@ -61,6 +61,9 @@ class FilteringConfigurationMaintainerImpl + + void RemoveAutoInstalledSubscriptions() final; + ++ void StartUpdate() final; ++ raw_ptr GetMetadata() final; ++ + // FilteringConfiguration::Observer: + void OnFilterListsChanged(FilteringConfiguration* config) final; + void OnAllowedDomainsChanged(FilteringConfiguration* config) final; +@@ -88,7 +91,6 @@ class FilteringConfigurationMaintainerImpl + void SubscriptionAddedToStorage( + scoped_refptr ongoing_installation, + scoped_refptr subscription); +- void PingAcceptableAds(); + void OnHeadRequestDone(const std::string version); + void UninstallSubscription(const GURL& subscription_url); + bool UninstallSubscriptionInternal(const GURL& subscription_url); +diff --git a/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc +--- a/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc ++++ b/components/adblock/core/subscription/preloaded_subscription_provider_impl.cc +@@ -63,10 +63,10 @@ class PreloadedSubscriptionProviderImpl::SingleSubscriptionProvider { + utils::MakeFlatbufferDataFromResourceBundle( + info_.flatbuffer_resource_id), + Subscription::InstallationState::Preloaded, base::Time()); +- VLOG(1) << "[eyeo] Preloaded subscription now in use: " ++ LOG(INFO) << "[eyeo] Preloaded subscription now in use: " + << subscription_->GetSourceUrl(); + } else if (!needs_subscription && subscription_) { +- VLOG(1) << "[eyeo] Preloaded subscription no longer in use: " ++ LOG(INFO) << "[eyeo] Preloaded subscription no longer in use: " + << subscription_->GetSourceUrl(); + subscription_.reset(); + } +diff --git a/components/adblock/core/subscription/recommended_subscription_installer_impl.cc b/components/adblock/core/subscription/recommended_subscription_installer_impl.cc +--- a/components/adblock/core/subscription/recommended_subscription_installer_impl.cc ++++ b/components/adblock/core/subscription/recommended_subscription_installer_impl.cc +@@ -69,6 +69,7 @@ void RecommendedSubscriptionInstallerImpl::RunUpdateCheck() { + } + + bool RecommendedSubscriptionInstallerImpl::IsUpdateDue() const { ++ if ((true)) return false; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return pref_service_->GetBoolean( + common::prefs::kEnableAutoInstalledSubscriptions) && +diff --git a/components/adblock/core/subscription/subscription.cc b/components/adblock/core/subscription/subscription.cc +--- a/components/adblock/core/subscription/subscription.cc ++++ b/components/adblock/core/subscription/subscription.cc +@@ -16,9 +16,29 @@ + */ + + #include "components/adblock/core/subscription/subscription.h" ++#include "base/notreached.h" + + namespace adblock { + + Subscription::~Subscription() = default; + ++// static ++const std::string Subscription::SubscriptionInstallationStateToString( ++ Subscription::InstallationState state) { ++ using State = Subscription::InstallationState; ++ switch (state) { ++ case State::Installed: ++ return "Installed"; ++ case State::AutoInstalled: ++ return "AutoInstalled"; ++ case State::Installing: ++ return "Installing"; ++ case State::Preloaded: ++ return "Preloaded"; ++ case State::Unknown: ++ return "Unknown"; ++ } ++ NOTREACHED(); ++} ++ + } // namespace adblock +diff --git a/components/adblock/core/subscription/subscription.h b/components/adblock/core/subscription/subscription.h +--- a/components/adblock/core/subscription/subscription.h ++++ b/components/adblock/core/subscription/subscription.h +@@ -71,6 +71,9 @@ class Subscription : public base::RefCountedThreadSafe { + // Typically, update checks are performed once per expiration interval. + virtual base::TimeDelta GetExpirationInterval() const = 0; + ++ const static std::string SubscriptionInstallationStateToString( ++ InstallationState state); ++ + protected: + friend class base::RefCountedThreadSafe; + virtual ~Subscription(); +diff --git a/components/adblock/core/subscription/subscription_collection_impl.cc b/components/adblock/core/subscription/subscription_collection_impl.cc +--- a/components/adblock/core/subscription/subscription_collection_impl.cc ++++ b/components/adblock/core/subscription/subscription_collection_impl.cc +@@ -371,6 +371,7 @@ std::set SubscriptionCollectionImpl::GetHeaderFilters( + ContentType content_type, + FilterCategory category) const { + std::set filters{}; ++ if ((true)) return filters; + for (const auto& subscription : subscriptions_) { + subscription->FindHeaderFilters( + request_url, content_type, DocumentDomain(request_url, frame_hierarchy), +diff --git a/components/adblock/core/subscription/subscription_config.cc b/components/adblock/core/subscription/subscription_config.cc +--- a/components/adblock/core/subscription/subscription_config.cc ++++ b/components/adblock/core/subscription/subscription_config.cc +@@ -29,7 +29,7 @@ namespace { + int g_port_for_testing = 0; + + std::string GetHost() { +- GURL url("https://easylist-downloads.adblockplus.org"); ++ GURL url("https://www.cromite.org/filters/"); + if (!g_port_for_testing) { + return url.spec(); + } +@@ -108,7 +108,19 @@ const std::vector& config::GetKnownSubscriptions() { + "EasyList", + {"en"}, + SubscriptionUiVisibility::Visible, +- SubscriptionFirstRunBehavior::Subscribe, ++ SubscriptionFirstRunBehavior::SubscribeAtFirstRun, ++ SubscriptionPrivilegedFilterStatus::Forbidden}, ++ {GURL(GetHost() + "badblock_lite.txt"), ++ "Celenity/BadBlock Lite", ++ {"en"}, ++ SubscriptionUiVisibility::Visible, ++ SubscriptionFirstRunBehavior::SubscribeAtFirstRun, ++ SubscriptionPrivilegedFilterStatus::Forbidden}, ++ {GURL(GetHost() + "badmojr-1Hosts-master-Pro-adblock.txt"), ++ "badmojr/1Hosts", ++ {"en"}, ++ SubscriptionUiVisibility::Visible, ++ SubscriptionFirstRunBehavior::SubscribeAtFirstRun, + SubscriptionPrivilegedFilterStatus::Forbidden}, + {GURL(GetHost() + "abpindo.txt"), + "ABPindo", +@@ -262,17 +274,23 @@ const std::vector& config::GetKnownSubscriptions() { + SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, + SubscriptionPrivilegedFilterStatus::Forbidden}, + {AcceptableAdsUrl(), +- "Acceptable Ads", ++ "Acceptable Ads", // Always disable + {}, + SubscriptionUiVisibility::Invisible, +- SubscriptionFirstRunBehavior::Subscribe, ++ SubscriptionFirstRunBehavior::Ignore, // in bromite + SubscriptionPrivilegedFilterStatus::Forbidden}, + {AntiCVUrl(), + "ABP filters", + {}, + SubscriptionUiVisibility::Visible, +- SubscriptionFirstRunBehavior::Subscribe, +- SubscriptionPrivilegedFilterStatus::Allowed}, ++ SubscriptionFirstRunBehavior::SubscribeAtFirstRun, ++ SubscriptionPrivilegedFilterStatus::AllowedAndChecked}, ++ {GURL("https://raw.githubusercontent.com/uazo/cromite/master/tools/filters/experimental-cromite-filters.txt"), ++ "Cromite experimental filters", ++ {}, ++ SubscriptionUiVisibility::Visible, ++ SubscriptionFirstRunBehavior::SubscribeAtFirstRun, ++ SubscriptionPrivilegedFilterStatus::AllowedAndChecked}, + {GURL(GetHost() + "i_dont_care_about_cookies.txt"), + "I don't care about cookies", + {}, +@@ -303,13 +321,13 @@ const std::vector& config::GetKnownSubscriptions() { + {}, + SubscriptionUiVisibility::Invisible, + SubscriptionFirstRunBehavior::Ignore, +- SubscriptionPrivilegedFilterStatus::Allowed}, ++ SubscriptionPrivilegedFilterStatus::AllowedAndChecked}, + {TestPagesSubscriptionUrl(), + "ABP Test filters", + {}, + SubscriptionUiVisibility::Invisible, + SubscriptionFirstRunBehavior::Ignore, +- SubscriptionPrivilegedFilterStatus::Allowed} ++ SubscriptionPrivilegedFilterStatus::Forbidden} + + // You can customize subscriptions available on first run and in settings + // here. Items are displayed in settings in order declared here. See +@@ -346,7 +364,7 @@ bool config::AllowPrivilegedFilters(const GURL& url) { + for (const auto& cur : GetKnownSubscriptions()) { + if (cur.url == url) { + return cur.privileged_status == +- SubscriptionPrivilegedFilterStatus::Allowed; ++ SubscriptionPrivilegedFilterStatus::AllowedAndChecked; + } + } + +@@ -356,9 +374,7 @@ bool config::AllowPrivilegedFilters(const GURL& url) { + const std::vector& + config::GetPreloadedSubscriptionConfiguration() { + static const std::vector 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; + } + +diff --git a/components/adblock/core/subscription/subscription_config.h b/components/adblock/core/subscription/subscription_config.h +--- a/components/adblock/core/subscription/subscription_config.h ++++ b/components/adblock/core/subscription/subscription_config.h +@@ -39,7 +39,7 @@ enum class SubscriptionUiVisibility { Visible, Invisible }; + + enum class SubscriptionFirstRunBehavior { + // Download and install as soon as possible. +- Subscribe, ++ SubscribeAtFirstRun, + // Download and install as soon as possible but only if the device's region + // matches one of the |languages| defined in KnownSubscriptionInfo. + SubscribeIfLocaleMatch, +@@ -50,7 +50,7 @@ enum class SubscriptionFirstRunBehavior { + // Privileged filters include: + // - Snippet filters + // - Header filters +-enum class SubscriptionPrivilegedFilterStatus { Allowed, Forbidden }; ++enum class SubscriptionPrivilegedFilterStatus { AllowedAndChecked, 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 +@@ -74,7 +74,7 @@ struct KnownSubscriptionInfo { + std::vector languages; + SubscriptionUiVisibility ui_visibility = SubscriptionUiVisibility::Visible; + SubscriptionFirstRunBehavior first_run = +- SubscriptionFirstRunBehavior::Subscribe; ++ SubscriptionFirstRunBehavior::Ignore; + SubscriptionPrivilegedFilterStatus privileged_status = + SubscriptionPrivilegedFilterStatus::Forbidden; + }; +diff --git a/components/adblock/core/subscription/subscription_downloader_impl.cc b/components/adblock/core/subscription/subscription_downloader_impl.cc +--- a/components/adblock/core/subscription/subscription_downloader_impl.cc ++++ b/components/adblock/core/subscription/subscription_downloader_impl.cc +@@ -53,6 +53,7 @@ std::string BuildSubscriptionQueryParams( + const GURL& subscription_url, + const SubscriptionPersistentMetadata* persistent_metadata, + const bool is_disabled) { ++ if ((true)) return subscription_url.spec(); + return base::StrCat( + {"lastVersion=", + base::EscapeQueryParamValue( +@@ -117,6 +118,8 @@ void SubscriptionDownloaderImpl::StartDownload( + } + + void SubscriptionDownloaderImpl::CancelDownload(const GURL& subscription_url) { ++ LOG(WARNING) << "[eyeo] Download cancelled: " ++ << subscription_url; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ongoing_downloads_.erase(subscription_url); + } +@@ -144,10 +147,6 @@ void SubscriptionDownloaderImpl::DoHeadRequest( + + 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; +@@ -187,7 +186,7 @@ void SubscriptionDownloaderImpl::OnDownloadFinished( + + if (downloaded_file.empty()) { + persistent_metadata_->IncrementDownloadErrorCount(subscription_url); +- DLOG(WARNING) << "[eyeo] Failed to retrieve content for " ++ LOG(WARNING) << "[eyeo] Failed to retrieve content for " + << subscription_url << ", will abort"; + std::move(std::get(download_it->second)) + .Run(nullptr); +@@ -203,8 +202,10 @@ void SubscriptionDownloaderImpl::OnDownloadFinished( + TRACE_ID_LOCAL(GenerateTraceId(subscription_url)), "url", + subscription_url.spec()); + ++ bool allow_privileged_filter = ++ persistent_metadata_->AllowPrivilegedFilters(subscription_url); + conversion_executor_->ConvertFilterListFile( +- subscription_url, downloaded_file, ++ subscription_url, downloaded_file, allow_privileged_filter, + base::BindOnce(&SubscriptionDownloaderImpl::OnConversionFinished, + weak_ptr_factory_.GetWeakPtr(), subscription_url)); + } +@@ -218,14 +219,14 @@ void SubscriptionDownloaderImpl::OnConversionFinished( + 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 " ++ LOG(WARNING) << "[eyeo] Conversion result discarded, subscription download " + "was cancelled."; + return; + } + + if (absl::holds_alternative>( + converter_result)) { +- VLOG(1) << "[eyeo] Finished converting " << subscription_url ++ LOG(WARNING) << "[eyeo] Finished converting " << subscription_url + << " successfully"; + std::move(std::get(download_it->second)) + .Run(std::move( +@@ -269,7 +270,7 @@ void SubscriptionDownloaderImpl::AbortWithWarning( + if (ongoing_download_it == ongoing_downloads_.end()) { + return; + } +- DLOG(WARNING) << "[eyeo] " << warning << " Aborting download of " ++ LOG(WARNING) << "[eyeo] " << warning << " Aborting download of " + << ongoing_download_it->first; + std::move(std::get(ongoing_download_it->second)) + .Run(nullptr); +diff --git a/components/adblock/core/subscription/subscription_persistent_metadata.h b/components/adblock/core/subscription/subscription_persistent_metadata.h +--- a/components/adblock/core/subscription/subscription_persistent_metadata.h ++++ b/components/adblock/core/subscription/subscription_persistent_metadata.h +@@ -74,6 +74,7 @@ class SubscriptionPersistentMetadata : public KeyedService { + // Returns the number of successful downloads of this subscription in the + // past. + virtual int GetDownloadSuccessCount(const GURL& subscription_url) const = 0; ++ virtual bool AllowPrivilegedFilters(const GURL& subscription_url) = 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 +diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc +--- a/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc ++++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.cc +@@ -24,6 +24,7 @@ + #include "base/time/time.h" + #include "base/values.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" + + namespace adblock { +@@ -193,6 +194,12 @@ void SubscriptionPersistentMetadataImpl::UpdatePrefs() { + prefs_->SetDict(common::prefs::kSubscriptionMetadata, std::move(dict)); + } + ++bool SubscriptionPersistentMetadataImpl::AllowPrivilegedFilters( ++ const GURL& subscription_url) { ++ return prefs_ && prefs_->GetBoolean(common::prefs::kAllowPrivilegedFilters) ++ && config::AllowPrivilegedFilters(subscription_url); ++} ++ + void SubscriptionPersistentMetadataImpl::LoadFromPrefs() { + const base::Value& dict = + prefs_->GetValue(common::prefs::kSubscriptionMetadata); +diff --git a/components/adblock/core/subscription/subscription_persistent_metadata_impl.h b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h +--- a/components/adblock/core/subscription/subscription_persistent_metadata_impl.h ++++ b/components/adblock/core/subscription/subscription_persistent_metadata_impl.h +@@ -44,6 +44,7 @@ class SubscriptionPersistentMetadataImpl 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; ++ bool AllowPrivilegedFilters(const GURL& subscription_url) final; + int GetDownloadErrorCount(const GURL& subscription_url) const final; + + void SetAutoInstalledExpirationInterval(const GURL& subscription_url, +diff --git a/components/adblock/core/subscription/subscription_persistent_storage_impl.cc b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc +--- a/components/adblock/core/subscription/subscription_persistent_storage_impl.cc ++++ b/components/adblock/core/subscription/subscription_persistent_storage_impl.cc +@@ -99,7 +99,7 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( + const base::FilePath& storage_dir, + SubscriptionValidator::IsSignatureValidThreadSafeCallback + is_signature_valid) { +- DLOG(INFO) << "[eyeo] Reading subscriptions from directory"; ++ LOG(INFO) << "[eyeo] Reading subscriptions from directory " << storage_dir; + TRACE_EVENT0("eyeo", "ReadSubscriptionsFromDirectory"); + // Does nothing if directory already exists: + base::CreateDirectory(storage_dir); +@@ -117,6 +117,8 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( + if (!base::ReadFileToString(flatbuffer_path, &contents)) { + // File could not be read. + base::DeleteFile(flatbuffer_path); ++ LOG(INFO) << "[eyeo] Deleting " << flatbuffer_path.BaseName().AsUTF8Unsafe() ++ << "reason: File could not be read"; + continue; + } + TRACE_EVENT_END1("eyeo", "ReadFileToString", "path", +@@ -124,6 +126,8 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( + TRACE_EVENT_BEGIN0("eyeo", "VerifySubscriptionBuffer"); + if (!is_signature_valid.Run(InMemoryFlatbufferData(std::move(contents)), + flatbuffer_path)) { ++ LOG(INFO) << "[eyeo] Deleting " << flatbuffer_path.BaseName().AsUTF8Unsafe() ++ << "reason: This is not a valid subscription file"; + // This is not a valid subscription file, remove it. + base::DeleteFile(flatbuffer_path); + continue; +@@ -131,13 +135,16 @@ SubscriptionPersistentStorageImpl::ReadSubscriptionsFromDirectory( + TRACE_EVENT_END0("eyeo", "VerifySubscriptionBuffer"); + auto buffer = std::make_unique(flatbuffer_path); + if (!buffer->data()) { ++ LOG(INFO) << "[eyeo] Could not create mapped memory region to file content for " ++ << flatbuffer_path.BaseName().AsUTF8Unsafe(); + // Could not create mapped memory region to file content. + // TODO(mpawlowski) revert to in-memory buffer? + continue; + } ++ LOG(INFO) << "[eyeo] Loaded " << flatbuffer_path.BaseName().AsUTF8Unsafe(); + result.emplace_back(std::move(buffer), std::move(flatbuffer_path)); + } +- DLOG(INFO) << "[eyeo] Finished reading and validating subscriptions. Loaded " ++ LOG(INFO) << "[eyeo] Finished reading and validating subscriptions. Loaded " + << result.size() << " subscriptions."; + return result; + } +diff --git a/components/adblock/core/subscription/subscription_service.h b/components/adblock/core/subscription/subscription_service.h +--- a/components/adblock/core/subscription/subscription_service.h ++++ b/components/adblock/core/subscription/subscription_service.h +@@ -38,6 +38,17 @@ namespace adblock { + // FilteringConfigurations. + class SubscriptionService : public KeyedService { + public: ++ virtual void StartUpdate() = 0; ++ virtual raw_ptr GetMetadata() = 0; ++ virtual raw_ptr GetMetadataFor( ++ raw_ptr configuration) = 0; ++ virtual void SetPrivilegedFiltersEnabled(bool enabled) = 0; ++ virtual bool IsPrivilegedFiltersEnabled() = 0; ++ virtual std::vector> GetCustomSubscriptions( ++ FilteringConfiguration* configuration) const = 0; ++ // Get default "adblock" filtering configuration. ++ virtual FilteringConfiguration* GetAdblockFilteringConfiguration() const = 0; ++ + using Snapshot = std::vector>; + class SubscriptionObserver : public base::CheckedObserver { + public: +diff --git a/components/adblock/core/subscription/subscription_service_impl.cc b/components/adblock/core/subscription/subscription_service_impl.cc +--- a/components/adblock/core/subscription/subscription_service_impl.cc ++++ b/components/adblock/core/subscription/subscription_service_impl.cc +@@ -34,9 +34,23 @@ + #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/subscription_config.h" + + namespace adblock { + ++namespace { + -+ const regex = toRegExp(search); ++bool IsKnownSubscription( ++ const std::vector& known_subscriptions, ++ const GURL& url) { ++ return std::ranges::any_of(known_subscriptions, ++ [&](const auto& known_subscription) { ++ return known_subscription.url == url; ++ }); ++} + -+ const globalisedRegEx = new RegExp(regex, "g"); -+ xhrRules.set(globalisedRegEx, -+ {replacement, needle, formattedArgs: formattedArgsToLog}); ++} ++ + class EmptySubscription : public Subscription { + public: + explicit EmptySubscription(const GURL& url) : url_(url) {} +@@ -162,6 +176,77 @@ void SubscriptionServiceImpl::UninstallFilteringConfiguration( + } + } + ++void SubscriptionServiceImpl::StartUpdate() { ++ for (auto& entry : maintainers_) { ++ if (!entry.second) { ++ continue; // Configuration is disabled ++ } ++ entry.second->StartUpdate(); + } ++} + -+ let {delete: deleteParam, has: hasParam} = caller(URLSearchParams.prototype); ++bool SubscriptionServiceImpl::IsPrivilegedFiltersEnabled() { ++ auto* adblock_filtering_configuration = GetAdblockFilteringConfiguration(); ++ return adblock_filtering_configuration->IsPrivilegedFiltersEnabled(); ++} + -+ let parameters; ++void SubscriptionServiceImpl::SetPrivilegedFiltersEnabled(bool enabled) { ++ auto* adblock_filtering_configuration = GetAdblockFilteringConfiguration(); ++ adblock_filtering_configuration->SetPrivilegedFiltersEnabled(enabled); ++ auto known_subscriptions = adblock::config::GetKnownSubscriptions(); ++ for (const auto& cur : known_subscriptions) { ++ if (config::AllowPrivilegedFilters(cur.url)) { ++ if (enabled) ++ adblock_filtering_configuration->AddFilterList(cur.url); ++ else ++ adblock_filtering_configuration->RemoveFilterList(cur.url); ++ } ++ } ++ StartUpdate(); ++} + -+ function stripFetchQueryParameter(name, urlPattern = null) { -+ const formattedArgs = formatArguments(arguments); -+ const debugLog = getDebugger("strip-fetch-query-parameter"); ++std::vector> ++SubscriptionServiceImpl::GetCustomSubscriptions( ++ FilteringConfiguration* configuration) const { ++ std::vector> selected = ++ GetCurrentSubscriptions(configuration); + -+ const stripFunction = url => { -+ for (let [key, value] of parameters.entries()) { -+ const {reg, args} = value; -+ if (!reg || reg.test(url)) { -+ if (hasParam(url.searchParams, key)) { -+ debugLog("success", `${key} has been stripped from url ${url}`, `\nFILTER: strip-fetch-query-parameter ${args}`); -+ deleteParam(url.searchParams, key); -+ } -+ } -+ } -+ }; ++ auto known_subscriptions = adblock::config::GetKnownSubscriptions(); ++ selected.erase(std::remove_if(selected.begin(), selected.end(), ++ [&known_subscriptions](const auto& subscription) { ++ return IsKnownSubscription( ++ known_subscriptions, ++ subscription->GetSourceUrl()); ++ }), ++ selected.end()); ++ return selected; ++} + -+ if (!parameters) { -+ parameters = new Map(); -+ addPreFetchCallback(stripFunction); -+ } ++raw_ptr SubscriptionServiceImpl::GetMetadata() { ++ return GetMetadataFor(GetAdblockFilteringConfiguration()); ++} + -+ parameters.set(name, -+ {reg: urlPattern && toRegExp(urlPattern), -+ args: formattedArgs}); ++raw_ptr SubscriptionServiceImpl::GetMetadataFor( ++ raw_ptr configuration) { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ auto it = std::ranges::find_if(maintainers_, [&](const auto& entry) { ++ return entry.first.get() == configuration; ++ }); ++ if (it != maintainers_.end() && it->second) { ++ return it->second->GetMetadata(); + } ++ return nullptr; ++} + -+ function trace(...args) { ++FilteringConfiguration* ++SubscriptionServiceImpl::GetAdblockFilteringConfiguration() const { ++ const auto it = std::ranges::find_if(maintainers_, [](const auto& pair) { ++ return pair.first->GetName() == kAdblockFilteringConfigurationName; ++ }); ++ DCHECK(it != maintainers_.end()); ++ return it->first.get(); ++} + -+ apply$2(log, null, args); -+ } + std::vector + SubscriptionServiceImpl::GetInstalledFilteringConfigurations() { + std::vector result; +@@ -232,7 +317,7 @@ void SubscriptionServiceImpl::OnEnabledStateChanged( + }); + DCHECK(it != maintainers_.end()) << "Received OnEnabledStateChanged from " + "unregistered FilteringConfiguration"; +- VLOG(1) << "[eyeo] FilteringConfiguration " << config->GetName() ++ LOG(INFO) << "[eyeo] FilteringConfiguration " << config->GetName() + << (config->IsEnabled() ? " enabled" : " disabled"); + if (config->IsEnabled()) { + // Enable the configuration by creating a new +diff --git a/components/adblock/core/subscription/subscription_service_impl.h b/components/adblock/core/subscription/subscription_service_impl.h +--- a/components/adblock/core/subscription/subscription_service_impl.h ++++ b/components/adblock/core/subscription/subscription_service_impl.h +@@ -42,6 +42,16 @@ namespace adblock { + class SubscriptionServiceImpl final : public SubscriptionService, + public FilteringConfiguration::Observer { + public: ++ void StartUpdate() override; ++ raw_ptr GetMetadata() override; ++ raw_ptr GetMetadataFor( ++ raw_ptr configuration) final; ++ void SetPrivilegedFiltersEnabled(bool enabled) override; ++ bool IsPrivilegedFiltersEnabled() override; ++ std::vector> GetCustomSubscriptions( ++ FilteringConfiguration* configuration) const override; ++ FilteringConfiguration* GetAdblockFilteringConfiguration() const final; + -+ const snippets = { -+ "abort-current-inline-script": abortCurrentInlineScript, -+ "abort-on-iframe-property-read": abortOnIframePropertyRead, -+ "abort-on-iframe-property-write": abortOnIframePropertyWrite, -+ "abort-on-property-read": abortOnPropertyRead, -+ "abort-on-property-write": abortOnPropertyWrite, -+ "cookie-remover": cookieRemover, -+ "debug": setDebug, -+ "freeze-element": freezeElement, -+ "hide-if-canvas-contains": hideIfCanvasContains, -+ "hide-if-shadow-contains": hideIfShadowContains, -+ "json-override": jsonOverride, -+ "json-prune": jsonPrune, -+ "override-property-read": overridePropertyRead, -+ "prevent-listener": preventListener, -+ "replace-fetch-response": replaceFetchResponse, -+ "replace-xhr-response": replaceXhrResponse, -+ "strip-fetch-query-parameter": stripFetchQueryParameter, -+ "trace": trace -+ }; -+ let context; -+ for (const [name, ...args] of filters) { -+ if (snippets.hasOwnProperty(name)) { -+ try { context = snippets[name].apply(context, args); } -+ catch (error) { console.error(error); } -+ } -+ } -+ context = void 0; -+}; -+const graph = new Map([["abort-current-inline-script",null],["abort-on-iframe-property-read",null],["abort-on-iframe-property-write",null],["abort-on-property-read",null],["abort-on-property-write",null],["cookie-remover",null],["debug",null],["freeze-element",null],["hide-if-canvas-contains",null],["hide-if-shadow-contains",null],["json-override",null],["json-prune",null],["override-property-read",null],["prevent-listener",null],["replace-fetch-response",null],["replace-xhr-response",null],["strip-fetch-query-parameter",null],["trace",null]]); -+callback.get = snippet => graph.get(snippet); -+callback.has = snippet => graph.has(snippet); + // Used to notify this about updates to installed subscriptions. + using SubscriptionUpdatedCallback = + base::RepeatingCallback; +diff --git a/components/adblock/core/subscription/subscription_validator_impl.cc b/components/adblock/core/subscription/subscription_validator_impl.cc +--- a/components/adblock/core/subscription/subscription_validator_impl.cc ++++ b/components/adblock/core/subscription/subscription_validator_impl.cc +@@ -63,11 +63,11 @@ bool IsSignatureValidInternal( + const auto* expected_hash = initial_subscription_signatures.FindString( + path.BaseName().AsUTF8Unsafe()); + if (!expected_hash) { +- DLOG(WARNING) << "[eyeo] " << path << " has no matching signature in prefs"; ++ LOG(WARNING) << "[eyeo] " << path.BaseName().AsUTF8Unsafe() << " has no matching signature in prefs"; + return false; + } + if (*expected_hash != ComputeSubscriptionHash(data)) { +- DLOG(WARNING) << "[eyeo] " << path << " has invalid signature in prefs"; ++ LOG(WARNING) << "[eyeo] " << path.BaseName().AsUTF8Unsafe() << " has invalid signature in prefs"; + return false; + } + return true; +diff --git a/components/adblock/features.gni b/components/adblock/features.gni +--- a/components/adblock/features.gni ++++ b/components/adblock/features.gni +@@ -47,5 +47,5 @@ declare_args() { + 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 ++ eyeo_disable_aa_by_default = true + } +diff --git a/components/blocked_content/popup_blocker.cc b/components/blocked_content/popup_blocker.cc +--- a/components/blocked_content/popup_blocker.cc ++++ b/components/blocked_content/popup_blocker.cc +@@ -20,6 +20,11 @@ + #include "third_party/blink/public/mojom/frame/frame.mojom-shared.h" + + namespace blocked_content { + -+ debugger; -+ if (t.every(([name]) => !callback.has(name))) return; ++CROMITE_FEATURE(kStrictPopupBlocker, ++ "StrictPopupBlocker", ++ base::FEATURE_DISABLED_BY_DEFAULT); + -+ callback(e,...t); -+} + namespace { + + content::Page& GetSourcePageForPopup( +@@ -91,7 +96,9 @@ PopupBlockType ShouldBlockPopup(content::WebContents* web_contents, + GetSourcePageForPopup(open_url_params, web_contents))) { + return PopupBlockType::kAbusive; + } +- return PopupBlockType::kNotBlocked; ++ return base::FeatureList::IsEnabled(kStrictPopupBlocker) ++ ? PopupBlockType::kAbusive ++ : PopupBlockType::kNotBlocked; + } + + } // namespace +diff --git a/components/blocked_content/popup_blocker.h b/components/blocked_content/popup_blocker.h +--- a/components/blocked_content/popup_blocker.h ++++ b/components/blocked_content/popup_blocker.h +@@ -5,6 +5,7 @@ + #ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_ + #define COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_ + ++#include "base/feature_list.h" + #include "components/content_settings/core/browser/host_content_settings_map.h" + #include "third_party/blink/public/mojom/window_features/window_features.mojom-forward.h" + #include "ui/base/window_open_disposition.h" +@@ -18,6 +19,8 @@ struct OpenURLParams; + } // namespace content + + namespace blocked_content { ++BASE_DECLARE_FEATURE(kStrictPopupBlocker); ++ + class PopupNavigationDelegate; + + // Classifies what caused a popup to be blocked. +diff --git a/components/content_settings/core/browser/bromite_content_settings/ads.inc b/components/content_settings/core/browser/bromite_content_settings/ads.inc +new file mode 100644 +--- /dev/null ++++ b/components/content_settings/core/browser/bromite_content_settings/ads.inc +@@ -0,0 +1,3 @@ ++ content_settings::WebsiteSettingsRegistry::GetInstance() ++ ->GetMutable(ContentSettingsType::ADS) ++ ->set_show_into_info_page(); +diff --git a/components/error_page_strings.grdp b/components/error_page_strings.grdp +--- a/components/error_page_strings.grdp ++++ b/components/error_page_strings.grdp +@@ -266,6 +266,22 @@ + Your organization doesn’t allow you to view this site + + ++ ++ Blocked by Adblock ++ ++ ++ Blocked by Adblock ++ ++ ++ Adblock doesn’t allow you to view this site (subscription url <strong>$1</strong>example.com/) ++ ++ ++ Allow navigation until restart ++ ++ ++ Add a custom persistent allow rule ++ ++ + + This content is blocked. Contact the site owner to fix the issue. + diff --git a/content/browser/websockets/websocket_connector_impl.cc b/content/browser/websockets/websocket_connector_impl.cc --- a/content/browser/websockets/websocket_connector_impl.cc +++ b/content/browser/websockets/websocket_connector_impl.cc @@ -10564,7 +12318,7 @@ diff --git a/content/browser/websockets/websocket_connector_impl.cc b/content/br diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc --- a/content/public/browser/content_browser_client.cc +++ b/content/public/browser/content_browser_client.cc -@@ -1108,7 +1108,7 @@ void ContentBrowserClient::WillCreateURLLoaderFactory( +@@ -1104,7 +1104,7 @@ void ContentBrowserClient::WillCreateURLLoaderFactory( DCHECK(browser_context); } @@ -10573,7 +12327,7 @@ diff --git a/content/public/browser/content_browser_client.cc b/content/public/b return false; } -@@ -1117,9 +1117,11 @@ uint32_t ContentBrowserClient::GetWebSocketOptions(RenderFrameHost* frame) { +@@ -1113,9 +1113,11 @@ uint32_t ContentBrowserClient::GetWebSocketOptions(RenderFrameHost* frame) { } void ContentBrowserClient::CreateWebSocket( @@ -10588,7 +12342,7 @@ diff --git a/content/public/browser/content_browser_client.cc b/content/public/b diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h -@@ -2070,7 +2070,7 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -2061,7 +2061,7 @@ class CONTENT_EXPORT ContentBrowserClient { scoped_refptr navigation_response_task_runner); // Returns true when the embedder wants to intercept a websocket connection. @@ -10597,7 +12351,7 @@ diff --git a/content/public/browser/content_browser_client.h b/content/public/br // Returns the WebSocket creation options. virtual uint32_t GetWebSocketOptions(RenderFrameHost* frame); -@@ -2092,9 +2092,11 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -2083,9 +2083,11 @@ class CONTENT_EXPORT ContentBrowserClient { // Always called on the UI thread and only when the Network Service is // enabled. virtual void CreateWebSocket( @@ -10609,6 +12363,18 @@ diff --git a/content/public/browser/content_browser_client.h b/content/public/br const net::SiteForCookies& site_for_cookies, const std::optional& user_agent, mojo::PendingRemote +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 +@@ -21,7 +21,7 @@ enum IsolatedWorldIDs : int32_t { + ISOLATED_WORLD_ID_GLOBAL = 0, + + // Isolated world for eyeo ad blocking (element hiding) +- ISOLATED_WORLD_ID_ADBLOCK, ++ ISOLATED_WORLD_ID_ADBLOCK_VERIFIED, + + // Custom isolated world ids used by other embedders should start from here. + ISOLATED_WORLD_ID_CONTENT_END, diff --git a/cromite_flags/chrome/browser/about_flags_cc/Stricter-popup-blocker.inc b/cromite_flags/chrome/browser/about_flags_cc/Stricter-popup-blocker.inc new file mode 100644 --- /dev/null @@ -10631,7 +12397,7 @@ new file mode 100644 diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc --- a/third_party/blink/renderer/core/css/style_engine.cc +++ b/third_party/blink/renderer/core/css/style_engine.cc -@@ -647,6 +647,14 @@ void StyleEngine::UpdateActiveStyleSheetsInShadow( +@@ -653,6 +653,14 @@ void StyleEngine::UpdateActiveStyleSheetsInShadow( } } @@ -10649,7 +12415,7 @@ diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/b diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h --- a/third_party/blink/renderer/core/css/style_engine.h +++ b/third_party/blink/renderer/core/css/style_engine.h -@@ -280,6 +280,7 @@ class CORE_EXPORT StyleEngine final : public GarbageCollected, +@@ -281,6 +281,7 @@ class CORE_EXPORT StyleEngine final : public GarbageCollected, const ComputedStyle* embedder_style) const; void ViewportStyleSettingChanged(); @@ -10657,10 +12423,25 @@ diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/bl void InjectSheet(const StyleSheetKey&, StyleSheetContents*, WebCssOrigin = WebCssOrigin::kAuthor); +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 +@@ -871,10 +871,7 @@ bool EventTarget::dispatchEventForBindings(Event* event, + if (!GetExecutionContext()) + return false; + +- auto* world = GetExecutionContext()->GetCurrentWorld(); +- // content::IsolatedWorldIDs::ISOLATED_WORLD_ID_ADBLOCK == 1 +- bool make_trusted = world && (world->GetWorldId() == 1); +- event->SetTrusted(make_trusted); ++ event->SetTrusted(false); + + // 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 -@@ -286,6 +286,16 @@ WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet( +@@ -288,6 +288,16 @@ WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet( Document* document = Unwrap(); DCHECK(document); @@ -10677,16 +12458,7 @@ diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_pa auto* parsed_sheet = MakeGarbageCollected( MakeGarbageCollected(*document)); parsed_sheet->ParseString(source_code); -@@ -295,7 +305,7 @@ WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet( - if (IsValidAbpRule(parsed_sheet->RuleAt(n))) - ++n; - else { -- parsed_sheet->SetMutable(); -+ parsed_sheet->StartMutation(); - parsed_sheet->WrapperDeleteRule(n); - LOG(WARNING) << "[eyeo] Broken rule"; - } -@@ -307,9 +317,6 @@ WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet( +@@ -309,9 +319,6 @@ WebStyleSheetKey WebDocument::InsertAbpElemhideStylesheet( {SchedulingPolicy::DisableBackForwardCache()}); } @@ -10696,6 +12468,24 @@ diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_pa document->GetStyleEngine().InjectSheet(injection_key, parsed_sheet, origin); return injection_key; } +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 +@@ -2418,13 +2418,7 @@ void HTMLElement::setSpellcheck(bool enable) { + } + + void HTMLElement::click() { +- 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); ++ DispatchSimulatedClick(nullptr, SimulatedClickCreationScope::kFromScript); + if (IsA(this)) { + UseCounter::Count(GetDocument(), + WebFeature::kHTMLInputElementSimulatedClick); diff --git a/tools/typescript/definitions/adblock_private.d.ts b/tools/typescript/definitions/adblock_private.d.ts --- a/tools/typescript/definitions/adblock_private.d.ts +++ b/tools/typescript/definitions/adblock_private.d.ts diff --git a/build/cromite_patches/Fix-chromium-build-bugs.patch b/build/cromite_patches/Fix-chromium-build-bugs.patch index d7921db68a7f75997ad1ae2b90732ade94455e96..56d1ed2a84afc83924521f8bf69eb5db4555c74c 100644 --- a/build/cromite_patches/Fix-chromium-build-bugs.patch +++ b/build/cromite_patches/Fix-chromium-build-bugs.patch @@ -21,7 +21,7 @@ https://bugs.chromium.org/p/chromium/issues/detail?id=1491776#c10 diff --git a/BUILD.gn b/BUILD.gn --- a/BUILD.gn +++ b/BUILD.gn -@@ -427,7 +427,6 @@ group("gn_all") { +@@ -429,7 +429,6 @@ group("gn_all") { "//android_webview:empty_group", "//android_webview/test", "//android_webview/tools/automated_ui_tests:webview_ui_test_app_test_apk", @@ -44,7 +44,7 @@ diff --git a/chrome/browser/navigation_predictor/navigation_predictor_metrics_do diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn --- a/chrome/browser/safe_browsing/BUILD.gn +++ b/chrome/browser/safe_browsing/BUILD.gn -@@ -231,6 +231,7 @@ static_library("safe_browsing") { +@@ -264,6 +264,7 @@ static_library("safe_browsing") { "//components/webdata/common", "//content/public/browser", "//services/preferences/public/mojom:mojom", @@ -55,7 +55,7 @@ diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsin diff --git a/components/component_updater/installer_policies/BUILD.gn b/components/component_updater/installer_policies/BUILD.gn --- a/components/component_updater/installer_policies/BUILD.gn +++ b/components/component_updater/installer_policies/BUILD.gn -@@ -54,6 +54,7 @@ static_library("installer_policies_no_content_deps") { +@@ -56,6 +56,7 @@ static_library("installer_policies_no_content_deps") { "//components/update_client", "//mojo/public/cpp/base:protobuf_support", "//services/network/public/cpp", @@ -80,7 +80,7 @@ diff --git a/components/omnibox/browser/autocomplete_classifier.cc b/components/ diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc --- a/components/omnibox/browser/omnibox_field_trial.cc +++ b/components/omnibox/browser/omnibox_field_trial.cc -@@ -1030,11 +1030,11 @@ MLConfig& MLConfig::operator=(const MLConfig& other) = default; +@@ -1040,11 +1040,11 @@ MLConfig& MLConfig::operator=(const MLConfig& other) = default; ScopedMLConfigForTesting::ScopedMLConfigForTesting() : original_config_(std::make_unique(GetMLConfig())) { @@ -108,7 +108,7 @@ diff --git a/components/plus_addresses/BUILD.gn b/components/plus_addresses/BUIL diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_image_embedder.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_image_embedder.cc --- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_image_embedder.cc +++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_image_embedder.cc -@@ -53,17 +53,9 @@ void PhishingImageEmbedder::BeginImageEmbedding(DoneCallback done_callback) { +@@ -49,17 +49,9 @@ void PhishingImageEmbedder::BeginImageEmbedding(DoneCallback done_callback) { void PhishingImageEmbedder::OnPlaybackDone(std::unique_ptr bitmap) { #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) @@ -132,7 +132,7 @@ diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phish diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc --- a/content/browser/browser_interface_binders.cc +++ b/content/browser/browser_interface_binders.cc -@@ -365,6 +365,7 @@ void BindDateTimeChooserForFrame( +@@ -379,6 +379,7 @@ void BindDateTimeChooserForFrame( void BindTextSuggestionHostForFrame( RenderFrameHost* host, mojo::PendingReceiver receiver) { @@ -143,7 +143,7 @@ diff --git a/content/browser/browser_interface_binders.cc b/content/browser/brow diff --git a/net/BUILD.gn b/net/BUILD.gn --- a/net/BUILD.gn +++ b/net/BUILD.gn -@@ -1746,6 +1746,7 @@ component("net") { +@@ -1750,6 +1750,7 @@ component("net") { if (!is_cronet_build) { deps += [ "//mojo/public/cpp/bindings:default_construct_tag" ] } diff --git a/build/cromite_patches/History-number-of-days-privacy-setting.patch b/build/cromite_patches/History-number-of-days-privacy-setting.patch index 60597ea73437c51244f162d74a1703d40a3f27b9..738ef20c369b228dc7724cfcac5fb2751abfa705 100644 --- a/build/cromite_patches/History-number-of-days-privacy-setting.patch +++ b/build/cromite_patches/History-number-of-days-privacy-setting.patch @@ -157,7 +157,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/setting diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc -@@ -41,6 +41,7 @@ +@@ -42,6 +42,7 @@ #include "components/content_settings/core/common/pref_names.h" #include "components/dom_distiller/core/pref_names.h" #include "components/drive/drive_pref_names.h" @@ -165,7 +165,7 @@ diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chro #include "components/embedder_support/pref_names.h" #include "components/language/core/browser/pref_names.h" #include "components/live_caption/pref_names.h" -@@ -218,6 +219,9 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { +@@ -219,6 +220,9 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { (*s_allowlist)[::prefs::kShowForwardButton] = settings_api::PrefType::kBoolean; @@ -201,7 +201,7 @@ diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/p diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html --- a/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html -@@ -85,6 +85,15 @@ +@@ -92,6 +92,15 @@ sub-label="$i18n{permissionsPageDescription}" on-click="onPermissionsPageClick_" role-description="$i18n{subpageArrowRoleDescription}"> @@ -247,7 +247,7 @@ diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/ch diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -1400,6 +1400,18 @@ Your Google account may have other forms of browsing history like searches and a +@@ -1414,6 +1414,18 @@ Your Google account may have other forms of browsing history like searches and a Browsing history @@ -269,7 +269,7 @@ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chro diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc -@@ -1948,6 +1948,11 @@ void AddPrivacyStrings(content::WebUIDataSource* html_source, +@@ -1956,6 +1956,11 @@ void AddPrivacyStrings(content::WebUIDataSource* html_source, IDS_SETTINGS_SAFEBROWSING_ENHANCED_THINGS_TO_CONSIDER_BULLET_THREE}, {"safeBrowsingStandard", IDS_SETTINGS_SAFEBROWSING_STANDARD}, {"safeBrowsingStandardDesc", IDS_SETTINGS_SAFEBROWSING_STANDARD_DESC}, @@ -457,7 +457,7 @@ diff --git a/components/history/core/browser/expire_history_backend.h b/componen diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc --- a/components/history/core/browser/history_backend.cc +++ b/components/history/core/browser/history_backend.cc -@@ -163,6 +163,10 @@ const int kCommitIntervalSeconds = 10; +@@ -164,6 +164,10 @@ const int kCommitIntervalSeconds = 10; // deleting some. const int kMaxRedirectCount = 32; @@ -468,7 +468,7 @@ diff --git a/components/history/core/browser/history_backend.cc b/components/his // The maximum number of days for which domain visit metrics are computed // each time HistoryBackend::GetDomainDiversity() is called. constexpr int kDomainDiversityMaxBacktrackedDays = 7; -@@ -1360,6 +1364,19 @@ void HistoryBackend::InitImpl( +@@ -1361,6 +1365,19 @@ void HistoryBackend::InitImpl( expirer_.StartExpiringOldStuff(base::Days(kExpireDaysThreshold)); } @@ -558,7 +558,7 @@ diff --git a/components/history/core/browser/history_service.cc b/components/his void HistoryService::ScheduleDBTaskForUI( base::OnceCallback callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -@@ -1512,6 +1542,9 @@ void HistoryService::Cleanup() { +@@ -1487,6 +1517,9 @@ void HistoryService::Cleanup() { return; } @@ -589,7 +589,7 @@ diff --git a/components/history/core/browser/history_service.h b/components/hist // Triggers the backend to load if it hasn't already, and then returns whether // it's finished loading. // Note: Virtual needed for mocking. -@@ -1215,6 +1219,10 @@ class HistoryService : public KeyedService, +@@ -1194,6 +1198,10 @@ class HistoryService : public KeyedService, raw_ptr local_device_info_provider_ = nullptr; diff --git a/build/cromite_patches/Increase-number-of-autocomplete-matches-to-10.patch b/build/cromite_patches/Increase-number-of-autocomplete-matches-to-10.patch index 13ac70203ea94323bee47e9e665fa8da8f3eaca4..ee2cb75e54b942fcb4a617c55c3583ad53f79403 100644 --- a/build/cromite_patches/Increase-number-of-autocomplete-matches-to-10.patch +++ b/build/cromite_patches/Increase-number-of-autocomplete-matches-to-10.patch @@ -13,7 +13,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc --- a/components/omnibox/browser/autocomplete_result.cc +++ b/components/omnibox/browser/autocomplete_result.cc -@@ -92,10 +92,11 @@ constexpr size_t kMaxPedalMatchIndex = +@@ -97,10 +97,11 @@ constexpr size_t kMaxPedalMatchIndex = size_t AutocompleteResult::GetMaxMatches( bool is_zero_suggest, AutocompleteInput::FeaturedKeywordMode featured_keyword_mode) { diff --git a/build/cromite_patches/JIT-site-settings.patch b/build/cromite_patches/JIT-site-settings.patch index 049ac7c57a741bba896b572cb5d88269974938dd..8e1d19eda9d2cc5a4eec3646654178f0cd14ec45 100644 --- a/build/cromite_patches/JIT-site-settings.patch +++ b/build/cromite_patches/JIT-site-settings.patch @@ -22,8 +22,12 @@ Require: Content-settings-infrastructure.patch .../javascript_jit.grdp | 21 ++++ .../javascript_jit.inc | 12 +++ .../core/browser/content_settings_registry.cc | 2 +- + .../renderer_host/navigation_request.cc | 7 +- .../renderer_host/render_process_host_impl.cc | 3 + - 10 files changed, 130 insertions(+), 1 deletion(-) + content/browser/site_info.cc | 7 ++ + content/browser/url_info.cc | 6 ++ + content/browser/url_info.h | 3 + + 14 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-hdpi/permission_javascript_jit.png create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-mdpi/permission_javascript_jit.png create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-xhdpi/permission_javascript_jit.png @@ -275,7 +279,7 @@ new file mode 100644 diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc --- a/components/content_settings/core/browser/content_settings_registry.cc +++ b/components/content_settings/core/browser/content_settings_registry.cc -@@ -611,7 +611,7 @@ void ContentSettingsRegistry::Init() { +@@ -612,7 +612,7 @@ void ContentSettingsRegistry::Init() { ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY); Register(ContentSettingsType::JAVASCRIPT_JIT, "javascript-jit", @@ -284,10 +288,32 @@ diff --git a/components/content_settings/core/browser/content_settings_registry. /*allowlisted_primary_schemes=*/{}, /*valid_settings=*/{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK}, WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE, +diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc +--- a/content/browser/renderer_host/navigation_request.cc ++++ b/content/browser/renderer_host/navigation_request.cc +@@ -4091,12 +4091,17 @@ UrlInfo NavigationRequest::GetUrlInfo() { + // Compute the WebExposedIsolationInfo that will be bundled into UrlInfo. + auto web_exposed_isolation_info = ComputeWebExposedIsolationInfo(); + ++ url::Origin top_origin = frame_tree_node_ ++ ->current_frame_host() ++ ->GetMainFrame()->GetLastCommittedOrigin(); ++ + UrlInfoInit url_info_init(GetURL()); + url_info_init.WithOriginIsolationRequest(isolation_request) + .WithCOOPSiteIsolation(ShouldRequestSiteIsolationForCOOP()) + .WithWebExposedIsolationInfo(web_exposed_isolation_info) + .WithCrossOriginIsolationKey(cross_origin_isolation_key) +- .WithIsPdf(is_pdf_); ++ .WithIsPdf(is_pdf_) ++ .WithTopOrigin(top_origin); + + // Records in the UrlInfo if COOP: same-origin or COOP: restrict-properties + // was set, and from which origin. diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc -@@ -3295,6 +3295,9 @@ void RenderProcessHostImpl::AppendRendererCommandLine( +@@ -3299,6 +3299,9 @@ void RenderProcessHostImpl::AppendRendererCommandLine( if (IsJitDisabled()) { command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags, "--jitless"); @@ -297,4 +323,71 @@ diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content } else if (AreV8OptimizationsDisabled()) { command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags, "--disable-optimizing-compilers"); +diff --git a/content/browser/site_info.cc b/content/browser/site_info.cc +--- a/content/browser/site_info.cc ++++ b/content/browser/site_info.cc +@@ -265,6 +265,13 @@ SiteInfo SiteInfo::CreateInternal(const IsolationContext& isolation_context, + is_jitless = + is_jitless || GetContentClient()->browser()->IsJitDisabledForSite( + browser_context, lock_url); ++ if (!url_info.is_pdf && url_info.top_origin.has_value()) { ++ GURL top_origin = url_info.top_origin->GetURL(); ++ if (!GetContentClient()->browser()->IsJitDisabledForSite( ++ browser_context, top_origin)) { ++ is_jitless = false; ++ } ++ } + are_v8_optimizations_disabled = + GetContentClient()->browser()->AreV8OptimizationsDisabledForSite( + browser_context, lock_url); +diff --git a/content/browser/url_info.cc b/content/browser/url_info.cc +--- a/content/browser/url_info.cc ++++ b/content/browser/url_info.cc +@@ -24,6 +24,7 @@ UrlInfo::UrlInfo(const UrlInfoInit& init) + is_prefetch_with_cross_site_contamination( + init.is_prefetch_with_cross_site_contamination_), + origin(init.origin_), ++ top_origin(init.top_origin_), + is_sandboxed(init.is_sandboxed_), + unique_sandbox_id(init.unique_sandbox_id_), + storage_partition_config(init.storage_partition_config_), +@@ -138,6 +139,11 @@ UrlInfoInit& UrlInfoInit::WithOrigin(const url::Origin& origin) { + return *this; + } + ++UrlInfoInit& UrlInfoInit::WithTopOrigin(const url::Origin& origin) { ++ top_origin_ = origin; ++ return *this; ++} ++ + UrlInfoInit& UrlInfoInit::WithSandbox(bool is_sandboxed) { + is_sandboxed_ = is_sandboxed; + return *this; +diff --git a/content/browser/url_info.h b/content/browser/url_info.h +--- a/content/browser/url_info.h ++++ b/content/browser/url_info.h +@@ -168,6 +168,7 @@ struct CONTENT_EXPORT UrlInfo { + // and origin checks that require a UrlInfo. Investigate whether there's a + // cleaner way to organize these checks. See https://crbug.com/1320402. + std::optional origin; ++ std::optional top_origin; + + // If url is being loaded in a frame that is in a origin-restricted sandboxed, + // then this flag will be true. +@@ -244,6 +245,7 @@ class CONTENT_EXPORT UrlInfoInit { + UrlInfoInit& WithCOOPSiteIsolation(bool requests_coop_isolation); + UrlInfoInit& WithCrossSitePrefetchContamination(bool contaminated); + UrlInfoInit& WithOrigin(const url::Origin& origin); ++ UrlInfoInit& WithTopOrigin(const url::Origin& origin); + UrlInfoInit& WithSandbox(bool is_sandboxed); + UrlInfoInit& WithUniqueSandboxId(int unique_sandbox_id); + UrlInfoInit& WithStoragePartitionConfig( +@@ -269,6 +271,7 @@ class CONTENT_EXPORT UrlInfoInit { + bool requests_coop_isolation_ = false; + bool is_prefetch_with_cross_site_contamination_ = false; + std::optional origin_; ++ std::optional top_origin_; + bool is_sandboxed_ = false; + int64_t unique_sandbox_id_ = UrlInfo::kInvalidUniqueSandboxId; + std::optional storage_partition_config_; -- diff --git a/build/cromite_patches/Keep-empty-tabs-between-sessions.patch b/build/cromite_patches/Keep-empty-tabs-between-sessions.patch index cd680b2357f4137d860c52976dbc8fee68dee9e6..1d437500ebc9e720b03b928aa8b1938cdbda485a 100644 --- a/build/cromite_patches/Keep-empty-tabs-between-sessions.patch +++ b/build/cromite_patches/Keep-empty-tabs-between-sessions.patch @@ -5,13 +5,26 @@ Subject: Keep empty tabs between sessions Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- + .../browser/multiwindow/MultiWindowUtils.java | 2 +- .../browser/tabmodel/TabPersistentStore.java | 15 --------------- - 1 file changed, 15 deletions(-) + 2 files changed, 1 insertion(+), 16 deletions(-) +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java +@@ -915,7 +915,7 @@ public class MultiWindowUtils implements ActivityStateListener { + for (TabModel model : models) { + for (int i = 0; i < model.getCount(); i++) { + Tab tab = model.getTabAt(i); +- if (!TabPersistentStore.shouldSkipTab(tab)) { ++ if ((true)) { + totalCount++; + } + } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java -@@ -1229,27 +1229,12 @@ public class TabPersistentStore { +@@ -1248,27 +1248,12 @@ public class TabPersistentStore { continue; } @@ -28,7 +41,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPer return modelInfo; } -- private static boolean shouldSkipTab(@NonNull Tab tab) { +- public static boolean shouldSkipTab(@NonNull Tab tab) { - boolean isNtp = tab.isNativePage() && UrlUtilities.isNtpUrl(tab.getUrl()); - if (!isNtp) return false; - diff --git a/build/cromite_patches/Keep-flag-to-allow-screenshots-in-Incognito-mode.patch b/build/cromite_patches/Keep-flag-to-allow-screenshots-in-Incognito-mode.patch index ca9c176164598b092aca97f1ce2a70cc7b487c34..3d7bd359a32f5633171bba4c3936e9e5ae284f26 100644 --- a/build/cromite_patches/Keep-flag-to-allow-screenshots-in-Incognito-mode.patch +++ b/build/cromite_patches/Keep-flag-to-allow-screenshots-in-Incognito-mode.patch @@ -28,7 +28,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/Incog diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json -@@ -5223,9 +5223,9 @@ +@@ -5234,9 +5234,9 @@ "expiry_milestone": 136 }, { diff --git a/build/cromite_patches/Logcat-crash-reports-UI.patch b/build/cromite_patches/Logcat-crash-reports-UI.patch index e32493bb2fd403f6b3c9bbe9f33ba66eb50f9e36..ab9a9fbc967835ec6043bb7271bfa4dd44ebafc6 100644 --- a/build/cromite_patches/Logcat-crash-reports-UI.patch +++ b/build/cromite_patches/Logcat-crash-reports-UI.patch @@ -153,7 +153,7 @@ diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/ diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn -@@ -663,6 +663,7 @@ static_library("ui") { +@@ -666,6 +666,7 @@ static_library("ui") { "//third_party/re2", "//third_party/webrtc_overrides:webrtc_component", "//third_party/zlib", diff --git a/build/cromite_patches/Modify-default-preferences.patch b/build/cromite_patches/Modify-default-preferences.patch index 3531a4202c0804f1dac0676239986f63005466b6..5c63d286fe1aa88add4b9d14e6e6a412d1db1bb7 100644 --- a/build/cromite_patches/Modify-default-preferences.patch +++ b/build/cromite_patches/Modify-default-preferences.patch @@ -5,7 +5,7 @@ Subject: Modify default preferences License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- chrome/browser/about_flags.cc | 1 + - .../background/background_mode_manager.cc | 2 +- + .../extensions/background_mode_manager.cc | 2 +- .../browser/chrome_content_browser_client.cc | 2 +- chrome/browser/preloading/preloading_prefs.h | 2 +- chrome/browser/profiles/profile.cc | 2 +- @@ -31,7 +31,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc -@@ -81,6 +81,7 @@ +@@ -82,6 +82,7 @@ #include "components/autofill/core/common/autofill_payments_features.h" #include "components/autofill/core/common/autofill_switches.h" #include "components/autofill/core/common/autofill_util.h" @@ -39,10 +39,10 @@ diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc #include "components/browser_sync/browser_sync_switches.h" #include "components/collaboration/public/features.h" #include "components/commerce/core/commerce_feature_list.h" -diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc ---- a/chrome/browser/background/background_mode_manager.cc -+++ b/chrome/browser/background/background_mode_manager.cc -@@ -358,7 +358,7 @@ BackgroundModeManager::~BackgroundModeManager() { +diff --git a/chrome/browser/background/extensions/background_mode_manager.cc b/chrome/browser/background/extensions/background_mode_manager.cc +--- a/chrome/browser/background/extensions/background_mode_manager.cc ++++ b/chrome/browser/background/extensions/background_mode_manager.cc +@@ -364,7 +364,7 @@ BackgroundModeManager::~BackgroundModeManager() { // static void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) { @@ -54,7 +54,7 @@ diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/brows diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc -@@ -1547,7 +1547,7 @@ void ChromeContentBrowserClient::RegisterLocalStatePrefs( +@@ -1554,7 +1554,7 @@ void ChromeContentBrowserClient::RegisterLocalStatePrefs( void ChromeContentBrowserClient::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref(prefs::kDisable3DAPIs, false); @@ -154,7 +154,7 @@ diff --git a/components/bookmarks/browser/bookmark_utils.cc b/components/bookmar #include "components/bookmarks/browser/scoped_group_bookmark_actions.h" #include "components/bookmarks/common/bookmark_pref_names.h" #include "components/pref_registry/pref_registry_syncable.h" -@@ -462,11 +463,13 @@ bool DoesBookmarkContainWords(const std::u16string& title, +@@ -487,11 +488,13 @@ bool DoesBookmarkContainWords(const std::u16string& title, void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref( @@ -291,7 +291,7 @@ diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.cc b/compo diff --git a/components/signin/internal/identity_manager/primary_account_manager.cc b/components/signin/internal/identity_manager/primary_account_manager.cc --- a/components/signin/internal/identity_manager/primary_account_manager.cc +++ b/components/signin/internal/identity_manager/primary_account_manager.cc -@@ -297,7 +297,7 @@ void PrimaryAccountManager::RegisterProfilePrefs(PrefRegistrySimple* registry) { +@@ -341,7 +341,7 @@ void PrimaryAccountManager::RegisterProfilePrefs(PrefRegistrySimple* registry) { prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn, std::string()); registry->RegisterStringPref( prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn, std::string()); diff --git a/build/cromite_patches/Move-navigation-bar-to-bottom.patch b/build/cromite_patches/Move-navigation-bar-to-bottom.patch index b29824e3ab92458b866284d96119da54153048fc..0aa65ceded1ecff5d71d14f0a795df9b54aacf27 100644 --- a/build/cromite_patches/Move-navigation-bar-to-bottom.patch +++ b/build/cromite_patches/Move-navigation-bar-to-bottom.patch @@ -154,7 +154,7 @@ diff --git a/cc/input/browser_controls_offset_manager.cc b/cc/input/browser_cont diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc -@@ -4819,6 +4819,9 @@ bool LayerTreeHostImpl::AnimateBrowserControls(base::TimeTicks time) { +@@ -4867,6 +4867,9 @@ bool LayerTreeHostImpl::AnimateBrowserControls(base::TimeTicks time) { if (scroll_delta.IsZero()) return false; @@ -167,7 +167,7 @@ diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java -@@ -50,6 +50,7 @@ import org.chromium.components.sensitive_content.SensitiveContentFeatures; +@@ -49,6 +49,7 @@ import org.chromium.components.sensitive_content.SensitiveContentFeatures; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModelChangeProcessor; @@ -175,30 +175,30 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser import java.util.List; -@@ -174,7 +175,8 @@ public class TabGroupUiCoordinator implements TabGroupUiMediator.ResetHandler, T +@@ -171,7 +172,8 @@ public class TabGroupUiCoordinator implements TabGroupUiMediator.ResetHandler, T public void initializeWithNative( Activity activity, BottomControlsCoordinator.BottomControlsVisibilityController visibilityController, -- Callback onModelTokenChange) { -+ Callback onModelTokenChange, +- Callback onSnapshotTokenChange) { ++ Callback onSnapshotTokenChange, + TopUiThemeColorProvider topUiThemeColorProvider, ObservableSupplier tabSupplier) { + ObservableSupplierImpl tabStripTokenSupplier = new ObservableSupplierImpl<>(); + var currentTabGroupModelFilterSupplier = - mTabModelSelector - .getTabGroupModelFilterProvider() -@@ -257,7 +259,8 @@ public class TabGroupUiCoordinator implements TabGroupUiMediator.ResetHandler, T - mOmniboxFocusStateSupplier, +@@ -259,7 +261,8 @@ public class TabGroupUiCoordinator implements TabGroupUiMediator.ResetHandler, T sharedImageTilesCoordinator, mThemeColorProvider, -- mBackgroundColorSupplier); -+ mBackgroundColorSupplier, + onSnapshotTokenChange, +- tabStripTokenSupplier); ++ tabStripTokenSupplier, + topUiThemeColorProvider, tabSupplier); - Profile profile = mTabModelSelector.getModel(false).getProfile(); - CollaborationService collaborationService = + if (serviceStatus.isAllowedToJoin()) { + mTabBubbler = diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java -@@ -65,6 +65,12 @@ import org.chromium.url.GURL; +@@ -74,6 +74,12 @@ import org.chromium.url.GURL; import java.util.List; import java.util.Objects; @@ -211,9 +211,9 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser /** A mediator for the TabGroupUi. Responsible for managing the internal state of the component. */ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, TintObserver { -@@ -104,6 +110,11 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, - private final ThemeColorProvider mThemeColorProvider; - private final ObservableSupplierImpl mBackgroundColorSupplier; +@@ -144,6 +150,11 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, + private final Callback mOnSnapshotTokenChange; + private final ObservableSupplier mChildTokenSupplier; + private final TopUiThemeColorProvider mTopUiThemeColorProvider; + @@ -223,18 +223,18 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser // These should only be used when regular (non-incognito) tabs are set in the model. private final @Nullable SharedImageTilesCoordinator mSharedImageTilesCoordinator; private final @Nullable TransitiveSharedGroupObserver mTransitiveSharedGroupObserver; -@@ -134,7 +145,9 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, - ObservableSupplier omniboxFocusStateSupplier, +@@ -174,7 +185,9 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, SharedImageTilesCoordinator sharedImageTilesCoordinator, ThemeColorProvider themeColorProvider, -- ObservableSupplierImpl backgroundColorSupplier) { -+ ObservableSupplierImpl backgroundColorSupplier, + Callback onSnapshotTokenChange, +- ObservableSupplierImpl childTokenSupplier) { ++ ObservableSupplierImpl childTokenSupplier, + TopUiThemeColorProvider topUiThemeColorProvider, ObservableSupplier tabSupplier) { + mTopUiThemeColorProvider = topUiThemeColorProvider; mResetHandler = resetHandler; mModel = model; mTabModelSelector = tabModelSelector; -@@ -182,11 +195,30 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, +@@ -227,11 +240,30 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, mIsShowingHub = true; } @@ -256,7 +256,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser + } + }, activityTabCallback); + - // register for tab model + // Register for tab model. mTabModelObserver = new TabModelObserver() { @Override @@ -265,7 +265,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser resetTabStrip(); } -@@ -211,6 +243,8 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, +@@ -253,6 +285,8 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, @Override public void restoreCompleted() { resetTabStrip(); @@ -274,8 +274,8 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser } @Override -@@ -309,6 +343,8 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, - mModel.set(TabGroupUiProperties.IS_MAIN_CONTENT_VISIBLE, true); +@@ -346,6 +380,8 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, + mModel.set(TabGroupUiProperties.WIDTH_PX_CALLBACK, mWidthPxSupplier::set); resetTabStrip(); + mTabObserver.triggerWithCurrentTab(); @@ -283,8 +283,8 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser mHandleBackPressChangedSupplier = handleBackPressChangedSupplier; if (mTabGridDialogControllerSupplier != null) { mTabGridDialogControllerSupplier.onAvailable( -@@ -337,6 +373,18 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, - mModel.set(TabGroupUiProperties.TINT, mThemeColorProvider.getTint()); +@@ -374,6 +410,18 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, + mModel.set(TINT, mThemeColorProvider.getTint()); } + /** @@ -302,7 +302,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser private void setupToolbarButtons() { View.OnClickListener showGroupDialogOnClickListener = view -> { -@@ -515,6 +563,7 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, +@@ -561,6 +609,7 @@ public class TabGroupUiMediator implements BackPressHandler, ThemeColorObserver, } public void destroy() { @@ -313,35 +313,35 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiProperties.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiProperties.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiProperties.java -@@ -9,6 +9,7 @@ import android.view.View.OnClickListener; - - import org.chromium.ui.modelutil.PropertyKey; - import org.chromium.ui.modelutil.PropertyModel; +@@ -12,6 +12,7 @@ import org.chromium.ui.modelutil.PropertyKey; + import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey; + import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey; + import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; +import android.content.res.ColorStateList; /** {@link PropertyKey} list for the TabGroupUi. */ class TabGroupUiProperties { -@@ -34,6 +35,9 @@ class TabGroupUiProperties { - public static final PropertyModel.WritableObjectPropertyKey INITIAL_SCROLL_INDEX = - new PropertyModel.WritableObjectPropertyKey<>(true); +@@ -39,6 +40,9 @@ class TabGroupUiProperties { + public static final WritableObjectPropertyKey> WIDTH_PX_CALLBACK = + new WritableObjectPropertyKey<>(); -+ public static final PropertyModel.WritableIntPropertyKey PRIMARY_COLOR = -+ new PropertyModel.WritableIntPropertyKey(); ++ public static final WritableObjectPropertyKey PRIMARY_COLOR = ++ new WritableObjectPropertyKey<>(); + public static final PropertyKey[] ALL_KEYS = new PropertyKey[] { SHOW_GROUP_DIALOG_ON_CLICK_LISTENER, -@@ -44,5 +48,6 @@ class TabGroupUiProperties { - IMAGE_TILES_CONTAINER_VISIBLE, - INITIAL_SCROLL_INDEX, +@@ -50,5 +54,6 @@ class TabGroupUiProperties { TINT, + INITIAL_SCROLL_INDEX, + WIDTH_PX_CALLBACK, + PRIMARY_COLOR }; } diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java -@@ -21,6 +21,10 @@ import androidx.core.widget.ImageViewCompat; +@@ -19,6 +19,10 @@ import org.chromium.base.Callback; import org.chromium.chrome.tab_ui.R; import org.chromium.ui.widget.ChromeImageView; @@ -352,7 +352,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser /** Toolbar for the bottom tab strip see {@link TabGroupUiCoordinator}. */ public class TabGroupUiToolbarView extends FrameLayout { private ChromeImageView mNewTabButton; -@@ -96,6 +100,19 @@ public class TabGroupUiToolbarView extends FrameLayout { +@@ -93,6 +97,19 @@ public class TabGroupUiToolbarView extends FrameLayout { mFadingEdgeEnd.setColorFilter(color, PorterDuff.Mode.SRC_IN); } @@ -369,21 +369,21 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser + setTint(tint); + } + - void setTint(ColorStateList tint) { + /* package */ void setTint(ColorStateList tint) { ImageViewCompat.setImageTintList(mShowGroupDialogButton, tint); ImageViewCompat.setImageTintList(mNewTabButton, tint); diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiViewBinder.java --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiViewBinder.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiViewBinder.java -@@ -12,6 +12,7 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiPropert - import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiProperties.SHOW_GROUP_DIALOG_BUTTON_VISIBLE; +@@ -13,6 +13,7 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiPropert import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiProperties.SHOW_GROUP_DIALOG_ON_CLICK_LISTENER; import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiProperties.TINT; + import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiProperties.WIDTH_PX_CALLBACK; +import static org.chromium.chrome.browser.tasks.tab_management.TabGroupUiProperties.PRIMARY_COLOR; import android.view.View; -@@ -50,6 +51,8 @@ class TabGroupUiViewBinder { +@@ -51,6 +52,8 @@ class TabGroupUiViewBinder { } else if (NEW_TAB_BUTTON_ON_CLICK_LISTENER == propertyKey) { viewHolder.toolbarView.setNewTabButtonOnClickListener( model.get(NEW_TAB_BUTTON_ON_CLICK_LISTENER)); @@ -425,7 +425,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -@@ -132,6 +140,128 @@ public class TabListCoordinator +@@ -131,6 +139,128 @@ public class TabListCoordinator private int mAwaitingTabId = Tab.INVALID_TAB_ID; private @TabActionState int mTabActionState; @@ -554,7 +554,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser /** * Construct a coordinator for UI that shows a list of tabs. * -@@ -353,6 +483,12 @@ public class TabListCoordinator +@@ -348,6 +478,12 @@ public class TabListCoordinator checkAwaitingLayout(); } }; @@ -567,7 +567,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser mRecyclerView.setLayoutManager(gridLayoutManager); mMediator.registerOrientationListener(gridLayoutManager); mMediator.updateSpanCount( -@@ -362,7 +498,7 @@ public class TabListCoordinator +@@ -357,7 +493,7 @@ public class TabListCoordinator Rect frame = new Rect(); mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); updateGridCardLayout(frame.width()); @@ -576,7 +576,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser LinearLayoutManager layoutManager = new LinearLayoutManager( activity, -@@ -377,6 +513,25 @@ public class TabListCoordinator +@@ -372,6 +508,25 @@ public class TabListCoordinator } }; mRecyclerView.setLayoutManager(layoutManager); @@ -602,7 +602,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser } mMediator.setRecyclerViewItemAnimationToggle(mRecyclerView::setDisableItemAnimations); } -@@ -398,7 +553,7 @@ public class TabListCoordinator +@@ -392,7 +547,7 @@ public class TabListCoordinator if (hasEmptyView) { mTabListEmptyCoordinator = new TabListEmptyCoordinator( @@ -611,7 +611,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser } configureRecyclerViewTouchHelpers(); -@@ -691,6 +846,9 @@ public class TabListCoordinator +@@ -680,6 +835,9 @@ public class TabListCoordinator void prepareTabSwitcherPaneView() { registerLayoutChangeListener(); mRecyclerView.setupCustomItemAnimator(); @@ -853,15 +853,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/Comp diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java -@@ -39,6 +39,7 @@ import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateMa - import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection; +@@ -41,6 +41,7 @@ import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.Sc import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler; import org.chromium.ui.resources.dynamics.DynamicResourceLoader; + import org.chromium.ui.util.XrUtils; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import java.util.List; -@@ -154,7 +155,9 @@ public class LayoutManagerChrome extends LayoutManagerImpl +@@ -157,7 +158,9 @@ public class LayoutManagerChrome extends LayoutManagerImpl @Override public SwipeHandler createToolbarSwipeHandler(boolean supportsSwipeToShowTabSwitcher) { @@ -872,7 +872,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layo } @Override -@@ -364,9 +367,11 @@ public class LayoutManagerChrome extends LayoutManagerImpl +@@ -372,9 +375,11 @@ public class LayoutManagerChrome extends LayoutManagerImpl private static final float SWIPE_RANGE_DEG = 25; private final boolean mSupportsSwipeToShowTabSwitcher; @@ -885,7 +885,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layo } @Override -@@ -473,7 +478,8 @@ public class LayoutManagerChrome extends LayoutManagerImpl +@@ -481,7 +486,8 @@ public class LayoutManagerChrome extends LayoutManagerImpl return direction == showTabSwitcherScrollDirection || direction == ScrollDirection.LEFT @@ -957,7 +957,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layo diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/ScrollDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/ScrollDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/ScrollDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/ScrollDelegate.java -@@ -258,7 +258,7 @@ public class ScrollDelegate { +@@ -260,7 +260,7 @@ public class ScrollDelegate { boolean useUnadjustedScrollOffset = isRtl != isLeft; float scrollOffset = mScrollOffset; @@ -969,15 +969,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java -@@ -33,6 +33,7 @@ import androidx.appcompat.content.res.AppCompatResources; +@@ -32,6 +32,7 @@ import androidx.appcompat.content.res.AppCompatResources; + import org.chromium.base.Callback; - import org.chromium.base.Log; import org.chromium.base.metrics.RecordUserAction; +import org.chromium.base.supplier.Supplier; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.cc.input.BrowserControlsState; -@@ -105,6 +106,8 @@ import org.chromium.ui.modaldialog.ModalDialogManager; +@@ -104,6 +105,8 @@ import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.resources.ResourceManager; import org.chromium.ui.util.ColorUtils; import org.chromium.url.GURL; @@ -986,7 +986,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over import java.util.ArrayList; import java.util.List; -@@ -257,12 +260,16 @@ public class StripLayoutHelperManager +@@ -254,12 +257,16 @@ public class StripLayoutHelperManager // Drag-Drop @Nullable private TabDragSource mTabDragSource; @@ -1003,7 +1003,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over if (mModelSelectorButton != null && mModelSelectorButton.onDown(x, y, fromMouse, buttons)) { return; -@@ -288,6 +295,7 @@ public class StripLayoutHelperManager +@@ -285,6 +292,7 @@ public class StripLayoutHelperManager if (DragDropGlobalState.hasValue()) { return; } @@ -1011,7 +1011,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over if (mModelSelectorButton != null) { mModelSelectorButton.drag(x, y); } -@@ -299,6 +307,7 @@ public class StripLayoutHelperManager +@@ -296,6 +304,7 @@ public class StripLayoutHelperManager if (DragDropGlobalState.hasValue()) { return; } @@ -1019,7 +1019,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over long time = time(); if (mModelSelectorButton != null && mModelSelectorButton.click(x, y, fromMouse, buttons)) { -@@ -313,6 +322,7 @@ public class StripLayoutHelperManager +@@ -310,6 +319,7 @@ public class StripLayoutHelperManager if (DragDropGlobalState.hasValue()) { return; } @@ -1027,7 +1027,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over getActiveStripLayoutHelper().fling(time(), x, y, velocityX, velocityY); } -@@ -321,6 +331,7 @@ public class StripLayoutHelperManager +@@ -318,6 +328,7 @@ public class StripLayoutHelperManager if (DragDropGlobalState.hasValue()) { return; } @@ -1035,7 +1035,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over getActiveStripLayoutHelper().onLongPress(time(), x, y); } -@@ -436,7 +447,8 @@ public class StripLayoutHelperManager +@@ -433,7 +444,8 @@ public class StripLayoutHelperManager @Nullable DesktopWindowStateManager desktopWindowStateManager, ActionConfirmationManager actionConfirmationManager, ModalDialogManager modalDialogManager, @@ -1045,7 +1045,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over Resources res = context.getResources(); mUpdateHost = updateHost; mLayerTitleCacheSupplier = layerTitleCacheSupplier; -@@ -533,6 +545,8 @@ public class StripLayoutHelperManager +@@ -530,6 +542,8 @@ public class StripLayoutHelperManager modalDialogManager, dataSharingTabManager, () -> getStripVisibilityState() == StripVisibilityState.VISIBLE); @@ -1054,7 +1054,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over tabHoverCardViewStub.setOnInflateListener( (viewStub, view) -> { -@@ -780,6 +794,10 @@ public class StripLayoutHelperManager +@@ -777,6 +791,10 @@ public class StripLayoutHelperManager // layer should be a negative value. yOffset -= getHeight(); } @@ -1065,7 +1065,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over mTabStripTreeProvider.pushAndUpdateStrip( this, mLayerTitleCacheSupplier.get(), -@@ -793,7 +811,9 @@ public class StripLayoutHelperManager +@@ -790,7 +808,9 @@ public class StripLayoutHelperManager mStripTransitionScrimOpacity, mLeftPadding, mRightPadding, @@ -1076,7 +1076,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/over return mTabStripTreeProvider; } -@@ -859,11 +879,21 @@ public class StripLayoutHelperManager +@@ -856,11 +876,21 @@ public class StripLayoutHelperManager mRightPadding, mTopPadding); @@ -1226,7 +1226,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/Brow /** A class that manages browser control visibility and positioning. */ public class BrowserControlsManager implements ActivityStateListener, BrowserControlsSizer { // The amount of time to delay the control show request after returning to a once visible -@@ -485,6 +487,14 @@ public class BrowserControlsManager implements ActivityStateListener, BrowserCon +@@ -544,6 +546,14 @@ public class BrowserControlsManager implements ActivityStateListener, BrowserCon return mTopControlsHeight; } @@ -1241,7 +1241,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/Brow @Override public int getTopControlsMinHeight() { return mTopControlsMinHeight; -@@ -581,6 +591,8 @@ public class BrowserControlsManager implements ActivityStateListener, BrowserCon +@@ -645,6 +655,8 @@ public class BrowserControlsManager implements ActivityStateListener, BrowserCon @Override public float getTopVisibleContentOffset() { @@ -1293,7 +1293,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/messages/Messag diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenter.java --- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenter.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenter.java -@@ -283,7 +283,7 @@ public class ChromeTabModalPresenter extends TabModalPresenter +@@ -338,7 +338,7 @@ public class ChromeTabModalPresenter extends TabModalPresenter Resources resources, BrowserControlsStateProvider provider) { int scrimVerticalMargin = resources.getDimensionPixelSize(R.dimen.tab_modal_scrim_vertical_margin); @@ -1305,15 +1305,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/Chr diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java -@@ -115,6 +115,7 @@ import org.chromium.content_public.browser.NavigationController; +@@ -116,6 +116,7 @@ import org.chromium.content_public.browser.NavigationController; import org.chromium.ui.base.DeviceFormFactor; import org.chromium.ui.base.WindowAndroid; import org.chromium.url.GURL; +import org.chromium.chrome.browser.flags.ChromeFeatureList; - import java.util.List; - -@@ -761,11 +762,16 @@ public class NewTabPage + import androidx.annotation.NonNull; + import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; +@@ -772,11 +773,16 @@ public class NewTabPage final int topControlsDistanceToRest = mBrowserControlsStateProvider.getContentOffset() - mBrowserControlsStateProvider.getTopControlsHeight(); @@ -1332,7 +1332,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. if (topMargin != layoutParams.topMargin || bottomMargin != layoutParams.bottomMargin) { layoutParams.topMargin = topMargin; -@@ -781,9 +787,7 @@ public class NewTabPage +@@ -792,9 +798,7 @@ public class NewTabPage * strip. */ private int getToolbarExtraYOffset() { @@ -1448,7 +1448,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/RecentTabsP diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java -@@ -81,6 +81,11 @@ import org.chromium.url.GURL; +@@ -84,6 +84,11 @@ import org.chromium.url.GURL; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1460,7 +1460,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/Se import java.lang.ref.WeakReference; /** Queries the user's default search engine and shows autocomplete suggestions. */ -@@ -286,6 +291,12 @@ public class SearchActivity extends AsyncInitializationActivity +@@ -294,6 +299,12 @@ public class SearchActivity extends AsyncInitializationActivity // Build the search box. mSearchBox = contentView.findViewById(R.id.search_location_bar); mAnchorView = contentView.findViewById(R.id.toolbar); @@ -1472,7 +1472,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/Se + } // Update the status bar's color based on the toolbar color. - Drawable anchorViewBackground = mAnchorView.getBackground(); + setStatusAndNavBarColors(); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java @@ -1540,9 +1540,9 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/status_indicato diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java -@@ -194,6 +194,9 @@ import org.chromium.url.GURL; +@@ -193,6 +193,9 @@ import org.chromium.url.GURL; + import java.util.List; - import java.util.Objects; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import android.view.Gravity; @@ -1550,7 +1550,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar /** * Contains logic for managing the toolbar visual component. This class manages the interactions * with the rest of the application to ensure the toolbar is always visually up to date. -@@ -756,6 +759,17 @@ public class ToolbarManager +@@ -752,6 +755,17 @@ public class ToolbarManager }, AlwaysIncognitoLinkInterceptor.isAlwaysIncognito()); mControlContainer = controlContainer; mToolbarHairline = mControlContainer.findViewById(R.id.toolbar_hairline); @@ -1568,7 +1568,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar mBookmarkModelSupplier = bookmarkModelSupplier; mBookmarkModelSupplier.addObserver(mBookmarkModelSupplierObserver); -@@ -1316,6 +1330,7 @@ public class ToolbarManager +@@ -1306,6 +1320,7 @@ public class ToolbarManager // the height won't be measured by the background image. if (mControlContainer.getBackground() == null) { setControlContainerTopMargin(getToolbarExtraYOffset()); @@ -1576,7 +1576,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar } else if (mLayoutChangeListener == null) { mLayoutChangeListener = (view, -@@ -1329,6 +1344,7 @@ public class ToolbarManager +@@ -1319,6 +1334,7 @@ public class ToolbarManager oldBottom) -> { if (mControlContainer.getBackground() == null) { setControlContainerTopMargin(getToolbarExtraYOffset()); @@ -1584,7 +1584,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar mControlContainer.removeOnLayoutChangeListener( mLayoutChangeListener); mLayoutChangeListener = null; -@@ -1766,9 +1782,21 @@ public class ToolbarManager +@@ -1761,9 +1777,21 @@ public class ToolbarManager return ((LocationBarCoordinator) mLocationBar).getUrlBarViewRectProvider(); } @@ -1607,7 +1607,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar assert mTabGroupUiOneshotSupplier == null; ThemeColorProvider bottomUiThemeColorProvider = new BottomUiThemeColorProvider( -@@ -1782,7 +1810,7 @@ public class ToolbarManager +@@ -1777,7 +1805,7 @@ public class ToolbarManager mActivityTabProvider, mTabModelSelector, mActivity, @@ -1616,7 +1616,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar mBrowserControlsSizer, mScrimManager, mOmniboxFocusStateSupplier, -@@ -1807,7 +1835,7 @@ public class ToolbarManager +@@ -1802,7 +1830,7 @@ public class ToolbarManager mControlsVisibilityDelegate, mFullscreenManager, mEdgeToEdgeControllerSupplier, @@ -1625,7 +1625,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar bottomControlsContentDelegateSupplier, mTabObscuringHandler, mOverlayPanelVisibilitySupplier, -@@ -1815,7 +1843,8 @@ public class ToolbarManager +@@ -1810,7 +1838,8 @@ public class ToolbarManager /* readAloudRestoringSupplier= */ () -> { final var readAloud = mReadAloudControllerSupplier.get(); return readAloud != null && readAloud.isRestoringPlayer(); @@ -1686,15 +1686,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/Statu import androidx.annotation.ColorInt; import androidx.annotation.Nullable; -@@ -17,6 +18,7 @@ import org.chromium.base.CallbackController; - import org.chromium.base.supplier.ObservableSupplier; +@@ -18,6 +19,7 @@ import org.chromium.base.supplier.ObservableSupplier; + import org.chromium.base.supplier.OneshotSupplier; import org.chromium.chrome.R; import org.chromium.chrome.browser.ActivityTabProvider; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.layouts.LayoutManager; import org.chromium.chrome.browser.layouts.LayoutStateProvider; import org.chromium.chrome.browser.layouts.LayoutStateProvider.LayoutStateObserver; -@@ -482,6 +484,12 @@ public class StatusBarColorController +@@ -509,6 +511,12 @@ public class StatusBarColorController UiUtils.setStatusBarIconColor(root, needsDarkStatusBarIcons); UiUtils.setStatusBarColor(window, color); } @@ -1784,7 +1784,7 @@ diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java --- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java +++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java -@@ -271,7 +271,7 @@ public class BottomControlsStacker implements BrowserControlsStateProvider.Obser +@@ -273,7 +273,7 @@ public class BottomControlsStacker implements BrowserControlsStateProvider.Obser /** * @return {@link BrowserControlsStateProvider} instance in the current Activity. */ @@ -1796,15 +1796,15 @@ diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrom diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java --- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java +++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java -@@ -8,6 +8,7 @@ import android.graphics.Rect; - +@@ -9,6 +9,7 @@ import android.graphics.Rect; import org.chromium.base.supplier.DestroyableObservableSupplier; import org.chromium.base.supplier.ObservableSupplierImpl; + import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.flags.ChromeFeatureList; /** * An implementation of {@link DestroyableObservableSupplier} that monitors changes to browser -@@ -61,6 +62,10 @@ public class BrowserControlsMarginSupplier extends ObservableSupplierImpl +@@ -63,6 +64,10 @@ public class BrowserControlsMarginSupplier extends ObservableSupplierImpl int bottomMargin = mBrowserControlsStateProvider.getBottomControlsHeight() - mBrowserControlsStateProvider.getBottomControlOffset(); @@ -1818,7 +1818,7 @@ diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrom diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsStateProvider.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsStateProvider.java --- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsStateProvider.java +++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsStateProvider.java -@@ -115,6 +115,12 @@ public interface BrowserControlsStateProvider { +@@ -119,6 +119,12 @@ public interface BrowserControlsStateProvider { */ int getTopControlsHeight(); @@ -1865,7 +1865,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse #include "chrome/browser/android/webapk/webapk_features.h" #include "chrome/browser/browser_features.h" #include "chrome/browser/flags/android/chrome_session_state.h" -@@ -295,6 +296,8 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -296,6 +297,8 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kNavBarColorMatchesTabBackground, &kNewTabSearchEngineUrlAndroid, &kNewTabPageAndroidTriggerForPrerender2, @@ -1877,7 +1877,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -406,6 +406,10 @@ public abstract class ChromeFeatureList { +@@ -415,6 +415,10 @@ public abstract class ChromeFeatureList { public static final String NEW_TAB_PAGE_ANDROID_TRIGGER_FOR_PRERENDER2 = "NewTabPageAndroidTriggerForPrerender2"; public static final String NOTIFICATION_ONE_TAP_UNSUBSCRIBE = "NotificationOneTapUnsubscribe"; @@ -1888,7 +1888,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f public static final String NOTIFICATION_PERMISSION_VARIANT = "NotificationPermissionVariant"; public static final String NOTIFICATION_PERMISSION_BOTTOM_SHEET = "NotificationPermissionBottomSheet"; -@@ -765,6 +769,10 @@ public abstract class ChromeFeatureList { +@@ -802,6 +806,10 @@ public abstract class ChromeFeatureList { PRICE_INSIGHTS, /* defaultValue= */ false, /* defaultValueInTests= */ true); public static final CachedFlag sOptimizationGuidePushNotifications = newCachedFlag(OPTIMIZATION_GUIDE_PUSH_NOTIFICATIONS, true); @@ -1899,7 +1899,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f public static final CachedFlag sPaintPreviewDemo = newCachedFlag(PAINT_PREVIEW_DEMO, false); public static final CachedFlag sPostGetMyMemoryStateToBackground = newCachedFlag(POST_GET_MEMORY_PRESSURE_TO_BACKGROUND, true); -@@ -921,6 +929,8 @@ public abstract class ChromeFeatureList { +@@ -957,6 +965,8 @@ public abstract class ChromeFeatureList { sPriceChangeModule, sPriceInsights, sOptimizationGuidePushNotifications, @@ -2105,7 +2105,7 @@ diff --git a/chrome/browser/hub/internal/android/res/layout/hub_toolbar_layout.x diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc -@@ -2364,7 +2364,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, +@@ -2258,7 +2258,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, #if BUILDFLAG(IS_ANDROID) registry->RegisterBooleanPref(prefs::kVirtualKeyboardResizesLayoutByDefault, @@ -2117,7 +2117,7 @@ diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browse diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenu.java -@@ -12,6 +12,7 @@ import android.graphics.Color; +@@ -13,6 +13,7 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -2125,15 +2125,15 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch import android.os.SystemClock; import android.text.TextUtils; import android.view.Gravity; -@@ -42,6 +43,7 @@ import org.chromium.base.metrics.RecordHistogram; +@@ -44,6 +45,7 @@ import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.task.PostTask; import org.chromium.base.task.TaskTraits; import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider.ControlsPosition; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.ui.appmenu.internal.R; import org.chromium.components.browser_ui.styles.ChromeColors; - import org.chromium.components.browser_ui.widget.chips.ChipView; -@@ -264,6 +266,12 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler + import org.chromium.components.browser_ui.styles.SemanticColorUtils; +@@ -278,6 +280,12 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler } mListView = (ListView) contentView.findViewById(R.id.app_menu_list); @@ -2146,7 +2146,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch int footerHeight = inflateFooter(footerResourceId, contentView, menuWidth); int headerHeight = inflateHeader(headerResourceId, contentView, menuWidth); -@@ -301,7 +309,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -315,7 +323,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler Math.min( Math.abs(mTempLocation[1] - visibleDisplayFrame.top), Math.abs(mTempLocation[1] - visibleDisplayFrame.bottom)); @@ -2155,7 +2155,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch menuItemIds, heightList, visibleDisplayFrame, -@@ -321,8 +329,14 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -335,8 +343,14 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler sizingPadding, anchorView, popupWidth, @@ -2171,7 +2171,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch try { mPopup.showAtLocation( -@@ -383,11 +397,20 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -397,11 +411,20 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler Rect padding, View anchorView, int popupWidth, @@ -2193,7 +2193,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch int[] offsets = new int[2]; // If we have a hardware menu button, locate the app menu closer to the estimated // hardware menu button location. -@@ -561,7 +584,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -575,7 +598,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler if (mAdapter != null) mAdapter.notifyDataSetChanged(); } @@ -2202,7 +2202,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch List menuItemIds, List heightList, Rect appDimensions, -@@ -580,7 +603,13 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -594,7 +617,13 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler - footerHeight - headerHeight - anchorViewImpactHeight; @@ -2217,7 +2217,7 @@ diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/ch if (mIsByPermanentButton) availableScreenSpace -= padding.top; if (availableScreenSpace <= 0 && sExceptionReporter != null) { String logMessage = -@@ -610,6 +639,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler +@@ -624,6 +653,7 @@ class AppMenu implements OnItemClickListener, OnKeyListener, AppMenuClickHandler menuItemIds, heightList, groupDividerResourceId, availableScreenSpace); menuHeight += footerHeight + headerHeight + padding.top + padding.bottom; mPopup.setHeight(menuHeight); @@ -2579,7 +2579,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -1689,6 +1689,12 @@ Your Google account may have other forms of browsing history like searches and a +@@ -1703,6 +1703,12 @@ Your Google account may have other forms of browsing history like searches and a Force Tablet Mode @@ -2595,7 +2595,7 @@ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chro diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn --- a/chrome/browser/ui/android/toolbar/BUILD.gn +++ b/chrome/browser/ui/android/toolbar/BUILD.gn -@@ -176,6 +176,7 @@ android_library("java") { +@@ -177,6 +177,7 @@ android_library("java") { "//content/public/android:content_java", "//third_party/android_deps:material_design_java", "//third_party/androidx:androidx_annotation_annotation_experimental_java", @@ -2729,7 +2729,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow // Method call routed to onBrowserControlsOffsetUpdate. if (BottomControlsStacker.isDispatchingYOffset()) return; -@@ -321,11 +329,13 @@ class BottomControlsMediator +@@ -314,11 +322,13 @@ class BottomControlsMediator && !mIsInSwipeLayout && getBrowserControls().getBottomControlOffset() == 0; if (visible) { @@ -2853,7 +2853,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow /** Layout for the browser controls (omnibox, menu, tab strip, etc..). */ public class ToolbarControlContainer extends OptimizedFrameLayout implements ControlContainer, DesktopWindowStateManager.AppHeaderObserver { -@@ -163,6 +167,11 @@ public class ToolbarControlContainer extends OptimizedFrameLayout +@@ -164,6 +168,11 @@ public class ToolbarControlContainer extends OptimizedFrameLayout if (view != null) ((MarginLayoutParams)view.getLayoutParams()).topMargin = tab_strip_height; } @@ -2930,7 +2930,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarSceneLayer.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarSceneLayer.java --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarSceneLayer.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarSceneLayer.java -@@ -15,6 +15,7 @@ import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.Drawing +@@ -16,6 +16,7 @@ import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.Drawing import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.resources.ResourceManager; @@ -2938,7 +2938,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow /** A SceneLayer to render the top toolbar. This is the "view" piece of the top toolbar overlay. */ @JNINamespace("android") -@@ -40,6 +41,13 @@ class TopToolbarSceneLayer extends SceneOverlayLayer { +@@ -41,6 +42,13 @@ class TopToolbarSceneLayer extends SceneOverlayLayer { /** Push all information about the texture to native at once. */ private void pushProperties(PropertyModel model) { if (mResourceManagerSupplier.get() == null) return; @@ -2952,7 +2952,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow TopToolbarSceneLayerJni.get() .updateToolbarLayer( mNativePtr, -@@ -50,7 +58,7 @@ class TopToolbarSceneLayer extends SceneOverlayLayer { +@@ -51,7 +59,7 @@ class TopToolbarSceneLayer extends SceneOverlayLayer { model.get(TopToolbarOverlayProperties.URL_BAR_RESOURCE_ID), model.get(TopToolbarOverlayProperties.URL_BAR_COLOR), model.get(TopToolbarOverlayProperties.X_OFFSET), @@ -3022,7 +3022,7 @@ diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/c Preference captions = findPreference(PREF_CAPTIONS); captions.setOnPreferenceClickListener( preference -> { -@@ -187,6 +208,12 @@ public class AccessibilitySettings extends ChromeBaseSettingsFragment +@@ -185,6 +206,12 @@ public class AccessibilitySettings extends ChromeBaseSettingsFragment mDelegate.getBrowserContextHandle(), (Integer) newValue); } else if (PREF_PAGE_ZOOM_ALWAYS_SHOW.equals(preference.getKey())) { PageZoomUtils.setShouldAlwaysShowZoomMenuItem((Boolean) newValue); @@ -3072,7 +3072,7 @@ diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/ #include "cc/base/math_util.h" #include "cc/slim/layer.h" #include "components/input/events_helper.h" -@@ -863,6 +864,8 @@ void RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation( +@@ -865,6 +866,8 @@ void RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation( // factor. Thus, |top_content_offset| in CSS pixels is also in DIPs. float top_content_offset = metadata.top_controls_height * metadata.top_controls_shown_ratio; diff --git a/build/cromite_patches/OpenSearch-miscellaneous.patch b/build/cromite_patches/OpenSearch-miscellaneous.patch index d2a1c3b2415257179c2ae9f41d925a6ee24c3648..69929fef2bdfa75d48aad1eb41df51b6eaf0fb2b 100644 --- a/build/cromite_patches/OpenSearch-miscellaneous.patch +++ b/build/cromite_patches/OpenSearch-miscellaneous.patch @@ -5,19 +5,19 @@ Subject: OpenSearch: miscellaneous Fix upstream bug with recently added engines prematurely discarded because they have no last-visit timestamp Fix upstream bug with visited engines visit time not updated -Allow adding search engines in incognito mode Allow using search engine URLs with non-empty paths Add verbose logging License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- - .../settings/SearchEngineAdapter.java | 4 ++- - .../search_engine_tab_helper.cc | 35 ++++++++++++++----- - .../renderer/chrome_render_frame_observer.cc | 2 ++ - .../search_engines/template_url_fetcher.cc | 24 ++++++++++--- + .../settings/SearchEngineAdapter.java | 4 +- + .../search_engine_tab_helper.cc | 36 ++++++++++++---- + .../renderer/chrome_render_frame_observer.cc | 2 + + .../search_engines/template_url_fetcher.cc | 41 ++++++++++++++----- + .../search_engines/template_url_fetcher.h | 2 +- .../search_engines/template_url_parser.cc | 2 +- - .../search_engines/template_url_service.h | 8 ++--- - 6 files changed, 56 insertions(+), 19 deletions(-) + .../search_engines/template_url_service.h | 8 ++-- + 7 files changed, 70 insertions(+), 25 deletions(-) diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SearchEngineAdapter.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SearchEngineAdapter.java --- a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SearchEngineAdapter.java @@ -44,7 +44,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro #include "base/metrics/histogram_macros.h" #include "chrome/browser/favicon/favicon_utils.h" #include "chrome/browser/profiles/profile.h" -@@ -47,6 +48,7 @@ void SearchEngineTabHelper::BindOpenSearchDescriptionDocumentHandler( +@@ -48,6 +49,7 @@ void SearchEngineTabHelper::BindOpenSearchDescriptionDocumentHandler( receiver) { // Bind only for outermost main frames. if (rfh->GetParentOrOuterDocument()) { @@ -52,7 +52,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return; } -@@ -77,6 +79,7 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( +@@ -78,6 +80,7 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( // Don't autogenerate keywords for pages that are the result of form // submissions. if (IsFormSubmit(entry)) { @@ -60,7 +60,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return std::u16string(); } -@@ -86,6 +89,7 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( +@@ -87,6 +90,7 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( if (!url.is_valid()) { url = entry->GetURL(); if (!url.is_valid()) { @@ -68,7 +68,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return std::u16string(); } } -@@ -95,10 +99,10 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( +@@ -96,10 +100,10 @@ std::u16string SearchEngineTabHelper::GenerateKeywordFromNavigationEntry( // b) have a path. // // If we relax the path constraint, we need to be sure to sanitize the path @@ -82,7 +82,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return std::u16string(); } -@@ -127,14 +131,15 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( +@@ -128,14 +132,15 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( // http://b/issue?id=863583. For that reason, this doesn't check and allow // urls referring to osdd urls with same schemes. if (!osdd_url.is_valid() || !osdd_url.SchemeIsHTTPOrHTTPS()) { @@ -100,7 +100,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return; } -@@ -148,6 +153,7 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( +@@ -149,6 +154,7 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( --index; } if (!entry || IsFormSubmit(entry)) { @@ -108,7 +108,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro return; } -@@ -158,6 +164,12 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( +@@ -159,11 +165,18 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( return; } @@ -121,15 +121,21 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro auto* frame = web_contents()->GetPrimaryMainFrame(); mojo::Remote url_loader_factory; frame->CreateNetworkServiceDefaultFactory( -@@ -165,6 +177,7 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( + url_loader_factory.BindNewPipeAndPassReceiver()); ++ bool is_off_the_record = profile->IsOffTheRecord(); // Download the OpenSearch description document. If this is successful, a // new keyword will be created when done. -+ // NOTE: for search pages under the same domain only 1 keyword is supported + #if BUILDFLAG(IS_ANDROID) +@@ -172,6 +185,7 @@ void SearchEngineTabHelper::PageHasOpenSearchDescriptionDocument( + } + #endif TemplateURLFetcherFactory::GetForProfile(profile)->ScheduleDownload( ++ is_off_the_record, keyword, osdd_url, entry->GetFavicon().url, frame->GetLastCommittedOrigin(), url_loader_factory.get(), -@@ -210,12 +223,19 @@ void SearchEngineTabHelper::GenerateKeywordIfNecessary( + frame->GetRoutingID(), +@@ -216,12 +230,19 @@ void SearchEngineTabHelper::GenerateKeywordIfNecessary( return; } @@ -151,7 +157,7 @@ diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper.cc b/chro TemplateURLService* url_service = TemplateURLServiceFactory::GetForProfile(profile); if (!url_service) { -@@ -227,7 +247,6 @@ void SearchEngineTabHelper::GenerateKeywordIfNecessary( +@@ -233,7 +254,6 @@ void SearchEngineTabHelper::GenerateKeywordIfNecessary( return; } @@ -181,7 +187,60 @@ diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/c diff --git a/components/search_engines/template_url_fetcher.cc b/components/search_engines/template_url_fetcher.cc --- a/components/search_engines/template_url_fetcher.cc +++ b/components/search_engines/template_url_fetcher.cc -@@ -261,18 +261,32 @@ void TemplateURLFetcher::ScheduleDownload( +@@ -72,7 +72,8 @@ class TemplateURLFetcher::RequestDelegate { + const url::Origin& initiator, + network::mojom::URLLoaderFactory* url_loader_factory, + int render_frame_id, +- int32_t request_id); ++ int32_t request_id, ++ bool is_off_the_record); + + RequestDelegate(const RequestDelegate&) = delete; + RequestDelegate& operator=(const RequestDelegate&) = delete; +@@ -98,6 +99,7 @@ class TemplateURLFetcher::RequestDelegate { + std::u16string keyword_; + const GURL osdd_url_; + const GURL favicon_url_; ++ bool is_off_the_record_; + + base::CallbackListSubscription template_url_subscription_; + +@@ -112,11 +114,13 @@ TemplateURLFetcher::RequestDelegate::RequestDelegate( + const url::Origin& initiator, + network::mojom::URLLoaderFactory* url_loader_factory, + int render_frame_id, +- int32_t request_id) ++ int32_t request_id, ++ bool is_off_the_record) + : fetcher_(fetcher), + keyword_(keyword), + osdd_url_(osdd_url), +- favicon_url_(favicon_url) { ++ favicon_url_(favicon_url), ++ is_off_the_record_(is_off_the_record) { + TemplateURLService* model = fetcher_->template_url_service_; + DCHECK(model); // TemplateURLFetcher::ScheduleDownload verifies this. + +@@ -229,7 +233,9 @@ void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { + // omnibox until they are activated. + data.is_active = TemplateURLData::ActiveStatus::kUnspecified; + +- model->Add(std::make_unique(data)); ++ if (!is_off_the_record_) { ++ model->Add(std::make_unique(data)); ++ } + + fetcher_->RequestCompleted(this); + // WARNING: RequestCompleted deletes us. +@@ -244,6 +250,7 @@ TemplateURLFetcher::~TemplateURLFetcher() { + } + + void TemplateURLFetcher::ScheduleDownload( ++ bool is_off_the_record, + const std::u16string& keyword, + const GURL& osdd_url, + const GURL& favicon_url, +@@ -261,21 +268,35 @@ void TemplateURLFetcher::ScheduleDownload( return; } @@ -218,7 +277,23 @@ diff --git a/components/search_engines/template_url_fetcher.cc b/components/sear + LOG(INFO) << "OpenSearch: getting " << osdd_url; requests_.push_back(std::make_unique( this, keyword, osdd_url, favicon_url, initiator, url_loader_factory, - render_frame_id, request_id)); +- render_frame_id, request_id)); ++ render_frame_id, request_id, is_off_the_record)); + } + + void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) { +diff --git a/components/search_engines/template_url_fetcher.h b/components/search_engines/template_url_fetcher.h +--- a/components/search_engines/template_url_fetcher.h ++++ b/components/search_engines/template_url_fetcher.h +@@ -48,7 +48,7 @@ class TemplateURLFetcher : public KeyedService { + // TemplateURL in the model for |keyword|, or we're already downloading an + // OSDD for this keyword, no download is started. + // +- void ScheduleDownload(const std::u16string& keyword, ++ void ScheduleDownload(bool is_off_the_record, const std::u16string& keyword, + const GURL& osdd_url, + const GURL& favicon_url, + const url::Origin& initiator, diff --git a/components/search_engines/template_url_parser.cc b/components/search_engines/template_url_parser.cc --- a/components/search_engines/template_url_parser.cc +++ b/components/search_engines/template_url_parser.cc @@ -234,7 +309,7 @@ diff --git a/components/search_engines/template_url_parser.cc b/components/searc diff --git a/components/search_engines/template_url_service.h b/components/search_engines/template_url_service.h --- a/components/search_engines/template_url_service.h +++ b/components/search_engines/template_url_service.h -@@ -364,7 +364,10 @@ class TemplateURLService final : public WebDataServiceConsumer, +@@ -379,7 +379,10 @@ class TemplateURLService final : public WebDataServiceConsumer, void UpdateProviderFavicons(const GURL& potential_search_url, const GURL& favicon_url); @@ -246,7 +321,7 @@ diff --git a/components/search_engines/template_url_service.h b/components/searc // regardless of |url| if the default search provider is managed by policy or // controlled by an extension. bool CanMakeDefault(const TemplateURL* url) const; -@@ -747,9 +750,6 @@ class TemplateURLService final : public WebDataServiceConsumer, +@@ -763,9 +766,6 @@ class TemplateURLService final : public WebDataServiceConsumer, // SetKeywordSearchTermsForURL is invoked. void UpdateKeywordSearchTermsForURL(const URLVisitedDetails& details); diff --git a/build/cromite_patches/Override-Navigator-Language.patch b/build/cromite_patches/Override-Navigator-Language.patch index 427d97ae2551893f1a843382900f7678253fcb91..d01044ecba71504c7dd97fbff34befa0a47125aa 100644 --- a/build/cromite_patches/Override-Navigator-Language.patch +++ b/build/cromite_patches/Override-Navigator-Language.patch @@ -61,15 +61,15 @@ diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browse diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc -@@ -71,6 +71,7 @@ - #include "cc/base/switches.h" +@@ -72,6 +72,7 @@ + #include "components/embedder_support/switches.h" #include "components/input/utils.h" #include "components/metrics/histogram_controller.h" +#include "components/language/core/browser/language_prefs.h" #include "components/metrics/single_sample_metrics.h" #include "components/services/storage/privileged/mojom/indexed_db_control.mojom.h" #include "components/services/storage/public/cpp/buckets/bucket_id.h" -@@ -3277,8 +3278,11 @@ void RenderProcessHostImpl::AppendRendererCommandLine( +@@ -3281,8 +3282,11 @@ void RenderProcessHostImpl::AppendRendererCommandLine( PropagateBrowserCommandLineToRenderer(browser_command_line, command_line); // Pass on the browser locale. diff --git a/build/cromite_patches/Partition-Blink-memory-cache.patch b/build/cromite_patches/Partition-Blink-memory-cache.patch index ea14398cac2ac2b7ee7b812d7e7094d43c0e926e..219ef17a7ddb4a62ab4188a408bfb77f56cc4f8d 100644 --- a/build/cromite_patches/Partition-Blink-memory-cache.patch +++ b/build/cromite_patches/Partition-Blink-memory-cache.patch @@ -19,12 +19,12 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../core/inspector/inspector_page_agent.cc | 2 +- .../core/loader/frame_fetch_context.cc | 2 + .../renderer/core/loader/image_loader.cc | 3 +- - .../core/loader/worker_fetch_context.cc | 3 ++ + .../core/loader/worker_fetch_context.cc | 3 + .../platform/loader/fetch/memory_cache.cc | 8 +-- .../platform/loader/fetch/memory_cache.h | 5 +- - .../platform/loader/fetch/resource_fetcher.cc | 51 +++++++++++++++---- + .../platform/loader/fetch/resource_fetcher.cc | 57 ++++++++++++++++--- .../platform/loader/fetch/resource_fetcher.h | 7 ++- - 10 files changed, 63 insertions(+), 23 deletions(-) + 10 files changed, 69 insertions(+), 23 deletions(-) diff --git a/third_party/blink/renderer/core/html/parser/html_srcset_parser.cc b/third_party/blink/renderer/core/html/parser/html_srcset_parser.cc --- a/third_party/blink/renderer/core/html/parser/html_srcset_parser.cc @@ -42,7 +42,7 @@ diff --git a/third_party/blink/renderer/core/html/parser/html_srcset_parser.cc b diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc --- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc -@@ -2432,7 +2432,7 @@ bool InspectorNetworkAgent::FetchResourceContent(Document* document, +@@ -2436,7 +2436,7 @@ bool InspectorNetworkAgent::FetchResourceContent(Document* document, if (!cached_resource) { cached_resource = MemoryCache::Get()->ResourceForURL( url, document->Fetcher()->GetCacheIdentifier( @@ -54,19 +54,19 @@ diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.c diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc --- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc -@@ -174,7 +174,7 @@ Resource* CachedResource(LocalFrame* frame, +@@ -176,7 +176,7 @@ Resource* CachedResource(LocalFrame* frame, if (!cached_resource) { cached_resource = MemoryCache::Get()->ResourceForURL( url, document->Fetcher()->GetCacheIdentifier( - url, /*skip_service_worker=*/false)); + url, /*skip_service_worker=*/false, document->TopFrameOrigin())); } - if (!cached_resource) + if (!cached_resource) { cached_resource = loader->ResourceForURL(url); diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc --- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc -@@ -852,6 +852,8 @@ void FrameFetchContext::PopulateResourceRequestBeforeCacheAccess( +@@ -851,6 +851,8 @@ void FrameFetchContext::PopulateResourceRequestBeforeCacheAccess( if (document_loader_->ForceFetchCacheMode()) { request.SetCacheMode(*document_loader_->ForceFetchCacheMode()); } @@ -78,7 +78,7 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/thi diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc --- a/third_party/blink/renderer/core/loader/image_loader.cc +++ b/third_party/blink/renderer/core/loader/image_loader.cc -@@ -716,7 +716,8 @@ bool ImageLoader::ShouldLoadImmediately(const KURL& url) const { +@@ -717,7 +717,8 @@ bool ImageLoader::ShouldLoadImmediately(const KURL& url) const { if (!url.IsNull()) { Resource* resource = MemoryCache::Get()->ResourceForURL( url, element_->GetDocument().Fetcher()->GetCacheIdentifier( @@ -91,16 +91,16 @@ diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_part diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.cc b/third_party/blink/renderer/core/loader/worker_fetch_context.cc --- a/third_party/blink/renderer/core/loader/worker_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/worker_fetch_context.cc -@@ -268,6 +268,9 @@ void WorkerFetchContext::PopulateResourceRequestBeforeCacheAccess( - DCHECK(RuntimeEnabledFeatures:: - MinimimalResourceRequestPrepBeforeCacheLookupEnabled()); +@@ -260,6 +260,9 @@ void WorkerFetchContext::AddResourceTiming( + void WorkerFetchContext::ModifyRequestForMixedContentUpgrade( + ResourceRequest& request) { + if (!request.TopFrameOrigin()) + request.SetTopFrameOrigin(GetTopFrameOrigin()); + MixedContentChecker::UpgradeInsecureRequest( request, &GetResourceFetcherProperties().GetFetchClientSettingsObject(), - global_scope_, mojom::RequestContextFrameType::kNone, + global_scope_, mojom::blink::RequestContextFrameType::kNone, diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc --- a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc +++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc @@ -164,7 +164,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c // Most off-main-thread resource fetches use Resource::kRaw and don't reach // this point, but off-main-thread module fetches might. if (IsMainThread()) { -@@ -1462,7 +1463,8 @@ Resource* ResourceFetcher::RequestResource(FetchParameters& params, +@@ -1482,7 +1483,8 @@ Resource* ResourceFetcher::RequestResource(FetchParameters& params, params.Url(), GetCacheIdentifier( params.Url(), @@ -174,7 +174,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c if (resource) { policy = DetermineRevalidationPolicy(resource_type, params, *resource, is_static_data); -@@ -1794,7 +1796,8 @@ Resource* ResourceFetcher::CreateResourceForLoading( +@@ -1814,7 +1816,8 @@ Resource* ResourceFetcher::CreateResourceForLoading( const ResourceFactory& factory) { const String cache_identifier = GetCacheIdentifier(params.GetResourceRequest().Url(), @@ -184,7 +184,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c DCHECK(!IsMainThread() || params.IsStaleRevalidation() || !MemoryCache::Get()->ResourceForURL(params.GetResourceRequest().Url(), cache_identifier)); -@@ -2903,11 +2906,41 @@ void ResourceFetcher::UpdateImagePrioritiesAndSpeculativeDecodes() { +@@ -2927,11 +2930,47 @@ void ResourceFetcher::UpdateImagePrioritiesAndSpeculativeDecodes() { } String ResourceFetcher::GetCacheIdentifier(const KURL& url, @@ -200,16 +200,22 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c + String origin_url = top_origin ? top_origin->ToRawString() : ""; + String cache_identifier = ResourceFetcher::GetCacheIdentifier( + url, skip_service_worker, origin_url); -+ // LOG(INFO) << "---t (" << cache_identifier << ") " << url.GetString() << "='" << origin_url << "'"; ++ // LOG(INFO) << "---t (" << cache_identifier << ") " ++ // << url.GetString() << "='" << origin_url << "'"; + return cache_identifier; + } + // service workers cannot use the memory cache + // } else if (resource_request.GetRequestContext() == -+ // mojom::blink::RequestContextType::SERVICE_WORKER) { -+ // const scoped_refptr requestor_origin = resource_request.RequestorOrigin(); -+ // String origin_url = requestor_origin ? requestor_origin->ToRawString() : ""; //context_.Url()->ToRawString(); -+ // String cache_identifier = ResourceFetcher::GetCacheIdentifier(url, origin_url); -+ // // LOG(INFO) << "---o (" << cache_identifier << ") " << url.GetString() << "='" << origin_url << "'"; ++ // mojom::blink::RequestContextType::SERVICE_WORKER) { ++ // const scoped_refptr requestor_origin = ++ // resource_request.RequestorOrigin(); ++ // String origin_url = requestor_origin ++ // ? requestor_origin->ToRawString() ++ // : ""; //context_.Url()->ToRawString(); ++ // String cache_identifier = ResourceFetcher::GetCacheIdentifier( ++ // url, skip_service_worker, origin_url); ++ // LOG(INFO) << "---o (" << cache_identifier << ") " ++ // << url.GetString() << "='" << origin_url << "'"; + // return cache_identifier; + // } + return MemoryCache::DefaultCacheIdentifier(); @@ -231,7 +237,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c } // Requests that can be satisfied via `archive_` (i.e. MHTML) or -@@ -2922,7 +2955,7 @@ String ResourceFetcher::GetCacheIdentifier(const KURL& url, +@@ -2946,7 +2985,7 @@ String ResourceFetcher::GetCacheIdentifier(const KURL& url, return bundle->GetCacheIdentifier(); } diff --git a/build/cromite_patches/Partition-blobs-by-top-frame-URL.patch b/build/cromite_patches/Partition-blobs-by-top-frame-URL.patch index bb7d5552672d5c16b593e1d3eeb3cf94351181b7..db190a5cf704fb42a96b24a404148c0a8b5576f9 100644 --- a/build/cromite_patches/Partition-blobs-by-top-frame-URL.patch +++ b/build/cromite_patches/Partition-blobs-by-top-frame-URL.patch @@ -9,12 +9,12 @@ Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later. License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- .../Partition-blobs-by-top-frame-URL.inc | 1 + - storage/browser/blob/blob_url_store_impl.cc | 35 +++++++++++++++++++ - storage/browser/blob/blob_url_store_impl.h | 11 ++++++ + storage/browser/blob/blob_url_store_impl.cc | 51 +++++++++++++++++++ + storage/browser/blob/blob_url_store_impl.h | 11 ++++ storage/browser/blob/features.cc | 1 + - .../public/mojom/blob/blob_url_store.mojom | 12 +++++-- - .../core/fileapi/public_url_manager.cc | 18 ++++++++++ - 6 files changed, 75 insertions(+), 3 deletions(-) + .../public/mojom/blob/blob_url_store.mojom | 12 +++-- + .../core/fileapi/public_url_manager.cc | 18 +++++++ + 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc diff --git a/cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc b/cromite_flags/third_party/blink/common/features_cc/Partition-blobs-by-top-frame-URL.inc @@ -26,7 +26,7 @@ new file mode 100644 diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/blob_url_store_impl.cc --- a/storage/browser/blob/blob_url_store_impl.cc +++ b/storage/browser/blob/blob_url_store_impl.cc -@@ -89,6 +89,20 @@ BlobURLStoreImpl::~BlobURLStoreImpl() { +@@ -91,6 +91,30 @@ BlobURLStoreImpl::~BlobURLStoreImpl() { } } @@ -34,20 +34,45 @@ diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/ + const GURL& blob_url, + const base::UnguessableToken& unsafe_agent_cluster_id, + const std::optional& unsafe_top_level_site) { ++ bool is_same_partition = false; + const std::optional& top_level_site = + registry_->GetUnsafeTopLevelSite(blob_url); -+ if (top_level_site.has_value()) -+ return top_level_site == unsafe_top_level_site; -+ -+ std::optional agent_cluster_id = ++ const std::optional agent_cluster_id = + registry_->GetUnsafeAgentClusterID(blob_url); -+ return agent_cluster_id == unsafe_agent_cluster_id; ++ if (top_level_site.has_value()) { ++ is_same_partition = (top_level_site == unsafe_top_level_site); ++ } else { ++ is_same_partition = (agent_cluster_id == unsafe_agent_cluster_id); ++ } ++ // LOG(INFO) << "---BlobURLStoreImpl " ++ // << " is_same_partition=" << is_same_partition ++ // << " blob_url=" << blob_url ++ // << " top_level_site=" << (top_level_site.has_value() ? top_level_site->GetDebugString() : "") ++ // << " unsafe_top_level_site=" << (unsafe_top_level_site.has_value() ? unsafe_top_level_site->GetDebugString() : "") ++ // << " agent_cluster_id=" << (agent_cluster_id.has_value() ? agent_cluster_id->ToString() : "") ++ // << " unsafe_agent_cluster_id=" << unsafe_agent_cluster_id.ToString(); ++ return is_same_partition; +} + void BlobURLStoreImpl::Register( mojo::PendingRemote blob, const GURL& url, -@@ -123,12 +137,18 @@ void BlobURLStoreImpl::Revoke(const GURL& url) { +@@ -105,6 +129,14 @@ void BlobURLStoreImpl::Register( + return; + } + ++ // LOG(INFO) << "---BlobURLStoreImpl Register" ++ // << " url=" << url ++ // << " storage_key_origin=" << storage_key_.GetDebugString() ++ // << " renderer_origin_=" << renderer_origin_ ++ // << " render_process_host_id_=" << render_process_host_id_ ++ // << " unsafe_top_level_site=" << (unsafe_top_level_site.has_value() ? unsafe_top_level_site->GetDebugString() : "") ++ // << " unsafe_agent_cluster_id=" << unsafe_agent_cluster_id.ToString(); ++ + if (registry_) + registry_->AddUrlMapping(url, std::move(blob), storage_key_, + renderer_origin_, render_process_host_id_, +@@ -125,6 +157,8 @@ void BlobURLStoreImpl::Revoke(const GURL& url) { void BlobURLStoreImpl::ResolveAsURLLoaderFactory( const GURL& url, mojo::PendingReceiver receiver, @@ -56,18 +81,16 @@ diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/ ResolveAsURLLoaderFactoryCallback callback) { if (!registry_) { BlobURLLoaderFactory::Create(mojo::NullRemote(), url, std::move(receiver)); - std::move(callback).Run(std::nullopt, std::nullopt); - return; - } -+ if (!IsSamePartition(url, unsafe_agent_cluster_id, unsafe_top_level_site)) { -+ std::move(callback).Run(std::nullopt, std::nullopt); -+ return; -+ } - if (!registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url), - storage_key_)) { - partitioned_fetch_failure_closure_.Run(); -@@ -142,6 +162,12 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( - } +@@ -140,6 +174,7 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( + features::kBlockCrossPartitionBlobUrlFetching) && + !partitioning_disabled_by_policy_; + if (feature_and_policy_check) { ++ // LOG(INFO) << "---ResolveAsURLLoaderFactory blocked" << url; + partitioning_blob_url_closure_.Run(url, + blink::mojom::PartitioningBlobURLInfo:: + kBlockedCrossPartitionFetching); +@@ -152,6 +187,13 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory( + partitioning_blob_url_closure_.Run(url, std::nullopt); } + if (!IsSamePartition(url, unsafe_agent_cluster_id, unsafe_top_level_site)) { @@ -76,10 +99,11 @@ diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/ + return; + } + ++ // LOG(INFO) << "---ResolveAsURLLoaderFactory allowed " << url; BlobURLLoaderFactory::Create(registry_->GetBlobFromUrl(url), url, std::move(receiver)); // When a fragment URL is present, registry_->GetUnsafeAgentClusterID(url) and -@@ -156,12 +182,18 @@ void BlobURLStoreImpl::ResolveForNavigation( +@@ -166,12 +208,18 @@ void BlobURLStoreImpl::ResolveForNavigation( const GURL& url, mojo::PendingReceiver token, bool is_top_level_navigation, @@ -96,9 +120,9 @@ diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/ + return; + } if (!is_top_level_navigation && - !registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url), - storage_key_)) { -@@ -187,12 +219,15 @@ void BlobURLStoreImpl::ResolveForNavigation( + (registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url), + storage_key_) == +@@ -203,12 +251,15 @@ void BlobURLStoreImpl::ResolveForNavigation( void BlobURLStoreImpl::ResolveForWorkerScriptFetch( const GURL& url, mojo::PendingReceiver token, @@ -117,7 +141,7 @@ diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/ diff --git a/storage/browser/blob/blob_url_store_impl.h b/storage/browser/blob/blob_url_store_impl.h --- a/storage/browser/blob/blob_url_store_impl.h +++ b/storage/browser/blob/blob_url_store_impl.h -@@ -54,15 +54,21 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl +@@ -57,15 +57,21 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl void ResolveAsURLLoaderFactory( const GURL& url, mojo::PendingReceiver receiver, @@ -139,7 +163,7 @@ diff --git a/storage/browser/blob/blob_url_store_impl.h b/storage/browser/blob/b ResolveForNavigationCallback callback) override; private: -@@ -72,6 +78,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl +@@ -75,6 +81,11 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl // `Revoke()`. bool BlobUrlIsValid(const GURL& url, const char* method) const; diff --git a/build/cromite_patches/Re-introduce-modal-dialog-flag-to-close-all-tabs.patch b/build/cromite_patches/Re-introduce-modal-dialog-flag-to-close-all-tabs.patch index d6fb1bc07ecd080520764407cd0ac5d5941d1f75..c55f811f5b8c61d027b8870d6b10ad42a312e324 100644 --- a/build/cromite_patches/Re-introduce-modal-dialog-flag-to-close-all-tabs.patch +++ b/build/cromite_patches/Re-introduce-modal-dialog-flag-to-close-all-tabs.patch @@ -45,7 +45,7 @@ diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -202,6 +202,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -201,6 +201,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kAutomotiveFullscreenToolbarImprovements, &kAuxiliarySearchDonation, &kAvoidSelectedTabFocusOnLayoutDoneShowing, @@ -56,7 +56,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -293,6 +293,7 @@ public abstract class ChromeFeatureList { +@@ -297,6 +297,7 @@ public abstract class ChromeFeatureList { public static final String CLANK_WHATS_NEW = "ClankWhatsNew"; public static final String CLEAR_BROWSING_DATA_ANDROID_SURVEY = "ClearBrowsingDataAndroidSurvey"; diff --git a/build/cromite_patches/Re-introduce-override_build_timestamp.patch b/build/cromite_patches/Re-introduce-override_build_timestamp.patch index 80f70f0a4f4fbd6a79f85552a66ffb2a9751e2e6..63a9afce8b42d3905e4b48674912e335a02ae916 100644 --- a/build/cromite_patches/Re-introduce-override_build_timestamp.patch +++ b/build/cromite_patches/Re-introduce-override_build_timestamp.patch @@ -16,7 +16,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/base/BUILD.gn b/base/BUILD.gn --- a/base/BUILD.gn +++ b/base/BUILD.gn -@@ -4203,8 +4203,13 @@ action("build_date") { +@@ -4232,8 +4232,13 @@ action("build_date") { args = [ rebase_path("$target_gen_dir/generated_build_date.h", root_build_dir), diff --git a/build/cromite_patches/Reduce-HTTP-headers-in-DoH-requests-to-bare-minimum.patch b/build/cromite_patches/Reduce-HTTP-headers-in-DoH-requests-to-bare-minimum.patch index f518ef4dc6b25587d91a403899d231402230688f..38524350e2fff26bdfad12c850f6db0ef2361662 100644 --- a/build/cromite_patches/Reduce-HTTP-headers-in-DoH-requests-to-bare-minimum.patch +++ b/build/cromite_patches/Reduce-HTTP-headers-in-DoH-requests-to-bare-minimum.patch @@ -35,7 +35,7 @@ diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc -@@ -500,6 +500,7 @@ void URLRequestHttpJob::OnGotFirstPartySetMetadata( +@@ -515,6 +515,7 @@ void URLRequestHttpJob::OnGotFirstPartySetMetadata( // fields in the referrer. GURL referrer(request_->referrer()); @@ -43,7 +43,7 @@ diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_reque // Our consumer should have made sure that this is a safe referrer (e.g. via // URLRequestJob::ComputeReferrerForPolicy). if (referrer.is_valid()) { -@@ -507,11 +508,14 @@ void URLRequestHttpJob::OnGotFirstPartySetMetadata( +@@ -522,11 +523,14 @@ void URLRequestHttpJob::OnGotFirstPartySetMetadata( request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer, referer_value); } @@ -58,7 +58,7 @@ diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_reque AddExtraHeaders(); -@@ -756,10 +760,10 @@ void URLRequestHttpJob::StartTransactionInternal() { +@@ -771,10 +775,10 @@ void URLRequestHttpJob::StartTransactionInternal() { void URLRequestHttpJob::AddExtraHeaders() { request_info_.extra_headers.SetAcceptEncodingIfMissing( request()->url(), request()->accepted_stream_types(), diff --git a/build/cromite_patches/Remove-HTTP-referrals-in-cross-origin-navigation.patch b/build/cromite_patches/Remove-HTTP-referrals-in-cross-origin-navigation.patch index 47e50c844c77881037a42f3f9a00bd66fea56fde..1d5d417975b8e745dc983a5e11d8e86bb44acb87 100644 --- a/build/cromite_patches/Remove-HTTP-referrals-in-cross-origin-navigation.patch +++ b/build/cromite_patches/Remove-HTTP-referrals-in-cross-origin-navigation.patch @@ -44,7 +44,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -643,6 +643,8 @@ chrome_java_resources = [ +@@ -650,6 +650,8 @@ chrome_java_resources = [ "java/res/xml/personalize_google_services_preferences.xml", "java/res/xml/incognito_preferences.xml", "java/res/xml/privacy_preferences.xml", @@ -56,7 +56,7 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -961,6 +961,8 @@ chrome_java_sources = [ +@@ -894,6 +894,8 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionSnackbarController.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionSnackbarLimiter.java", @@ -379,7 +379,7 @@ new file mode 100644 diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc -@@ -222,6 +222,11 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { +@@ -223,6 +223,11 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { (*s_allowlist)[::prefs::kExpireDaysThreshold] = settings_api::PrefType::kNumber; @@ -394,7 +394,7 @@ diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chro diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc --- a/chrome/browser/net/system_network_context_manager.cc +++ b/chrome/browser/net/system_network_context_manager.cc -@@ -604,6 +604,9 @@ SystemNetworkContextManager::SystemNetworkContextManager( +@@ -603,6 +603,9 @@ SystemNetworkContextManager::SystemNetworkContextManager( base::BindRepeating(&SystemNetworkContextManager::UpdateReferrersEnabled, base::Unretained(this))); @@ -404,7 +404,7 @@ diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/brows pref_change_registrar_.Add( prefs::kExplicitlyAllowedNetworkPorts, base::BindRepeating( -@@ -679,6 +682,7 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) { +@@ -678,6 +681,7 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) { // the system NetworkContext, and the per-profile pref values are used for // the profile NetworkContexts. registry->RegisterBooleanPref(prefs::kEnableReferrers, true); @@ -426,7 +426,7 @@ diff --git a/chrome/browser/renderer_preferences_util.cc b/chrome/browser/render diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html --- a/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html -@@ -94,6 +94,16 @@ +@@ -101,6 +101,16 @@ menu-options="[[historyExpireDaysThresholdOptions_]]"> @@ -464,7 +464,7 @@ diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/ch /** * Preferences state. */ -@@ -493,6 +504,12 @@ export class SettingsPrivacyPageElement extends SettingsPrivacyPageElementBase { +@@ -506,6 +517,12 @@ export class SettingsPrivacyPageElement extends SettingsPrivacyPageElementBase { Router.getInstance().navigateTo(routes.TRACKING_PROTECTION); } @@ -480,7 +480,7 @@ diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/ch diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -1213,6 +1213,30 @@ For more settings that use data to improve your Chrome experience, go to @@ -538,7 +538,7 @@ diff --git a/chrome/browser/ui/prefs/prefs_tab_helper.cc b/chrome/browser/ui/pre diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc -@@ -1953,6 +1953,10 @@ void AddPrivacyStrings(content::WebUIDataSource* html_source, +@@ -1961,6 +1961,10 @@ void AddPrivacyStrings(content::WebUIDataSource* html_source, {"dayHistory", IDS_SETTINGS_DAY_HISTORY_DESCRIPTION}, {"daysHistory", IDS_SETTINGS_DAYS_HISTORY_DESCRIPTION}, {"foreverHistory", IDS_SETTINGS_FOREVER_HISTORY_DESCRIPTION}, @@ -552,7 +552,7 @@ diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provide diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h -@@ -1566,6 +1566,9 @@ inline constexpr char kEnableReferrers[] = "enable_referrers"; +@@ -1551,6 +1551,9 @@ inline constexpr char kEnableReferrers[] = "enable_referrers"; inline constexpr char kEnableEncryptedMedia[] = "webkit.webprefs.encrypted_media_enabled"; @@ -615,7 +615,7 @@ diff --git a/components/download/content/internal/context_menu_download.cc b/com diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc --- a/content/browser/renderer_host/navigation_request.cc +++ b/content/browser/renderer_host/navigation_request.cc -@@ -392,6 +392,15 @@ void AddAdditionalRequestHeaders( +@@ -388,6 +388,15 @@ void AddAdditionalRequestHeaders( blink::mojom::Referrer(GURL(), network::mojom::ReferrerPolicy::kNever); } diff --git a/build/cromite_patches/Remove-binary-blob-integrations.patch b/build/cromite_patches/Remove-binary-blob-integrations.patch index 67bee4e9b6095e6f81fcc4e4857bd9c78d36eb95..1fb6a20ab4a8e98e9f21e315d259ec742ea1b575 100644 --- a/build/cromite_patches/Remove-binary-blob-integrations.patch +++ b/build/cromite_patches/Remove-binary-blob-integrations.patch @@ -30,13 +30,11 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html chrome/android/BUILD.gn | 32 +- chrome/android/chrome_java_sources.gni | 5 - chrome/android/java/AndroidManifest.xml | 50 -- - .../org/chromium/chrome/browser/AppHooks.java | 1 - - .../browser/ChromeBackgroundService.java | 11 +- + .../browser/ChromeBackgroundService.java | 10 +- .../browser/PlayServicesVersionInfo.java | 11 +- .../ChromeBackgroundTaskFactory.java | 3 - .../gcore/ChromeGoogleApiClientImpl.java | 25 +- .../browser/gcore/GoogleApiClientHelper.java | 89 +-- - .../instantapps/InstantAppsHandler.java | 6 +- .../settings/PasswordSettings.java | 9 - chrome/android/javatests/BUILD.gn | 7 - chrome/android/junit/BUILD.gn | 6 - @@ -67,6 +65,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html components/gcm_driver/gcm_client_impl.cc | 4 + .../gcm_driver/instance_id/android/BUILD.gn | 3 - .../instance_id/InstanceIDBridge.java | 56 +- + .../InstalledAppProviderImpl.java | 2 +- .../media_router/browser/android/BUILD.gn | 11 - .../media_router/BrowserMediaRouter.java | 21 +- .../components/media_router/MediaSink.java | 44 +- @@ -91,7 +90,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../components/omnibox/OmniboxFeatures.java | 8 - components/signin/public/android/BUILD.gn | 3 - components/webauthn/android/BUILD.gn | 15 +- - .../webauthn/AuthenticatorImpl.java | 80 +- + .../webauthn/AuthenticatorImpl.java | 81 +- .../webauthn/ConditionalUiState.java | 15 + .../components/webauthn/GmsCoreUtils.java | 28 +- .../webauthn/WebauthnModeProvider.java | 15 - @@ -106,12 +105,12 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html services/BUILD.gn | 9 - services/device/geolocation/BUILD.gn | 4 - services/shape_detection/BUILD.gn | 2 - - third_party/android_deps/BUILD.gn | 705 +----------------- + third_party/android_deps/BUILD.gn | 704 +----------------- .../preconditions/javatests/BUILD.gn | 1 - .../gms/ChromiumPlayServicesAvailability.java | 10 +- - third_party/androidx/customizations.gni | 13 +- + third_party/androidx/customizations.gni | 14 +- third_party/cardboard/BUILD.gn | 4 - - 87 files changed, 128 insertions(+), 2295 deletions(-) + 86 files changed, 127 insertions(+), 2291 deletions(-) create mode 100644 components/webauthn/android/java/src/org/chromium/components/webauthn/ConditionalUiState.java diff --git a/android_webview/expectations/system_webview_bundle.AndroidManifest.expected b/android_webview/expectations/system_webview_bundle.AndroidManifest.expected @@ -146,12 +145,12 @@ diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py + # com.google.android.gms.cast is stripped down from the build + r'class com\.google\.android\.gms\.', ]) + ')', - # TODO(agrieve): Remove once we update to U SDK. - r'OnBackAnimationCallback', + # We enforce that this class is removed via -checkdiscard. + r'FastServiceLoader\.class:.*Could not inline ServiceLoader\.load', diff --git a/build/config/android/config.gni b/build/config/android/config.gni --- a/build/config/android/config.gni +++ b/build/config/android/config.gni -@@ -170,9 +170,7 @@ if (is_android) { +@@ -166,9 +166,7 @@ if (is_android) { # google_play_services_package contains the path where individual client # targets (e.g. google_play_services_base_java) are located. @@ -165,7 +164,7 @@ diff --git a/build/config/android/config.gni b/build/config/android/config.gni diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -311,14 +311,6 @@ if (current_toolchain == default_toolchain) { +@@ -309,14 +309,6 @@ if (current_toolchain == default_toolchain) { ":java_overlay_resources", ":system_ai_service_proto_java", ":usage_stats_proto_java", @@ -180,7 +179,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//base:content_uri_utils_java", "//base:service_loader_java", "//base/version_info/android:version_constants_java", -@@ -718,11 +710,20 @@ if (current_toolchain == default_toolchain) { +@@ -719,11 +711,20 @@ if (current_toolchain == default_toolchain) { "//services/shape_detection:shape_detection_java", "//services/shape_detection/public/mojom:mojom_java", "//skia/public/mojom:mojom_java", @@ -203,7 +202,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//third_party/android_deps:protobuf_lite_runtime_java", "//third_party/android_media:android_media_java", "//third_party/android_swipe_refresh:android_swipe_refresh_java", -@@ -1884,7 +1885,6 @@ if (current_toolchain == default_toolchain) { +@@ -1897,7 +1898,6 @@ if (current_toolchain == default_toolchain) { ] deps = [ ":chrome_java", @@ -211,7 +210,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//base:base_java", "//base:base_java_test_support", "//build/android:build_java", -@@ -1986,7 +1986,6 @@ if (current_toolchain == default_toolchain) { +@@ -2001,7 +2001,6 @@ if (current_toolchain == default_toolchain) { # is in a DFM. android_library("base_module_java") { sources = [ @@ -219,7 +218,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "java/src/org/chromium/chrome/app/TrichromeZygotePreload.java", "java/src/org/chromium/chrome/browser/ChromeBackgroundService.java", "java/src/org/chromium/chrome/browser/DeferredStartupHandler.java", -@@ -2003,8 +2002,6 @@ if (current_toolchain == default_toolchain) { +@@ -2018,8 +2017,6 @@ if (current_toolchain == default_toolchain) { "java/src/org/chromium/chrome/browser/base/SplitCompatBackupAgent.java", "java/src/org/chromium/chrome/browser/base/SplitCompatContentProvider.java", "java/src/org/chromium/chrome/browser/base/SplitCompatCustomTabsService.java", @@ -228,7 +227,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "java/src/org/chromium/chrome/browser/base/SplitCompatIntentService.java", "java/src/org/chromium/chrome/browser/base/SplitCompatJobService.java", "java/src/org/chromium/chrome/browser/base/SplitCompatMinidumpUploadJobService.java", -@@ -2030,15 +2027,10 @@ if (current_toolchain == default_toolchain) { +@@ -2045,15 +2042,10 @@ if (current_toolchain == default_toolchain) { "java/src/org/chromium/chrome/browser/photo_picker/DecoderService.java", "java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java", "java/src/org/chromium/chrome/browser/provider/PageContentProvider.java", @@ -244,7 +243,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//base:base_java", "//chrome/browser/autofill/android:third_party_provider_java", "//chrome/browser/download/android:file_provider_java", -@@ -2053,7 +2045,6 @@ if (current_toolchain == default_toolchain) { +@@ -2068,7 +2060,6 @@ if (current_toolchain == default_toolchain) { "//components/media_router/browser/android:cast_options_provider_java", "//components/minidump_uploader:minidump_uploader_java", "//components/module_installer/android:module_installer_java", @@ -252,7 +251,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//third_party/androidx:androidx_annotation_annotation_java", "//third_party/androidx:androidx_collection_collection_java", "//third_party/jni_zero:jni_zero_java", -@@ -2073,7 +2064,6 @@ if (current_toolchain == default_toolchain) { +@@ -2088,7 +2079,6 @@ if (current_toolchain == default_toolchain) { # Deps to pull services into base module. # TODO(crbug.com/40148088): Consider moving these to the chrome module to # reduce base dex size. @@ -271,7 +270,7 @@ diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java "java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java", "java/src/org/chromium/chrome/browser/ChromeInactivityTracker.java", "java/src/org/chromium/chrome/browser/ChromeKeyboardVisibilityDelegate.java", -@@ -989,10 +988,6 @@ chrome_java_sources = [ +@@ -921,10 +920,6 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/segmentation_platform/ReaderModeActionProvider.java", "java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.java", "java/src/org/chromium/chrome/browser/selection/ChromeSelectionDropdownMenuDelegate.java", @@ -378,28 +377,17 @@ diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/Andro ()); map.AddWebUIConfig(std::make_unique()); map.AddWebUIConfig(std::make_unique()); @@ -1208,7 +1181,7 @@ diff --git a/chrome/browser/webauthn/android/java/src/org/chromium/chrome/browse /** * Provides linking information to the native side. -@@ -85,37 +82,7 @@ public class CableAuthenticatorModuleProvider { +@@ -74,37 +71,7 @@ public class CableAuthenticatorModuleProvider { ok = false; } @@ -1263,7 +1236,7 @@ diff --git a/chrome/browser/webauthn/cablev2_devices.cc b/chrome/browser/webauth diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn --- a/chrome/test/android/BUILD.gn +++ b/chrome/test/android/BUILD.gn -@@ -379,8 +379,6 @@ android_library("chrome_java_integration_test_support") { +@@ -382,8 +382,6 @@ android_library("chrome_java_integration_test_support") { deps = [ ":chrome_java_test_support_common", @@ -1275,7 +1248,7 @@ diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java --- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java +++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java -@@ -2552,14 +2552,6 @@ public class ExternalNavigationHandler { +@@ -2554,14 +2554,6 @@ public class ExternalNavigationHandler { * @return Whether the given intent is going to open an Instant App. */ private static boolean isIntentToInstantApp(Intent intent) { @@ -1313,7 +1286,7 @@ diff --git a/components/externalauth/android/BUILD.gn b/components/externalauth/ diff --git a/components/externalauth/android/java/src/org/chromium/components/externalauth/ExternalAuthUtils.java b/components/externalauth/android/java/src/org/chromium/components/externalauth/ExternalAuthUtils.java --- a/components/externalauth/android/java/src/org/chromium/components/externalauth/ExternalAuthUtils.java +++ b/components/externalauth/android/java/src/org/chromium/components/externalauth/ExternalAuthUtils.java -@@ -16,9 +16,6 @@ import androidx.annotation.Nullable; +@@ -17,9 +17,6 @@ import android.text.TextUtils; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; @@ -1323,15 +1296,15 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.ResettersForTesting; -@@ -28,7 +25,6 @@ import org.chromium.base.TraceEvent; - import org.chromium.base.task.PostTask; - import org.chromium.base.task.TaskTraits; +@@ -31,7 +28,6 @@ import org.chromium.base.task.TaskTraits; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; import org.chromium.components.embedder_support.util.Origin; -import org.chromium.gms.ChromiumPlayServicesAvailability; /** * Utility class for external authentication tools. -@@ -172,9 +168,7 @@ public class ExternalAuthUtils { +@@ -176,9 +172,7 @@ public class ExternalAuthUtils { * when it is updating. */ public boolean isGooglePlayServicesMissing(final Context context) { @@ -1342,7 +1315,7 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex } /** -@@ -189,21 +183,6 @@ public class ExternalAuthUtils { +@@ -193,21 +187,6 @@ public class ExternalAuthUtils { * @return true if and only if Google Play Services can be used */ public boolean canUseGooglePlayServices(final UserRecoverableErrorHandler errorHandler) { @@ -1364,7 +1337,7 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex return false; } -@@ -256,12 +235,7 @@ public class ExternalAuthUtils { +@@ -260,12 +239,7 @@ public class ExternalAuthUtils { * @return The code produced by calling the external code */ protected int checkGooglePlayServicesAvailable(final Context context) { @@ -1378,7 +1351,7 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex } /** -@@ -272,7 +246,7 @@ public class ExternalAuthUtils { +@@ -276,7 +250,7 @@ public class ExternalAuthUtils { * @return true If the code represents a user-recoverable error */ protected boolean isUserRecoverableError(final int errorCode) { @@ -1387,7 +1360,7 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex } /** -@@ -282,7 +256,7 @@ public class ExternalAuthUtils { +@@ -286,7 +260,7 @@ public class ExternalAuthUtils { * @return a textual description of the error code */ protected String describeError(final int errorCode) { @@ -1407,8 +1380,8 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex - import org.chromium.base.ThreadUtils; import org.chromium.base.metrics.RecordUserAction; - -@@ -87,7 +85,6 @@ public abstract class UserRecoverableErrorHandler { + import org.chromium.build.annotations.NullMarked; +@@ -90,7 +88,6 @@ public abstract class UserRecoverableErrorHandler { if (!sNotificationShown.getAndSet(true)) { return; } @@ -1416,19 +1389,21 @@ diff --git a/components/externalauth/android/java/src/org/chromium/components/ex } } -@@ -174,12 +171,7 @@ public abstract class UserRecoverableErrorHandler { - cancelDialog(); +@@ -178,14 +175,9 @@ public abstract class UserRecoverableErrorHandler { } - if (mDialog == null) { -- mDialog = + Dialog dialog = mDialog; + if (dialog == null) { +- dialog = - GoogleApiAvailability.getInstance() - .getErrorDialog(mActivity, errorCode, NO_RESPONSE_REQUIRED); + assert dialog != null : "code was " + errorCode; + mDialog = dialog; mErrorCode = errorCode; - -- DialogUserActionRecorder.createAndAttachToDialog(mDialog); +- DialogUserActionRecorder.createAndAttachToDialog(dialog); } // This can happen if |errorCode| is ConnectionResult.SERVICE_INVALID. - if (mDialog != null && !mDialog.isShowing()) { + if (!dialog.isShowing()) { diff --git a/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/GoogleCloudMessagingV2.java b/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/GoogleCloudMessagingV2.java --- a/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/GoogleCloudMessagingV2.java +++ b/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/GoogleCloudMessagingV2.java @@ -1617,6 +1592,18 @@ diff --git a/components/gcm_driver/instance_id/android/java/src/org/chromium/com return doBackgroundWork(); } +diff --git a/components/installedapp/android/java/src/org/chromium/components/installedapp/InstalledAppProviderImpl.java b/components/installedapp/android/java/src/org/chromium/components/installedapp/InstalledAppProviderImpl.java +--- a/components/installedapp/android/java/src/org/chromium/components/installedapp/InstalledAppProviderImpl.java ++++ b/components/installedapp/android/java/src/org/chromium/components/installedapp/InstalledAppProviderImpl.java +@@ -88,7 +88,7 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { + + // The maximum number of related apps declared in the Web Manifest taken into account when + // determining whether the related app is installed and mutually related. +- @VisibleForTesting static final int MAX_ALLOWED_RELATED_APPS = 3; ++ @VisibleForTesting static final int MAX_ALLOWED_RELATED_APPS = 0; + + private static final String TAG = "InstalledAppProvider"; + diff --git a/components/media_router/browser/android/BUILD.gn b/components/media_router/browser/android/BUILD.gn --- a/components/media_router/browser/android/BUILD.gn +++ b/components/media_router/browser/android/BUILD.gn @@ -3105,8 +3092,8 @@ diff --git a/components/omnibox/common/BUILD.gn b/components/omnibox/common/BUIL deps = [ - "$google_play_services_package:google_play_services_location_java", "//base:base_java", + "//chrome/browser/feedback/android:java", "//components/cached_flags:java", - "//third_party/androidx:androidx_annotation_annotation_java", diff --git a/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java b/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java --- a/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java +++ b/components/omnibox/common/android/java/src/org/chromium/components/omnibox/OmniboxFeatures.java @@ -3208,11 +3195,11 @@ diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/ diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java -@@ -63,18 +63,10 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -69,18 +69,10 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - private MakeCredential_Response mMakeCredentialCallback; - private GetAssertion_Response mGetAssertionCallback; -- private Fido2CredentialRequest mPendingFido2CredentialRequest; + private @Nullable MakeCredential_Response mMakeCredentialCallback; + private @Nullable GetCredential_Response mGetCredentialCallback; +- private @Nullable Fido2CredentialRequest mPendingFido2CredentialRequest; - private Set mUnclosedFido2CredentialRequests = new HashSet<>(); - // Information about the request cached here for metric reporting purposes. @@ -3223,11 +3210,11 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut - // `Fido2CredentialRequest` contains a `Context`. But this field is only - // used in tests so a memory leak is irrelevent. - @SuppressLint("StaticFieldLeak") -- private static Fido2CredentialRequest sFido2CredentialRequestOverrideForTesting; +- private static @Nullable Fido2CredentialRequest sFido2CredentialRequestOverrideForTesting; /** * Builds the Authenticator service implementation. -@@ -106,19 +98,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -112,19 +104,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon mCreateConfirmationUiDelegate = createConfirmationUiDelegate; } @@ -3247,7 +3234,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut /** * Called by InternalAuthenticatorAndroid, which facilitates WebAuthn for processes that * originate from the browser process. Since the request is from the browser process, the -@@ -169,16 +148,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -175,16 +154,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon } private void continueMakeCredential(PublicKeyCredentialCreationOptions options) { @@ -3255,7 +3242,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut - mPendingFido2CredentialRequest.handleMakeCredentialRequest( - options, - maybeCreateBrowserOptions(), -- mOrigin, +- assertNonNull(mOrigin), - mTopOrigin, - mPayment, - this::onRegisterResponse, @@ -3264,7 +3251,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } private @Nullable Bundle maybeCreateBrowserOptions() { -@@ -209,16 +178,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -221,16 +190,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon onError(AuthenticatorStatus.NOT_IMPLEMENTED); return; } @@ -3272,7 +3259,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut - mPendingFido2CredentialRequest = getFido2CredentialRequest(); - mPendingFido2CredentialRequest.handleGetAssertionRequest( - options, -- mOrigin, +- assertNonNull(mOrigin), - mTopOrigin, - mPayment, - this::onSignResponse, @@ -3281,7 +3268,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } @Override -@@ -251,10 +210,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -263,10 +222,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon decoratedCallback.call(false); return; } @@ -3292,7 +3279,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } @Override -@@ -284,19 +239,7 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -296,19 +251,7 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon return; } @@ -3313,7 +3300,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } // Helper function to create WebAuthnClientCapability instances -@@ -326,14 +269,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -338,14 +281,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon callback.onResponse(new ArrayList()); return; } @@ -3328,7 +3315,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } @Override -@@ -343,13 +278,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -355,13 +290,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon callback.call(false); return; } @@ -3342,27 +3329,28 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } @Override -@@ -361,8 +289,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - if (!mIsOperationPending || mGetAssertionCallback == null) { +@@ -373,9 +301,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + if (!mIsOperationPending || mGetCredentialCallback == null) { return; } - +- assumeNonNull(mPendingFido2CredentialRequest); - mPendingFido2CredentialRequest.cancelConditionalGetAssertion(); } /** Callbacks for receiving responses from the internal handlers. */ -@@ -397,7 +323,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon - } else if (mGetAssertionCallback != null) { - mGetAssertionCallback.call(status, null, null); +@@ -410,7 +335,6 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon + } else if (mGetCredentialCallback != null) { + mGetCredentialCallback.call(getCredentialResponseForAssertion(status, null)); } - if (mPendingFido2CredentialRequest != null) mPendingFido2CredentialRequest.destroyBridge(); cleanupRequest(); } -@@ -435,13 +360,10 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon +@@ -448,13 +372,10 @@ public final class AuthenticatorImpl implements Authenticator, AuthenticationCon mIsOperationPending = false; mMakeCredentialCallback = null; - mGetAssertionCallback = null; + mGetCredentialCallback = null; - mPendingFido2CredentialRequest = null; } @@ -3396,9 +3384,9 @@ new file mode 100644 diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreUtils.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreUtils.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreUtils.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/GmsCoreUtils.java -@@ -7,27 +7,13 @@ package org.chromium.components.webauthn; - import org.chromium.base.PackageUtils; +@@ -9,27 +9,13 @@ import org.chromium.build.annotations.NullMarked; + @NullMarked public class GmsCoreUtils { - private static final String GMSCORE_PACKAGE_NAME = "com.google.android.gms"; - private static final int GMSCORE_MIN_VERSION_GET_MATCHING_CRED_IDS = 223300000; @@ -3426,7 +3414,7 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut } /** -@@ -36,20 +22,16 @@ public class GmsCoreUtils { +@@ -38,20 +24,16 @@ public class GmsCoreUtils { * version. */ public static boolean isGetMatchingCredentialIdsSupported() { @@ -3453,19 +3441,19 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnModeProvider.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnModeProvider.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnModeProvider.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnModeProvider.java -@@ -7,7 +7,6 @@ package org.chromium.components.webauthn; - import org.jni_zero.JNINamespace; - import org.jni_zero.NativeMethods; +@@ -9,7 +9,6 @@ import org.jni_zero.NativeMethods; + import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; -import org.chromium.components.webauthn.Fido2ApiCall.Fido2ApiCallParams; import org.chromium.components.webauthn.cred_man.AppCredManRequestDecorator; import org.chromium.components.webauthn.cred_man.BrowserCredManRequestDecorator; import org.chromium.components.webauthn.cred_man.CredManRequestDecorator; -@@ -47,20 +46,6 @@ public class WebauthnModeProvider { +@@ -51,20 +50,6 @@ public class WebauthnModeProvider { return null; } -- public Fido2ApiCallParams getFido2ApiCallParams(WebContents webContents) { +- public @Nullable Fido2ApiCallParams getFido2ApiCallParams(@Nullable WebContents webContents) { - int mode = getWebauthnMode(webContents); - if (mode == WebauthnMode.APP) { - return Fido2ApiCall.APP_API; @@ -3479,14 +3467,14 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut - return null; - } - - public @WebauthnMode int getWebauthnMode(WebContents webContents) { + public @WebauthnMode int getWebauthnMode(@Nullable WebContents webContents) { if (mGlobalMode != WebauthnMode.NONE) return mGlobalMode; return WebauthnModeProviderJni.get().getWebauthnModeForWebContents(webContents); diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java -@@ -32,8 +32,7 @@ import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions; - import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions; +@@ -36,8 +36,7 @@ import org.chromium.build.annotations.NullMarked; + import org.chromium.build.annotations.Nullable; import org.chromium.components.webauthn.AuthenticationContextProvider; import org.chromium.components.webauthn.Barrier; -import org.chromium.components.webauthn.Fido2CredentialRequest.ConditionalUiState; @@ -3495,8 +3483,8 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut import org.chromium.components.webauthn.GetAssertionOutcome; import org.chromium.components.webauthn.GetAssertionResponseCallback; import org.chromium.components.webauthn.MakeCredentialOutcome; -@@ -101,8 +100,7 @@ public class CredManHelper { - MakeCredentialResponseCallback makeCallback, +@@ -105,8 +104,7 @@ public class CredManHelper { + @Nullable MakeCredentialResponseCallback makeCallback, ErrorCallback errorCallback) { mClientDataJson = clientDataJson; - final String requestAsJson = @@ -3505,10 +3493,10 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut OutcomeReceiver receiver = new OutcomeReceiver<>() { -@@ -145,9 +143,7 @@ public class CredManHelper { - String json = +@@ -150,9 +148,7 @@ public class CredManHelper { data.getString( CRED_MAN_PREFIX + "BUNDLE_KEY_REGISTRATION_RESPONSE_JSON"); + assertNonNull(json); - byte[] responseSerialized = - Fido2CredentialRequestJni.get() - .makeCredentialResponseFromJson(json); @@ -3516,17 +3504,17 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut if (responseSerialized == null) { Log.e( TAG, -@@ -416,8 +412,7 @@ public class CredManHelper { - data.getString( +@@ -423,8 +419,7 @@ public class CredManHelper { CRED_MAN_PREFIX + "BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"); + assertNonNull(json); - byte[] responseSerialized = - Fido2CredentialRequestJni.get().getCredentialResponseFromJson(json); + byte[] responseSerialized = null; if (responseSerialized == null) { Log.e( TAG, -@@ -568,8 +563,7 @@ public class CredManHelper { +@@ -580,8 +575,7 @@ public class CredManHelper { boolean requestPasswords, boolean preferImmediatelyAvailable, boolean ignoreGpm) { @@ -3539,10 +3527,10 @@ diff --git a/components/webauthn/android/java/src/org/chromium/components/webaut diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java --- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java +++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManMetricsHelper.java -@@ -7,7 +7,7 @@ package org.chromium.components.webauthn.cred_man; - import androidx.annotation.IntDef; +@@ -8,7 +8,7 @@ import androidx.annotation.IntDef; import org.chromium.base.metrics.RecordHistogram; + import org.chromium.build.annotations.NullMarked; -import org.chromium.components.webauthn.Fido2CredentialRequest.ConditionalUiState; +import org.chromium.components.webauthn.ConditionalUiState; @@ -3679,7 +3667,7 @@ diff --git a/content/public/android/java/src/org/chromium/content/browser/webid/ diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn -@@ -3625,10 +3625,6 @@ if (is_android) { +@@ -3653,10 +3653,6 @@ if (is_android) { testonly = true sources = content_java_sources_needing_jni deps = [ @@ -3693,7 +3681,7 @@ diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn diff --git a/device/BUILD.gn b/device/BUILD.gn --- a/device/BUILD.gn +++ b/device/BUILD.gn -@@ -531,9 +531,6 @@ if (is_android) { +@@ -520,9 +520,6 @@ if (is_android) { "gamepad/android/junit/src/org/chromium/device/gamepad/GamepadMappingsTest.java", ] deps = [ @@ -3706,7 +3694,7 @@ diff --git a/device/BUILD.gn b/device/BUILD.gn diff --git a/device/fido/features.cc b/device/fido/features.cc --- a/device/fido/features.cc +++ b/device/fido/features.cc -@@ -123,6 +123,7 @@ BASE_FEATURE(kWebAuthniCloudKeychainPrf, +@@ -113,6 +113,7 @@ BASE_FEATURE(kWebAuthniCloudKeychainPrf, BASE_FEATURE(kWebAuthnHybridLinking, "WebAuthenticationHybridLinking", base::FEATURE_ENABLED_BY_DEFAULT); @@ -3717,7 +3705,7 @@ diff --git a/device/fido/features.cc b/device/fido/features.cc diff --git a/services/BUILD.gn b/services/BUILD.gn --- a/services/BUILD.gn +++ b/services/BUILD.gn -@@ -150,11 +150,6 @@ if (is_android) { +@@ -157,11 +157,6 @@ if (is_android) { "shape_detection/android/junit/src/org/chromium/shape_detection/BitmapUtilsTest.java", ] deps = [ @@ -3729,7 +3717,7 @@ diff --git a/services/BUILD.gn b/services/BUILD.gn "//base:base_java", "//base:base_java_test_support", "//base:base_junit_test_support", -@@ -184,10 +179,6 @@ if (is_android) { +@@ -191,10 +186,6 @@ if (is_android) { "shape_detection/android/javatests/src/org/chromium/shape_detection/TextDetectionImplTest.java", ] deps = [ @@ -3743,7 +3731,7 @@ diff --git a/services/BUILD.gn b/services/BUILD.gn diff --git a/services/device/geolocation/BUILD.gn b/services/device/geolocation/BUILD.gn --- a/services/device/geolocation/BUILD.gn +++ b/services/device/geolocation/BUILD.gn -@@ -158,10 +158,6 @@ if (is_android) { +@@ -156,10 +156,6 @@ if (is_android) { deps = [ ":geolocation_jni_headers", @@ -3793,7 +3781,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. } # The section below is generated by running -@@ -321,295 +312,6 @@ if (!limit_android_deps) { +@@ -318,295 +309,6 @@ if (!limit_android_deps) { [ "local_modifications/accessibility_test_framework.pcfg" ] } @@ -3883,7 +3871,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - info_path = "libs/com_google_android_gms_play_services_basement/com_google_android_gms_play_services_basement.info" - enable_bytecode_checks = false - deps = [ -- "//third_party/androidx:androidx_collection_collection_jvm_java", +- "//third_party/androidx:androidx_collection_collection_java", - "//third_party/androidx:androidx_fragment_fragment_java", - ] - @@ -3946,7 +3934,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - info_path = "libs/com_google_android_gms_play_services_fido/com_google_android_gms_play_services_fido.info" - enable_bytecode_checks = false - deps = [ -- ":org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm_java", +- ":org_jetbrains_kotlinx_kotlinx_coroutines_core_java", - "$google_play_services_package:google_play_services_base_java", - "$google_play_services_package:google_play_services_basement_java", - "$google_play_services_package:google_play_services_tasks_java", @@ -3976,7 +3964,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. - if (google_play_services_package == "//third_party/android_deps") { - android_aar_prebuilt("google_play_services_identity_credentials_java") { -- aar_path = "cipd/libs/com_google_android_gms_play_services_identity_credentials/play-services-identity-credentials-16.0.0-alpha04.aar" +- aar_path = "cipd/libs/com_google_android_gms_play_services_identity_credentials/play-services-identity-credentials-16.0.0-alpha05.aar" - info_path = "libs/com_google_android_gms_play_services_identity_credentials/com_google_android_gms_play_services_identity_credentials.info" - enable_bytecode_checks = false - deps = [ @@ -4089,7 +4077,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. if (!defined(material_design_target)) { android_aar_prebuilt("com_google_android_material_material_java") { -@@ -657,24 +359,6 @@ if (!limit_android_deps) { +@@ -655,24 +357,6 @@ if (!limit_android_deps) { } } @@ -4112,9 +4100,9 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - } - # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. - if (!defined(dagger_java_target)) { - java_prebuilt("com_google_dagger_dagger_java") { -@@ -693,56 +377,6 @@ if (!limit_android_deps) { + android_aar_prebuilt("com_google_ar_impress_java") { + aar_path = "cipd/libs/com_google_ar_impress/impress-0.0.2.aar" +@@ -706,56 +390,6 @@ if (!limit_android_deps) { } } @@ -4171,7 +4159,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. if (!defined(guava_android_target)) { java_prebuilt("com_google_guava_guava_android_java") { -@@ -1081,340 +715,6 @@ if (!limit_android_deps) { +@@ -1107,339 +741,6 @@ if (!limit_android_deps) { is_robolectric = true } @@ -4188,8 +4176,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - ":*", - "//third_party/androidx:*", - ] -- deps = -- [ "//third_party/androidx:androidx_annotation_annotation_jvm_java" ] +- deps = [ "//third_party/androidx:androidx_annotation_annotation_java" ] - } - } - @@ -4325,6 +4312,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - ":*", - "//third_party/androidx:*", - ] +- testonly = true - } - - # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. @@ -4380,7 +4368,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - ] - deps = [ - "$google_play_services_package:google_firebase_firebase_annotations_java", -- "//third_party/androidx:androidx_annotation_annotation_jvm_java", +- "//third_party/androidx:androidx_annotation_annotation_java", - ] - } - } @@ -4399,8 +4387,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - ":*", - "//third_party/androidx:*", - ] -- deps = -- [ "//third_party/androidx:androidx_annotation_annotation_jvm_java" ] +- deps = [ "//third_party/androidx:androidx_annotation_annotation_java" ] - - # https://crbug.com/1412551 - requires_android = true @@ -4422,7 +4409,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. - ] - deps = [ - "$google_play_services_package:google_firebase_firebase_encoders_java", -- "//third_party/androidx:androidx_annotation_annotation_jvm_java", +- "//third_party/androidx:androidx_annotation_annotation_java", - ] - } - } @@ -4512,7 +4499,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. # This is generated, do not edit. Update BuildConfigGenerator.groovy instead. java_prebuilt( "com_google_testparameterinjector_test_parameter_injector_java") { -@@ -1700,7 +1000,6 @@ if (!limit_android_deps) { +@@ -1835,7 +1136,6 @@ if (!limit_android_deps) { ] testonly = true deps = [ @@ -4520,7 +4507,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. ":org_robolectric_pluginapi_java", ":org_robolectric_utils_java", "//third_party/android_deps:guava_android_java", -@@ -1722,7 +1021,6 @@ if (!limit_android_deps) { +@@ -1857,7 +1157,6 @@ if (!limit_android_deps) { ] testonly = true deps = [ @@ -4528,7 +4515,7 @@ diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD. ":org_robolectric_annotations_java", ":org_robolectric_pluginapi_java", ":org_robolectric_utils_java", -@@ -1792,7 +1090,6 @@ if (!limit_android_deps) { +@@ -1927,7 +1226,6 @@ if (!limit_android_deps) { ] testonly = true deps = [ @@ -4583,7 +4570,7 @@ diff --git a/third_party/android_deps/util/org/chromium/gms/ChromiumPlayServices diff --git a/third_party/androidx/customizations.gni b/third_party/androidx/customizations.gni --- a/third_party/androidx/customizations.gni +++ b/third_party/androidx/customizations.gni -@@ -136,7 +136,7 @@ template("androidx_java_prebuilt") { +@@ -151,7 +151,7 @@ template("androidx_java_prebuilt") { } } @@ -4592,12 +4579,13 @@ diff --git a/third_party/androidx/customizations.gni b/third_party/androidx/cust _androidx_prebuilt(target_name) { forward_variables_from(invoker, "*") target_type = "android_aar_prebuilt" -@@ -157,6 +157,17 @@ template("androidx_android_aar_prebuilt") { +@@ -172,6 +172,18 @@ template("androidx_android_aar_prebuilt") { } } +template("androidx_android_aar_prebuilt") { -+ if (target_name == "androidx_credentials_credentials_play_services_auth_java") { ++ if (target_name == "androidx_credentials_credentials_play_services_auth_java" ++ || target_name == "androidx_credentials_registry_registry_provider_play_services_java") { + forward_variables_from(invoker, "*") + not_needed(["deps", "aar_path", "info_path", "enable_bytecode_checks"]) + } else { diff --git a/build/cromite_patches/Remove-blocklisted-URLs-upon-bookmark-creation.patch b/build/cromite_patches/Remove-blocklisted-URLs-upon-bookmark-creation.patch index 8053980cc283e142469b6ad2e8e7deed28868b4b..d7b9c0665c0aed4b8a597cd76f7cccff597edf25 100644 --- a/build/cromite_patches/Remove-blocklisted-URLs-upon-bookmark-creation.patch +++ b/build/cromite_patches/Remove-blocklisted-URLs-upon-bookmark-creation.patch @@ -7,9 +7,9 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../chrome/browser/bookmarks/TabBookmarker.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java @@ -19,6 +19,12 @@ import org.chromium.components.bookmarks.BookmarkItem; import org.chromium.components.bookmarks.BookmarkType; import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; diff --git a/build/cromite_patches/Remove-help-menu-item.patch b/build/cromite_patches/Remove-help-menu-item.patch index 8aacb96df8002178dad9c4c68f2917a283d4f9be..cdc7c5a71f1e1f48f57b1ddb34223cfe81c4a884 100644 --- a/build/cromite_patches/Remove-help-menu-item.patch +++ b/build/cromite_patches/Remove-help-menu-item.patch @@ -30,10 +30,10 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml --- a/chrome/android/java/res/menu/main_menu.xml +++ b/chrome/android/java/res/menu/main_menu.xml -@@ -158,9 +158,6 @@ found in the LICENSE file. - +@@ -167,9 +167,6 @@ found in the LICENSE file. + - @@ -67,7 +67,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcu diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java -@@ -2389,15 +2389,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity +@@ -2390,15 +2390,6 @@ public abstract class ChromeActivity extends AsyncInitializationActivity final Tab currentTab = getActivityTab(); @@ -152,7 +152,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/setting diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java -@@ -420,6 +420,7 @@ public class SettingsActivity extends ChromeBaseAppCompatActivity +@@ -419,6 +419,7 @@ public class SettingsActivity extends ChromeBaseAppCompatActivity help.setIcon( TraceEventVectorDrawableCompat.create( getResources(), R.drawable.ic_help_and_feedback, getTheme())); diff --git a/build/cromite_patches/Remove-voice-recognition-integration.patch b/build/cromite_patches/Remove-voice-recognition-integration.patch index dbf39f1df9e908c0758fa12dd1c3f9c3bc247f0c..0fcf1f97500daf39adf293233b4788815cb18eba 100644 --- a/build/cromite_patches/Remove-voice-recognition-integration.patch +++ b/build/cromite_patches/Remove-voice-recognition-integration.patch @@ -4,12 +4,15 @@ Subject: Remove voice recognition integration License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- + .../BaseCustomTabRootUiCoordinator.java | 2 +- .../chrome/browser/ntp/NewTabPage.java | 38 +---------- .../browser/ntp/search/SearchBoxMediator.java | 1 - .../browser/searchwidget/SearchActivity.java | 3 +- .../SearchActivityLocationBarLayout.java | 8 --- + .../tabbed_mode/TabbedRootUiCoordinator.java | 2 +- .../browser/toolbar/ToolbarManager.java | 5 -- - .../chrome/browser/ui/RootUiCoordinator.java | 43 ------------ + .../ui/AdaptiveToolbarUiCoordinator.java | 48 -------------- + .../chrome/browser/ui/RootUiCoordinator.java | 18 ----- chrome/browser/ui/android/omnibox/BUILD.gn | 1 - .../chrome/browser/omnibox/LocationBar.java | 6 -- .../omnibox/LocationBarCoordinator.java | 13 ---- @@ -21,12 +24,24 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../suggestions/AutocompleteMediator.java | 8 --- .../omnibox/voice/VoiceRecognitionUtil.java | 1 + chrome/browser/ui/android/toolbar/BUILD.gn | 1 - - 17 files changed, 7 insertions(+), 234 deletions(-) + 20 files changed, 9 insertions(+), 259 deletions(-) +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java +@@ -419,7 +419,7 @@ public class BaseCustomTabRootUiCoordinator extends RootUiCoordinator { + protected AdaptiveToolbarBehavior createAdaptiveToolbarBehavior( + Supplier trackerSupplier) { + return new CustomTabAdaptiveToolbarBehavior( +- () -> addVoiceSearchAdaptiveButton(trackerSupplier)); ++ () -> {}); + } + + @Override diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java -@@ -69,7 +69,6 @@ import org.chromium.chrome.browser.magic_stack.ModuleRegistry; +@@ -70,7 +70,6 @@ import org.chromium.chrome.browser.metrics.StartupMetricsTracker; import org.chromium.chrome.browser.native_page.ContextMenuManager; import org.chromium.chrome.browser.omnibox.OmniboxFocusReason; import org.chromium.chrome.browser.omnibox.OmniboxStub; @@ -34,7 +49,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; -@@ -126,7 +125,6 @@ public class NewTabPage +@@ -127,7 +126,6 @@ public class NewTabPage TemplateUrlServiceObserver, BrowserControlsStateProvider.Observer, FeedSurfaceDelegate, @@ -42,7 +57,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. ModuleDelegateHost { private static final String TAG = "NewTabPage"; -@@ -156,7 +154,6 @@ public class NewTabPage +@@ -157,7 +155,6 @@ public class NewTabPage protected boolean mSearchProviderHasLogo; protected OmniboxStub mOmniboxStub; @@ -50,7 +65,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. // The timestamp at which the constructor was called. protected final long mConstructedTimeNs; -@@ -361,8 +358,7 @@ public class NewTabPage +@@ -362,8 +359,7 @@ public class NewTabPage @Override public boolean isVoiceSearchEnabled() { @@ -60,7 +75,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. } @Override -@@ -370,24 +366,6 @@ public class NewTabPage +@@ -371,24 +367,6 @@ public class NewTabPage if (mIsDestroyed) return; FeedReliabilityLogger feedReliabilityLogger = mFeedSurfaceProvider.getReliabilityLogger(); @@ -85,7 +100,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. } @Override -@@ -943,12 +921,6 @@ public class NewTabPage +@@ -950,12 +928,6 @@ public class NewTabPage mOmniboxStub.addUrlFocusChangeListener(feedReliabilityLogger); } } @@ -98,7 +113,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. } @Override -@@ -959,11 +931,6 @@ public class NewTabPage +@@ -966,11 +938,6 @@ public class NewTabPage } } @@ -110,7 +125,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage. /** Adds an observer to be notified on most visited tile clicks. */ public void addMostVisitedTileClickObserver(MostVisitedTileClickObserver observer) { mMostVisitedTileClickObservers.addObserver(observer); -@@ -1050,9 +1017,6 @@ public class NewTabPage +@@ -1057,9 +1024,6 @@ public class NewTabPage } mFeedSurfaceProvider.destroy(); mTab.getWindowAndroid().removeContextMenuCloseListener(mContextMenuManager); @@ -134,15 +149,15 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/search/Sear diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java --- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java -@@ -64,7 +64,6 @@ import org.chromium.chrome.browser.profiles.ProfileProvider; - import org.chromium.chrome.browser.rlz.RevenueStats; +@@ -66,7 +66,6 @@ import org.chromium.chrome.browser.rlz.RevenueStats; import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.chrome.browser.tab.Tab; + import org.chromium.chrome.browser.tabmodel.TabModelSelector; -import org.chromium.chrome.browser.toolbar.VoiceToolbarButtonController; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarManageable; import org.chromium.chrome.browser.ui.native_page.NativePage; -@@ -325,7 +324,7 @@ public class SearchActivity extends AsyncInitializationActivity +@@ -323,7 +322,7 @@ public class SearchActivity extends AsyncInitializationActivity /*omniboxUma*/ (url, transition, isNtp) -> {}, TabWindowManagerSingleton::getInstance, /* bookmarkState= */ (url) -> false, @@ -176,6 +191,18 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/Se @Override public int getLensEntryPoint() { return mInteractionFromWidget +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +@@ -886,7 +886,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { + mActivityLifecycleDispatcher, + mTabCreatorManagerSupplier, + mActivityTabProvider, +- () -> addVoiceSearchAdaptiveButton(trackerSupplier)); ++ () -> {}); + } + + @Override diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java @@ -187,7 +214,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar import org.chromium.chrome.browser.page_info.ChromePageInfo; import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations; import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; -@@ -2682,10 +2681,6 @@ public class ToolbarManager +@@ -2669,10 +2668,6 @@ public class ToolbarManager return mLocationBar.getOmniboxStub(); } @@ -198,27 +225,104 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/Toolbar /** Returns the app menu coordinator. */ public @Nullable MenuButtonCoordinator getOverviewModeMenuButtonCoordinator() { return mOverviewModeMenuButtonCoordinator; +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/AdaptiveToolbarUiCoordinator.java +@@ -26,8 +26,6 @@ import org.chromium.chrome.browser.dom_distiller.ReaderModeToolbarButtonControll + import org.chromium.chrome.browser.flags.ChromeFeatureList; + import org.chromium.chrome.browser.identity_disc.IdentityDiscController; + import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; +-import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; +-import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceInteractionSource; + import org.chromium.chrome.browser.price_insights.PriceInsightsButtonController; + import org.chromium.chrome.browser.price_tracking.CurrentTabPriceTrackingStateSupplier; + import org.chromium.chrome.browser.price_tracking.PriceTrackingBottomSheetContentCoordinator; +@@ -40,7 +38,6 @@ import org.chromium.chrome.browser.share.ShareButtonController; + import org.chromium.chrome.browser.share.ShareDelegate; + import org.chromium.chrome.browser.tabmodel.TabModelSelector; + import org.chromium.chrome.browser.toolbar.ButtonDataProvider; +-import org.chromium.chrome.browser.toolbar.VoiceToolbarButtonController; + import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveButtonActionMenuCoordinator; + import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarBehavior; + import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonController; +@@ -68,7 +65,6 @@ public class AdaptiveToolbarUiCoordinator { + private CurrentTabPriceTrackingStateSupplier mCurrentTabPriceTrackingStateSupplier; + private ContextualPageActionController mContextualPageActionController; + private AdaptiveToolbarButtonController mAdaptiveToolbarButtonController; +- private VoiceToolbarButtonController mVoiceToolbarButtonController; + private BottomSheetController mBottomSheetController; + private ObservableSupplier mProfileSupplier; + private Supplier mScrimSupplier; +@@ -232,45 +228,6 @@ public class AdaptiveToolbarUiCoordinator { + mButtonDataProviders = List.of(identityDiscController, adaptiveToolbarButtonController); + } + +- /** +- * Add voice search action button. +- * +- * @param Supplies {@link VoiceRecognitionHandler} object. +- * @param Supplies {@link Tracker} object. +- */ +- public void addVoiceSearchAdaptiveButton( +- Supplier voiceRecognitionHandler, +- Supplier trackerSupplier) { +- var voiceSearchDelegate = +- new VoiceToolbarButtonController.VoiceSearchDelegate() { +- @Override +- public boolean isVoiceSearchEnabled() { +- if (voiceRecognitionHandler.get() == null) return false; +- return voiceRecognitionHandler.get().isVoiceSearchEnabled(); +- } +- +- @Override +- public void startVoiceRecognition() { +- if (voiceRecognitionHandler.get() == null) return; +- voiceRecognitionHandler +- .get() +- .startVoiceRecognition(VoiceInteractionSource.TOOLBAR); +- } +- }; +- mVoiceToolbarButtonController = +- new VoiceToolbarButtonController( +- mContext, +- AppCompatResources.getDrawable(mContext, R.drawable.ic_mic_white_24dp), +- mActivityTabProvider, +- trackerSupplier, +- mModalDialogManagerSupplier.get(), +- voiceSearchDelegate); +- +- assert mAdaptiveToolbarButtonController != null; +- mAdaptiveToolbarButtonController.addButtonVariant( +- AdaptiveToolbarButtonVariant.VOICE, mVoiceToolbarButtonController); +- } +- + /** + * Returns the list of {@link ButtonDataProvider}. The order in which the providers determines + * which one will be shown first. +@@ -279,11 +236,6 @@ public class AdaptiveToolbarUiCoordinator { + return mButtonDataProviders; + } + +- /** Returns {@link VoiceToolbarButtonController} used for voice search button. */ +- public VoiceToolbarButtonController getVoiceToolbarButtonController() { +- return mVoiceToolbarButtonController; +- } +- + /** Destroy internally used objects. */ + public void destroy() { + if (mCurrentTabPriceTrackingStateSupplier != null) { diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java -@@ -106,8 +106,6 @@ import org.chromium.chrome.browser.metrics.UmaSessionStats; +@@ -97,7 +97,6 @@ import org.chromium.chrome.browser.metrics.UmaSessionStats; import org.chromium.chrome.browser.omnibox.OmniboxFocusReason; import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxActionDelegateImpl; -import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; --import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceInteractionSource; import org.chromium.chrome.browser.paint_preview.DemoPaintPreview; import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer; import org.chromium.chrome.browser.password_manager.PasswordManagerLauncher; -@@ -148,7 +146,6 @@ import org.chromium.chrome.browser.theme.TopUiThemeColorProvider; - import org.chromium.chrome.browser.toolbar.ButtonDataProvider; - import org.chromium.chrome.browser.toolbar.ToolbarIntentMetadata; - import org.chromium.chrome.browser.toolbar.ToolbarManager; --import org.chromium.chrome.browser.toolbar.VoiceToolbarButtonController; - import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveButtonActionMenuCoordinator; - import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonController; - import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant; -@@ -310,7 +307,6 @@ public class RootUiCoordinator +@@ -285,7 +284,6 @@ public class RootUiCoordinator private LayoutManagerImpl mLayoutManager; protected OneshotSupplier mIntentMetadataOneshotSupplier; protected OneshotSupplierImpl mPromoShownOneshotSupplier = new OneshotSupplierImpl<>(); @@ -226,7 +330,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordi private MediaCaptureOverlayController mCaptureController; private @Nullable ScrollCaptureManager mScrollCaptureManager; protected final ActivityLifecycleDispatcher mActivityLifecycleDispatcher; -@@ -636,9 +632,6 @@ public class RootUiCoordinator +@@ -612,9 +610,6 @@ public class RootUiCoordinator } if (mToolbarManager != null) { @@ -236,69 +340,30 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordi mToolbarManager.destroy(); mToolbarManager = null; } -@@ -1472,39 +1465,12 @@ public class RootUiCoordinator - () -> - mToolbarManager.setUrlBarFocus( - false, OmniboxFocusReason.UNFOCUS)); -- VoiceToolbarButtonController.VoiceSearchDelegate voiceSearchDelegate = -- new VoiceToolbarButtonController.VoiceSearchDelegate() { -- @Override -- public boolean isVoiceSearchEnabled() { -- VoiceRecognitionHandler voiceRecognitionHandler = -- mToolbarManager.getVoiceRecognitionHandler(); -- if (voiceRecognitionHandler == null) return false; -- return voiceRecognitionHandler.isVoiceSearchEnabled(); -- } -- -- @Override -- public void startVoiceRecognition() { -- VoiceRecognitionHandler voiceRecognitionHandler = -- mToolbarManager.getVoiceRecognitionHandler(); -- if (voiceRecognitionHandler == null) return; -- voiceRecognitionHandler.startVoiceRecognition( -- VoiceInteractionSource.TOOLBAR); -- } -- }; - TranslateToolbarButtonController translateToolbarButtonController = - new TranslateToolbarButtonController( - mActivityTabProvider, - AppCompatResources.getDrawable(mActivity, R.drawable.ic_translate), - mActivity.getString(R.string.menu_translate), - trackerSupplier); -- VoiceToolbarButtonController voiceToolbarButtonController = -- new VoiceToolbarButtonController( -- mActivity, -- AppCompatResources.getDrawable(mActivity, R.drawable.ic_mic_white_24dp), -- mActivityTabProvider, -- trackerSupplier, -- mModalDialogManagerSupplier.get(), -- voiceSearchDelegate); - OptionalNewTabButtonController newTabButtonController = - new OptionalNewTabButtonController( - mActivity, -@@ -1550,8 +1516,6 @@ public class RootUiCoordinator - AdaptiveToolbarButtonVariant.NEW_TAB, newTabButtonController); - adaptiveToolbarButtonController.addButtonVariant( - AdaptiveToolbarButtonVariant.SHARE, shareButtonController); -- adaptiveToolbarButtonController.addButtonVariant( -- AdaptiveToolbarButtonVariant.VOICE, voiceToolbarButtonController); - adaptiveToolbarButtonController.addButtonVariant( - AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS, - addToBookmarksToolbarButtonController); -@@ -1669,13 +1633,6 @@ public class RootUiCoordinator +@@ -1488,23 +1483,10 @@ public class RootUiCoordinator if (!mSupportsAppMenuSupplier.getAsBoolean()) { mToolbarManager.getToolbar().disableMenuButton(); } - -- VoiceRecognitionHandler voiceRecognitionHandler = -- mToolbarManager.getVoiceRecognitionHandler(); -- if (voiceRecognitionHandler != null) { -- mMicStateObserver = voiceToolbarButtonController::updateMicButtonState; +- var voiceButtonController = +- mAdaptiveToolbarUiCoordinator.getVoiceToolbarButtonController(); +- var voiceRecognitionHandler = mToolbarManager.getVoiceRecognitionHandler(); +- if (voiceButtonController != null && voiceRecognitionHandler != null) { +- mMicStateObserver = voiceButtonController::updateMicButtonState; - voiceRecognitionHandler.addObserver(mMicStateObserver); - } mToolbarManagerOneshotSupplier.set(mToolbarManager); } } + +- protected void addVoiceSearchAdaptiveButton(Supplier trackerSupplier) { +- mAdaptiveToolbarUiCoordinator.addVoiceSearchAdaptiveButton( +- () -> mToolbarManager.getVoiceRecognitionHandler(), trackerSupplier); +- } +- + /** + * Constructs a {@link ScrimManager} and sets up observers. Lifetime of all these objects should + * match. diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn --- a/chrome/browser/ui/android/omnibox/BUILD.gn +++ b/chrome/browser/ui/android/omnibox/BUILD.gn @@ -321,7 +386,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow import org.chromium.chrome.browser.tab.Tab; import java.util.Optional; -@@ -86,11 +85,6 @@ public interface LocationBar { +@@ -78,11 +77,6 @@ public interface LocationBar { */ View getSecurityIconView(); @@ -344,7 +409,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; import org.chromium.chrome.browser.share.ShareDelegate; -@@ -464,13 +463,6 @@ public class LocationBarCoordinator +@@ -455,13 +454,6 @@ public class LocationBarCoordinator return mLocationBarLayout.getSecurityIconView(); } @@ -358,7 +423,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow @Nullable @Override public OmniboxStub getOmniboxStub() { -@@ -792,11 +784,6 @@ public class LocationBarCoordinator +@@ -783,11 +775,6 @@ public class LocationBarCoordinator // End tablet-specific methods. @@ -678,7 +743,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionUtil.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionUtil.java --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionUtil.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionUtil.java -@@ -121,6 +121,7 @@ public class VoiceRecognitionUtil { +@@ -122,6 +122,7 @@ public class VoiceRecognitionUtil { * @return {@code true} if recognition is supported. {@code false} otherwise. */ public static boolean isRecognitionIntentPresent(boolean useCachedValue) { @@ -695,6 +760,6 @@ diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/andr "java/src/org/chromium/chrome/browser/toolbar/ToolbarTabControllerImpl.java", - "java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.java", "java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuCoordinator.java", + "java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarBehavior.java", "java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarBridge.java", - "java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java", -- diff --git a/build/cromite_patches/Remove-window-name-on-cross-origin-navigation.patch b/build/cromite_patches/Remove-window-name-on-cross-origin-navigation.patch index d7846150ac9ac9a68cdc827b13c811bb224ce4c1..9531dc75e51d77015b7cf203bcd24fd19e14b0c7 100644 --- a/build/cromite_patches/Remove-window-name-on-cross-origin-navigation.patch +++ b/build/cromite_patches/Remove-window-name-on-cross-origin-navigation.patch @@ -21,7 +21,7 @@ new file mode 100644 diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc --- a/third_party/blink/renderer/core/loader/document_loader.cc +++ b/third_party/blink/renderer/core/loader/document_loader.cc -@@ -3004,7 +3004,7 @@ void DocumentLoader::CommitNavigation() { +@@ -3014,7 +3014,7 @@ void DocumentLoader::CommitNavigation() { // that the name would be nulled and if the name is accessed after we will // fire a UseCounter. If we decide to move forward with this change, we'd // actually clean the name here. @@ -30,7 +30,7 @@ diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_p frame_->Tree().ExperimentalSetNulledName(); } -@@ -3015,6 +3015,7 @@ void DocumentLoader::CommitNavigation() { +@@ -3025,6 +3025,7 @@ void DocumentLoader::CommitNavigation() { // TODO(shuuran): CrossSiteCrossBrowsingContextGroupSetNulledName will just // record the fact that the name would be nulled and if the name is accessed // after we will fire a UseCounter. diff --git a/build/cromite_patches/Restore-BookmarkToolbar-setCurrentFolder.patch b/build/cromite_patches/Restore-BookmarkToolbar-setCurrentFolder.patch index a2d63f95e6d1bb010c8ccdec5d5c61ed3036e42d..ac22d56b37beab42522b5d74b90960afd24ac9af 100644 --- a/build/cromite_patches/Restore-BookmarkToolbar-setCurrentFolder.patch +++ b/build/cromite_patches/Restore-BookmarkToolbar-setCurrentFolder.patch @@ -6,17 +6,17 @@ restore https://chromium-review.googlesource.com/c/chromium/src/+/5554182 License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html --- - .../chrome/browser/bookmarks/BookmarkToolbar.java | 12 ++++++++++++ - .../bookmarks/BookmarkToolbarCoordinator.java | 1 + - .../browser/bookmarks/BookmarkToolbarMediator.java | 1 + - .../browser/bookmarks/BookmarkToolbarProperties.java | 9 +++++++++ - .../browser/bookmarks/BookmarkToolbarViewBinder.java | 4 ++++ - 5 files changed, 27 insertions(+) + .../chrome/browser/bookmarks/BookmarkToolbar.java | 13 +++++++++++++ + .../bookmarks/BookmarkToolbarCoordinator.java | 1 + + .../browser/bookmarks/BookmarkToolbarMediator.java | 1 + + .../bookmarks/BookmarkToolbarProperties.java | 9 +++++++++ + .../bookmarks/BookmarkToolbarViewBinder.java | 4 ++++ + 5 files changed, 28 insertions(+) -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java -@@ -10,11 +10,13 @@ import android.view.MenuItem; +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java +@@ -10,10 +10,12 @@ import android.view.MenuItem; import android.view.View.OnClickListener; import androidx.annotation.IdRes; @@ -24,13 +24,12 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener; import androidx.core.view.MenuCompat; - import org.chromium.chrome.R; import org.chromium.chrome.browser.bookmarks.BookmarkUiState.BookmarkUiMode; +import org.chromium.components.bookmarks.BookmarkItem; import org.chromium.components.bookmarks.BookmarkId; import org.chromium.components.browser_ui.util.ToolbarUtils; import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar; -@@ -30,8 +32,10 @@ import java.util.function.Function; +@@ -29,8 +31,10 @@ import java.util.function.Function; */ public class BookmarkToolbar extends SelectableListToolbar implements OnMenuItemClickListener, OnClickListener { @@ -41,7 +40,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm private boolean mEditButtonVisible; private boolean mNewFolderButtonVisible; private boolean mNewFolderButtonEnabled; -@@ -59,6 +63,10 @@ public class BookmarkToolbar extends SelectableListToolbar +@@ -58,6 +62,11 @@ public class BookmarkToolbar extends SelectableListToolbar setOnMenuItemClickListener(this); } @@ -49,10 +48,11 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm + mBookmarkModel = bookmarkModel; + } + - void setBookmarkOpener(BookmarkOpener bookmarkOpener) {} - ++ void setSelectionDelegate(SelectionDelegate selectionDelegate) { -@@ -164,6 +172,10 @@ public class BookmarkToolbar extends SelectableListToolbar + mSelectionDelegate = selectionDelegate; + getMenu().setGroupEnabled(R.id.selection_mode_menu_group, true); +@@ -161,6 +170,10 @@ public class BookmarkToolbar extends SelectableListToolbar getMenu().findItem(id).setChecked(true); } @@ -63,10 +63,10 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm void setNavigateBackRunnable(Runnable navigateBackRunnable) { mNavigateBackRunnable = navigateBackRunnable; } -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java -@@ -54,6 +54,7 @@ public class BookmarkToolbarCoordinator { +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java +@@ -55,6 +55,7 @@ public class BookmarkToolbarCoordinator { searchDelegate, R.string.bookmark_toolbar_search, R.id.search_menu_id); mModel = new PropertyModel.Builder(BookmarkToolbarProperties.ALL_KEYS).build(); @@ -74,10 +74,10 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm mModel.set(BookmarkToolbarProperties.SELECTION_DELEGATE, selectionDelegate); mModel.set(BookmarkToolbarProperties.BOOKMARK_UI_MODE, BookmarkUiMode.LOADING); mModel.set(BookmarkToolbarProperties.IS_DIALOG_UI, isDialogUi); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java -@@ -290,6 +290,7 @@ class BookmarkToolbarMediator +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java +@@ -293,6 +293,7 @@ class BookmarkToolbarMediator @Override public void onFolderStateSet(BookmarkId folder) { mCurrentFolder = folder; @@ -85,18 +85,18 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm BookmarkItem folderItem = mCurrentFolder == null ? null : mBookmarkModel.getBookmarkById(mCurrentFolder); -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java @@ -4,6 +4,7 @@ package org.chromium.chrome.browser.bookmarks; +import org.chromium.components.bookmarks.BookmarkId; + import org.chromium.chrome.browser.bookmarks.BookmarkUiState.BookmarkUiMode; import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate; import org.chromium.ui.modelutil.PropertyKey; - import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey; -@@ -19,6 +20,8 @@ import java.util.function.Function; +@@ -20,6 +21,8 @@ import java.util.function.Function; */ class BookmarkToolbarProperties { /** Dependencies */ @@ -105,7 +105,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm static final WritableObjectPropertyKey SELECTION_DELEGATE = new WritableObjectPropertyKey<>(); -@@ -65,6 +68,10 @@ class BookmarkToolbarProperties { +@@ -66,6 +69,10 @@ class BookmarkToolbarProperties { static final WritableObjectPropertyKey FAKE_SELECTION_STATE_CHANGE = new WritableObjectPropertyKey<>(/* skipEquality= */ true); @@ -116,7 +116,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm /** Callables to delegate business logic back to the mediator */ static final WritableObjectPropertyKey> MENU_ID_CLICKED_FUNCTION = new WritableObjectPropertyKey<>(); -@@ -84,6 +91,8 @@ class BookmarkToolbarProperties { +@@ -85,6 +92,8 @@ class BookmarkToolbarProperties { NEW_FOLDER_BUTTON_VISIBLE, NEW_FOLDER_BUTTON_ENABLED, NAVIGATION_BUTTON_STATE, @@ -125,9 +125,9 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/Bookm SORT_MENU_IDS, SORT_MENU_IDS_ENABLED, CHECKED_SORT_MENU_ID, -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java ---- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java +diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java +--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java ++++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java @@ -14,6 +14,8 @@ class BookmarkToolbarViewBinder { if (key == BookmarkToolbarProperties.SELECTION_DELEGATE) { bookmarkToolbar.setSelectionDelegate( diff --git a/build/cromite_patches/Restore-LastTabStandingTracker.patch b/build/cromite_patches/Restore-LastTabStandingTracker.patch index 217e7bb010f1887d33db30c952ff909e1ff271c3..d30b1ef92c0d47e75f7d9d372a24dff40932a737 100644 --- a/build/cromite_patches/Restore-LastTabStandingTracker.patch +++ b/build/cromite_patches/Restore-LastTabStandingTracker.patch @@ -27,7 +27,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn -@@ -1698,6 +1698,16 @@ static_library("browser") { +@@ -1713,6 +1713,16 @@ static_library("browser") { ] } @@ -55,7 +55,7 @@ diff --git a/chrome/browser/content_settings/host_content_settings_map_factory.c #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_key.h" #include "chrome/browser/profiles/profiles_state.h" -@@ -58,6 +59,7 @@ HostContentSettingsMapFactory::HostContentSettingsMapFactory() +@@ -59,6 +60,7 @@ HostContentSettingsMapFactory::HostContentSettingsMapFactory() .WithAshInternals(ProfileSelection::kOwnInstance) .Build()) { DependsOn(SupervisedUserSettingsServiceFactory::GetInstance()); @@ -382,7 +382,7 @@ new file mode 100644 diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -@@ -135,6 +135,7 @@ +@@ -124,6 +124,7 @@ #include "chrome/browser/password_manager/password_manager_settings_service_factory.h" #include "chrome/browser/password_manager/password_reuse_manager_factory.h" #include "chrome/browser/password_manager/profile_password_store_factory.h" @@ -390,7 +390,7 @@ diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc #include "chrome/browser/permissions/notifications_engagement_service_factory.h" #include "chrome/browser/permissions/one_time_permissions_tracker_factory.h" #include "chrome/browser/permissions/origin_keyed_permission_action_service_factory.h" -@@ -1058,6 +1059,7 @@ void ChromeBrowserMainExtraPartsProfiles:: +@@ -1046,6 +1047,7 @@ void ChromeBrowserMainExtraPartsProfiles:: #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) OnDeviceTailModelServiceFactory::GetInstance(); #endif @@ -409,7 +409,7 @@ diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc #include "chrome/browser/permissions/one_time_permissions_tracker_helper.h" #include "chrome/browser/predictors/loading_predictor_factory.h" #include "chrome/browser/predictors/loading_predictor_tab_helper.h" -@@ -405,6 +406,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { +@@ -416,6 +417,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { HistoryEmbeddingsTabHelper::CreateForWebContents(web_contents); HttpsOnlyModeTabHelper::CreateForWebContents(web_contents); webapps::InstallableManager::CreateForWebContents(web_contents); diff --git a/build/cromite_patches/Restore-Search-Ready-Omnibox-flag.patch b/build/cromite_patches/Restore-Search-Ready-Omnibox-flag.patch index f62657ac8eaf6f67e4d6023c161d15eb5defb477..47b659bb5fd293e66a63d46d39aeff1448d1ecf5 100644 --- a/build/cromite_patches/Restore-Search-Ready-Omnibox-flag.patch +++ b/build/cromite_patches/Restore-Search-Ready-Omnibox-flag.patch @@ -23,7 +23,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -316,6 +316,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -317,6 +317,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kReadAloudIPHMenuButtonHighlightCCT, &kRecordSuppressionMetrics, &kReengagementNotification, @@ -34,7 +34,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -389,6 +389,7 @@ public abstract class ChromeFeatureList { +@@ -398,6 +398,7 @@ public abstract class ChromeFeatureList { public static final String MAYLAUNCHURL_USES_SEPARATE_STORAGE_PARTITION = "MayLaunchUrlUsesSeparateStoragePartition"; public static final String MOST_VISITED_TILES_CUSTOMIZATION = "MostVisitedTilesCustomization"; diff --git a/build/cromite_patches/Restore-Simplified-NTP-launch.patch b/build/cromite_patches/Restore-Simplified-NTP-launch.patch index 9012187a47c3a9eb25389237e115c372654bc27e..1e024663c215251cb3ec569fc7334a0014c04c73 100644 --- a/build/cromite_patches/Restore-Simplified-NTP-launch.patch +++ b/build/cromite_patches/Restore-Simplified-NTP-launch.patch @@ -15,12 +15,16 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../android/java/res/layout/ntp_shortcuts.xml | 65 ++++++ chrome/android/java/res/values/attrs.xml | 4 + chrome/android/java/res/values/dimens.xml | 2 + + .../chrome/browser/ChromeTabbedActivity.java | 3 +- + .../browser/TabbedModeTabDelegateFactory.java | 9 +- .../feedback/ChromeFeedbackCollector.java | 1 + .../feedback/SimplifiedNtpFeedbackSource.java | 38 ++++ .../identity_disc/IdentityDiscController.java | 5 +- + .../native_page/NativePageFactory.java | 19 +- .../NativePageNavigationDelegateImpl.java | 2 +- + .../chrome/browser/ntp/NewTabPage.java | 8 +- .../chrome/browser/ntp/NewTabPageLayout.java | 25 ++- - .../SuggestionsNavigationDelegate.java | 17 ++ + .../SuggestionsNavigationDelegate.java | 27 ++- .../mostvisited/MostVisitedSitesBridge.java | 2 +- .../tile/MostVisitedTilesCoordinator.java | 7 +- .../tile/MostVisitedTilesGridLayout.java | 193 ++++++++++++++++++ @@ -33,7 +37,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../Restore-Simplified-NTP-launch.inc | 12 ++ .../Restore-Simplified-NTP-launch.inc | 5 + .../Restore-Simplified-NTP-launch.inc | 1 + - 27 files changed, 399 insertions(+), 31 deletions(-) + 31 files changed, 437 insertions(+), 42 deletions(-) create mode 100644 chrome/android/java/res/layout/ntp_shortcuts.xml create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/feedback/SimplifiedNtpFeedbackSource.java create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedTilesGridLayout.java @@ -44,7 +48,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -547,6 +547,7 @@ chrome_java_resources = [ +@@ -553,6 +553,7 @@ chrome_java_resources = [ "java/res/layout/new_tab_page_tile_grid_placeholder.xml", "java/res/layout/one_line_list_item.xml", "java/res/layout/open_full_chrome_history_header.xml", @@ -55,7 +59,7 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -640,6 +640,7 @@ chrome_java_sources = [ +@@ -573,6 +573,7 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/feedback/FeedFeedbackCollector.java", "java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherImpl.java", "java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java", @@ -63,7 +67,7 @@ diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java "java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java", "java/src/org/chromium/chrome/browser/findinpage/FindToolbarManager.java", "java/src/org/chromium/chrome/browser/findinpage/FindToolbarObserver.java", -@@ -1053,6 +1054,7 @@ chrome_java_sources = [ +@@ -984,6 +985,7 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesMetadataUtils.java", "java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedTilesCoordinator.java", "java/src/org/chromium/chrome/browser/suggestions/tile/MostVisitedTilesLayout.java", @@ -253,12 +257,72 @@ diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res 20dp @dimen/signin_promo_lateral_paddings 17dp +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +@@ -2780,7 +2780,8 @@ public class ChromeTabbedActivity extends ChromeActivity implements MismatchedIn + getToolbarManager().getTabStripHeightSupplier(), + mModuleRegistrySupplier, + mEdgeToEdgeControllerSupplier, +- getStartupMetricsTracker()); ++ getStartupMetricsTracker(), ++ mBookmarkManagerOpenerSupplier); + } + return mTabDelegateFactory; + } +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java +@@ -15,6 +15,7 @@ import org.chromium.base.supplier.OneshotSupplier; + import org.chromium.base.supplier.Supplier; + import org.chromium.chrome.browser.app.ChromeActivity; + import org.chromium.chrome.browser.app.tab_activity_glue.ActivityTabWebContentsDelegateAndroid; ++import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; + import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; + import org.chromium.chrome.browser.compositor.CompositorViewHolder; + import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator; +@@ -82,6 +83,7 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { + private final OneshotSupplier mModuleRegistrySupplier; + private final ObservableSupplier mEdgeToEdgeControllerSupplier; + private final StartupMetricsTracker mStartupMetricsTracker; ++ private final ObservableSupplier mBookmarkManagerOpenerSupplier; + + private NativePageFactory mNativePageFactory; + +@@ -111,7 +113,8 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { + @NonNull ObservableSupplier tabStripHeightSupplier, + @NonNull OneshotSupplier moduleRegistrySupplier, + @NonNull ObservableSupplier edgeToEdgeControllerSupplier, +- StartupMetricsTracker startupMetricsTracker) { ++ StartupMetricsTracker startupMetricsTracker, ++ @NonNull ObservableSupplier bookmarkManagerOpenerSupplier) { + mActivity = activity; + mAppBrowserControlsVisibilityDelegate = appBrowserControlsVisibilityDelegate; + mShareDelegateSupplier = shareDelegateSupplier; +@@ -138,6 +141,7 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { + mModuleRegistrySupplier = moduleRegistrySupplier; + mEdgeToEdgeControllerSupplier = edgeToEdgeControllerSupplier; + mStartupMetricsTracker = startupMetricsTracker; ++ mBookmarkManagerOpenerSupplier = bookmarkManagerOpenerSupplier; + } + + @Override +@@ -204,7 +208,8 @@ public class TabbedModeTabDelegateFactory implements TabDelegateFactory { + mTabStripHeightSupplier, + mModuleRegistrySupplier, + mEdgeToEdgeControllerSupplier, +- mStartupMetricsTracker); ++ mStartupMetricsTracker, ++ mBookmarkManagerOpenerSupplier); + } + return mNativePageFactory.createNativePage(url, candidatePage, tab, pdfInfo); + } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java --- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ChromeFeedbackCollector.java -@@ -67,6 +67,7 @@ public class ChromeFeedbackCollector extends FeedbackCollector mModuleRegistrySupplier; + private final ObservableSupplier mEdgeToEdgeControllerSupplier; + private final StartupMetricsTracker mStartupMetricsTracker; ++ private final ObservableSupplier mBookmarkManagerOpenerSupplier; + private NewTabPageUma mNewTabPageUma; + + private NativePageBuilder mNativePageBuilder; +@@ -100,7 +102,8 @@ public class NativePageFactory { + @NonNull ObservableSupplier tabStripHeightSupplier, + @NonNull OneshotSupplier moduleRegistrySupplier, + @NonNull ObservableSupplier edgeToEdgeControllerSupplier, +- @NonNull StartupMetricsTracker startupMetricsTracker) { ++ @NonNull StartupMetricsTracker startupMetricsTracker, ++ @NonNull ObservableSupplier bookmarkManagerOpenerSupplier) { + mActivity = activity; + mBottomSheetController = sheetController; + mBrowserControlsManager = browserControlsManager; +@@ -118,6 +121,7 @@ public class NativePageFactory { + mModuleRegistrySupplier = moduleRegistrySupplier; + mEdgeToEdgeControllerSupplier = edgeToEdgeControllerSupplier; + mStartupMetricsTracker = startupMetricsTracker; ++ mBookmarkManagerOpenerSupplier = bookmarkManagerOpenerSupplier; + } + + private NativePageBuilder getBuilder() { +@@ -141,7 +145,8 @@ public class NativePageFactory { + mTabStripHeightSupplier, + mModuleRegistrySupplier, + mEdgeToEdgeControllerSupplier, +- mStartupMetricsTracker); ++ mStartupMetricsTracker, ++ mBookmarkManagerOpenerSupplier); + } + return mNativePageBuilder; + } +@@ -174,9 +179,10 @@ public class NativePageFactory { + private final OneshotSupplier mModuleRegistrySupplier; + private final ObservableSupplier mEdgeToEdgeControllerSupplier; + private final StartupMetricsTracker mStartupMetricsTracker; ++ private final ObservableSupplier mBookmarkManagerOpenerSupplier; + + public NativePageBuilder( +- Activity activity, ++ ChromeActivity activity, + Supplier uma, + BottomSheetController sheetController, + BrowserControlsManager browserControlsManager, +@@ -193,7 +199,8 @@ public class NativePageFactory { + ObservableSupplier tabStripHeightSupplier, + OneshotSupplier moduleRegistrySupplier, + ObservableSupplier edgeToEdgeControllerSupplier, +- StartupMetricsTracker startupMetricsTracker) { ++ StartupMetricsTracker startupMetricsTracker, ++ @NonNull ObservableSupplier bookmarkManagerOpenerSupplier) { + mActivity = activity; + mUma = uma; + mBottomSheetController = sheetController; +@@ -212,6 +219,7 @@ public class NativePageFactory { + mModuleRegistrySupplier = moduleRegistrySupplier; + mEdgeToEdgeControllerSupplier = edgeToEdgeControllerSupplier; + mStartupMetricsTracker = startupMetricsTracker; ++ mBookmarkManagerOpenerSupplier = bookmarkManagerOpenerSupplier; + } + + protected NativePage buildNewTabPage(Tab tab, String url) { +@@ -245,7 +253,8 @@ public class NativePageFactory { + mTabStripHeightSupplier, + mModuleRegistrySupplier, + mEdgeToEdgeControllerSupplier, +- mStartupMetricsTracker); ++ mStartupMetricsTracker, ++ mBookmarkManagerOpenerSupplier); + } + + protected NativePage buildBookmarksPage(Tab tab) { diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java --- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageNavigationDelegateImpl.java @@ -334,6 +485,38 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/Nat protected final TabModelSelector mTabModelSelector; protected final Tab mTab; +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java +--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java ++++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java +@@ -117,6 +117,9 @@ import org.chromium.ui.base.DeviceFormFactor; + import org.chromium.ui.base.WindowAndroid; + import org.chromium.url.GURL; + ++import androidx.annotation.NonNull; ++import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; ++ + import java.util.List; + + /** Provides functionality when the user interacts with the NTP. */ +@@ -475,7 +478,8 @@ public class NewTabPage + ObservableSupplier tabStripHeightSupplier, + OneshotSupplier moduleRegistrySupplier, + ObservableSupplier edgeToEdgeControllerSupplier, +- StartupMetricsTracker startupMetricsTracker) { ++ StartupMetricsTracker startupMetricsTracker, ++ @NonNull ObservableSupplier bookmarkManagerOpenerSupplier) { + mConstructedTimeNs = System.nanoTime(); + TraceEvent.begin(TAG); + +@@ -499,7 +503,7 @@ public class NewTabPage + + SuggestionsNavigationDelegate navigationDelegate = + new SuggestionsNavigationDelegate( +- activity, profile, nativePageHost, tabModelSelector, mTab); ++ activity, profile, nativePageHost, tabModelSelector, mTab, bookmarkManagerOpenerSupplier); + mNewTabPageManager = + new NewTabPageManagerImpl( + navigationDelegate, profile, nativePageHost, snackbarManager); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java @@ -414,24 +597,38 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageL diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java --- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsNavigationDelegate.java -@@ -22,6 +22,11 @@ import org.chromium.ui.base.PageTransition; +@@ -22,16 +22,41 @@ import org.chromium.ui.base.PageTransition; import org.chromium.ui.mojom.WindowOpenDisposition; import org.chromium.url.GURL; ++import androidx.annotation.NonNull; ++import org.chromium.base.supplier.ObservableSupplier; +import org.chromium.chrome.browser.bookmarks.BookmarkUtils; +import org.chromium.chrome.browser.download.DownloadUtils; +import org.chromium.chrome.browser.download.DownloadOpenSource; +import org.chromium.chrome.browser.profiles.OtrProfileId; ++import org.chromium.chrome.browser.bookmarks.BookmarkManagerOpener; + /** Extension of {@link NativePageNavigationDelegate} with suggestions-specific methods. */ public class SuggestionsNavigationDelegate extends NativePageNavigationDelegateImpl { -@@ -34,6 +39,18 @@ public class SuggestionsNavigationDelegate extends NativePageNavigationDelegateI ++ private final @NonNull ObservableSupplier mBookmarkManagerOpenerSupplier; ++ + public SuggestionsNavigationDelegate( + Activity activity, + Profile profile, + NativePageHost host, + TabModelSelector tabModelSelector, +- Tab tab) { ++ Tab tab, ++ @NonNull ObservableSupplier bookmarkManagerOpenerSupplier) { super(activity, profile, host, tabModelSelector, tab); - } - ++ mBookmarkManagerOpenerSupplier = bookmarkManagerOpenerSupplier; ++ } ++ + public void navigateToBookmarks() { -+ BookmarkUtils.showBookmarkManager(mActivity, mTab.isIncognito()); ++ mBookmarkManagerOpenerSupplier.get() ++ .showBookmarkManager(mActivity, mTab.getProfile()); + } + + public void navigateToDownloadManager() { @@ -440,11 +637,9 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/Sug + otrProfileID = mProfile.getOtrProfileId(); + } + DownloadUtils.showDownloadManager(mActivity, mTab, otrProfileID, DownloadOpenSource.NEW_TAB_PAGE); -+ } -+ + } + /** - * Opens the suggestions page without recording metrics. - * diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java --- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/mostvisited/MostVisitedSitesBridge.java @@ -738,8 +933,8 @@ diff --git a/chrome/browser/engagement/important_sites_util.cc b/chrome/browser/ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -279,6 +279,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { - &kHistoryPaneAndroid, +@@ -280,6 +280,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { + &kLegacyTabStateDeprecation, &kLockBackPressHandlerAtStart, &kIncognitoScreenshot, + &kSimplifiedNTP, @@ -749,7 +944,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -390,6 +390,7 @@ public abstract class ChromeFeatureList { +@@ -399,6 +399,7 @@ public abstract class ChromeFeatureList { public static final String MAYLAUNCHURL_USES_SEPARATE_STORAGE_PARTITION = "MayLaunchUrlUsesSeparateStoragePartition"; public static final String MOST_VISITED_TILES_CUSTOMIZATION = "MostVisitedTilesCustomization"; @@ -757,7 +952,7 @@ diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/f public static final String SEARCH_READY_OMNIBOX = "SearchReadyOmnibox"; public static final String MOST_VISITED_TILES_RESELECT = "MostVisitedTilesReselect"; public static final String MUlTI_INSTANCE_APPLICATION_STATUS_CLEANUP = -@@ -730,7 +731,7 @@ public abstract class ChromeFeatureList { +@@ -767,7 +768,7 @@ public abstract class ChromeFeatureList { public static final CachedFlag sLogoPolish = newCachedFlag(LOGO_POLISH, true); public static final CachedFlag sLogoPolishAnimationKillSwitch = newCachedFlag(LOGO_POLISH_ANIMATION_KILL_SWITCH, true); diff --git a/build/cromite_patches/Restore-adaptive-button-in-top-toolbar-customization.patch b/build/cromite_patches/Restore-adaptive-button-in-top-toolbar-customization.patch index c0b0bbba893efd29a80578d43824cf5dd287c3f8..22fad924108f7004b2a083c4a9e7363f5b4f46b9 100644 --- a/build/cromite_patches/Restore-adaptive-button-in-top-toolbar-customization.patch +++ b/build/cromite_patches/Restore-adaptive-button-in-top-toolbar-customization.patch @@ -7,36 +7,35 @@ Voice button and legacy share/voice functionality is not restored. License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- - .../chrome/browser/settings/MainSettings.java | 10 +--------- + .../chrome/browser/settings/MainSettings.java | 9 +-------- .../segmentation_platform_config.cc | 1 + .../adaptive/AdaptiveToolbarButtonController.java | 2 +- .../toolbar/adaptive/AdaptiveToolbarPrefs.java | 2 +- .../adaptive/AdaptiveToolbarStatePredictor.java | 4 ++++ .../RadioButtonGroupAdaptiveToolbarPreference.java | 11 ++--------- ...e-adaptive-button-in-top-toolbar-customization.inc | 1 + - 7 files changed, 11 insertions(+), 20 deletions(-) + 7 files changed, 11 insertions(+), 19 deletions(-) create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/Restore-adaptive-button-in-top-toolbar-customization.inc diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java -@@ -264,15 +264,7 @@ public class MainSettings extends ChromeBaseSettingsFragment - templateUrlService.load(); - } +@@ -293,14 +293,7 @@ public class MainSettings extends ChromeBaseSettingsFragment + getContext(), + getProfile(), + /* androidPermissionDelegate= */ null, +- /* behavior= */ null) +- .recomputeUiState( +- uiState -> { +- // Don't show toolbar shortcut settings if disabled from finch. +- if (!uiState.canShowUi) { +- removePreferenceIfPresent(PREF_TOOLBAR_SHORTCUT); +- } +- }); ++ /* behavior= */ null); + // LINT.ThenChange(//chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java:InitPrefToolbarShortcut) -- new AdaptiveToolbarStatePredictor(getContext(), getProfile(), null) -- .recomputeUiState( -- uiState -> { -- // We don't show the toolbar shortcut settings page if disabled from -- // finch. -- if (uiState.canShowUi) return; -- getPreferenceScreen() -- .removePreference(findPreference(PREF_TOOLBAR_SHORTCUT)); -- }); -+ new AdaptiveToolbarStatePredictor(getContext(), getProfile(), null); - - if (((true)) || BuildInfo.getInstance().isAutomotive) { - getPreferenceScreen().removePreference(findPreference(PREF_SAFETY_CHECK)); + // LINT.IfChange(InitPrefUiTheme) diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc --- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc +++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc @@ -51,7 +50,7 @@ diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.c diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonController.java -@@ -297,7 +297,7 @@ public class AdaptiveToolbarButtonController +@@ -301,7 +301,7 @@ public class AdaptiveToolbarButtonController } private boolean isScreenWideEnoughForButton() { @@ -75,7 +74,7 @@ diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/brow diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java -@@ -201,6 +201,10 @@ public class AdaptiveToolbarStatePredictor { +@@ -261,6 +261,10 @@ public class AdaptiveToolbarStatePredictor { * @param callback A callback for results. */ public void readFromSegmentationPlatform(Callback> callback) { diff --git a/build/cromite_patches/Restore-chrome-password-store.patch b/build/cromite_patches/Restore-chrome-password-store.patch index fa7ecc73b681d6bbc604b59f22ee189cc9e85f13..98fb3812a8faf3b7224ba04c915bfe84d0db5fe3 100644 --- a/build/cromite_patches/Restore-chrome-password-store.patch +++ b/build/cromite_patches/Restore-chrome-password-store.patch @@ -36,7 +36,7 @@ diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/Andro diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java --- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java +++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java -@@ -119,6 +119,7 @@ public class AutofillClientProviderUtils { +@@ -110,6 +110,7 @@ public class AutofillClientProviderUtils { @CalledByNative public static void setAutofillOptionsDeepLinkPref(boolean featureOn) { @@ -59,7 +59,7 @@ diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd -@@ -733,7 +733,7 @@ For more settings that use data to improve your Chrome experience, go to @@ -97,7 +97,7 @@ diff --git a/chrome/browser/ui/autofill/autofill_client_provider.cc b/chrome/bro diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc -@@ -741,6 +741,7 @@ BASE_FEATURE(kAutofillBetterLocalHeuristicPlaceholderSupport, +@@ -772,6 +772,7 @@ BASE_FEATURE(kUseSettingsAddressEditorInPaymentsRequest, BASE_FEATURE(kAutofillDeepLinkAutofillOptions, "AutofillDeepLinkAutofillOptions", base::FEATURE_ENABLED_BY_DEFAULT); @@ -131,9 +131,9 @@ diff --git a/components/password_manager/core/browser/features/password_manager_ diff --git a/components/sync/service/sync_prefs.cc b/components/sync/service/sync_prefs.cc --- a/components/sync/service/sync_prefs.cc +++ b/components/sync/service/sync_prefs.cc -@@ -726,7 +726,7 @@ bool SyncPrefs::IsTypeSupportedInTransportMode(UserSelectableType type) { - return base::FeatureList::IsEnabled(kReplaceSyncPromosWithSignInPromos) && - base::FeatureList::IsEnabled(kEnablePreferencesAccountStorage); +@@ -752,7 +752,7 @@ bool SyncPrefs::IsTypeSupportedInTransportMode(UserSelectableType type) { + kSeparateLocalAndAccountSearchEngines); + #endif case UserSelectableType::kPasswords: - return true; + return false; diff --git a/build/cromite_patches/Restore-offline-indicator-v2-flag.patch b/build/cromite_patches/Restore-offline-indicator-v2-flag.patch index 4a2b168df86678dc0a24ac940b90902070caeee8..448e2febf75ced06c1fabf53be3dca4124d06d3f 100644 --- a/build/cromite_patches/Restore-offline-indicator-v2-flag.patch +++ b/build/cromite_patches/Restore-offline-indicator-v2-flag.patch @@ -92,7 +92,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/in diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java -@@ -1174,7 +1174,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -1244,7 +1244,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { private void initStatusIndicatorCoordinator(LayoutManagerImpl layoutManager) { // TODO(crbug.com/40112282): Disable on tablets for now as we need to do one or two extra // things for tablets. @@ -102,7 +102,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/Tab return; } -@@ -1207,6 +1208,11 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -1277,6 +1278,11 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { hubManager.setStatusIndicatorHeight(mStatusIndicatorHeight); }); @@ -117,7 +117,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/Tab diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc -@@ -298,6 +298,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { +@@ -299,6 +299,7 @@ const base::Feature* const kFeaturesExposedToJava[] = { &kNotificationPermissionVariant, &kNotificationPermissionBottomSheet, &kNotificationTrampoline, @@ -128,7 +128,7 @@ diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browse diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java -@@ -410,6 +410,7 @@ public abstract class ChromeFeatureList { +@@ -419,6 +419,7 @@ public abstract class ChromeFeatureList { public static final String NOTIFICATION_PERMISSION_BOTTOM_SHEET = "NotificationPermissionBottomSheet"; public static final String NOTIFICATION_TRAMPOLINE = "NotificationTrampoline"; diff --git a/build/cromite_patches/Revert-flags-remove-disable-pull-to-refresh-effect.patch b/build/cromite_patches/Revert-flags-remove-disable-pull-to-refresh-effect.patch index 8f7f756251ec6969f3ad96ea001d5594106ec259..473ea637d9235e636f8726a456b198d916be41df 100644 --- a/build/cromite_patches/Revert-flags-remove-disable-pull-to-refresh-effect.patch +++ b/build/cromite_patches/Revert-flags-remove-disable-pull-to-refresh-effect.patch @@ -15,7 +15,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json -@@ -2073,6 +2073,10 @@ +@@ -2095,6 +2095,10 @@ "yangsharon@google.com", "alexmos@google.com", "creis@google.com" ], "expiry_milestone": 140 }, diff --git a/build/cromite_patches/Revert-removal-of-execution-context-address-space.patch b/build/cromite_patches/Revert-removal-of-execution-context-address-space.patch index aaa1cd153a0bab051b247c0324ac903c110cfeff..4a5d171fd6200d46225fb5ff47ff3fbd79a034b3 100644 --- a/build/cromite_patches/Revert-removal-of-execution-context-address-space.patch +++ b/build/cromite_patches/Revert-removal-of-execution-context-address-space.patch @@ -117,7 +117,7 @@ diff --git a/third_party/blink/renderer/core/exported/web_shared_worker_impl.cc diff --git a/third_party/blink/renderer/core/frame/policy_container.cc b/third_party/blink/renderer/core/frame/policy_container.cc --- a/third_party/blink/renderer/core/frame/policy_container.cc +++ b/third_party/blink/renderer/core/frame/policy_container.cc -@@ -56,12 +56,22 @@ network::mojom::blink::ReferrerPolicy PolicyContainer::GetReferrerPolicy() +@@ -57,12 +57,22 @@ network::mojom::blink::ReferrerPolicy PolicyContainer::GetReferrerPolicy() return policies_->referrer_policy; } @@ -171,7 +171,7 @@ diff --git a/third_party/blink/renderer/core/frame/policy_container.h b/third_pa diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc --- a/third_party/blink/renderer/core/frame/web_frame_test.cc +++ b/third_party/blink/renderer/core/frame/web_frame_test.cc -@@ -8066,6 +8066,40 @@ TEST_F(WebFrameTest, PushStateStartsAndStops) { +@@ -8067,6 +8067,40 @@ TEST_F(WebFrameTest, PushStateStartsAndStops) { EXPECT_EQ(client.StopLoadingCount(), 2); } @@ -436,7 +436,7 @@ diff --git a/third_party/blink/renderer/core/workers/dedicated_worker_global_sco diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_params.cc b/third_party/blink/renderer/core/workers/global_scope_creation_params.cc --- a/third_party/blink/renderer/core/workers/global_scope_creation_params.cc +++ b/third_party/blink/renderer/core/workers/global_scope_creation_params.cc -@@ -32,6 +32,7 @@ GlobalScopeCreationParams::GlobalScopeCreationParams( +@@ -33,6 +33,7 @@ GlobalScopeCreationParams::GlobalScopeCreationParams( HttpsState starter_https_state, WorkerClients* worker_clients, std::unique_ptr content_settings_client, @@ -444,7 +444,7 @@ diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_param const Vector* inherited_trial_features, const base::UnguessableToken& parent_devtools_token, std::unique_ptr worker_settings, -@@ -76,6 +77,7 @@ GlobalScopeCreationParams::GlobalScopeCreationParams( +@@ -77,6 +78,7 @@ GlobalScopeCreationParams::GlobalScopeCreationParams( starter_https_state(starter_https_state), worker_clients(worker_clients), content_settings_client(std::move(content_settings_client)), @@ -455,14 +455,14 @@ diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_param diff --git a/third_party/blink/renderer/core/workers/global_scope_creation_params.h b/third_party/blink/renderer/core/workers/global_scope_creation_params.h --- a/third_party/blink/renderer/core/workers/global_scope_creation_params.h +++ b/third_party/blink/renderer/core/workers/global_scope_creation_params.h -@@ -13,6 +13,7 @@ - #include "mojo/public/cpp/bindings/pending_remote.h" +@@ -14,6 +14,7 @@ #include "net/storage_access_api/status.h" #include "services/metrics/public/cpp/ukm_source_id.h" + #include "services/network/public/cpp/permissions_policy/permissions_policy.h" +#include "services/network/public/mojom/ip_address_space.mojom-blink-forward.h" #include "services/network/public/mojom/referrer_policy.mojom-blink-forward.h" - #include "third_party/blink/public/common/permissions_policy/permissions_policy.h" #include "third_party/blink/public/common/tokens/tokens.h" + #include "third_party/blink/public/common/user_agent/user_agent_metadata.h" @@ -63,6 +64,7 @@ struct CORE_EXPORT GlobalScopeCreationParams final { HttpsState starter_https_state, WorkerClients*, @@ -799,7 +799,7 @@ diff --git a/third_party/blink/renderer/modules/exported/web_embedded_worker_imp diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc --- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc +++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc -@@ -477,6 +477,7 @@ void ServiceWorkerGlobalScope::DidFetchClassicScript( +@@ -475,6 +475,7 @@ void ServiceWorkerGlobalScope::DidFetchClassicScript( // is set, and with the following callback steps given evaluationStatus:" RunClassicScript( classic_script_loader->ResponseURL(), referrer_policy, @@ -807,7 +807,7 @@ diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_gl classic_script_loader->GetContentSecurityPolicy() ? mojo::Clone(classic_script_loader->GetContentSecurityPolicy() ->GetParsedPolicies()) -@@ -490,6 +491,7 @@ void ServiceWorkerGlobalScope::DidFetchClassicScript( +@@ -488,6 +489,7 @@ void ServiceWorkerGlobalScope::DidFetchClassicScript( void ServiceWorkerGlobalScope::Initialize( const KURL& response_url, network::mojom::ReferrerPolicy response_referrer_policy, @@ -815,7 +815,7 @@ diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_gl Vector response_csp, const Vector* response_origin_trial_tokens) { // Step 4.5. "Set workerGlobalScope's url to serviceWorker's script url." -@@ -503,6 +505,9 @@ void ServiceWorkerGlobalScope::Initialize( +@@ -501,6 +503,9 @@ void ServiceWorkerGlobalScope::Initialize( // script resource's referrer policy." SetReferrerPolicy(response_referrer_policy); @@ -825,7 +825,7 @@ diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_gl // This is quoted from the "Content Security Policy" algorithm in the service // workers spec: // "Whenever a user agent invokes Run Service Worker algorithm with a service -@@ -513,6 +518,9 @@ void ServiceWorkerGlobalScope::Initialize( +@@ -511,6 +516,9 @@ void ServiceWorkerGlobalScope::Initialize( // - If serviceWorker's script resource was delivered with a // Content-Security-Policy-Report-Only HTTP header containing the value // policy, the user agent must monitor policy for serviceWorker." @@ -835,7 +835,7 @@ diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_gl InitContentSecurityPolicyFromVector(std::move(response_csp)); BindContentSecurityPolicyToExecutionContext(); -@@ -553,26 +561,27 @@ void ServiceWorkerGlobalScope::LoadAndRunInstalledClassicScript( +@@ -551,26 +559,27 @@ void ServiceWorkerGlobalScope::LoadAndRunInstalledClassicScript( kDoNotSupportReferrerPolicyLegacyKeywords, &referrer_policy); } diff --git a/build/cromite_patches/Revert-remove-allowscript-content-setting-secondary-url.patch b/build/cromite_patches/Revert-remove-allowscript-content-setting-secondary-url.patch index 14e3330fb8e37ce28e56a809c9f69fb7dbf42743..3dfb7876d5dce5dbb353985f5723244e6808e7df 100644 --- a/build/cromite_patches/Revert-remove-allowscript-content-setting-secondary-url.patch +++ b/build/cromite_patches/Revert-remove-allowscript-content-setting-secondary-url.patch @@ -211,7 +211,7 @@ diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_p diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc -@@ -4065,19 +4065,6 @@ bool LocalFrame::IsSameOrigin() { +@@ -4021,19 +4021,6 @@ bool LocalFrame::IsSameOrigin() { return security_origin->IsSameOriginWith(top_security_origin); } @@ -234,7 +234,7 @@ diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/ diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h -@@ -928,12 +928,6 @@ class CORE_EXPORT LocalFrame final +@@ -919,12 +919,6 @@ class CORE_EXPORT LocalFrame final return *v8_local_compile_hints_producer_; } @@ -250,7 +250,7 @@ diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/b diff --git a/third_party/blink/renderer/core/html/html_meta_element.cc b/third_party/blink/renderer/core/html/html_meta_element.cc --- a/third_party/blink/renderer/core/html/html_meta_element.cc +++ b/third_party/blink/renderer/core/html/html_meta_element.cc -@@ -780,7 +780,9 @@ void HTMLMetaElement::ProcessMetaCH(Document& document, +@@ -781,7 +781,9 @@ void HTMLMetaElement::ProcessMetaCH(Document& document, return; } @@ -264,7 +264,7 @@ diff --git a/third_party/blink/renderer/core/html/html_meta_element.cc b/third_p diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc --- a/third_party/blink/renderer/core/loader/base_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc -@@ -255,7 +255,7 @@ BaseFetchContext::CanRequestInternal( +@@ -258,7 +258,7 @@ BaseFetchContext::CanRequestInternal( } if (type == ResourceType::kScript) { @@ -276,7 +276,7 @@ diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/thir diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.h b/third_party/blink/renderer/core/loader/base_fetch_context.h --- a/third_party/blink/renderer/core/loader/base_fetch_context.h +++ b/third_party/blink/renderer/core/loader/base_fetch_context.h -@@ -103,7 +103,7 @@ class CORE_EXPORT BaseFetchContext : public FetchContext { +@@ -105,7 +105,7 @@ class CORE_EXPORT BaseFetchContext : public FetchContext { : fetcher_properties_(properties), console_logger_(logger) {} // Used for security checks. @@ -288,7 +288,7 @@ diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.h b/third diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc --- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc -@@ -518,7 +518,8 @@ void FrameFetchContext::AddClientHintsIfNecessary( +@@ -514,7 +514,8 @@ void FrameFetchContext::AddClientHintsIfNecessary( // Check if |url| is allowed to run JavaScript. If not, client hints are not // attached to the requests that initiate on the render side. @@ -298,8 +298,8 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/thi return; } -@@ -964,15 +965,26 @@ void FrameFetchContext::SetFirstPartyCookie(ResourceRequest& request) { - request.SetSiteForCookies(GetSiteForCookies()); +@@ -968,15 +969,26 @@ void FrameFetchContext::SetFirstPartyCookie(ResourceRequest& request) { + } } -bool FrameFetchContext::AllowScript() const { @@ -354,7 +354,7 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/thir static ResourceFetcher* CreateFetcherForCommittedDocument(DocumentLoader&, Document&); FrameFetchContext(DocumentLoader& document_loader, -@@ -168,7 +179,7 @@ class CORE_EXPORT FrameFetchContext final : public BaseFetchContext, +@@ -169,7 +180,7 @@ class CORE_EXPORT FrameFetchContext final : public BaseFetchContext, // BaseFetchContext overrides: net::SiteForCookies GetSiteForCookies() const override; SubresourceFilter* GetSubresourceFilter() const override; diff --git a/build/cromite_patches/Samsung-Note-9-SDK27-crazylinker-workaround.patch b/build/cromite_patches/Samsung-Note-9-SDK27-crazylinker-workaround.patch index 1eceb81b53367ccee9037a8e135acc7ee3c1ba36..7bf0b3d1879e9ad9c7394aaf45c62dc5f75f7278 100644 --- a/build/cromite_patches/Samsung-Note-9-SDK27-crazylinker-workaround.patch +++ b/build/cromite_patches/Samsung-Note-9-SDK27-crazylinker-workaround.patch @@ -11,7 +11,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java --- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java +++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java -@@ -39,6 +39,7 @@ import org.chromium.build.annotations.Nullable; +@@ -37,6 +37,7 @@ import org.chromium.build.annotations.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -19,7 +19,7 @@ diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoade import javax.annotation.concurrent.GuardedBy; -@@ -278,7 +279,21 @@ public class LibraryLoader { +@@ -276,7 +277,21 @@ public class LibraryLoader { */ public void initInAppZygote() { assert !mInitDone; diff --git a/build/cromite_patches/Show-site-settings-for-cookies-javascript-and-ads.patch b/build/cromite_patches/Show-site-settings-for-cookies-javascript-and-ads.patch index 2d001b04d581a56a0fd0868fce9f30abe4ee196a..423b8b1aeca09909be3faa2784cbd173633e5faf 100644 --- a/build/cromite_patches/Show-site-settings-for-cookies-javascript-and-ads.patch +++ b/build/cromite_patches/Show-site-settings-for-cookies-javascript-and-ads.patch @@ -6,16 +6,16 @@ Avoid displaying info about intrusive ads License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- - .../site_settings/SingleWebsiteSettings.java | 51 ++++++++++++------- + .../site_settings/SingleWebsiteSettings.java | 52 ++++++++++++------- .../browser_ui/site_settings/Website.java | 14 ++++- .../bromite_content_settings/javascript.inc | 4 ++ - 3 files changed, 50 insertions(+), 19 deletions(-) + 3 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 components/content_settings/core/browser/bromite_content_settings/javascript.inc diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java -@@ -590,6 +590,8 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -601,6 +601,8 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment setUpSoundPreference(preference); } else if (type == ContentSettingsType.JAVASCRIPT) { setUpJavascriptPreference(preference); @@ -24,7 +24,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } else if (type == ContentSettingsType.GEOLOCATION) { setUpLocationPreference(preference); } else if (type == ContentSettingsType.NOTIFICATIONS) { -@@ -1072,17 +1074,8 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -1105,18 +1107,8 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment private void setUpAdsInformationalBanner() { // Add the informational banner which shows at the top of the UI if ad blocking is // activated on this site. @@ -33,7 +33,8 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c - && WebsitePreferenceBridge.getAdBlockingActivated( - getSiteSettingsDelegate().getBrowserContextHandle(), - mSite.getAddress().getOrigin()) -- && findPreference(getPreferenceKey(ContentSettingsType.ADS)) != null; +- && findPreference(assumeNonNull(getPreferenceKey(ContentSettingsType.ADS))) +- != null; - - if (!adBlockingActivated) { removePreferenceSafely(PREF_INTRUSIVE_ADS_INFO); @@ -41,10 +42,10 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c - } } - private SiteSettingsCategory getWarningCategory() { -@@ -1284,12 +1277,13 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment - @Nullable - Integer currentValue = + @RequiresNonNull({"mSite"}) +@@ -1323,12 +1315,13 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment + @ContentSettingValues + @Nullable Integer currentValue = mSite.getContentSetting(browserContextHandle, ContentSettingsType.JAVASCRIPT); - // If Javascript is blocked by default, then always show a Javascript permission. - // To do this, set it to the default value (blocked). @@ -62,7 +63,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c } // Not possible to embargo JAVASCRIPT. setupContentSettingsPreference( -@@ -1299,6 +1293,29 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -1338,6 +1331,29 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment isOneTime(ContentSettingsType.JAVASCRIPT)); } @@ -92,7 +93,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c /** * Updates the ads list preference based on whether the site is a candidate for blocking. This * has some custom behavior. -@@ -1317,9 +1334,7 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment +@@ -1357,9 +1373,7 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment } // If the ad blocker is activated, then this site will have ads blocked unless there is an // explicit permission disallowing the blocking. @@ -101,12 +102,12 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c - browserContextHandle, mSite.getAddress().getOrigin()); + boolean activated = true; @ContentSettingValues - @Nullable - Integer permission = mSite.getContentSetting(browserContextHandle, ContentSettingsType.ADS); + @Nullable Integer permission = + mSite.getContentSetting(browserContextHandle, ContentSettingsType.ADS); diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java -@@ -282,7 +282,19 @@ public final class Website implements WebsiteEntry { +@@ -286,7 +286,19 @@ public final class Website implements WebsiteEntry { new ContentSettingException( ContentSettingsType.ADS, getAddress().getOrigin(), diff --git a/build/cromite_patches/Site-setting-for-images.patch b/build/cromite_patches/Site-setting-for-images.patch index debeeeb49cfb25b6dfa948b2808e26df957a95f4..c2669e2e2b5c8ccc506287f05b7a34379ef36634 100644 --- a/build/cromite_patches/Site-setting-for-images.patch +++ b/build/cromite_patches/Site-setting-for-images.patch @@ -21,13 +21,13 @@ Require: Content-settings-infrastructure.patch .../blink/renderer/core/frame/local_frame.cc | 13 --- .../blink/renderer/core/frame/local_frame.h | 6 -- .../renderer/core/html/image_document.cc | 9 +- - .../core/loader/frame_fetch_context.cc | 12 +-- + .../core/loader/frame_fetch_context.cc | 14 ++- .../core/loader/frame_fetch_context.h | 2 +- third_party/blink/renderer/core/page/page.cc | 5 +- .../platform/loader/fetch/fetch_context.h | 2 +- .../platform/loader/fetch/resource_fetcher.cc | 17 +++- .../platform/loader/fetch/resource_fetcher.h | 2 + - 21 files changed, 178 insertions(+), 36 deletions(-) + 21 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-hdpi/permission_images.png create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-mdpi/permission_images.png create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-xhdpi/permission_images.png @@ -323,7 +323,7 @@ diff --git a/third_party/blink/public/platform/web_content_settings_client.h b/t diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc -@@ -4052,19 +4052,6 @@ bool LocalFrame::IsSameOrigin() { +@@ -4008,19 +4008,6 @@ bool LocalFrame::IsSameOrigin() { return security_origin->IsSameOriginWith(top_security_origin); } @@ -346,7 +346,7 @@ diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/ diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h -@@ -927,12 +927,6 @@ class CORE_EXPORT LocalFrame final +@@ -918,12 +918,6 @@ class CORE_EXPORT LocalFrame final return *v8_local_compile_hints_producer_; } @@ -381,7 +381,7 @@ diff --git a/third_party/blink/renderer/core/html/image_document.cc b/third_part diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc --- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc -@@ -304,6 +304,7 @@ ResourceFetcher* FrameFetchContext::CreateFetcherForCommittedDocument( +@@ -312,6 +312,7 @@ ResourceFetcher* FrameFetchContext::CreateFetcherForCommittedDocument( fetcher->SetResourceLoadObserver( MakeGarbageCollected( loader, document, fetcher->GetProperties())); @@ -389,26 +389,28 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/thi fetcher->SetAutoLoadImages( frame->GetSettings()->GetLoadsImagesAutomatically()); fetcher->SetEarlyHintsPreloadedResources( -@@ -485,16 +486,11 @@ void FrameFetchContext::AddResourceTiming( +@@ -475,16 +476,13 @@ void FrameFetchContext::AddResourceTiming( ->AddResourceTiming(std::move(info), initiator_type); } -bool FrameFetchContext::AllowImage() const { +bool FrameFetchContext::AllowImage(bool images_enabled, const KURL& url) const { - if (GetResourceFetcherProperties().IsDetached()) - return true; + if (GetResourceFetcherProperties().IsDetached()) { +- return true; ++ return images_enabled; + } - - bool images_enabled = GetFrame()->ImagesEnabled(); - if (!images_enabled) { - if (auto* settings_client = GetContentSettingsClient()) { - settings_client->DidNotAllowImage(); - } -- } -+ if (auto* settings_client = GetContentSettingsClient()) ++ if (auto* settings_client = GetContentSettingsClient()) { + images_enabled = settings_client->AllowImage(images_enabled, url); ++ if (!images_enabled) settings_client->DidNotAllowImage(); + } return images_enabled; } - diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h --- a/third_party/blink/renderer/core/loader/frame_fetch_context.h +++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h @@ -419,12 +421,12 @@ diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/thir - bool AllowImage() const override; + bool AllowImage(bool images_enabled, const KURL&) const override; - void PopulateResourceRequestBeforeCacheAccess( - const ResourceLoaderOptions& options, + void ModifyRequestForMixedContentUpgrade(ResourceRequest&) override; + diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc --- a/third_party/blink/renderer/core/page/page.cc +++ b/third_party/blink/renderer/core/page/page.cc -@@ -1034,9 +1034,8 @@ void Page::SettingsChanged(ChangeType change_type) { +@@ -1060,9 +1060,8 @@ void Page::SettingsChanged(ChangeType change_type) { for (Frame* frame = MainFrame(); frame; frame = frame->Tree().TraverseNext()) { if (auto* local_frame = DynamicTo(frame)) { @@ -439,7 +441,7 @@ diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/re diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h --- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h +++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h -@@ -107,7 +107,7 @@ class PLATFORM_EXPORT FetchContext : public GarbageCollected { +@@ -110,7 +110,7 @@ class PLATFORM_EXPORT FetchContext : public GarbageCollected { virtual void AddResourceTiming(mojom::blink::ResourceTimingInfoPtr, const AtomicString& initiator_type); @@ -459,7 +461,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c allow_stale_resources_(false), image_fetched_(false), transparent_image_optimization_enabled_(base::FeatureList::IsEnabled( -@@ -2001,7 +2002,7 @@ bool ResourceFetcher::IsImageResourceDisallowedToBeReused( +@@ -2025,7 +2026,7 @@ bool ResourceFetcher::IsImageResourceDisallowedToBeReused( return false; } @@ -468,7 +470,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.c } ResourceFetcher::RevalidationPolicy -@@ -2246,8 +2247,20 @@ void ResourceFetcher::SetAutoLoadImages(bool enable) { +@@ -2270,8 +2271,20 @@ void ResourceFetcher::SetAutoLoadImages(bool enable) { ReloadImagesIfNotDeferred(); } @@ -501,7 +503,7 @@ diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h FetchContext& Context() const; void ClearContext(); -@@ -697,6 +698,7 @@ class PLATFORM_EXPORT ResourceFetcher +@@ -700,6 +701,7 @@ class PLATFORM_EXPORT ResourceFetcher bool is_in_request_resource_ = false; bool auto_load_images_ : 1; diff --git a/build/cromite_patches/Timezone-customization.patch b/build/cromite_patches/Timezone-customization.patch index f8fcc18076dfdd5e300d3bd3cc7819de39c8bbe3..b926200bb015a87e4735d273d423e12aa5ead450 100644 --- a/build/cromite_patches/Timezone-customization.patch +++ b/build/cromite_patches/Timezone-customization.patch @@ -50,7 +50,7 @@ Require: Content-settings-infrastructure.patch diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn --- a/components/browser_ui/site_settings/android/BUILD.gn +++ b/components/browser_ui/site_settings/android/BUILD.gn -@@ -99,6 +99,7 @@ android_library("java") { +@@ -100,6 +100,7 @@ android_library("java") { "java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java", "java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java", "java/src/org/chromium/components/browser_ui/site_settings/WebsiteRowPreference.java", @@ -58,7 +58,7 @@ diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/b ] resources_package = "org.chromium.components.browser_ui.site_settings" -@@ -283,6 +284,8 @@ android_resources("java_resources") { +@@ -287,6 +288,8 @@ android_resources("java_resources") { "java/res/xml/site_settings_preferences.xml", "java/res/xml/storage_access_settings.xml", "java/res/xml/website_preferences.xml", @@ -386,7 +386,7 @@ new file mode 100755 diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java -@@ -471,6 +471,14 @@ public class WebsitePreferenceBridge { +@@ -501,6 +501,14 @@ public class WebsitePreferenceBridge { .toHostOnlyPattern(pattern); } @@ -401,7 +401,7 @@ diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/c @NativeMethods public interface Natives { boolean isNotificationEmbargoedForOrigin( -@@ -622,5 +630,7 @@ public class WebsitePreferenceBridge { +@@ -650,5 +658,7 @@ public class WebsitePreferenceBridge { String toDomainWildcardPattern(String pattern); String toHostOnlyPattern(String pattern); @@ -565,7 +565,7 @@ new file mode 100644 diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc -@@ -1131,3 +1131,19 @@ JNI_WebsitePreferenceBridge_ToHostOnlyPattern( +@@ -1123,3 +1123,19 @@ JNI_WebsitePreferenceBridge_ToHostOnlyPattern( ContentSettingsPattern::FromString(pattern_string)); return ConvertUTF8ToJavaString(env, host_only_pattern.ToString()); } @@ -735,7 +735,7 @@ diff --git a/components/content_settings/core/browser/content_settings_utils.cc diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc --- a/components/content_settings/core/browser/host_content_settings_map.cc +++ b/components/content_settings/core/browser/host_content_settings_map.cc -@@ -665,6 +665,14 @@ void HostContentSettingsMap::SetClockForTesting(const base::Clock* clock) { +@@ -648,6 +648,14 @@ void HostContentSettingsMap::SetClockForTesting(const base::Clock* clock) { } } diff --git a/build/cromite_patches/Use-browser-navigation-handler.patch b/build/cromite_patches/Use-browser-navigation-handler.patch index 9b75b3b3ad61cc7f0a75f39b38007d99db7e4de3..5bf512389920a8872fddc29e6b11b75a40c5cd18 100644 --- a/build/cromite_patches/Use-browser-navigation-handler.patch +++ b/build/cromite_patches/Use-browser-navigation-handler.patch @@ -14,7 +14,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html .../gesturenav/HistoryNavigationLayout.java | 6 ++-- .../browser/gesturenav/NavigationHandler.java | 12 +++++-- .../browser/gesturenav/SideSlideLayout.java | 25 +++++++++++--- - .../tabbed_mode/TabbedRootUiCoordinator.java | 3 ++ + .../tabbed_mode/TabbedRootUiCoordinator.java | 4 ++- .../cromite/sUseBrowserNavigationHandler.java | 33 +++++++++++++++++++ .../Use-browser-navigation-handler.grdp | 12 +++++++ .../res/xml/accessibility_preferences.xml | 9 +++++ @@ -22,7 +22,7 @@ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html .../Use-browser-navigation-handler.inc | 12 +++++++ .../Use-browser-navigation-handler.inc | 3 ++ .../Use-browser-navigation-handler.inc | 1 + - 15 files changed, 135 insertions(+), 16 deletions(-) + 15 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sUseBrowserNavigationHandler.java create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Use-browser-navigation-handler.grdp create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Use-browser-navigation-handler.inc @@ -227,7 +227,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/Side diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java -@@ -69,6 +69,7 @@ import org.chromium.chrome.browser.firstrun.FirstRunStatus; +@@ -73,6 +73,7 @@ import org.chromium.chrome.browser.firstrun.FirstRunStatus; import org.chromium.chrome.browser.flags.ActivityType; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; @@ -235,15 +235,16 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/Tab import org.chromium.chrome.browser.fullscreen.BrowserControlsManager; import org.chromium.chrome.browser.fullscreen.FullscreenManager; import org.chromium.chrome.browser.gesturenav.BackActionDelegate; -@@ -678,6 +679,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { +@@ -722,7 +723,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator { @Override public void onInflationComplete() { mCoordinator = mActivity.findViewById(R.id.coordinator); +- + ((CoordinatorLayoutForPointer)mCoordinator).setUseBrowserNavigationHandler( + sUseBrowserNavigationHandler.getInstance().isEnabled()); super.onInflationComplete(); - } + ViewStub loadingStub = mActivity.findViewById(R.id.loading_stub); diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sUseBrowserNavigationHandler.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sUseBrowserNavigationHandler.java new file mode 100644 --- /dev/null diff --git a/build/cromite_patches/User-agent-customization.patch b/build/cromite_patches/User-agent-customization.patch index 7419ad0aebfd8db7e0df6296732e704eedddfde9..09b28b2783812bb78b0c5efc0e91ad8cf14fc81b 100644 --- a/build/cromite_patches/User-agent-customization.patch +++ b/build/cromite_patches/User-agent-customization.patch @@ -10,6 +10,7 @@ whereas the content setting is intended for use in a specific site. Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- + .../browser/aw_content_browser_client.cc | 2 +- base/base_switches.cc | 2 + base/base_switches.h | 2 + chrome/android/chrome_java_resources.gni | 2 + @@ -24,7 +25,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html .../browser/tab/RequestDesktopUtils.java | 1 + .../chromium/chrome/browser/tab/TabImpl.java | 64 +++++- .../chromium/chrome/browser/tab/TabUtils.java | 11 +- - .../browser/android/content/content_utils.cc | 30 +++ + .../browser/android/content/content_utils.cc | 32 ++- .../preferences/browser_prefs_android.cc | 7 + .../privacy_preferences_manager_impl.cc | 126 ++++++++++++ ...ktop_site_web_contents_observer_android.cc | 11 ++ @@ -39,24 +40,35 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html ...omiteRequestDesktopSiteContentSetting.java | 76 ++++++++ .../request_desktop_site.grdp | 18 ++ .../widget/RadioButtonWithEditText.java | 11 ++ - .../embedder_support/user_agent_utils.cc | 3 +- .../navigation_controller_android.cc | 4 + .../navigation_controller_android.h | 1 + .../renderer_host/render_process_host_impl.cc | 1 + .../browser/web_contents/web_contents_impl.cc | 6 +- .../framehost/NavigationControllerImpl.java | 3 +- content/renderer/render_thread_impl.cc | 1 - - 36 files changed, 799 insertions(+), 16 deletions(-) + 36 files changed, 799 insertions(+), 17 deletions(-) create mode 100644 chrome/android/java/res/layout/custom_useragent_preferences.xml create mode 100644 chrome/android/java/res/xml/useragent_preferences.xml create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java create mode 100644 components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/impl/BromiteRequestDesktopSiteContentSetting.java create mode 100644 components/browser_ui/strings/bromite_content_settings/request_desktop_site.grdp +diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc +--- a/android_webview/browser/aw_content_browser_client.cc ++++ b/android_webview/browser/aw_content_browser_client.cc +@@ -208,7 +208,7 @@ base::WeakPtr GetAsyncCheckTracker( + } // anonymous namespace + + std::string GetProduct() { +- return embedder_support::GetProductAndVersion(); ++ return embedder_support::GetProductAndVersion(embedder_support::UserAgentReductionEnterprisePolicyState::kForceEnabled); + } + + std::string GetUserAgent() { diff --git a/base/base_switches.cc b/base/base_switches.cc --- a/base/base_switches.cc +++ b/base/base_switches.cc -@@ -179,6 +179,8 @@ const char kPackageName[] = "package-name"; +@@ -178,6 +178,8 @@ const char kPackageName[] = "package-name"; const char kPackageVersionName[] = "package-version-name"; #endif @@ -68,7 +80,7 @@ diff --git a/base/base_switches.cc b/base/base_switches.cc diff --git a/base/base_switches.h b/base/base_switches.h --- a/base/base_switches.h +++ b/base/base_switches.h -@@ -64,6 +64,8 @@ extern const char kPackageVersionName[]; +@@ -63,6 +63,8 @@ extern const char kPackageVersionName[]; extern const char kSchedulerBoostUrgent[]; #endif @@ -80,7 +92,7 @@ diff --git a/base/base_switches.h b/base/base_switches.h diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -644,4 +644,6 @@ chrome_java_resources = [ +@@ -651,4 +651,6 @@ chrome_java_resources = [ "java/res/xml/search_widget_info.xml", "java/res/xml/tracing_preferences.xml", "java/res/xml/unified_account_settings_preferences.xml", @@ -90,7 +102,7 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -932,6 +932,7 @@ chrome_java_sources = [ +@@ -865,6 +865,7 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/payments/ui/DimmingDialog.java", "java/src/org/chromium/chrome/browser/payments/ui/LineItem.java", "java/src/org/chromium/chrome/browser/payments/ui/PaymentAppComparator.java", @@ -212,9 +224,9 @@ new file mode 100644 diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/java/res/xml/main_preferences.xml --- a/chrome/android/java/res/xml/main_preferences.xml +++ b/chrome/android/java/res/xml/main_preferences.xml -@@ -144,6 +144,11 @@ for the previous order (main_preferences_legacy). --> +@@ -149,6 +149,11 @@ for the previous order (main_preferences_legacy). --> android:key="content_settings" - android:order="25" + android:order="26" android:title="@string/prefs_site_settings"/> + & jweb_contents, jboolean j_override_in_new_tabs) { @@ -731,9 +743,18 @@ diff --git a/chrome/browser/android/content/content_utils.cc b/chrome/browser/an + return; + } + - content::WebContents* web_contents = - content::WebContents::FromJavaWebContents(jweb_contents); - embedder_support::SetDesktopUserAgentOverride( + constexpr char kLinuxInfoStr[] = "X11; Linux x86_64"; + + const blink::UserAgentMetadata metadata = +@@ -27,7 +57,7 @@ static void JNI_ContentUtils_SetUserAgentOverride( + + blink::UserAgentOverride spoofed_ua; + spoofed_ua.ua_string_override = content::BuildUserAgentFromOSAndProduct( +- kLinuxInfoStr, embedder_support::GetProductAndVersion()); ++ kLinuxInfoStr, embedder_support::GetProductAndVersion(embedder_support::UserAgentReductionEnterprisePolicyState::kForceEnabled)); + spoofed_ua.ua_metadata_override = metadata; + spoofed_ua.ua_metadata_override->platform = "Linux"; + spoofed_ua.ua_metadata_override->platform_version = diff --git a/chrome/browser/android/preferences/browser_prefs_android.cc b/chrome/browser/android/preferences/browser_prefs_android.cc --- a/chrome/browser/android/preferences/browser_prefs_android.cc +++ b/chrome/browser/android/preferences/browser_prefs_android.cc @@ -958,7 +979,7 @@ diff --git a/chrome/browser/content_settings/request_desktop_site_web_contents_o diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java -@@ -1059,8 +1059,6 @@ public final class ChromePreferenceKeys { +@@ -1080,8 +1080,6 @@ public final class ChromePreferenceKeys { SIGNIN_PROMO_NTP_LAST_SHOWN_TIME, SYNC_PROMO_TOTAL_SHOW_COUNT, SEARCH_RESUMPTION_MODULE_COLLAPSE_ON_NTP, @@ -988,7 +1009,7 @@ diff --git a/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browse diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java -@@ -331,6 +331,8 @@ public interface Tab extends TabLifecycle { +@@ -328,6 +328,8 @@ public interface Tab extends TabLifecycle { /** Goes to the navigation entry after the current one. */ void goForward(); @@ -1000,9 +1021,9 @@ diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.jav diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java --- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java +++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java -@@ -121,6 +121,8 @@ public interface TabWindowManager { +@@ -139,6 +139,8 @@ public interface TabWindowManager { */ - Tab getTabById(int tabId); + List getGroupedTabsByWindow(int windowId, int rootId, boolean isIncognito); + void SetOverrideUserAgentForAllTabs(boolean usingDesktopUserAgent); + @@ -1012,15 +1033,15 @@ diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browse diff --git a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java --- a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java +++ b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java -@@ -33,6 +33,7 @@ import org.chromium.ui.modaldialog.ModalDialogManager; +@@ -35,6 +35,7 @@ import org.chromium.ui.modaldialog.ModalDialogManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.Collection; import java.util.HashMap; - import java.util.Map; -@@ -418,6 +419,24 @@ public class TabWindowManagerImpl implements ActivityStateListener, TabWindowMan + import java.util.List; +@@ -423,6 +424,24 @@ public class TabWindowManagerImpl implements ActivityStateListener, TabWindowMan return null; } @@ -1084,7 +1105,7 @@ diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chro diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h -@@ -1917,6 +1917,14 @@ inline constexpr char kNaviOnboardGroup[] = "browser.navi_onboard_group"; +@@ -1902,6 +1902,14 @@ inline constexpr char kNaviOnboardGroup[] = "browser.navi_onboard_group"; inline constexpr char kHadThreeConsecutiveNotificationPermissionDenies[] = "profile.content_settings.had_three_consecutive_denies.notifications"; @@ -1224,19 +1245,6 @@ diff --git a/components/browser_ui/widget/android/java/src/org/chromium/componen } /** -diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc ---- a/components/embedder_support/user_agent_utils.cc -+++ b/components/embedder_support/user_agent_utils.cc -@@ -521,7 +521,8 @@ void SetDesktopUserAgentOverride(content::WebContents* web_contents, - - blink::UserAgentOverride spoofed_ua; - spoofed_ua.ua_string_override = content::BuildUserAgentFromOSAndProduct( -- kLinuxInfoStr, GetProductAndVersion()); -+ kLinuxInfoStr, -+ GetProductAndVersion(UserAgentReductionEnterprisePolicyState::kForceEnabled)); - spoofed_ua.ua_metadata_override = metadata; - spoofed_ua.ua_metadata_override->platform = "Linux"; - spoofed_ua.ua_metadata_override->platform_version = diff --git a/content/browser/renderer_host/navigation_controller_android.cc b/content/browser/renderer_host/navigation_controller_android.cc --- a/content/browser/renderer_host/navigation_controller_android.cc +++ b/content/browser/renderer_host/navigation_controller_android.cc @@ -1272,7 +1280,7 @@ diff --git a/content/browser/renderer_host/navigation_controller_android.h b/con diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc -@@ -3534,6 +3534,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( +@@ -3535,6 +3535,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( #if BUILDFLAG(IS_CHROMEOS) switches::kSchedulerBoostUrgent, #endif @@ -1291,7 +1299,7 @@ diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser #include "base/check_op.h" #include "base/command_line.h" #include "base/containers/contains.h" -@@ -2320,8 +2321,6 @@ void WebContentsImpl::SetUserAgentOverride( +@@ -2339,8 +2340,6 @@ void WebContentsImpl::SetUserAgentOverride( OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::SetUserAgentOverride", "ua_override", ua_override.ua_string_override, "override_in_new_tabs", override_in_new_tabs); @@ -1300,7 +1308,7 @@ diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser if (GetUserAgentOverride() == ua_override) { return; -@@ -3491,6 +3490,9 @@ const blink::web_pref::WebPreferences WebContentsImpl::ComputeWebPreferences( +@@ -3510,6 +3509,9 @@ const blink::web_pref::WebPreferences WebContentsImpl::ComputeWebPreferences( #else prefs.viewport_meta_enabled = false; #endif @@ -1333,7 +1341,7 @@ diff --git a/content/public/android/java/src/org/chromium/content/browser/frameh diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc --- a/content/renderer/render_thread_impl.cc +++ b/content/renderer/render_thread_impl.cc -@@ -908,7 +908,6 @@ void RenderThreadImpl::InitializeRenderer( +@@ -909,7 +909,6 @@ void RenderThreadImpl::InitializeRenderer( uint64_t trace_id) { TRACE_EVENT("navigation", "RenderThreadImpl::InitializeRenderer", perfetto::TerminatingFlow::Global(trace_id)); diff --git a/build/cromite_patches/Welcome-screen.patch b/build/cromite_patches/Welcome-screen.patch index 5550b413cad389696074971b99951e7db1f22a71..790859a915dfe38a8c758f1880be9b7f9d364c3e 100644 --- a/build/cromite_patches/Welcome-screen.patch +++ b/build/cromite_patches/Welcome-screen.patch @@ -30,7 +30,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni -@@ -508,6 +508,7 @@ chrome_java_resources = [ +@@ -514,6 +514,7 @@ chrome_java_resources = [ "java/res/layout/find_in_page.xml", "java/res/layout/find_toolbar.xml", "java/res/layout/fre_tos_privacy_disclaimer.xml", @@ -41,7 +41,7 @@ diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_ja diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni -@@ -664,6 +664,8 @@ chrome_java_sources = [ +@@ -597,6 +597,8 @@ chrome_java_sources = [ "java/src/org/chromium/chrome/browser/firstrun/SkipTosDialogPolicyListener.java", "java/src/org/chromium/chrome/browser/firstrun/SyncConsentFirstRunFragment.java", "java/src/org/chromium/chrome/browser/firstrun/TosDialogBehaviorSharedPrefInvalidator.java", @@ -239,7 +239,7 @@ diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml --- a/chrome/android/java/res/values/styles.xml +++ b/chrome/android/java/res/values/styles.xml -@@ -281,6 +281,30 @@ found in the LICENSE file. +@@ -276,6 +276,30 @@ found in the LICENSE file. 1 diff --git a/build/cromite_patches/autofill-miscellaneous.patch b/build/cromite_patches/autofill-miscellaneous.patch index a2f36fa85845c87cf3d0079d3eed4ed244f53e4b..9b16b243a33957234abe07fd7d6258b65c6ff51d 100644 --- a/build/cromite_patches/autofill-miscellaneous.patch +++ b/build/cromite_patches/autofill-miscellaneous.patch @@ -28,7 +28,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc --- a/chrome/browser/password_manager/chrome_password_manager_client.cc +++ b/chrome/browser/password_manager/chrome_password_manager_client.cc -@@ -1053,10 +1053,6 @@ ChromePasswordManagerClient::GetHttpAuthManager() { +@@ -1058,10 +1058,6 @@ ChromePasswordManagerClient::GetHttpAuthManager() { autofill::AutofillCrowdsourcingManager* ChromePasswordManagerClient::GetAutofillCrowdsourcingManager() { @@ -61,7 +61,7 @@ diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/brows diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h --- a/chrome/browser/ui/autofill/chrome_autofill_client.h +++ b/chrome/browser/ui/autofill/chrome_autofill_client.h -@@ -107,7 +107,6 @@ class ChromeAutofillClient : public ContentAutofillClient, +@@ -106,7 +106,6 @@ class ChromeAutofillClient : public ContentAutofillClient, version_info::Channel GetChannel() const final; bool IsOffTheRecord() const final; scoped_refptr GetURLLoaderFactory() final; @@ -104,7 +104,7 @@ diff --git a/components/android_autofill/browser/android_autofill_client.h b/com diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc --- a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc +++ b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc -@@ -475,33 +475,6 @@ std::optional GetUploadPayloadForApi( +@@ -487,33 +487,6 @@ std::optional GetUploadPayloadForApi( return std::move(payload); } @@ -138,7 +138,7 @@ diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourci // Gets HTTP body payload for API POST request. std::optional GetAPIBodyPayload(std::string payload, RequestType type) { -@@ -533,15 +506,7 @@ std::optional GetAPIQueryPayload( +@@ -545,15 +518,7 @@ std::optional GetAPIQueryPayload( } std::string GetAPIKeyForUrl(version_info::Channel channel) { @@ -155,7 +155,7 @@ diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourci } std::optional>& GetActiveExperiments() { -@@ -840,34 +805,13 @@ size_t AutofillCrowdsourcingManager::GetPayloadLength( +@@ -860,34 +825,13 @@ size_t AutofillCrowdsourcingManager::GetPayloadLength( std::tuple AutofillCrowdsourcingManager::GetRequestURLAndMethod( const FormRequestData& request_data) const { @@ -209,7 +209,7 @@ diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourci diff --git a/components/autofill/core/browser/crowdsourcing/votes_uploader.cc b/components/autofill/core/browser/crowdsourcing/votes_uploader.cc --- a/components/autofill/core/browser/crowdsourcing/votes_uploader.cc +++ b/components/autofill/core/browser/crowdsourcing/votes_uploader.cc -@@ -383,12 +383,6 @@ void VotesUploader::UploadVote( +@@ -396,12 +396,6 @@ void VotesUploader::UploadVote( non_empty_types.contains(CREDIT_CARD_NUMBER)) { non_empty_types.insert(CREDIT_CARD_VERIFICATION_CODE); } @@ -260,7 +260,7 @@ diff --git a/components/autofill/core/browser/foundations/autofill_manager.cc b/ diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc -@@ -877,6 +877,8 @@ BASE_FEATURE(kAutofillUploadThrottling, +@@ -909,6 +909,8 @@ BASE_FEATURE(kAutofillUploadThrottling, "AutofillUploadThrottling", base::FEATURE_ENABLED_BY_DEFAULT); diff --git a/build/cromite_patches/bromite-build-utils.patch b/build/cromite_patches/bromite-build-utils.patch index 374e53cc702e0d6d82fa536d3d27defe7f5e8362..f86be8bd6c8f41ad7a5743a6a15cb4f8b5875b97 100644 --- a/build/cromite_patches/bromite-build-utils.patch +++ b/build/cromite_patches/bromite-build-utils.patch @@ -10,7 +10,7 @@ Subject: bromite build utils build/bromite/gyp/cpp_bromite_include.pydeps | 9 ++ build/bromite/gyp/java_bromite_impl.py | 107 ++++++++++++++++++ build/bromite/gyp/java_bromite_impl.pydeps | 9 ++ - build/config/BUILDCONFIG.gn | 1 + + build/config/BUILDCONFIG.gn | 2 + build/config/android/rules.gni | 6 +- .../ChromeAccessibilitySettingsDelegate.java | 5 + chrome/browser/flags/BUILD.gn | 10 +- @@ -20,7 +20,7 @@ Subject: bromite build utils mojo/public/tools/mojom/mojom_parser.py | 22 +++- tools/grit/grit/grd_reader.py | 27 ++++- tools/grit/preprocess_if_expr.py | 32 +++++- - 17 files changed, 501 insertions(+), 19 deletions(-) + 17 files changed, 502 insertions(+), 19 deletions(-) create mode 100644 build/bromite/bromite_utils.gni create mode 100644 build/bromite/gyp/cpp_bromite_include.py create mode 100644 build/bromite/gyp/cpp_bromite_include.pydeps @@ -107,7 +107,7 @@ diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enu def GenerateOutput(source_path, enum_definition): -@@ -493,6 +513,10 @@ def DoMain(argv): +@@ -496,6 +516,10 @@ def DoMain(argv): parser.add_option('--srcjar', help='When specified, a .srcjar at the given path is ' 'created instead of individual .java files.') @@ -118,7 +118,7 @@ diff --git a/build/android/gyp/java_cpp_enum.py b/build/android/gyp/java_cpp_enu options, args = parser.parse_args(argv) -@@ -502,7 +526,7 @@ def DoMain(argv): +@@ -505,7 +529,7 @@ def DoMain(argv): with action_helpers.atomic_output(options.srcjar) as f: with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar: @@ -426,14 +426,12 @@ new file mode 100644 diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn --- a/build/config/BUILDCONFIG.gn +++ b/build/config/BUILDCONFIG.gn -@@ -734,6 +734,7 @@ set_defaults("component") { +@@ -764,3 +764,5 @@ if (is_component_build) { + set_defaults("component") { configs = default_component_configs } - ++ +import("//build/bromite/bromite_utils.gni") - # ============================================================================= - # ACTION OVERRIDE - # ============================================================================= diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni --- a/build/config/android/rules.gni +++ b/build/config/android/rules.gni @@ -516,7 +514,7 @@ new file mode 100755 +#include "chrome/browser/browser_process.h" +#include "chrome/browser/about_flags.h" +#include "chrome/browser/flags/jni_headers/CromiteNativeUtils_jni.h" -+#include "components/flags_ui/pref_service_flags_storage.h" ++#include "components/webui/flags/pref_service_flags_storage.h" + +#include "base/android/jni_string.h" + diff --git a/build/cromite_patches/disable-WebView-variations-support.patch b/build/cromite_patches/disable-WebView-variations-support.patch index fb849abbe6f424cb7875b528a29870ba8763e00b..e8329174fd8ecd506c2d6bae543cccc157b3c77a 100644 --- a/build/cromite_patches/disable-WebView-variations-support.patch +++ b/build/cromite_patches/disable-WebView-variations-support.patch @@ -12,7 +12,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java --- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java -@@ -349,12 +349,6 @@ public class WebViewChromiumAwInit { +@@ -342,12 +342,6 @@ public class WebViewChromiumAwInit { AwBrowserProcess.configureChildProcessLauncher(); @@ -28,7 +28,7 @@ diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewC diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java --- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java -@@ -588,10 +588,6 @@ public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { +@@ -591,10 +591,6 @@ public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { } } diff --git a/build/cromite_patches/dns-send-IPv6-connectivity-probes-to-RIPE-DNS.patch b/build/cromite_patches/dns-send-IPv6-connectivity-probes-to-RIPE-DNS.patch index 773ecccf6ac27ece9dc8c22b9b70da014c1c0227..94651c5b2012b78d83bc060caffcb7f3c6bc038f 100644 --- a/build/cromite_patches/dns-send-IPv6-connectivity-probes-to-RIPE-DNS.patch +++ b/build/cromite_patches/dns-send-IPv6-connectivity-probes-to-RIPE-DNS.patch @@ -46,7 +46,7 @@ diff --git a/net/dns/host_resolver_manager_request_impl.cc b/net/dns/host_resolv namespace net { HostResolverManager::RequestImpl::RequestImpl( -@@ -279,6 +281,9 @@ void HostResolverManager::RequestImpl::OnIOComplete(int rv) { +@@ -282,6 +284,9 @@ void HostResolverManager::RequestImpl::OnIOComplete(int rv) { int HostResolverManager::RequestImpl::DoIPv6Reachability() { next_state_ = STATE_GET_PARAMETERS; diff --git a/build/cromite_patches/eyeo-beta-118.0.5993.48-android_api.patch b/build/cromite_patches/eyeo-133.0.6943.49-android_api.patch similarity index 54% rename from build/cromite_patches/eyeo-beta-118.0.5993.48-android_api.patch rename to build/cromite_patches/eyeo-133.0.6943.49-android_api.patch index 96da3b63865206030b2c8b85bfd4247eee729238..3a2de0624e518ccaa9dc1d6728450b940de01314 100644 --- a/build/cromite_patches/eyeo-beta-118.0.5993.48-android_api.patch +++ b/build/cromite_patches/eyeo-133.0.6943.49-android_api.patch @@ -1,71 +1,67 @@ From: chromium-sdk -Date: Thu, 12 Oct 2023 14:46:06 +0200 +Date: Fri, 14 Feb 2025 08:26:39 +0100 Subject: eyeo Browser Ad filtering Solution: Android API Module -Based on Chromium 118.0.5993.48 +Based on Chromium 133.0.6943.49 Pre-requisites: eyeo Browser Ad filtering Solution: Base Module --- - chrome/android/BUILD.gn | 8 + - chrome/browser/BUILD.gn | 12 + - .../android/adblock/adblock_jni_factory.cc | 73 +++ - .../android/adblock/adblock_jni_factory.h | 50 ++ - ...iltering_configuration_bindings_factory.cc | 71 +++ - ...filtering_configuration_bindings_factory.h | 51 ++ - .../adblock/java_bindings_getters_impl.cc | 88 +++ - ...lassification_notifier_bindings_factory.cc | 71 +++ - ...classification_notifier_bindings_factory.h | 52 ++ - ...hrome_browser_main_extra_parts_profiles.cc | 10 + - components/adblock/android/BUILD.gn | 99 +++ - components/adblock/android/adblock_jni.cc | 176 ++++++ - components/adblock/android/adblock_jni.h | 52 ++ - .../filtering_configuration_bindings.cc | 294 +++++++++ - .../filtering_configuration_bindings.h | 68 +++ - .../adblock/AdblockContentType.java | 61 ++ - .../components/adblock/AdblockController.java | 203 +++++++ - .../components/adblock/AdblockCounters.java | 101 ++++ - .../adblock/FilteringConfiguration.java | 348 +++++++++++ - .../ResourceClassificationNotifier.java | 187 ++++++ - .../adblock/android/java_bindings_getters.h | 41 ++ - .../adblock/AdblockControllerTestBase.java | 63 ++ - .../adblock/TestAdBlockedObserver.java | 176 ++++++ - .../TestPagesCircumventionTestBase.java | 64 ++ - .../adblock/TestPagesCspTestBase.java | 108 ++++ - .../TestPagesElemhideEmuInvTestBase.java | 135 +++++ - .../adblock/TestPagesElemhideEmuTestBase.java | 183 ++++++ - .../adblock/TestPagesElemhideTestBase.java | 204 +++++++ - .../adblock/TestPagesExceptionTestBase.java | 174 ++++++ - .../adblock/TestPagesFilterTestBase.java | 254 ++++++++ - .../TestPagesHeaderFilterTestBase.java | 125 ++++ - .../adblock/TestPagesHelperBase.java | 180 ++++++ - .../adblock/TestPagesRewriteTestBase.java | 153 +++++ - .../adblock/TestPagesSiteKeyTestBase.java | 58 ++ - .../adblock/TestPagesSnippetsTestBase.java | 562 ++++++++++++++++++ - .../adblock/TestPagesWebsocketTestBase.java | 56 ++ - .../adblock/TestVerificationUtils.java | 163 +++++ - ...source_classification_notifier_bindings.cc | 163 +++++ - ...esource_classification_notifier_bindings.h | 69 +++ - 39 files changed, 5006 insertions(+) - create mode 100644 chrome/browser/android/adblock/adblock_jni_factory.cc - create mode 100644 chrome/browser/android/adblock/adblock_jni_factory.h - create mode 100644 chrome/browser/android/adblock/filtering_configuration_bindings_factory.cc - create mode 100644 chrome/browser/android/adblock/filtering_configuration_bindings_factory.h - create mode 100644 chrome/browser/android/adblock/java_bindings_getters_impl.cc - create mode 100644 chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.cc - create mode 100644 chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h + chrome/android/BUILD.gn | 14 + + chrome/android/chrome_java_sources.gni | 4 + + .../browser/adblock/TabIdTranslator.java | 36 ++ + chrome/browser/BUILD.gn | 4 + + .../android/adblock/tab_id_translator_jni.cc | 41 ++ + chrome/browser/ui/BUILD.gn | 6 + + components/adblock/android/BUILD.gn | 107 ++++ + .../adblock/android/adblock_controller_jni.cc | 132 +++++ + .../filtering_configuration_android.cc | 244 ++++++++ + .../android/filtering_configuration_android.h | 94 +++ + .../components/adblock/AdblockController.java | 283 +++++++++ + .../components/adblock/AdblockSwitches.java | 22 + + .../components/adblock/ContentType.java | 61 ++ + .../adblock/FilteringConfiguration.java | 424 +++++++++++++ + .../ResourceClassificationNotifier.java | 233 ++++++++ + .../adblock/ResourceFilteringCounters.java | 111 ++++ + .../adblock/AdblockControllerTestBase.java | 64 ++ + .../adblock/DefaultSettingsTestBase.java | 73 +++ + .../FilteringConfigurationTestBase.java | 421 +++++++++++++ + .../TestPagesCircumventionTestBase.java | 65 ++ + .../adblock/TestPagesCspTestBase.java | 86 +++ + .../TestPagesElemhideEmuInvTestBase.java | 116 ++++ + .../adblock/TestPagesElemhideEmuTestBase.java | 161 +++++ + .../adblock/TestPagesElemhideTestBase.java | 159 +++++ + .../adblock/TestPagesExceptionTestBase.java | 190 ++++++ + .../adblock/TestPagesFilterTestBase.java | 277 +++++++++ + .../TestPagesHeaderFilterTestBase.java | 102 ++++ + .../adblock/TestPagesHelperBase.java | 230 +++++++ + .../adblock/TestPagesInlineCssTestBase.java | 94 +++ + .../adblock/TestPagesRemoveTestBase.java | 94 +++ + .../adblock/TestPagesRewriteTestBase.java | 175 ++++++ + .../TestPagesServiceWorkersTestBase.java | 71 +++ + .../adblock/TestPagesSiteKeyTestBase.java | 51 ++ + .../adblock/TestPagesSnippetsTestBase.java | 560 ++++++++++++++++++ + .../adblock/TestPagesWebsocketTestBase.java | 54 ++ + .../TestPagesWildcardDomainTestBase.java | 116 ++++ + .../TestResourceFilteringObserver.java | 180 ++++++ + .../adblock/TestVerificationUtils.java | 220 +++++++ + ...esource_classification_notifier_android.cc | 163 +++++ + ...resource_classification_notifier_android.h | 72 +++ + 40 files changed, 5610 insertions(+) + create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java + create mode 100644 chrome/browser/android/adblock/tab_id_translator_jni.cc create mode 100644 components/adblock/android/BUILD.gn - create mode 100644 components/adblock/android/adblock_jni.cc - create mode 100644 components/adblock/android/adblock_jni.h - create mode 100644 components/adblock/android/filtering_configuration_bindings.cc - create mode 100644 components/adblock/android/filtering_configuration_bindings.h - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/AdblockContentType.java + create mode 100644 components/adblock/android/adblock_controller_jni.cc + create mode 100644 components/adblock/android/filtering_configuration_android.cc + create mode 100644 components/adblock/android/filtering_configuration_android.h create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/AdblockCounters.java + create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/AdblockSwitches.java + create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/ContentType.java create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/FilteringConfiguration.java create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/ResourceClassificationNotifier.java - create mode 100644 components/adblock/android/java_bindings_getters.h + create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/ResourceFilteringCounters.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/AdblockControllerTestBase.java - create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestAdBlockedObserver.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/DefaultSettingsTestBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/FilteringConfigurationTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCircumventionTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCspTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideEmuInvTestBase.java @@ -75,29 +71,41 @@ Pre-requisites: eyeo Browser Ad filtering Solution: Base Module create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesFilterTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesHeaderFilterTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesHelperBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesInlineCssTestBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRemoveTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRewriteTestBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesServiceWorkersTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSiteKeyTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSnippetsTestBase.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWildcardDomainTestBase.java + create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestResourceFilteringObserver.java create mode 100644 components/adblock/android/javatests/src/org/chromium/components/adblock/TestVerificationUtils.java - create mode 100644 components/adblock/android/resource_classification_notifier_bindings.cc - create mode 100644 components/adblock/android/resource_classification_notifier_bindings.h + create mode 100644 components/adblock/android/resource_classification_notifier_android.cc + create mode 100644 components/adblock/android/resource_classification_notifier_android.h diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -1,6 +1,10 @@ +@@ -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/android/resource_sizes.gni") import("//build/config/android/config.gni") -@@ -531,6 +535,7 @@ if (current_toolchain == default_toolchain) { +@@ -288,6 +291,7 @@ if (current_toolchain == default_toolchain) { + if (enable_screen_capture) { + deps += [ "//chrome/browser:screen_capture_java_resources" ] + } ++ + } + + android_resources("java_overlay_resources") { +@@ -532,6 +536,7 @@ if (current_toolchain == default_toolchain) { "//chrome/browser/xsurface:java", "//chrome/browser/xsurface_provider:dependency_provider_impl_java", "//chrome/browser/xsurface_provider:java", @@ -105,7 +113,7 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//components/android_autofill/browser:java", "//components/autofill/android:autofill_features_java", "//components/autofill/android:autofill_java", -@@ -1320,6 +1325,7 @@ if (current_toolchain == default_toolchain) { +@@ -1331,6 +1336,7 @@ if (current_toolchain == default_toolchain) { "javatests:chrome_test_java_org.chromium.chrome.browser.toolbar", "javatests:chrome_test_java_org.chromium.chrome.browser.webapps", "javatests:chrome_test_java_various", @@ -113,127 +121,61 @@ diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn "//chrome/browser/android/browserservices/verification:javatests", "//chrome/browser/android/httpclient:javatests", "//chrome/browser/android/metrics:ukm_javatests", -@@ -1366,6 +1372,7 @@ if (current_toolchain == default_toolchain) { - ] +@@ -1399,6 +1405,7 @@ if (current_toolchain == default_toolchain) { + "//chrome/test/data/translate/", + "//chrome/test/data/webauthn/", + "//chrome/test/media_router/resources/", ++ "//components/test/data/adblock/", + "//components/test/data/autofill/", + "//components/test/data/payments/", + "//content/test/data/browsing_data/", +@@ -3200,6 +3207,12 @@ generate_jni("chrome_jni_headers") { + sources += [ "java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialogBridge.java" ] + } - data = [ -+ "//chrome/test/data/adblock/", - "//chrome/test/data/android/", - "//chrome/test/data/autofill/", - "//chrome/test/data/background_sync/", -@@ -3262,6 +3269,7 @@ group("jni_headers") { ++ ### Android API module start ++ sources += ++ [ "java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java" ] ++ ++ ### Android API module end ++ + # Used for testing only, should not be shipped to end users. + if (enable_offline_pages_harness) { + sources += [ "java/src/org/chromium/chrome/browser/offlinepages/evaluation/OfflinePageEvaluationBridge.java" ] +@@ -3290,6 +3303,7 @@ group("jni_headers") { + "//chrome/browser/ui/messages/android:jni_headers", "//chrome/browser/util:jni_headers", "//chrome/browser/webauthn/android:jni_headers", - "//components/browser_ui/device_lock/android:device_lock_bridge_jni_headers", + "//components/adblock/android:jni_headers", + "//components/browser_ui/device_lock/android:device_lock_bridge_jni_headers", "//components/content_relationship_verification/android:jni_headers", "//components/image_fetcher:jni_headers", - "//components/media_router/browser/android:jni_headers", -diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn ---- a/chrome/browser/BUILD.gn -+++ b/chrome/browser/BUILD.gn -@@ -2561,6 +2561,18 @@ static_library("browser") { - } +diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni +--- a/chrome/android/chrome_java_sources.gni ++++ b/chrome/android/chrome_java_sources.gni +@@ -1,6 +1,9 @@ + # 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. - if (is_android) { -+ ### Android API module start -+ sources += [ -+ "android/adblock/adblock_jni_factory.cc", -+ "android/adblock/adblock_jni_factory.h", -+ "android/adblock/filtering_configuration_bindings_factory.cc", -+ "android/adblock/filtering_configuration_bindings_factory.h", -+ "android/adblock/java_bindings_getters_impl.cc", -+ "android/adblock/resource_classification_notifier_bindings_factory.cc", -+ "android/adblock/resource_classification_notifier_bindings_factory.h", -+ ] -+ deps += [ "//components/adblock/android:java_bindings" ] -+ ### Android API module end - sources += [ - "endpoint_fetcher/endpoint_fetcher_android.cc", - ] -diff --git a/chrome/browser/android/adblock/adblock_jni_factory.cc b/chrome/browser/android/adblock/adblock_jni_factory.cc -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/android/adblock/adblock_jni_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 . -+ */ -+ -+#include "chrome/browser/android/adblock/adblock_jni_factory.h" -+ -+#include -+ -+#include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/profiles/incognito_helpers.h" -+#include "chrome/browser/profiles/profile.h" -+#include "components/adblock/android/adblock_jni.h" -+#include "components/keyed_service/content/browser_context_dependency_manager.h" -+#include "content/public/browser/browser_context.h" -+ -+namespace adblock { -+ -+// static -+AdblockJNI* AdblockJNIFactory::GetForBrowserContext( -+ content::BrowserContext* context) { -+ return static_cast( -+ GetInstance()->GetServiceForBrowserContext(context, true)); -+} -+// static -+AdblockJNIFactory* AdblockJNIFactory::GetInstance() { -+ static base::NoDestructor instance; -+ return instance.get(); -+} -+ -+AdblockJNIFactory::AdblockJNIFactory() -+ : BrowserContextKeyedServiceFactory( -+ "AdblockJNI", -+ BrowserContextDependencyManager::GetInstance()) { -+ DependsOn(SubscriptionServiceFactory::GetInstance()); -+} -+ -+AdblockJNIFactory::~AdblockJNIFactory() = default; -+ -+std::unique_ptr -+AdblockJNIFactory::BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const { -+ return std::make_unique( -+ SubscriptionServiceFactory::GetForBrowserContext(context)); -+} -+ -+content::BrowserContext* AdblockJNIFactory::GetBrowserContextToUse( -+ content::BrowserContext* context) const { -+ return GetBrowserContextRedirectedInIncognito(context); -+} -+ -+bool AdblockJNIFactory::ServiceIsCreatedWithBrowserContext() const { -+ // This avoids manual instantiation in chrome_browser_main.cc -+ return true; -+} -+ -+bool AdblockJNIFactory::ServiceIsNULLWhileTesting() const { -+ return true; -+} -+ -+} // namespace adblock -diff --git a/chrome/browser/android/adblock/adblock_jni_factory.h b/chrome/browser/android/adblock/adblock_jni_factory.h + import("//content/public/common/features.gni") + +@@ -54,6 +57,7 @@ chrome_java_sources = [ + "java/src/org/chromium/chrome/browser/accessibility/AccessibilityTabHelper.java", + "java/src/org/chromium/chrome/browser/accessibility/PageZoomIphController.java", + "java/src/org/chromium/chrome/browser/accessibility/settings/ChromeAccessibilitySettingsDelegate.java", ++ "java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java", + "java/src/org/chromium/chrome/browser/ai/AiAssistantService.java", + "java/src/org/chromium/chrome/browser/ai/PageSummaryButtonController.java", + "java/src/org/chromium/chrome/browser/ai/SystemAiProvider.java", +diff --git a/chrome/android/java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java b/chrome/android/java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java new file mode 100644 --- /dev/null -+++ b/chrome/browser/android/adblock/adblock_jni_factory.h -@@ -0,0 +1,50 @@ ++++ b/chrome/android/java/src/org/chromium/chrome/browser/adblock/TabIdTranslator.java +@@ -0,0 +1,36 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -251,176 +193,44 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#ifndef CHROME_BROWSER_ANDROID_ADBLOCK_ADBLOCK_JNI_FACTORY_H_ -+#define CHROME_BROWSER_ANDROID_ADBLOCK_ADBLOCK_JNI_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 AdblockJNI; -+class AdblockJNIFactory : public BrowserContextKeyedServiceFactory { -+ public: -+ public: -+ static AdblockJNI* GetForBrowserContext(content::BrowserContext* context); -+ static AdblockJNIFactory* GetInstance(); ++package chromium.chrome.browser.adblock; + -+ private: -+ friend class base::NoDestructor; -+ AdblockJNIFactory(); -+ ~AdblockJNIFactory() override; -+ -+ // BrowserContextKeyedServiceFactory: -+ std::unique_ptr BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const override; -+ content::BrowserContext* GetBrowserContextToUse( -+ content::BrowserContext* context) const override; -+ bool ServiceIsCreatedWithBrowserContext() const override; -+ bool ServiceIsNULLWhileTesting() const override; -+}; ++import org.jni_zero.NativeMethods; + -+} // namespace adblock ++import org.chromium.content_public.browser.GlobalRenderFrameHostId; + -+#endif // CHROME_BROWSER_ANDROID_ADBLOCK_ADBLOCK_JNI_FACTORY_H_ -diff --git a/chrome/browser/android/adblock/filtering_configuration_bindings_factory.cc b/chrome/browser/android/adblock/filtering_configuration_bindings_factory.cc -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/android/adblock/filtering_configuration_bindings_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 . ++/** ++ * @brief Helper class to translate AdblockCounters.ResourceInfo.mMainFrameId to TabId. + */ ++public final class TabIdTranslator { ++ public static int fromRenderFrameHostId(GlobalRenderFrameHostId id) { ++ return TabIdTranslatorJni.get().fromRenderFrameHostId(id.childId(), id.frameRoutingId()); ++ } + -+#include "chrome/browser/android/adblock/filtering_configuration_bindings_factory.h" -+ -+#include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/profiles/incognito_helpers.h" -+#include "chrome/browser/profiles/profile.h" -+#include "components/adblock/android/filtering_configuration_bindings.h" -+#include "components/keyed_service/content/browser_context_dependency_manager.h" -+ -+namespace adblock { -+ -+// static -+FilteringConfigurationBindings* -+FilteringConfigurationBindingsFactory::GetForBrowserContext( -+ content::BrowserContext* context) { -+ return static_cast( -+ GetInstance()->GetServiceForBrowserContext(context, true)); -+} -+// static -+FilteringConfigurationBindingsFactory* -+FilteringConfigurationBindingsFactory::GetInstance() { -+ static base::NoDestructor instance; -+ return instance.get(); -+} -+ -+FilteringConfigurationBindingsFactory::FilteringConfigurationBindingsFactory() -+ : BrowserContextKeyedServiceFactory( -+ "FilteringConfigurationBindings", -+ BrowserContextDependencyManager::GetInstance()) { -+ DependsOn(SubscriptionServiceFactory::GetInstance()); -+} -+ -+FilteringConfigurationBindingsFactory:: -+ ~FilteringConfigurationBindingsFactory() = default; -+ -+std::unique_ptr -+FilteringConfigurationBindingsFactory::BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const { -+ return std::make_unique( -+ SubscriptionServiceFactory::GetForBrowserContext(context), -+ Profile::FromBrowserContext(context)->GetOriginalProfile()->GetPrefs()); -+} -+ -+content::BrowserContext* -+FilteringConfigurationBindingsFactory::GetBrowserContextToUse( -+ content::BrowserContext* context) const { -+ return GetBrowserContextRedirectedInIncognito(context); -+} -+ -+bool FilteringConfigurationBindingsFactory::ServiceIsCreatedWithBrowserContext() -+ const { -+ return true; ++ @NativeMethods ++ public interface Natives { ++ int fromRenderFrameHostId(int renderProcessId, int renderFrameId); ++ } +} +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +--- a/chrome/browser/BUILD.gn ++++ b/chrome/browser/BUILD.gn +@@ -3251,6 +3251,10 @@ static_library("browser") { + "touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h", + ] + ++ ### Android API module start ++ sources += [ "android/adblock/tab_id_translator_jni.cc" ] + -+} // namespace adblock -diff --git a/chrome/browser/android/adblock/filtering_configuration_bindings_factory.h b/chrome/browser/android/adblock/filtering_configuration_bindings_factory.h -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/android/adblock/filtering_configuration_bindings_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 . -+ */ -+ -+#ifndef CHROME_BROWSER_ANDROID_ADBLOCK_FILTERING_CONFIGURATION_BINDINGS_FACTORY_H_ -+#define CHROME_BROWSER_ANDROID_ADBLOCK_FILTERING_CONFIGURATION_BINDINGS_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 FilteringConfigurationBindings; -+class FilteringConfigurationBindingsFactory -+ : public BrowserContextKeyedServiceFactory { -+ public: -+ public: -+ static FilteringConfigurationBindings* GetForBrowserContext( -+ content::BrowserContext* context); -+ static FilteringConfigurationBindingsFactory* GetInstance(); -+ -+ private: -+ friend class base::NoDestructor; -+ FilteringConfigurationBindingsFactory(); -+ ~FilteringConfigurationBindingsFactory() override; -+ -+ // BrowserContextKeyedServiceFactory: -+ std::unique_ptr BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const override; -+ content::BrowserContext* GetBrowserContextToUse( -+ content::BrowserContext* context) const override; -+ bool ServiceIsCreatedWithBrowserContext() const override; -+}; -+ -+} // namespace adblock -+ -+#endif // CHROME_BROWSER_ANDROID_ADBLOCK_FILTERING_CONFIGURATION_BINDINGS_FACTORY_H_ -diff --git a/chrome/browser/android/adblock/java_bindings_getters_impl.cc b/chrome/browser/android/adblock/java_bindings_getters_impl.cc ++ ### Android API module end + + deps += [ + ":delta_file_proto", +diff --git a/chrome/browser/android/adblock/tab_id_translator_jni.cc b/chrome/browser/android/adblock/tab_id_translator_jni.cc new file mode 100644 --- /dev/null -+++ b/chrome/browser/android/adblock/java_bindings_getters_impl.cc -@@ -0,0 +1,88 @@ ++++ b/chrome/browser/android/adblock/tab_id_translator_jni.cc +@@ -0,0 +1,41 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -438,64 +248,19 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#include "components/adblock/android/java_bindings_getters.h" -+ -+#include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/android/adblock/adblock_jni_factory.h" -+#include "chrome/browser/android/adblock/filtering_configuration_bindings_factory.h" -+#include "chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h" ++#include "chrome/android/chrome_jni_headers/TabIdTranslator_jni.h" +#include "chrome/browser/android/tab_android.h" -+#include "chrome/browser/browser_process.h" -+#include "chrome/browser/profiles/profile.h" -+#include "chrome/browser/profiles/profile_manager.h" -+#include "components/adblock/android/filtering_configuration_bindings.h" -+#include "components/adblock/android/resource_classification_notifier_bindings.h" +#include "content/public/browser/web_contents.h" + -+namespace adblock { +namespace { +constexpr int kNoTabId = -1; +} + -+SubscriptionService* GetSubscriptionService() { -+ if (!g_browser_process || !g_browser_process->profile_manager()) { -+ return nullptr; -+ } -+ return SubscriptionServiceFactory::GetForBrowserContext( -+ g_browser_process->profile_manager()->GetLastUsedProfile()); -+} -+ -+AdblockJNI* GetJNI() { -+ if (!g_browser_process || !g_browser_process->profile_manager()) { -+ return nullptr; -+ } -+ return AdblockJNIFactory::GetForBrowserContext( -+ g_browser_process->profile_manager()->GetLastUsedProfile()); -+} -+ -+adblock::FilteringConfigurationBindings& GetFilteringConfigurationBindings() { -+ auto* bindings = -+ adblock::FilteringConfigurationBindingsFactory::GetForBrowserContext( -+ g_browser_process->profile_manager()->GetLastUsedProfile()); -+ DCHECK(bindings) << "FilteringConfigurationBindings should be non-null even " -+ "in tests, to keep the code simple"; -+ return *bindings; -+} -+ -+adblock::ResourceClassificationNotifierBindings& -+GetResourceClassificationNotifierBindings() { -+ auto* bindings = adblock::ResourceClassificationNotifierBindingsFactory:: -+ GetForBrowserContext( -+ g_browser_process->profile_manager()->GetLastUsedProfile()); -+ DCHECK(bindings) -+ << "ResourceClassificationNotifierBindings should be non-null even " -+ "in tests, to keep the code simple"; -+ return *bindings; -+} -+ -+int GetTabId(content::RenderFrameHost* render_frame_host) { -+ auto* web_contents = -+ content::WebContents::FromRenderFrameHost(render_frame_host); ++static jint JNI_TabIdTranslator_FromRenderFrameHostId(JNIEnv* env, ++ jint render_process_id, ++ jint render_frame_id) { ++ auto* web_contents = content::WebContents::FromRenderFrameHost( ++ content::RenderFrameHost::FromID(render_process_id, render_frame_id)); + if (!web_contents) { + return kNoTabId; + } @@ -507,173 +272,27 @@ new file mode 100644 + + return tab->GetAndroidId(); +} -+ -+} // namespace adblock -diff --git a/chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.cc b/chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.cc -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/android/adblock/resource_classification_notifier_bindings_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 . -+ */ -+ -+#include "chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h" -+ -+#include "chrome/browser/adblock/resource_classification_runner_factory.h" -+#include "chrome/browser/profiles/incognito_helpers.h" -+#include "components/adblock/android/resource_classification_notifier_bindings.h" -+#include "components/keyed_service/content/browser_context_dependency_manager.h" -+ -+namespace adblock { -+ -+// static -+ResourceClassificationNotifierBindings* -+ResourceClassificationNotifierBindingsFactory::GetForBrowserContext( -+ content::BrowserContext* context) { -+ return static_cast( -+ GetInstance()->GetServiceForBrowserContext(context, true)); -+} -+// static -+ResourceClassificationNotifierBindingsFactory* -+ResourceClassificationNotifierBindingsFactory::GetInstance() { -+ static base::NoDestructor -+ instance; -+ return instance.get(); -+} -+ -+ResourceClassificationNotifierBindingsFactory:: -+ ResourceClassificationNotifierBindingsFactory() -+ : BrowserContextKeyedServiceFactory( -+ "ResourceClassificationNotifierBindings", -+ BrowserContextDependencyManager::GetInstance()) { -+ DependsOn(ResourceClassificationRunnerFactory::GetInstance()); -+} -+ -+ResourceClassificationNotifierBindingsFactory:: -+ ~ResourceClassificationNotifierBindingsFactory() = default; -+ -+std::unique_ptr -+ResourceClassificationNotifierBindingsFactory::BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const { -+ return std::make_unique( -+ ResourceClassificationRunnerFactory::GetForBrowserContext(context)); -+} -+ -+content::BrowserContext* -+ResourceClassificationNotifierBindingsFactory::GetBrowserContextToUse( -+ content::BrowserContext* context) const { -+ return GetBrowserContextRedirectedInIncognito(context); -+} -+ -+bool ResourceClassificationNotifierBindingsFactory:: -+ ServiceIsCreatedWithBrowserContext() const { -+ return true; -+} -+ -+} // namespace adblock -diff --git a/chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h b/chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h -@@ -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 . -+ */ -+ -+#ifndef CHROME_BROWSER_ANDROID_ADBLOCK_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_FACTORY_H_ -+#define CHROME_BROWSER_ANDROID_ADBLOCK_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_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 ResourceClassificationNotifierBindings; -+class ResourceClassificationNotifierBindingsFactory -+ : public BrowserContextKeyedServiceFactory { -+ public: -+ public: -+ static ResourceClassificationNotifierBindings* GetForBrowserContext( -+ content::BrowserContext* context); -+ static ResourceClassificationNotifierBindingsFactory* GetInstance(); -+ -+ private: -+ friend class base::NoDestructor< -+ ResourceClassificationNotifierBindingsFactory>; -+ ResourceClassificationNotifierBindingsFactory(); -+ ~ResourceClassificationNotifierBindingsFactory() override; -+ -+ // BrowserContextKeyedServiceFactory: -+ std::unique_ptr BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const override; -+ content::BrowserContext* GetBrowserContextToUse( -+ content::BrowserContext* context) const override; -+ bool ServiceIsCreatedWithBrowserContext() const override; -+}; -+ -+} // namespace adblock -+ -+#endif // CHROME_BROWSER_ANDROID_ADBLOCK_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_FACTORY_H_ -diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc ---- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc -@@ -310,6 +310,11 @@ - #include "components/commerce/core/commerce_feature_list.h" - #include "components/commerce/core/proto/merchant_signal_db_content.pb.h" - -+// Android API module start -+#include "chrome/browser/android/adblock/adblock_jni_factory.h" -+#include "chrome/browser/android/adblock/filtering_configuration_bindings_factory.h" -+#include "chrome/browser/android/adblock/resource_classification_notifier_bindings_factory.h" -+// Android API module end - - #else - #include "chrome/browser/accessibility/live_caption/live_caption_controller_factory.h" -@@ -912,6 +917,11 @@ void ChromeBrowserMainExtraPartsProfiles:: - #if BUILDFLAG(IS_ANDROID) - FastCheckoutCapabilitiesFetcherFactory::GetInstance(); - -+ // Android API module start -+ adblock::AdblockJNIFactory::GetInstance(); -+ adblock::FilteringConfigurationBindingsFactory::GetInstance(); -+ adblock::ResourceClassificationNotifierBindingsFactory::GetInstance(); -+ // Android API module end - - #endif - FaviconServiceFactory::GetInstance(); +diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn +--- a/chrome/browser/ui/BUILD.gn ++++ b/chrome/browser/ui/BUILD.gn +@@ -1003,6 +1003,12 @@ static_library("ui") { + deps += [ + "//chrome/browser/search/background:ntp_background_proto", + ] ++ ### Android API module start ++ deps += [ ++ "//components/adblock/android:java_bindings", ++ "//components/adblock/android:jni_headers", ++ ] ++ ### Android API module end + } else { + # !is_android + sources += [ diff --git a/components/adblock/android/BUILD.gn b/components/adblock/android/BUILD.gn new file mode 100644 --- /dev/null +++ b/components/adblock/android/BUILD.gn -@@ -0,0 +1,99 @@ +@@ -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 @@ -686,25 +305,24 @@ new file mode 100644 +# You should have received a copy of the GNU General Public License +# along with eyeo Chromium SDK. If not, see . + -+ +import("//build/config/android/rules.gni") +import("//build/config/locales.gni") ++import("//third_party/jni_zero/jni_zero.gni") +import("//tools/grit/grit_rule.gni") + +source_set("java_bindings") { + sources = [ -+ "adblock_jni.cc", -+ "adblock_jni.h", -+ "filtering_configuration_bindings.cc", -+ "filtering_configuration_bindings.h", -+ "java_bindings_getters.h", -+ "resource_classification_notifier_bindings.cc", -+ "resource_classification_notifier_bindings.h", ++ "adblock_controller_jni.cc", ++ "filtering_configuration_android.cc", ++ "filtering_configuration_android.h", ++ "resource_classification_notifier_android.cc", ++ "resource_classification_notifier_android.h", + ] + deps = [ + ":jni_headers", + "//base", + "//components/keyed_service/content:content", ++ "//components/user_prefs:user_prefs", + ] + + public_deps = [ @@ -725,29 +343,31 @@ new file mode 100644 + +android_library("adblock_controller_java") { + sources = [ -+ "java/src/org/chromium/components/adblock/AdblockContentType.java", + "java/src/org/chromium/components/adblock/AdblockController.java", -+ "java/src/org/chromium/components/adblock/AdblockCounters.java", ++ "java/src/org/chromium/components/adblock/AdblockSwitches.java", ++ "java/src/org/chromium/components/adblock/ContentType.java", + "java/src/org/chromium/components/adblock/FilteringConfiguration.java", + "java/src/org/chromium/components/adblock/ResourceClassificationNotifier.java", ++ "java/src/org/chromium/components/adblock/ResourceFilteringCounters.java", + ] + + srcjar_deps = [ ":jni_headers" ] + deps = [ + "//base:base_java", -+ "//third_party/jni_zero:jni_zero_java", ++ "//content/public/android:content_java", + "//third_party/androidx:androidx_annotation_annotation_java", ++ "//third_party/jni_zero:jni_zero_java", + ] + + resources_package = "org.chromium.components.adblock.controller" +} + -+ +android_library("adblock_java_tests_base") { + testonly = true + sources = [ + "javatests/src/org/chromium/components/adblock/AdblockControllerTestBase.java", -+ "javatests/src/org/chromium/components/adblock/TestAdBlockedObserver.java", ++ "javatests/src/org/chromium/components/adblock/DefaultSettingsTestBase.java", ++ "javatests/src/org/chromium/components/adblock/FilteringConfigurationTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesCircumventionTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesCspTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesElemhideEmuInvTestBase.java", @@ -757,10 +377,15 @@ new file mode 100644 + "javatests/src/org/chromium/components/adblock/TestPagesFilterTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesHeaderFilterTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesHelperBase.java", ++ "javatests/src/org/chromium/components/adblock/TestPagesInlineCssTestBase.java", ++ "javatests/src/org/chromium/components/adblock/TestPagesRemoveTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesRewriteTestBase.java", ++ "javatests/src/org/chromium/components/adblock/TestPagesServiceWorkersTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesSiteKeyTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesSnippetsTestBase.java", + "javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java", ++ "javatests/src/org/chromium/components/adblock/TestPagesWildcardDomainTestBase.java", ++ "javatests/src/org/chromium/components/adblock/TestResourceFilteringObserver.java", + "javatests/src/org/chromium/components/adblock/TestVerificationUtils.java", + ] + deps = [ @@ -769,15 +394,17 @@ new file mode 100644 + "//base:base_java_test_support", + "//content/public/android:content_full_java", + "//content/public/test/android:content_java_test_support", ++ "//net/android:net_java_test_support", ++ "//third_party/androidx:androidx_test_monitor_java", + "//third_party/androidx:androidx_test_runner_java", + "//third_party/junit:junit", + ] +} -diff --git a/components/adblock/android/adblock_jni.cc b/components/adblock/android/adblock_jni.cc +diff --git a/components/adblock/android/adblock_controller_jni.cc b/components/adblock/android/adblock_controller_jni.cc new file mode 100644 --- /dev/null -+++ b/components/adblock/android/adblock_jni.cc -@@ -0,0 +1,176 @@ ++++ b/components/adblock/android/adblock_controller_jni.cc +@@ -0,0 +1,132 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -795,8 +422,6 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#include "components/adblock/android/adblock_jni.h" -+ +#include +#include +#include @@ -806,26 +431,22 @@ new file mode 100644 +#include "base/android/jni_string.h" +#include "base/android/jni_weak_ref.h" +#include "base/logging.h" -+#include "components/adblock/android/java_bindings_getters.h" +#include "components/adblock/android/jni_headers/AdblockController_jni.h" ++#include "components/adblock/content/browser/factories/subscription_service_factory.h" ++#include "components/adblock/core/common/adblock_constants.h" +#include "components/adblock/core/subscription/subscription_config.h" ++#include "content/public/browser/android/browser_context_handle.h" +#include "content/public/browser/browser_thread.h" + -+using base::android::AttachCurrentThread; +using base::android::CheckException; -+using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::GetClass; +using base::android::JavaParamRef; -+using base::android::JavaRef; +using base::android::MethodID; -+using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaArrayOfObjects; +using base::android::ToJavaArrayOfStrings; + -+namespace adblock { -+ +namespace { + +ScopedJavaLocalRef ToJava(JNIEnv* env, @@ -834,7 +455,8 @@ new file mode 100644 + const std::string& url, + const std::string& title, + const std::string& version, -+ const std::vector& languages) { ++ const std::vector& languages, ++ const bool autoinstalled) { + ScopedJavaLocalRef url_param( + env, env->NewObject(url_class.obj(), url_constructor, + ConvertUTF8ToJavaString(env, url).obj())); @@ -842,12 +464,13 @@ new file mode 100644 + return Java_Subscription_Constructor(env, url_param, + ConvertUTF8ToJavaString(env, title), + ConvertUTF8ToJavaString(env, version), -+ ToJavaArrayOfStrings(env, languages)); ++ ToJavaArrayOfStrings(env, languages), ++ autoinstalled ? JNI_TRUE : JNI_FALSE); +} + +std::vector> CSubscriptionsToJObjects( + JNIEnv* env, -+ const std::vector>& subscriptions) { ++ const std::vector>& subscriptions) { + ScopedJavaLocalRef url_class = GetClass(env, "java/net/URL"); + jmethodID url_constructor = MethodID::Get( + env, url_class.obj(), "", "(Ljava/lang/String;)V"); @@ -856,27 +479,29 @@ new file mode 100644 + for (auto& sub : subscriptions) { + jobjects.push_back(ToJava( + env, url_class, url_constructor, sub->GetSourceUrl().spec(), -+ sub->GetTitle(), sub->GetCurrentVersion(), std::vector{})); ++ sub->GetTitle(), sub->GetCurrentVersion(), std::vector{}, ++ sub->GetInstallationState() == ++ adblock::InstalledSubscription::InstallationState::AutoInstalled)); + } + return jobjects; +} + +std::vector> CSubscriptionsToJObjects( + JNIEnv* env, -+ std::vector& subscriptions) { ++ std::vector& subscriptions) { + ScopedJavaLocalRef url_class = GetClass(env, "java/net/URL"); + jmethodID url_constructor = MethodID::Get( + env, url_class.obj(), "", "(Ljava/lang/String;)V"); + std::vector> jobjects; + jobjects.reserve(subscriptions.size()); + for (auto& sub : subscriptions) { -+ if (sub.ui_visibility == SubscriptionUiVisibility::Visible) { ++ if (sub.ui_visibility == adblock::SubscriptionUiVisibility::Visible) { + // The checks here are when one makes f.e. adblock:custom visible + DCHECK(sub.url.is_valid()); + if (sub.url.is_valid()) { + jobjects.push_back(ToJava(env, url_class, url_constructor, -+ sub.url.spec(), sub.title, "", -+ sub.languages)); ++ sub.url.spec(), sub.title, "", sub.languages, ++ false)); + } + } + } @@ -885,137 +510,38 @@ new file mode 100644 + +} // namespace + -+AdblockJNI::AdblockJNI(SubscriptionService* subscription_service) -+ : subscription_service_(subscription_service) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ if (subscription_service_) { -+ subscription_service_->AddObserver(this); -+ } -+} -+ -+AdblockJNI::~AdblockJNI() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ if (subscription_service_) { -+ subscription_service_->RemoveObserver(this); -+ } -+} -+ -+void AdblockJNI::Bind(JavaObjectWeakGlobalRef weak_java_controller) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ weak_java_controller_ = weak_java_controller; -+} -+ -+void AdblockJNI::OnSubscriptionInstalled(const GURL& url) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ JNIEnv* env = AttachCurrentThread(); -+ ScopedJavaLocalRef obj = weak_java_controller_.get(env); -+ if (obj.is_null()) { -+ return; -+ } -+ -+ ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, url.spec()); -+ Java_AdblockController_subscriptionUpdatedCallback(env, obj, j_url); -+} -+ -+} // namespace adblock -+ -+static void JNI_AdblockController_Bind( -+ JNIEnv* env, -+ const base::android::JavaParamRef& caller) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ if (!adblock::GetSubscriptionService()) { -+ return; -+ } -+ JavaObjectWeakGlobalRef weak_controller_ref(env, caller); -+ adblock::GetJNI()->Bind(weak_controller_ref); -+} -+ +static base::android::ScopedJavaLocalRef -+JNI_AdblockController_GetInstalledSubscriptions(JNIEnv* env) { ++JNI_AdblockController_GetInstalledSubscriptions( ++ JNIEnv* env, ++ const base::android::JavaParamRef& jbrowser_context_handle) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ auto* subscription_service = adblock::GetSubscriptionService(); ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)); + if (!subscription_service) { + return ToJavaArrayOfObjects(env, -+ std::vector>{}); -+ } -+ -+ return ToJavaArrayOfObjects( -+ env, -+ adblock::CSubscriptionsToJObjects( -+ env, subscription_service->GetCurrentSubscriptions( -+ subscription_service->GetAdblockFilteringConfiguration()))); -+} -+ -+static base::android::ScopedJavaLocalRef -+JNI_AdblockController_GetRecommendedSubscriptions(JNIEnv* env) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ -+ auto list = adblock::config::GetKnownSubscriptions(); -+ return ToJavaArrayOfObjects(env, -+ adblock::CSubscriptionsToJObjects(env, list)); -+} -diff --git a/components/adblock/android/adblock_jni.h b/components/adblock/android/adblock_jni.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/android/adblock_jni.h -@@ -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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_ANDROID_ADBLOCK_JNI_H_ -+#define COMPONENTS_ADBLOCK_ANDROID_ADBLOCK_JNI_H_ -+ -+#include -+ -+#include "base/android/jni_weak_ref.h" -+#include "base/memory/raw_ptr.h" -+#include "components/adblock/core/subscription/subscription_service.h" -+#include "components/keyed_service/core/keyed_service.h" -+ -+namespace content { -+class BrowserContext; -+} -+ -+namespace adblock { -+ -+class AdblockJNI : public SubscriptionService::SubscriptionObserver, -+ public KeyedService { -+ public: -+ explicit AdblockJNI(SubscriptionService* subscription_service); -+ ~AdblockJNI() override; -+ -+ void Bind(JavaObjectWeakGlobalRef weak_java_controller); -+ // SubscriptionService::SubscriptionObserver -+ void OnSubscriptionInstalled(const GURL& subscription_url) override; ++ std::vector>{}); ++ } + -+ private: -+ SEQUENCE_CHECKER(sequence_checker_); -+ raw_ptr subscription_service_; -+ JavaObjectWeakGlobalRef weak_java_controller_; -+}; ++ return ToJavaArrayOfObjects( ++ env, CSubscriptionsToJObjects( ++ env, subscription_service->GetCurrentSubscriptions( ++ subscription_service->GetFilteringConfiguration( ++ adblock::kAdblockFilteringConfigurationName)))); ++} + -+} // namespace adblock ++static base::android::ScopedJavaLocalRef ++JNI_AdblockController_GetRecommendedSubscriptions(JNIEnv* env) { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + -+#endif // COMPONENTS_ADBLOCK_ANDROID_ADBLOCK_JNI_H_ -diff --git a/components/adblock/android/filtering_configuration_bindings.cc b/components/adblock/android/filtering_configuration_bindings.cc ++ auto list = adblock::config::GetKnownSubscriptions(); ++ return ToJavaArrayOfObjects(env, CSubscriptionsToJObjects(env, list)); ++} +diff --git a/components/adblock/android/filtering_configuration_android.cc b/components/adblock/android/filtering_configuration_android.cc new file mode 100644 --- /dev/null -+++ b/components/adblock/android/filtering_configuration_bindings.cc -@@ -0,0 +1,294 @@ ++++ b/components/adblock/android/filtering_configuration_android.cc +@@ -0,0 +1,244 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1033,288 +559,238 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#include "components/adblock/android/filtering_configuration_bindings.h" ++#include "components/adblock/android/filtering_configuration_android.h" + +#include +#include + ++#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" -+#include "components/adblock/android/java_bindings_getters.h" +#include "components/adblock/android/jni_headers/FilteringConfiguration_jni.h" ++#include "components/adblock/content/browser/factories/subscription_service_factory.h" +#include "components/adblock/core/configuration/filtering_configuration.h" +#include "components/adblock/core/configuration/persistent_filtering_configuration.h" -+#include "components/adblock/core/subscription/subscription_service.h" ++#include "components/adblock/core/subscription/subscription_config.h" ++#include "components/user_prefs/user_prefs.h" ++#include "content/public/browser/android/browser_context_handle.h" + -+namespace adblock { ++using base::android::AttachCurrentThread; ++using base::android::ConvertJavaStringToUTF8; ++using base::android::ConvertUTF8ToJavaString; ++using base::android::JavaParamRef; ++using base::android::ScopedJavaLocalRef; ++using base::android::ToJavaArrayOfStrings; ++ ++using namespace adblock; + -+FilteringConfigurationBindings::FilteringConfigurationBindings( ++FilteringConfigurationAndroid::FilteringConfigurationAndroid( ++ JNIEnv* env, ++ const JavaParamRef& jcontroller, ++ const std::string& configuration_name, + SubscriptionService* subscription_service, + PrefService* pref_service) + : subscription_service_(subscription_service), -+ pref_service_(pref_service) {} -+ -+FilteringConfigurationBindings::~FilteringConfigurationBindings() { -+ for (auto& pair : bound_counterparts_) { -+ pair.first->RemoveObserver(this); -+ } -+} -+ -+void FilteringConfigurationBindings::Bind( -+ const std::string& configuration_name, -+ JavaObjectWeakGlobalRef filtering_configuration_java) { ++ pref_service_(pref_service), ++ java_weak_controller_(env, jcontroller) { ++ subscription_service_->AddObserver(this); + auto* existing_configuration = -+ GetInstalledConfigurationWithName(configuration_name); ++ subscription_service_->GetFilteringConfiguration(configuration_name); + if (existing_configuration) { -+ auto existing_binding = bound_counterparts_.find(existing_configuration); -+ if (existing_binding == bound_counterparts_.end()) { -+ // There is no Java-side counterpart bound to this -+ // FilteringConfiguration. -+ existing_configuration->AddObserver(this); -+ } -+ bound_counterparts_[existing_configuration] = -+ std::move(filtering_configuration_java); ++ filtering_configuration_ptr = existing_configuration; + } else { -+ // There is no already-installed FilteringConfiguration with this name. -+ // Create one and bind to it. + auto new_filtering_configuration = + std::make_unique(pref_service_, + configuration_name); -+ new_filtering_configuration->AddObserver(this); -+ bound_counterparts_[new_filtering_configuration.get()] = -+ std::move(filtering_configuration_java); ++ filtering_configuration_ptr = new_filtering_configuration.get(); + subscription_service_->InstallFilteringConfiguration( + std::move(new_filtering_configuration)); + } ++ filtering_configuration_ptr->AddObserver(this); +} + -+void FilteringConfigurationBindings::RemoveConfiguration( -+ const std::string& configuration_name) { -+ auto* existing_configuration = -+ GetInstalledConfigurationWithName(configuration_name); -+ if (existing_configuration) { -+ existing_configuration->RemoveObserver(this); -+ bound_counterparts_.erase(existing_configuration); -+ subscription_service_->UninstallFilteringConfiguration(configuration_name); -+ } -+} -+ -+std::vector -+FilteringConfigurationBindings::GetConfigurations() { -+ return subscription_service_->GetInstalledFilteringConfigurations(); ++FilteringConfigurationAndroid::~FilteringConfigurationAndroid() { ++ filtering_configuration_ptr->RemoveObserver(this); ++ subscription_service_->UninstallFilteringConfiguration( ++ filtering_configuration_ptr->GetName()); ++ subscription_service_->RemoveObserver(this); +} + -+FilteringConfiguration* -+FilteringConfigurationBindings::GetInstalledConfigurationWithName( -+ const std::string& configuration_name) { -+ const auto installed_configurations = GetConfigurations(); -+ auto existing_configuration_it = -+ std::ranges::find(installed_configurations, configuration_name, -+ &FilteringConfiguration::GetName); -+ return existing_configuration_it != installed_configurations.end() -+ ? *existing_configuration_it -+ : nullptr; ++void FilteringConfigurationAndroid::Destroy(JNIEnv*) { ++ delete this; +} + -+void FilteringConfigurationBindings::OnEnabledStateChanged( ++void FilteringConfigurationAndroid::OnEnabledStateChanged( + FilteringConfiguration* config) { + Notify(config, Java_FilteringConfiguration_enabledStateChanged); +} + -+void FilteringConfigurationBindings::OnFilterListsChanged( ++void FilteringConfigurationAndroid::OnFilterListsChanged( + FilteringConfiguration* config) { + Notify(config, Java_FilteringConfiguration_filterListsChanged); +} + -+void FilteringConfigurationBindings::OnAllowedDomainsChanged( ++void FilteringConfigurationAndroid::OnAllowedDomainsChanged( + FilteringConfiguration* config) { + Notify(config, Java_FilteringConfiguration_allowedDomainsChanged); +} + -+void FilteringConfigurationBindings::OnCustomFiltersChanged( ++void FilteringConfigurationAndroid::OnCustomFiltersChanged( + FilteringConfiguration* config) { + Notify(config, Java_FilteringConfiguration_customFiltersChanged); +} + -+void FilteringConfigurationBindings::Notify( ++void FilteringConfigurationAndroid::Notify( + FilteringConfiguration* config, -+ FilteringConfigurationBindings::JavaEventListener event_listener_function) { -+ auto bound_weak_ref = bound_counterparts_.find(config); -+ DCHECK(bound_weak_ref != bound_counterparts_.end()) -+ << "This should never receive notifications from unobserved " -+ "FilteringConfigurations"; -+ JNIEnv* env = base::android::AttachCurrentThread(); -+ auto java_counterpart = bound_weak_ref->second.get(env); -+ if (!java_counterpart.is_null()) { -+ event_listener_function(env, java_counterpart); ++ FilteringConfigurationAndroid::JavaEventListener event_listener_function) { ++ JNIEnv* env = AttachCurrentThread(); ++ auto java_controller = java_weak_controller_.get(env); ++ if (!java_controller.is_null()) { ++ event_listener_function(env, java_controller); + } +} + -+} // namespace adblock -+ -+// Throws when called with a name of a non existing configuration. -+adblock::FilteringConfiguration* GetConfigurationWithName( -+ JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ auto& bindings = adblock::GetFilteringConfigurationBindings(); -+ auto* configuration = bindings.GetInstalledConfigurationWithName( -+ base::android::ConvertJavaStringToUTF8(configuration_name)); -+ if (!configuration) { -+ env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), -+ "Configuration does not exist!"); -+ return nullptr; ++void FilteringConfigurationAndroid::OnSubscriptionInstalled(const GURL& url) { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ JNIEnv* env = AttachCurrentThread(); ++ ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, url.spec()); ++ auto java_controller = java_weak_controller_.get(env); ++ if (!java_controller.is_null()) { ++ Java_FilteringConfiguration_onSubscriptionUpdated(env, java_controller, ++ j_url); + } -+ return configuration; +} + -+static void JNI_FilteringConfiguration_Bind( ++jboolean FilteringConfigurationAndroid::IsEnabled(JNIEnv* env) const { ++ return filtering_configuration_ptr->IsEnabled() ? JNI_TRUE : JNI_FALSE; ++} ++ ++void FilteringConfigurationAndroid::SetEnabled(JNIEnv* env, ++ jboolean j_enabled) { ++ filtering_configuration_ptr->SetEnabled(j_enabled == JNI_TRUE); ++} ++ ++void FilteringConfigurationAndroid::AddAllowedDomain( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& caller) { -+ auto& bindings = adblock::GetFilteringConfigurationBindings(); -+ JavaObjectWeakGlobalRef weak_configuration_ref(env, caller); -+ auto cpp_name = base::android::ConvertJavaStringToUTF8(configuration_name); -+ bindings.Bind(cpp_name, weak_configuration_ref); ++ const JavaParamRef& allowed_domain) { ++ filtering_configuration_ptr->AddAllowedDomain( ++ ConvertJavaStringToUTF8(allowed_domain)); +} + -+static void JNI_FilteringConfiguration_RemoveConfiguration( ++void FilteringConfigurationAndroid::RemoveAllowedDomain( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ auto& bindings = adblock::GetFilteringConfigurationBindings(); -+ bindings.RemoveConfiguration( -+ base::android::ConvertJavaStringToUTF8(configuration_name)); ++ const JavaParamRef& allowed_domain) { ++ filtering_configuration_ptr->RemoveAllowedDomain( ++ ConvertJavaStringToUTF8(allowed_domain)); +} + -+static base::android::ScopedJavaLocalRef -+JNI_FilteringConfiguration_GetConfigurations(JNIEnv* env) { -+ auto& bindings = adblock::GetFilteringConfigurationBindings(); -+ std::vector configurations; -+ std::ranges::transform( -+ bindings.GetConfigurations(), std::back_inserter(configurations), -+ [](adblock::FilteringConfiguration* fc) { return fc->GetName(); }); -+ return base::android::ToJavaArrayOfStrings(env, configurations); ++ScopedJavaLocalRef ++FilteringConfigurationAndroid::GetAllowedDomains(JNIEnv* env) const { ++ return ToJavaArrayOfStrings(env, ++ filtering_configuration_ptr->GetAllowedDomains()); +} + -+static jboolean JNI_FilteringConfiguration_IsEnabled( ++void FilteringConfigurationAndroid::AddCustomFilter( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ return (configuration && configuration->IsEnabled()) ? JNI_TRUE : JNI_FALSE; ++ const JavaParamRef& custom_filter) { ++ filtering_configuration_ptr->AddCustomFilter( ++ ConvertJavaStringToUTF8(custom_filter)); +} + -+static void JNI_FilteringConfiguration_SetEnabled( ++void FilteringConfigurationAndroid::RemoveCustomFilter( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ jboolean j_enabled) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->SetEnabled(j_enabled == JNI_TRUE); -+ } ++ const JavaParamRef& custom_filter) { ++ filtering_configuration_ptr->RemoveCustomFilter( ++ ConvertJavaStringToUTF8(custom_filter)); +} + -+static void JNI_FilteringConfiguration_AddFilterList( -+ JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& url) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->AddFilterList( -+ GURL{base::android::ConvertJavaStringToUTF8(url)}); -+ } ++ScopedJavaLocalRef ++FilteringConfigurationAndroid::GetCustomFilters(JNIEnv* env) const { ++ return ToJavaArrayOfStrings(env, ++ filtering_configuration_ptr->GetCustomFilters()); +} + -+static void JNI_FilteringConfiguration_RemoveFilterList( ++void FilteringConfigurationAndroid::AddFilterList( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& url) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->RemoveFilterList( -+ GURL{base::android::ConvertJavaStringToUTF8(url)}); -+ } ++ const JavaParamRef& url) { ++ filtering_configuration_ptr->AddFilterList( ++ GURL{ConvertJavaStringToUTF8(url)}); +} + -+static base::android::ScopedJavaLocalRef -+JNI_FilteringConfiguration_GetFilterLists( ++void FilteringConfigurationAndroid::RemoveFilterList( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ // For simplicity, convert GURL to std::string, pass to Java, and convert from -+ // String to URL. Strings are easier to pass through JNI. -+ std::vector urls; -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ std::ranges::transform(configuration->GetFilterLists(), -+ std::back_inserter(urls), &GURL::spec); -+ } -+ return base::android::ToJavaArrayOfStrings(env, urls); ++ const JavaParamRef& url) { ++ filtering_configuration_ptr->RemoveFilterList( ++ GURL{ConvertJavaStringToUTF8(url)}); +} + -+static void JNI_FilteringConfiguration_AddAllowedDomain( -+ JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& allowed_domain) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->AddAllowedDomain( -+ base::android::ConvertJavaStringToUTF8(allowed_domain)); -+ } ++ScopedJavaLocalRef FilteringConfigurationAndroid::GetFilterLists( ++ JNIEnv* env) const { ++ // For simplicity, convert GURL to std::string, pass to Java, and convert ++ // from String to URL. Strings are easier to pass through JNI. ++ std::vector urls; ++ std::ranges::transform(filtering_configuration_ptr->GetFilterLists(), ++ std::back_inserter(urls), &GURL::spec); ++ return ToJavaArrayOfStrings(env, urls); +} + -+static void JNI_FilteringConfiguration_RemoveAllowedDomain( -+ JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& allowed_domain) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->RemoveAllowedDomain(base::android::ConvertJavaStringToUTF8(allowed_domain)); -+ } ++static ScopedJavaLocalRef ++JNI_FilteringConfiguration_GetAcceptableAdsUrl(JNIEnv* env) { ++ return ConvertUTF8ToJavaString(env, adblock::AcceptableAdsUrl().spec()); +} + -+static base::android::ScopedJavaLocalRef -+JNI_FilteringConfiguration_GetAllowedDomains( ++static jlong JNI_FilteringConfiguration_Create( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ return base::android::ToJavaArrayOfStrings( -+ env, configuration ? configuration->GetAllowedDomains() -+ : std::vector{}); ++ const JavaParamRef& jcontroller, ++ const JavaParamRef& jstring, ++ const JavaParamRef& jbrowser_context_handle) { ++ DCHECK(!jcontroller.is_null()); ++ DCHECK(!jbrowser_context_handle.is_null()); ++ auto* context = ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle); ++ return reinterpret_cast(new FilteringConfigurationAndroid( ++ env, std::move(jcontroller), ConvertJavaStringToUTF8(jstring), ++ adblock::SubscriptionServiceFactory::GetForBrowserContext(context), ++ user_prefs::UserPrefs::Get(context))); +} + -+static void JNI_FilteringConfiguration_AddCustomFilter( ++static ScopedJavaLocalRef ++JNI_FilteringConfiguration_GetConfigurations( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& custom_filter) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->AddCustomFilter(base::android::ConvertJavaStringToUTF8(custom_filter)); -+ } ++ const JavaParamRef& jbrowser_context_handle) { ++ std::vector configurations; ++ std::ranges::transform( ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)) ++ ->GetInstalledFilteringConfigurations(), ++ std::back_inserter(configurations), ++ [](adblock::FilteringConfiguration* fc) { return fc->GetName(); }); ++ return ToJavaArrayOfStrings(env, configurations); +} + -+static void JNI_FilteringConfiguration_RemoveCustomFilter( ++static jboolean JNI_FilteringConfiguration_IsAutoInstallEnabled( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name, -+ const base::android::JavaParamRef& custom_filter) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ if (configuration) { -+ configuration->RemoveCustomFilter( -+ base::android::ConvertJavaStringToUTF8(custom_filter)); -+ } ++ const JavaParamRef& jbrowser_context_handle) { ++ return adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)) ++ ->IsAutoInstallEnabled() ++ ? JNI_TRUE ++ : JNI_FALSE; +} + -+static base::android::ScopedJavaLocalRef -+JNI_FilteringConfiguration_GetCustomFilters( ++static void JNI_FilteringConfiguration_SetAutoInstallEnabled( + JNIEnv* env, -+ const base::android::JavaParamRef& configuration_name) { -+ auto* configuration = GetConfigurationWithName(env, configuration_name); -+ return base::android::ToJavaArrayOfStrings( -+ env, configuration ? configuration->GetCustomFilters() -+ : std::vector{}); ++ jboolean j_enabled, ++ const JavaParamRef& jbrowser_context_handle) { ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)) ++ ->SetAutoInstallEnabled(j_enabled == JNI_TRUE); +} -diff --git a/components/adblock/android/filtering_configuration_bindings.h b/components/adblock/android/filtering_configuration_bindings.h +diff --git a/components/adblock/android/filtering_configuration_android.h b/components/adblock/android/filtering_configuration_android.h new file mode 100644 --- /dev/null -+++ b/components/adblock/android/filtering_configuration_bindings.h -@@ -0,0 +1,68 @@ ++++ b/components/adblock/android/filtering_configuration_android.h +@@ -0,0 +1,94 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1332,8 +808,8 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#ifndef COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_BINDINGS_H_ -+#define COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_BINDINGS_H_ ++#ifndef COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_ANDROID_H_ ++#define COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_ANDROID_H_ + +#include +#include @@ -1346,114 +822,74 @@ new file mode 100644 +#include "components/keyed_service/core/keyed_service.h" +#include "components/prefs/pref_service.h" + -+namespace adblock { -+ -+class FilteringConfigurationBindings : public KeyedService, -+ public FilteringConfiguration::Observer { ++class FilteringConfigurationAndroid ++ : public adblock::FilteringConfiguration::Observer, ++ public adblock::SubscriptionService::SubscriptionObserver { + public: -+ explicit FilteringConfigurationBindings( -+ SubscriptionService* subscription_service, ++ explicit FilteringConfigurationAndroid( ++ JNIEnv* env, ++ const base::android::JavaParamRef& jcontroller, ++ const std::string& configuration_name, ++ adblock::SubscriptionService* subscription_service, + PrefService* pref_service); -+ ~FilteringConfigurationBindings() override; -+ void Bind(const std::string& configuration_name, -+ JavaObjectWeakGlobalRef filtering_configuration_java); -+ void RemoveConfiguration(const std::string& configuration_name); -+ std::vector GetConfigurations(); -+ FilteringConfiguration* GetInstalledConfigurationWithName( -+ const std::string& name); ++ ~FilteringConfigurationAndroid() override; ++ // Called by Java to destroy this instance. ++ void Destroy(JNIEnv*); ++ void SetEnabled(JNIEnv* env, jboolean j_enabled); ++ jboolean IsEnabled(JNIEnv* env) const; ++ void AddFilterList(JNIEnv* env, ++ const base::android::JavaParamRef& url); ++ void RemoveFilterList(JNIEnv* env, ++ const base::android::JavaParamRef& url); ++ base::android::ScopedJavaLocalRef GetFilterLists( ++ JNIEnv* env) const; ++ void AddAllowedDomain(JNIEnv* env, ++ const base::android::JavaParamRef& domain); ++ void RemoveAllowedDomain(JNIEnv* env, ++ const base::android::JavaParamRef& domain); ++ base::android::ScopedJavaLocalRef GetAllowedDomains( ++ JNIEnv* env) const; ++ void AddCustomFilter(JNIEnv* env, ++ const base::android::JavaParamRef& filter); ++ void RemoveCustomFilter(JNIEnv* env, ++ const base::android::JavaParamRef& filter); ++ base::android::ScopedJavaLocalRef GetCustomFilters( ++ JNIEnv* env) const; + + // FilteringConfiguration::Observer: -+ void OnEnabledStateChanged(FilteringConfiguration* config) override; -+ void OnFilterListsChanged(FilteringConfiguration* config) override; -+ void OnAllowedDomainsChanged(FilteringConfiguration* config) override; -+ void OnCustomFiltersChanged(FilteringConfiguration* config) override; ++ void OnEnabledStateChanged(adblock::FilteringConfiguration* config) override; ++ void OnFilterListsChanged(adblock::FilteringConfiguration* config) override; ++ void OnAllowedDomainsChanged( ++ adblock::FilteringConfiguration* config) override; ++ void OnCustomFiltersChanged(adblock::FilteringConfiguration* config) override; ++ ++ // SubcriptionService::Observer: ++ void OnSubscriptionInstalled(const GURL& subscription_url) override; + + private: + using JavaEventListener = void(JNIEnv* env, + const base::android::JavaRef& obj); -+ void Notify(FilteringConfiguration* config, ++ void Notify(adblock::FilteringConfiguration* config, + JavaEventListener event_listener_function); + SEQUENCE_CHECKER(sequence_checker_); -+ raw_ptr subscription_service_; ++ raw_ptr subscription_service_; + raw_ptr pref_service_; -+ std::map -+ bound_counterparts_; ++ raw_ptr filtering_configuration_ptr; ++ ++ // Direct reference to FilteringConfiguration java class. Kept for as ++ // long as this instance of FilteringConfigurationAndroid lives: ++ // until corresponding Profile gets destroyed. Destruction of Profile triggers ++ // destruction of both C++ FilteringConfigurationAndroid and Java ++ // FilteringConfiguration objects. ++ const JavaObjectWeakGlobalRef java_weak_controller_; +}; + -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_BINDINGS_H_ -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockContentType.java b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockContentType.java -new file mode 100644 ---- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockContentType.java -@@ -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 . -+ */ -+ -+package org.chromium.components.adblock; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public enum AdblockContentType { -+ // Note. This has to be kept in sync with c++ enum so some values -+ // are skipped -+ CONTENT_TYPE_OTHER(1 << 0), -+ CONTENT_TYPE_SCRIPT(1 << 1), -+ CONTENT_TYPE_IMAGE(1 << 2), -+ CONTENT_TYPE_STYLESHEET(1 << 3), -+ CONTENT_TYPE_OBJECT(1 << 4), -+ CONTENT_TYPE_SUBDOCUMENT(1 << 5), -+ CONTENT_TYPE_WEBSOCKET(1 << 7), -+ CONTENT_TYPE_WEBRTC(1 << 8), -+ CONTENT_TYPE_PING(1 << 10), -+ CONTENT_TYPE_XMLHTTPREQUEST(1 << 11), -+ CONTENT_TYPE_MEDIA(1 << 14), -+ CONTENT_TYPE_FONT(1 << 15); -+ -+ private final int contentType; -+ -+ private AdblockContentType(int contentType) { -+ this.contentType = contentType; -+ } -+ -+ private static final Map intToContentTypeMap = -+ new HashMap(); -+ -+ static { -+ for (AdblockContentType type : AdblockContentType.values()) { -+ intToContentTypeMap.put(type.contentType, type); -+ } -+ } -+ -+ public static AdblockContentType fromInt(int i) { -+ return intToContentTypeMap.get(i); -+ } -+ -+ public int getValue() { -+ return contentType; -+ } -+} ++#endif // COMPONENTS_ADBLOCK_ANDROID_FILTERING_CONFIGURATION_ANDROID_H_ diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockController.java -@@ -0,0 +1,203 @@ +@@ -0,0 +1,283 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1473,37 +909,40 @@ new file mode 100644 + +package org.chromium.components.adblock; + -+import android.content.Context; -+import android.util.Log; -+import android.webkit.URLUtil; -+ +import androidx.annotation.UiThread; + -+import org.chromium.base.ThreadUtils; +import org.jni_zero.CalledByNative; +import org.jni_zero.NativeMethods; -+import org.chromium.components.adblock.controller.R; + -+import java.net.MalformedURLException; ++import org.chromium.base.ResettersForTesting; ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.library_loader.LibraryLoader; ++import org.chromium.content_public.browser.BrowserContextHandle; ++ +import java.net.URL; -+import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** -+ * @brief Main access point for java UI code to control ad filtering. -+ * It calls its native counter part also AdblockController. -+ * It lives in UI thread on the browser process. ++ * DEPRECATED, please use {@link FilteringConfiguration} instead. ++ * ++ * @brief Main access point for java UI code to control ad filtering. It calls its native counter ++ * part also AdblockController. It lives in UI thread on the browser process. + */ -+public final class AdblockController extends FilteringConfiguration { ++@Deprecated ++public class AdblockController { + private static final String TAG = AdblockController.class.getSimpleName(); -+ -+ private static AdblockController sInstance; ++ private FilteringConfiguration mFilteringConfiguration; ++ private BrowserContextHandle mBrowserContextHandle; + + private URL mAcceptableAds; + -+ private AdblockController() { -+ super("adblock"); ++ private static AdblockController sInstance; ++ ++ private AdblockController(BrowserContextHandle contextHandle) { ++ mBrowserContextHandle = contextHandle; ++ mFilteringConfiguration = ++ FilteringConfiguration.createConfiguration("adblock", mBrowserContextHandle); + try { + mAcceptableAds = + new URL("https://easylist-downloads.adblockplus.org/exceptionrules.txt"); @@ -1515,20 +954,28 @@ new file mode 100644 + /** + * @return The singleton object. + */ -+ public static AdblockController getInstance() { ++ public static AdblockController getInstance(BrowserContextHandle contextHandle) { ++ // Make sure native libraries are ready to avoid org.chromium.base.JniException ++ LibraryLoader.getInstance().ensureInitialized(); + ThreadUtils.assertOnUiThread(); + if (sInstance == null) { -+ sInstance = new AdblockController(); -+ AdblockControllerJni.get().bind(sInstance); ++ sInstance = new AdblockController(contextHandle); + } + return sInstance; + } + ++ public static void setInstanceForTesting(final AdblockController adblockController) { ++ var oldValue = sInstance; ++ sInstance = adblockController; ++ ResettersForTesting.register(() -> sInstance = oldValue); ++ } ++ + public static class Subscription { + private URL mUrl; + private String mTitle; + private String mVersion = ""; + private String[] mLanguages = {}; ++ private boolean mAutoInstalled; + + public Subscription(final URL url, final String title, final String version) { + this.mUrl = url; @@ -1538,11 +985,16 @@ new file mode 100644 + + @CalledByNative("Subscription") + public Subscription( -+ final URL url, final String title, final String version, final String[] languages) { ++ final URL url, ++ final String title, ++ final String version, ++ final String[] languages, ++ boolean autoinstalled) { + this.mUrl = url; + this.mTitle = title; + this.mVersion = version; + this.mLanguages = languages; ++ this.mAutoInstalled = autoinstalled; + } + + public String title() { @@ -1561,6 +1013,10 @@ new file mode 100644 + return mLanguages; + } + ++ public boolean autoinstalled() { ++ return mAutoInstalled; ++ } ++ + @Override + public boolean equals(final Object object) { + if (object == null) return false; @@ -1573,95 +1029,182 @@ new file mode 100644 + + @UiThread + public void setAcceptableAdsEnabled(boolean enabled) { -+ if (enabled) -+ addFilterList(mAcceptableAds); -+ else -+ removeFilterList(mAcceptableAds); ++ if (enabled) mFilteringConfiguration.addFilterList(mAcceptableAds); ++ else mFilteringConfiguration.removeFilterList(mAcceptableAds); + } + + @UiThread + public boolean isAcceptableAdsEnabled() { -+ return getFilterLists().contains(mAcceptableAds); ++ return mFilteringConfiguration.getFilterLists().contains(mAcceptableAds); + } + + @UiThread + public List getRecommendedSubscriptions() { -+ return (List) (List) Arrays.asList( -+ AdblockControllerJni.get().getRecommendedSubscriptions()); ++ return (List) ++ (List) Arrays.asList(AdblockControllerJni.get().getRecommendedSubscriptions()); ++ } ++ ++ @UiThread ++ public void setAutoInstallEnabled(boolean enable) { ++ FilteringConfiguration.setAutoInstallEnabled(enable, mBrowserContextHandle); ++ } ++ ++ @UiThread ++ public boolean isAutoInstallEnabled() { ++ return FilteringConfiguration.isAutoInstallEnabled(mBrowserContextHandle); + } + + @UiThread + public void installSubscription(final URL url) { -+ addFilterList(url); ++ mFilteringConfiguration.addFilterList(url); + } + + @UiThread + public void uninstallSubscription(final URL url) { -+ removeFilterList(url); ++ mFilteringConfiguration.removeFilterList(url); + } + + @UiThread + public List getInstalledSubscriptions() { -+ return (List) (List) Arrays.asList( -+ AdblockControllerJni.get().getInstalledSubscriptions()); ++ return (List) ++ (List) ++ Arrays.asList( ++ AdblockControllerJni.get() ++ .getInstalledSubscriptions(mBrowserContextHandle)); ++ } ++ ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public void setEnabled(boolean enabled) throws IllegalStateException { ++ mFilteringConfiguration.setEnabled(enabled); + } + -+ // TODO(mpawlowski) temporary pass-through, to enable gradual deprecation. -+ public interface AdBlockedObserver extends ResourceClassificationNotifier.AdBlockedObserver {} -+ // TODO(mpawlowski) deprecate and remove, use ResourceClassificationNotifier directly. ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ + @UiThread -+ public void addOnAdBlockedObserver( -+ final ResourceClassificationNotifier.AdBlockedObserver observer) { -+ ResourceClassificationNotifier.getInstance().addOnAdBlockedObserver(observer); ++ public boolean isEnabled() throws IllegalStateException { ++ return mFilteringConfiguration.isEnabled(); + } + -+ // TODO(mpawlowski) deprecate and remove, use ResourceClassifier directly. ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ + @UiThread -+ public void removeOnAdBlockedObserver( -+ final ResourceClassificationNotifier.AdBlockedObserver observer) { -+ ResourceClassificationNotifier.getInstance().removeOnAdBlockedObserver(observer); ++ public void removeFilterList(final URL url) throws IllegalStateException { ++ mFilteringConfiguration.removeFilterList(url); + } + -+ private List transform(String[] urls) { -+ if (urls == null) return null; ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public List getFilterLists() throws IllegalStateException { ++ return mFilteringConfiguration.getFilterLists(); ++ } + -+ List result = new ArrayList(); -+ for (String url : urls) { -+ try { -+ result.add(new URL(URLUtil.guessUrl(url))); -+ } catch (MalformedURLException e) { -+ Log.e(TAG, "Error parsing url: " + url); -+ } -+ } ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public void addAllowedDomain(final String domain) throws IllegalStateException { ++ mFilteringConfiguration.addAllowedDomain(domain); ++ } + -+ return result; ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public void removeAllowedDomain(final String domain) throws IllegalStateException { ++ mFilteringConfiguration.removeAllowedDomain(domain); ++ } ++ ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public List getAllowedDomains() throws IllegalStateException { ++ return mFilteringConfiguration.getAllowedDomains(); ++ } ++ ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public void addCustomFilter(final String filter) throws IllegalStateException { ++ mFilteringConfiguration.addCustomFilter(filter); ++ } ++ ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public void removeCustomFilter(final String filter) throws IllegalStateException { ++ mFilteringConfiguration.removeCustomFilter(filter); ++ } ++ ++ /** ++ * @throws IllegalStateException when called on removed FilteringConfiguration. ++ */ ++ @UiThread ++ public List getCustomFilters() throws IllegalStateException { ++ return mFilteringConfiguration.getCustomFilters(); + } + -+ @CalledByNative -+ private void subscriptionUpdatedCallback(final String url) { -+ ThreadUtils.assertOnUiThread(); -+ try { -+ URL subscriptionUrl = new URL(URLUtil.guessUrl(url)); -+ for (final SubscriptionUpdateObserver observer : mSubscriptionUpdateObservers) { -+ observer.onSubscriptionDownloaded(subscriptionUrl); -+ } -+ } catch (MalformedURLException e) { -+ Log.e(TAG, "Error parsing subscription url: " + url); -+ } -+ } ++ public interface SubscriptionUpdateObserver ++ extends FilteringConfiguration.SubscriptionUpdateObserver {} ++ ++ @UiThread ++ public void addSubscriptionUpdateObserver(final SubscriptionUpdateObserver observer) { ++ mFilteringConfiguration.addSubscriptionUpdateObserver(observer); ++ } ++ ++ @UiThread ++ public void removeSubscriptionUpdateObserver(final SubscriptionUpdateObserver observer) { ++ mFilteringConfiguration.removeSubscriptionUpdateObserver(observer); ++ } ++ ++ @NativeMethods ++ interface Natives { ++ Object[] getInstalledSubscriptions(BrowserContextHandle contextHandle); ++ ++ Object[] getRecommendedSubscriptions(); ++ } ++} +diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockSwitches.java b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockSwitches.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockSwitches.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; + -+ @NativeMethods -+ interface Natives { -+ void bind(AdblockController caller); -+ Object[] getInstalledSubscriptions(); -+ Object[] getRecommendedSubscriptions(); -+ } ++public class AdblockSwitches { ++ public static final String DISABLE_EYEO_REQUEST_THROTTLING = "disable-eyeo-request-throttling"; +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/AdblockCounters.java b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockCounters.java +diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/ContentType.java b/components/adblock/android/java/src/org/chromium/components/adblock/ContentType.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/AdblockCounters.java -@@ -0,0 +1,101 @@ ++++ b/components/adblock/android/java/src/org/chromium/components/adblock/ContentType.java +@@ -0,0 +1,61 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1682,92 +1225,52 @@ new file mode 100644 +package org.chromium.components.adblock; + +import java.util.HashMap; -+import java.util.List; +import java.util.Map; + -+public class AdblockCounters { -+ /** -+ * Immutable data-class containing an auxiliary information about resource event. -+ */ -+ public static class ResourceInfo { -+ private final String mRequestUrl; -+ private final List mParentFrameUrls; -+ private final String mSubscriptionUrl; -+ private final String mConfigurationName; -+ private final AdblockContentType mAdblockContentType; -+ private final int mTabId; -+ -+ ResourceInfo(final String requestUrl, final List parentFrameUrls, -+ final String subscriptionUrl, final String configurationName, -+ final int adblockContentType, final int tabId) { -+ this.mRequestUrl = requestUrl; -+ this.mParentFrameUrls = parentFrameUrls; -+ this.mSubscriptionUrl = subscriptionUrl; -+ this.mConfigurationName = configurationName; -+ this.mAdblockContentType = AdblockContentType.fromInt(adblockContentType); -+ this.mTabId = tabId; -+ } -+ -+ /** -+ * @return The request which was blocked or allowed. -+ */ -+ public String getRequestUrl() { -+ return mRequestUrl; -+ } ++public enum ContentType { ++ // Note. This has to be kept in sync with c++ enum so some values ++ // are skipped ++ CONTENT_TYPE_OTHER(1 << 0), ++ CONTENT_TYPE_SCRIPT(1 << 1), ++ CONTENT_TYPE_IMAGE(1 << 2), ++ CONTENT_TYPE_STYLESHEET(1 << 3), ++ CONTENT_TYPE_OBJECT(1 << 4), ++ CONTENT_TYPE_SUBDOCUMENT(1 << 5), ++ CONTENT_TYPE_WEBSOCKET(1 << 7), ++ CONTENT_TYPE_WEBRTC(1 << 8), ++ CONTENT_TYPE_PING(1 << 10), ++ CONTENT_TYPE_XMLHTTPREQUEST(1 << 11), ++ CONTENT_TYPE_MEDIA(1 << 14), ++ CONTENT_TYPE_FONT(1 << 15); + -+ /** -+ * @return The parent frames of the mRequestUrl. -+ */ -+ public List getParentFrameUrls() { -+ return mParentFrameUrls; -+ } ++ private final int contentType; + -+ /** -+ * @return Subscription url for filter done blocking or allowing decision, -+ * empty string otherwise. -+ */ -+ public String getSubscription() { -+ return mSubscriptionUrl; -+ } ++ private ContentType(int contentType) { ++ this.contentType = contentType; ++ } + -+ /** -+ * @return Configuration name containing subscription with matched filer for -+ * blocking or allowing decision, empty string otherwise. -+ */ -+ public String getConfigurationName() { -+ return mConfigurationName; -+ } ++ private static final Map intToContentTypeMap = ++ new HashMap(); + -+ /** -+ * @return The Adblock content type. See enum ContentType in -+ * components/adblock/types.h -+ */ -+ public AdblockContentType getAdblockContentType() { -+ return mAdblockContentType; ++ static { ++ for (ContentType type : ContentType.values()) { ++ intToContentTypeMap.put(type.contentType, type); + } ++ } + -+ /** -+ * @return The current tab id for the mRequestUrl. -1 means no tab id, likely tab closed -+ * before event arrived. Numbers start from 0. -+ */ -+ public int getTabId() { -+ return mTabId; -+ } ++ public static ContentType fromInt(int i) { ++ return intToContentTypeMap.get(i); ++ } + -+ @Override -+ public String toString() { -+ return "mRequestUrl: " + mRequestUrl + ", mParentFrameUrls: " -+ + mParentFrameUrls.toString() + ", mSubscriptionUrl:" + mSubscriptionUrl -+ + ", mConfigurationName:" + mConfigurationName + ", mAdblockContentType: " -+ + mAdblockContentType.getValue() + ", mTabId: " + mTabId; -+ } ++ public int getValue() { ++ return contentType; + } +} diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/FilteringConfiguration.java b/components/adblock/android/java/src/org/chromium/components/adblock/FilteringConfiguration.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/java/src/org/chromium/components/adblock/FilteringConfiguration.java -@@ -0,0 +1,348 @@ +@@ -0,0 +1,424 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1787,18 +1290,18 @@ new file mode 100644 + +package org.chromium.components.adblock; + -+import android.content.Context; -+import android.content.res.Resources; +import android.util.Log; +import android.webkit.URLUtil; + +import androidx.annotation.UiThread; + -+import org.chromium.base.ContextUtils; -+import org.chromium.base.ThreadUtils; +import org.jni_zero.CalledByNative; +import org.jni_zero.NativeMethods; + ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.library_loader.LibraryLoader; ++import org.chromium.content_public.browser.BrowserContextHandle; ++ +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; @@ -1810,46 +1313,58 @@ new file mode 100644 +import java.util.Set; + +/** -+ * @brief Represents an independent configuration of filters, filter lists, -+ * allowed domains and other settings that influence resource filtering and -+ * content blocking. -+ * Multiple Filtering Configurations can co-exist and be controlled separately. -+ * A network resource is blocked if any enabled Filtering Configuration -+ * determines it should be, through its filters. -+ * Elements on websites are hidden according to a superset of element-hiding -+ * selectors from all enabled Filtering Configurations. -+ * Lives on UI thread, not thread-safe. ++ * @brief Represents an independent configuration of filters, filter lists, allowed domains and ++ * other settings that influence resource filtering and content blocking. Multiple Filtering ++ * Configurations can co-exist and be controlled separately. A network resource is blocked if ++ * any enabled Filtering Configuration determines it should be, through its filters. Elements on ++ * websites are hidden according to a superset of element-hiding selectors from all enabled ++ * Filtering Configurations. Lives on UI thread, not thread-safe. + */ -+public class FilteringConfiguration { ++public final class FilteringConfiguration { + private static final String TAG = FilteringConfiguration.class.getSimpleName(); ++ private static final Set mConfigurations = new HashSet<>(); + + private final Set mConfigurationChangeObservers = new HashSet<>(); -+ protected final Set mSubscriptionUpdateObservers = new HashSet<>(); -+ private final String mName; -+ private final static Set mConfigurations = new HashSet<>(); ++ private final Set mSubscriptionUpdateObservers = new HashSet<>(); ++ private String mName; ++ private long mNativeController; ++ ++ private FilteringConfiguration( ++ final String configuration_name, BrowserContextHandle browserContextHandle) { ++ // Make sure native libraries are ready to avoid org.chromium.base.JniException ++ LibraryLoader.getInstance().ensureInitialized(); ++ ++ mName = configuration_name; ++ mNativeController = ++ FilteringConfigurationJni.get() ++ .create(this, configuration_name, browserContextHandle); ++ } ++ ++ private void DestroyNative() { ++ FilteringConfigurationJni.get().destroy(mNativeController); ++ mNativeController = 0; ++ } ++ ++ private void ValidateConfiguration() throws IllegalStateException { ++ if (mNativeController == 0) { ++ throw new IllegalStateException("Configuration does not exist!"); ++ } ++ } + + public interface ConfigurationChangeObserver { -+ /** -+ * Triggered when the FilteringConfiguration becomes disabled or enabled. -+ */ ++ /** Triggered when the FilteringConfiguration becomes disabled or enabled. */ + @UiThread + void onEnabledStateChanged(); + -+ /** -+ * Triggered when the collection of installed filter lists changes. -+ */ ++ /** Triggered when the collection of installed filter lists changes. */ + @UiThread + void onFilterListsChanged(); + -+ /** -+ * Triggered when the set of allowed domain changes. -+ */ ++ /** Triggered when the set of allowed domain changes. */ + @UiThread + void onAllowedDomainsChanged(); + -+ /** -+ * Triggered when the set of custom filters changes. -+ */ ++ /** Triggered when the set of custom filters changes. */ + @UiThread + void onCustomFiltersChanged(); + } @@ -1884,7 +1399,8 @@ new file mode 100644 + */ + @UiThread + public void setEnabled(boolean enabled) throws IllegalStateException { -+ FilteringConfigurationJni.get().setEnabled(mName, enabled); ++ ValidateConfiguration(); ++ FilteringConfigurationJni.get().setEnabled(mNativeController, enabled); + } + + /** @@ -1892,7 +1408,8 @@ new file mode 100644 + */ + @UiThread + public boolean isEnabled() throws IllegalStateException { -+ return FilteringConfigurationJni.get().isEnabled(mName); ++ ValidateConfiguration(); ++ return FilteringConfigurationJni.get().isEnabled(mNativeController); + } + + /** @@ -1900,7 +1417,8 @@ new file mode 100644 + */ + @UiThread + public void addFilterList(final URL url) throws IllegalStateException { -+ FilteringConfigurationJni.get().addFilterList(mName, url.toString()); ++ ValidateConfiguration(); ++ FilteringConfigurationJni.get().addFilterList(mNativeController, url.toString()); + } + + /** @@ -1908,7 +1426,8 @@ new file mode 100644 + */ + @UiThread + public void removeFilterList(final URL url) throws IllegalStateException { -+ FilteringConfigurationJni.get().removeFilterList(mName, url.toString()); ++ ValidateConfiguration(); ++ FilteringConfigurationJni.get().removeFilterList(mNativeController, url.toString()); + } + + /** @@ -1916,8 +1435,9 @@ new file mode 100644 + */ + @UiThread + public List getFilterLists() throws IllegalStateException { ++ ValidateConfiguration(); + List filterListsStr = -+ Arrays.asList(FilteringConfigurationJni.get().getFilterLists(mName)); ++ Arrays.asList(FilteringConfigurationJni.get().getFilterLists(mNativeController)); + List filterLists = new ArrayList(); + for (String url : filterListsStr) { + try { @@ -1929,25 +1449,32 @@ new file mode 100644 + return filterLists; + } + ++ @UiThread ++ public String getAcceptableAdsUrl() { ++ ValidateConfiguration(); ++ return FilteringConfigurationJni.get().getAcceptableAdsUrl(); ++ } ++ + /** + * @throws IllegalStateException when called on removed FilteringConfiguration. + */ + @UiThread + public void addAllowedDomain(final String domain) throws IllegalStateException { ++ ValidateConfiguration(); + String sanitizedDomain = sanitizeSite(domain); + if (sanitizedDomain == null) return; -+ FilteringConfigurationJni.get().addAllowedDomain(mName, sanitizedDomain); ++ FilteringConfigurationJni.get().addAllowedDomain(mNativeController, sanitizedDomain); + } + + /** + * @throws IllegalStateException when called on removed FilteringConfiguration. + */ -+ + @UiThread + public void removeAllowedDomain(final String domain) throws IllegalStateException { ++ ValidateConfiguration(); + String sanitizedDomain = sanitizeSite(domain); + if (sanitizedDomain == null) return; -+ FilteringConfigurationJni.get().removeAllowedDomain(mName, sanitizedDomain); ++ FilteringConfigurationJni.get().removeAllowedDomain(mNativeController, sanitizedDomain); + } + + /** @@ -1955,8 +1482,9 @@ new file mode 100644 + */ + @UiThread + public List getAllowedDomains() throws IllegalStateException { ++ ValidateConfiguration(); + List allowedDomains = -+ Arrays.asList(FilteringConfigurationJni.get().getAllowedDomains(mName)); ++ Arrays.asList(FilteringConfigurationJni.get().getAllowedDomains(mNativeController)); + Collections.sort(allowedDomains); + return allowedDomains; + } @@ -1966,7 +1494,8 @@ new file mode 100644 + */ + @UiThread + public void addCustomFilter(final String filter) throws IllegalStateException { -+ FilteringConfigurationJni.get().addCustomFilter(mName, filter); ++ ValidateConfiguration(); ++ FilteringConfigurationJni.get().addCustomFilter(mNativeController, filter); + } + + /** @@ -1974,7 +1503,8 @@ new file mode 100644 + */ + @UiThread + public void removeCustomFilter(final String filter) throws IllegalStateException { -+ FilteringConfigurationJni.get().removeCustomFilter(mName, filter); ++ ValidateConfiguration(); ++ FilteringConfigurationJni.get().removeCustomFilter(mNativeController, filter); + } + + /** @@ -1982,69 +1512,72 @@ new file mode 100644 + */ + @UiThread + public List getCustomFilters() throws IllegalStateException { -+ return Arrays.asList(FilteringConfigurationJni.get().getCustomFilters(mName)); ++ ValidateConfiguration(); ++ return Arrays.asList(FilteringConfigurationJni.get().getCustomFilters(mNativeController)); + } + + @UiThread -+ public static FilteringConfiguration createConfiguration(final String configuration_name) { ++ public static FilteringConfiguration createConfiguration( ++ final String configuration_name, BrowserContextHandle browserContextHandle) { + FilteringConfiguration configuration = findConfigurationByName(configuration_name); + if (configuration == null) { -+ configuration = new FilteringConfiguration(configuration_name); ++ configuration = new FilteringConfiguration(configuration_name, browserContextHandle); + mConfigurations.add(configuration); + } + return configuration; + } + + @UiThread -+ public static void removeConfiguration(final String configuration_name) { ++ public static void removeConfiguration( ++ final String configuration_name, BrowserContextHandle browserContextHandle) { ++ // sync with native filtering configurations ++ getConfigurations(browserContextHandle); + final FilteringConfiguration configuration = findConfigurationByName(configuration_name); + if (configuration != null) { ++ configuration.DestroyNative(); + mConfigurations.remove(configuration); + } -+ // Even if on Java side there is no such a FilteringConfiguration object, it can exist -+ // on native side. -+ FilteringConfigurationJni.get().removeConfiguration(configuration_name); + } + + @UiThread -+ public static List getConfigurations() { ++ public static List getConfigurations( ++ BrowserContextHandle browserContextHandle) { + // Get all existing (on C++ side) configurations, if there is no matching Java + // instance present in mConfigurations set then create one and add. + final String[] existing_configurations_names = -+ FilteringConfigurationJni.get().getConfigurations(); ++ FilteringConfigurationJni.get().getConfigurations(browserContextHandle); + final List configurations_to_return = + new ArrayList(); + // If mConfigurations contains configurations which are not present on the list returned + // from FilteringConfigurationJni.get().getConfigurations() then we need to remove them + // as it means that a configuration has been removed on native side (not via Java API). -+ mConfigurations.removeIf(filteringConfiguration -> { -+ return !Arrays.asList(existing_configurations_names) ++ mConfigurations.removeIf( ++ filteringConfiguration -> { ++ return !Arrays.asList(existing_configurations_names) + .contains(filteringConfiguration.mName); -+ }); ++ }); + for (final String configuration_name : existing_configurations_names) { + FilteringConfiguration configuration = findConfigurationByName(configuration_name); + if (configuration == null) { -+ configuration = new FilteringConfiguration(configuration_name); ++ configuration = ++ new FilteringConfiguration(configuration_name, browserContextHandle); + mConfigurations.add(configuration); + } -+ configurations_to_return.add(configuration); ++ configurations_to_return.add((FilteringConfiguration) configuration); + } -+ Collections.sort(configurations_to_return, new Comparator() { -+ @Override -+ public int compare( -+ final FilteringConfiguration object1, final FilteringConfiguration object2) { -+ return object1.mName.compareTo(object2.mName); -+ } -+ }); ++ Collections.sort( ++ configurations_to_return, ++ new Comparator() { ++ @Override ++ public int compare( ++ final FilteringConfiguration object1, ++ final FilteringConfiguration object2) { ++ return object1.mName.compareTo(object2.mName); ++ } ++ }); + return configurations_to_return; + } + -+ // TODO(kzlomek): Make it private once we remove AdblockController (DPD-2120). -+ protected FilteringConfiguration(final String configuration_name) { -+ mName = configuration_name; -+ FilteringConfigurationJni.get().bind(mName, this); -+ } -+ + @UiThread + private static FilteringConfiguration findConfigurationByName(final String configuration_name) { + for (final FilteringConfiguration configuration : mConfigurations) { @@ -2055,6 +1588,17 @@ new file mode 100644 + return null; + } + ++ @UiThread ++ public static void setAutoInstallEnabled( ++ boolean enable, BrowserContextHandle browserContextHandle) { ++ FilteringConfigurationJni.get().setAutoInstallEnabled(enable, browserContextHandle); ++ } ++ ++ @UiThread ++ public static boolean isAutoInstallEnabled(BrowserContextHandle browserContextHandle) { ++ return FilteringConfigurationJni.get().isAutoInstallEnabled(browserContextHandle); ++ } ++ + private String sanitizeSite(String site) { + // |site| is raw user input. We expect it to be either a domain or a URL. + try { @@ -2098,29 +1642,64 @@ new file mode 100644 + } + } + ++ @CalledByNative ++ private void onSubscriptionUpdated(final String url) { ++ ThreadUtils.assertOnUiThread(); ++ try { ++ URL subscriptionUrl = new URL(URLUtil.guessUrl(url)); ++ for (final SubscriptionUpdateObserver observer : mSubscriptionUpdateObservers) { ++ observer.onSubscriptionDownloaded(subscriptionUrl); ++ } ++ } catch (MalformedURLException e) { ++ Log.e(TAG, "Error parsing subscription url: " + url); ++ } ++ } ++ + @NativeMethods + interface Natives { -+ void bind(String configuration_name, FilteringConfiguration caller); -+ void removeConfiguration(String configuration_name); -+ String[] getConfigurations(); -+ boolean isEnabled(String configuration_name); -+ void setEnabled(String configuration_name, boolean enabled); -+ void addFilterList(String configuration_name, String url); -+ void removeFilterList(String configuration_name, String url); -+ String[] getFilterLists(String configuration_name); -+ void addAllowedDomain(String configuration_name, String domain); -+ void removeAllowedDomain(String configuration_name, String domain); -+ String[] getAllowedDomains(String configuration_name); -+ void addCustomFilter(String configuration_name, String filter); -+ void removeCustomFilter(String configuration_name, String filter); -+ String[] getCustomFilters(String configuration_name); ++ void destroy(long nativeFilteringConfigurationAndroid); ++ ++ void setEnabled(long nativeFilteringConfigurationAndroid, boolean enabled); ++ ++ boolean isEnabled(long nativeFilteringConfigurationAndroid); ++ ++ void addAllowedDomain(long nativeFilteringConfigurationAndroid, String domain); ++ ++ void removeAllowedDomain(long nativeFilteringConfigurationAndroid, String domain); ++ ++ String[] getAllowedDomains(long nativeFilteringConfigurationAndroid); ++ ++ void addCustomFilter(long nativeFilteringConfigurationAndroid, String filter); ++ ++ void removeCustomFilter(long nativeFilteringConfigurationAndroid, String filter); ++ ++ String[] getCustomFilters(long nativeFilteringConfigurationAndroid); ++ ++ void addFilterList(long nativeFilteringConfigurationAndroid, String url); ++ ++ void removeFilterList(long nativeFilteringConfigurationAndroid, String url); ++ ++ String[] getFilterLists(long nativeFilteringConfigurationAndroid); ++ ++ long create( ++ FilteringConfiguration controller, ++ final String configuration_name, ++ BrowserContextHandle contextHandle); ++ ++ void setAutoInstallEnabled(boolean enable, BrowserContextHandle contextHandle); ++ ++ boolean isAutoInstallEnabled(BrowserContextHandle contextHandle); ++ ++ String[] getConfigurations(BrowserContextHandle contextHandle); ++ ++ String getAcceptableAdsUrl(); + } +} diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/ResourceClassificationNotifier.java b/components/adblock/android/java/src/org/chromium/components/adblock/ResourceClassificationNotifier.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/java/src/org/chromium/components/adblock/ResourceClassificationNotifier.java -@@ -0,0 +1,187 @@ +@@ -0,0 +1,233 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2140,161 +1719,205 @@ new file mode 100644 + +package org.chromium.components.adblock; + -+import android.content.Context; -+import android.content.res.Resources; +import android.util.Log; -+import android.webkit.URLUtil; + +import androidx.annotation.UiThread; + -+import org.chromium.base.ContextUtils; -+import org.chromium.base.ThreadUtils; +import org.jni_zero.CalledByNative; +import org.jni_zero.NativeMethods; + -+import java.net.MalformedURLException; -+import java.net.URL; ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.library_loader.LibraryLoader; ++import org.chromium.content_public.browser.BrowserContextHandle; ++import org.chromium.content_public.browser.GlobalRenderFrameHostId; ++ +import java.util.ArrayList; +import java.util.Arrays; -+import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + -+/** -+ * @brief Allows observing notifications from ongoing resource classification. -+ * Classification combines filters from all FilteringConfigurations. -+ * Lives on UI thread, not thread-safe. -+ */ -+public final class ResourceClassificationNotifier { ++public class ResourceClassificationNotifier { + private static final String TAG = ResourceClassificationNotifier.class.getSimpleName(); + ++ private long mNativeController; + private static ResourceClassificationNotifier sInstance; -+ private final Set mOnAdBlockedObservers = new HashSet<>(); ++ ++ private final Set mResourceFilteringObservers = new HashSet<>(); ++ + // TODO(mpawlowski) in the future, we can consider adding filter hit + // notifications here as well (DPD-1233) + -+ public interface AdBlockedObserver { ++ public interface ResourceFilteringObserver { + /** -+ * "Ad allowed" event for a request which would be blocked but there -+ * was an allowlisting filter found. ++ * "Request allowed" event for a request which would be blocked but there was an ++ * allowlisting filter found. ++ * ++ *

It should not block the UI thread for too long. + * -+ * It should not block the UI thread for too long. + * @param info contains auxiliary information about the resource. + */ + @UiThread -+ void onAdAllowed(AdblockCounters.ResourceInfo info); ++ void onRequestAllowed(ResourceFilteringCounters.ResourceInfo info); + + /** -+ * "Ad blocked" event for a request which was blocked. ++ * "Request blocked" event for a request which was blocked. ++ * ++ *

It should not block the UI thread for too long. + * -+ * It should not block the UI thread for too long. + * @param info contains auxiliary information about the resource. + */ + @UiThread -+ void onAdBlocked(AdblockCounters.ResourceInfo info); ++ void onRequestBlocked(ResourceFilteringCounters.ResourceInfo info); + + /** + * "Page allowed" event for an allowlisted domain (page). + * -+ * It should not block the UI thread for too long. ++ *

It should not block the UI thread for too long. ++ * + * @param info contains auxiliary information about the resource. + */ + @UiThread -+ void onPageAllowed(AdblockCounters.ResourceInfo info); ++ void onPageAllowed(ResourceFilteringCounters.ResourceInfo info); + + /** -+ * "Popup allowed" event for a popup which would be blocked but there -+ * was an allowlisting filter found. ++ * "Popup allowed" event for a popup which would be blocked but there was an allowlisting ++ * filter found. ++ * ++ *

It should not block the UI thread for too long. + * -+ * It should not block the UI thread for too long. + * @param info contains auxiliary information about the resource. + */ + @UiThread -+ void onPopupAllowed(AdblockCounters.ResourceInfo info); ++ void onPopupAllowed(ResourceFilteringCounters.ResourceInfo info); + + /** + * "Popup blocked" event for a popup which was blocked. + * -+ * It should not block the UI thread for too long. ++ *

It should not block the UI thread for too long. ++ * + * @param info contains auxiliary information about the resource. + */ + @UiThread -+ void onPopupBlocked(AdblockCounters.ResourceInfo info); ++ void onPopupBlocked(ResourceFilteringCounters.ResourceInfo info); + } + -+ private ResourceClassificationNotifier() { -+ ResourceClassificationNotifierJni.get().bind(this); ++ private ResourceClassificationNotifier(BrowserContextHandle contextHandle) { ++ mNativeController = ResourceClassificationNotifierJni.get().create(this, contextHandle); ++ assert mNativeController != 0 ++ : "Failed to instantiate native ResourceClassificationNotifier"; + } + -+ public static ResourceClassificationNotifier getInstance() { ++ public static ResourceClassificationNotifier getInstance(BrowserContextHandle contextHandle) { ++ // Make sure native libraries are ready to avoid org.chromium.base.JniException ++ LibraryLoader.getInstance().ensureInitialized(); + ThreadUtils.assertOnUiThread(); + if (sInstance == null) { -+ sInstance = new ResourceClassificationNotifier(); ++ sInstance = new ResourceClassificationNotifier(contextHandle); + } + return sInstance; + } + + @UiThread -+ public void addOnAdBlockedObserver(final AdBlockedObserver observer) { -+ mOnAdBlockedObservers.add(observer); ++ public void addResourceFilteringObserver(final ResourceFilteringObserver observer) { ++ mResourceFilteringObservers.add(observer); + } + + @UiThread -+ public void removeOnAdBlockedObserver(final AdBlockedObserver observer) { -+ mOnAdBlockedObservers.remove(observer); ++ public void removeResourceFilteringObserver(final ResourceFilteringObserver observer) { ++ mResourceFilteringObservers.remove(observer); + } + + @CalledByNative -+ private void adMatchedCallback(final String requestUrl, boolean wasBlocked, -+ final String[] parentFrameUrls, final String subscriptionUrl, -+ final String configurationName, final int contentType, final int tabId) { ++ private void requestMatchedCallback( ++ final String requestUrl, ++ boolean wasBlocked, ++ final String[] parentFrameUrls, ++ final String subscriptionUrl, ++ final String configurationName, ++ final int contentType, ++ final int renderProcessId, ++ final int renderFrameId) { + ThreadUtils.assertOnUiThread(); + final List parentsList = Arrays.asList(parentFrameUrls); -+ final AdblockCounters.ResourceInfo resourceInfo = new AdblockCounters.ResourceInfo( -+ requestUrl, parentsList, subscriptionUrl, configurationName, contentType, tabId); -+ Log.d(TAG, -+ "eyeo: adMatchedCallback() notifies " + mOnAdBlockedObservers.size() -+ + " listeners about " + resourceInfo.toString() ++ final ResourceFilteringCounters.ResourceInfo resourceInfo = ++ new ResourceFilteringCounters.ResourceInfo( ++ requestUrl, ++ parentsList, ++ subscriptionUrl, ++ configurationName, ++ contentType, ++ new GlobalRenderFrameHostId(renderProcessId, renderFrameId)); ++ Log.d( ++ TAG, ++ "eyeo: requestMatchedCallback() notifies " ++ + mResourceFilteringObservers.size() ++ + " listeners about " ++ + resourceInfo.toString() + + (wasBlocked ? " getting blocked" : " being allowed")); -+ for (final AdBlockedObserver observer : mOnAdBlockedObservers) { ++ for (final ResourceFilteringObserver observer : mResourceFilteringObservers) { + if (wasBlocked) { -+ observer.onAdBlocked(resourceInfo); ++ observer.onRequestBlocked(resourceInfo); + } else { -+ observer.onAdAllowed(resourceInfo); ++ observer.onRequestAllowed(resourceInfo); + } + } + } + + @CalledByNative -+ private void pageAllowedCallback(final String requestUrl, final String subscriptionUrl, -+ final String configurationName, final int tabId) { ++ private void pageAllowedCallback( ++ final String requestUrl, ++ final String subscriptionUrl, ++ final String configurationName, ++ final int renderProcessId, ++ final int renderFrameId) { + ThreadUtils.assertOnUiThread(); -+ final AdblockCounters.ResourceInfo resourceInfo = -+ new AdblockCounters.ResourceInfo(requestUrl, new ArrayList(), subscriptionUrl, -+ configurationName, AdblockContentType.CONTENT_TYPE_OTHER.getValue(), tabId); -+ Log.d(TAG, -+ "eyeo: pageAllowedCallback() notifies " + mOnAdBlockedObservers.size() -+ + " listeners about " + resourceInfo.toString()); -+ for (final AdBlockedObserver observer : mOnAdBlockedObservers) { ++ final ResourceFilteringCounters.ResourceInfo resourceInfo = ++ new ResourceFilteringCounters.ResourceInfo( ++ requestUrl, ++ new ArrayList(), ++ subscriptionUrl, ++ configurationName, ++ ContentType.CONTENT_TYPE_OTHER.getValue(), ++ new GlobalRenderFrameHostId(renderProcessId, renderFrameId)); ++ Log.d( ++ TAG, ++ "eyeo: pageAllowedCallback() notifies " ++ + mResourceFilteringObservers.size() ++ + " listeners about " ++ + resourceInfo.toString()); ++ for (final ResourceFilteringObserver observer : mResourceFilteringObservers) { + observer.onPageAllowed(resourceInfo); + } + } + + @CalledByNative -+ private void popupMatchedCallback(final String requestUrl, boolean wasBlocked, -+ final String openerUrl, final String subscription, final String configurationName, -+ final int tabId) { ++ private void popupMatchedCallback( ++ final String requestUrl, ++ boolean wasBlocked, ++ final String openerUrl, ++ final String subscription, ++ final String configurationName, ++ final int renderProcessId, ++ final int renderFrameId) { + ThreadUtils.assertOnUiThread(); + final List parentsList = Arrays.asList(openerUrl); -+ final AdblockCounters.ResourceInfo resourceInfo = -+ new AdblockCounters.ResourceInfo(requestUrl, parentsList, subscription, -+ configurationName, AdblockContentType.CONTENT_TYPE_OTHER.getValue(), tabId); -+ Log.d(TAG, -+ "eyeo: popupMatchedCallback() notifies " + mOnAdBlockedObservers.size() -+ + " listeners about " + resourceInfo.toString() ++ final ResourceFilteringCounters.ResourceInfo resourceInfo = ++ new ResourceFilteringCounters.ResourceInfo( ++ requestUrl, ++ parentsList, ++ subscription, ++ configurationName, ++ ContentType.CONTENT_TYPE_OTHER.getValue(), ++ new GlobalRenderFrameHostId(renderProcessId, renderFrameId)); ++ Log.d( ++ TAG, ++ "eyeo: popupMatchedCallback() notifies " ++ + mResourceFilteringObservers.size() ++ + " listeners about " ++ + resourceInfo.toString() + + (wasBlocked ? " getting blocked" : " being allowed")); -+ for (final AdBlockedObserver observer : mOnAdBlockedObservers) { ++ for (final ResourceFilteringObserver observer : mResourceFilteringObservers) { + if (wasBlocked) { + observer.onPopupBlocked(resourceInfo); + } else { @@ -2304,15 +1927,17 @@ new file mode 100644 + } + + @NativeMethods -+ interface Natives { -+ void bind(ResourceClassificationNotifier caller); ++ public interface Natives { ++ // Create an instance of ResourceClassificationNotifier associated with the supplied ++ // profile. ++ long create(ResourceClassificationNotifier controller, BrowserContextHandle contextHandle); + } +} -diff --git a/components/adblock/android/java_bindings_getters.h b/components/adblock/android/java_bindings_getters.h +diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/ResourceFilteringCounters.java b/components/adblock/android/java/src/org/chromium/components/adblock/ResourceFilteringCounters.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java_bindings_getters.h -@@ -0,0 +1,41 @@ ++++ b/components/adblock/android/java/src/org/chromium/components/adblock/ResourceFilteringCounters.java +@@ -0,0 +1,111 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2330,35 +1955,105 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#ifndef COMPONENTS_ADBLOCK_ANDROID_JAVA_BINDINGS_GETTERS_H_ -+#define COMPONENTS_ADBLOCK_ANDROID_JAVA_BINDINGS_GETTERS_H_ ++package org.chromium.components.adblock; + -+#include "components/adblock/android/adblock_jni.h" -+#include "components/adblock/android/filtering_configuration_bindings.h" -+#include "components/adblock/android/resource_classification_notifier_bindings.h" -+#include "components/adblock/core/subscription/subscription_service.h" ++import org.chromium.content_public.browser.GlobalRenderFrameHostId; + -+namespace adblock { ++import java.util.List; ++ ++public class ResourceFilteringCounters { ++ /** Immutable data-class containing an auxiliary information about resource event. */ ++ public static class ResourceInfo { ++ // TODO: Make members private ++ final String mRequestUrl; ++ final List mParentFrameUrls; ++ final String mSubscriptionUrl; ++ final String mConfigurationName; ++ final ContentType mContentType; ++ final GlobalRenderFrameHostId mMainFrameId; ++ ++ ResourceInfo( ++ final String requestUrl, ++ final List parentFrameUrls, ++ final String subscriptionUrl, ++ final String configurationName, ++ final int contentType, ++ final GlobalRenderFrameHostId mainFrameId) { ++ this.mRequestUrl = requestUrl; ++ this.mParentFrameUrls = parentFrameUrls; ++ this.mSubscriptionUrl = subscriptionUrl; ++ this.mConfigurationName = configurationName; ++ this.mContentType = ContentType.fromInt(contentType); ++ this.mMainFrameId = mainFrameId; ++ } + -+SubscriptionService* GetSubscriptionService(); ++ /** ++ * @return The request which was blocked or allowed. ++ */ ++ public String getRequestUrl() { ++ return mRequestUrl; ++ } + -+AdblockJNI* GetJNI(); ++ /** ++ * @return The parent frames of the mRequestUrl. ++ */ ++ public List getParentFrameUrls() { ++ return mParentFrameUrls; ++ } + -+FilteringConfigurationBindings& GetFilteringConfigurationBindings(); ++ /** ++ * @return Subscription url for filter done blocking or allowing decision, empty string ++ * otherwise. ++ */ ++ public String getSubscription() { ++ return mSubscriptionUrl; ++ } + -+ResourceClassificationNotifierBindings& -+GetResourceClassificationNotifierBindings(); ++ /** ++ * @return Configuration name containing subscription with matched filer for blocking or ++ * allowing decision, empty string otherwise. ++ */ ++ public String getConfigurationName() { ++ return mConfigurationName; ++ } + -+int GetTabId(content::RenderFrameHost* render_frame_host); ++ /** ++ * @return The resource content type. See enum ContentType in components/adblock/types.h ++ */ ++ public ContentType getContentType() { ++ return mContentType; ++ } + -+} // namespace adblock ++ /** ++ * @return The current tab id for the mRequestUrl. -1 means no tab id, likely tab closed ++ * before event arrived. Numbers start from 0. ++ */ ++ public GlobalRenderFrameHostId getMainFrameId() { ++ return mMainFrameId; ++ } + -+#endif // COMPONENTS_ADBLOCK_ANDROID_JAVA_BINDINGS_GETTERS_H_ ++ @Override ++ public String toString() { ++ return "mRequestUrl: " ++ + mRequestUrl ++ + ", mParentFrameUrls: " ++ + mParentFrameUrls.toString() ++ + ", mSubscriptionUrl:" ++ + mSubscriptionUrl ++ + ", mConfigurationName:" ++ + mConfigurationName ++ + ", mContentType: " ++ + mContentType.getValue() ++ + ", mMainFrameId: " ++ + mMainFrameId.frameRoutingId(); ++ } ++ } ++} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/AdblockControllerTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/AdblockControllerTestBase.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/AdblockControllerTestBase.java -@@ -0,0 +1,63 @@ +@@ -0,0 +1,64 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2383,11 +2078,10 @@ new file mode 100644 +import org.junit.Assert; +import org.junit.Test; + ++import org.chromium.base.ThreadUtils; +import org.chromium.base.test.util.CallbackHelper; +import org.chromium.base.test.util.Feature; +import org.chromium.base.test.util.IntegrationTest; -+import org.chromium.components.adblock.AdblockController; -+import org.chromium.content_public.browser.test.util.TestThreadUtils; + +import java.util.ArrayList; +import java.util.List; @@ -2396,6 +2090,7 @@ new file mode 100644 + +public abstract class AdblockControllerTestBase { + private final CallbackHelper mHelper = new CallbackHelper(); ++ protected AdblockController mAdblockController; + + @Test + @IntegrationTest @@ -2403,15 +2098,16 @@ new file mode 100644 + @Feature({"adblock"}) + public void addingAllowedDomains() throws TimeoutException { + final List allowedDomains = new ArrayList<>(); -+ TestThreadUtils.runOnUiThreadBlocking(() -> { -+ AdblockController.getInstance().addAllowedDomain("foobar.com"); -+ AdblockController.getInstance().addAllowedDomain("domain.com/path/to/page.html"); -+ AdblockController.getInstance().addAllowedDomain("domain.com/duplicate.html"); -+ AdblockController.getInstance().addAllowedDomain("https://scheme.com/path.html"); -+ AdblockController.getInstance().addAllowedDomain("gibberish"); -+ allowedDomains.addAll(AdblockController.getInstance().getAllowedDomains()); -+ mHelper.notifyCalled(); -+ }); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mAdblockController.addAllowedDomain("foobar.com"); ++ mAdblockController.addAllowedDomain("domain.com/path/to/page.html"); ++ mAdblockController.addAllowedDomain("domain.com/duplicate.html"); ++ mAdblockController.addAllowedDomain("https://scheme.com/path.html"); ++ mAdblockController.addAllowedDomain("gibberish"); ++ allowedDomains.addAll(mAdblockController.getAllowedDomains()); ++ mHelper.notifyCalled(); ++ }); + mHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); + // We expect to see a sorted collection of domains (not URLs) without duplicates. + ArrayList expectedAllowedDomains = new ArrayList(); @@ -2422,11 +2118,89 @@ new file mode 100644 + Assert.assertEquals(expectedAllowedDomains, allowedDomains); + } +} -diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestAdBlockedObserver.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestAdBlockedObserver.java +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/DefaultSettingsTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/DefaultSettingsTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/DefaultSettingsTestBase.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import org.junit.Assert; ++import org.junit.Test; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.Feature; ++import org.chromium.base.test.util.IntegrationTest; ++import org.chromium.content_public.browser.BrowserContextHandle; ++ ++import java.net.MalformedURLException; ++import java.net.URL; ++import java.util.List; ++ ++public abstract class DefaultSettingsTestBase { ++ protected abstract BrowserContextHandle getBrowserContext(); ++ ++ @Test ++ @IntegrationTest ++ @Feature({"adblock"}) ++ public void checkAdblockEnabledByDefault() { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ FilteringConfiguration adblockConfiguration = ++ FilteringConfiguration.createConfiguration( ++ "adblock", getBrowserContext()); ++ ++ Assert.assertNotNull(adblockConfiguration); ++ Assert.assertTrue(adblockConfiguration.isEnabled()); ++ Assert.assertNotEquals(adblockConfiguration.getFilterLists().size(), 0); ++ }); ++ } ++ ++ @Test ++ @IntegrationTest ++ @Feature({"adblock"}) ++ public void checkAcceptableAdsEnabledByDefault() { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ FilteringConfiguration adblockConfiguration = ++ FilteringConfiguration.createConfiguration( ++ "adblock", getBrowserContext()); ++ ++ Assert.assertNotNull(adblockConfiguration); ++ List filterLists = adblockConfiguration.getFilterLists(); ++ ++ try { ++ URL acceptableAdsUrl = new URL(adblockConfiguration.getAcceptableAdsUrl()); ++ Assert.assertTrue(filterLists.contains(acceptableAdsUrl)); ++ } catch (MalformedURLException e) { ++ e.printStackTrace(); ++ Assert.fail(); ++ } ++ }); ++ } ++} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/FilteringConfigurationTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/FilteringConfigurationTestBase.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestAdBlockedObserver.java -@@ -0,0 +1,176 @@ ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/FilteringConfigurationTestBase.java +@@ -0,0 +1,421 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2446,168 +2220,413 @@ new file mode 100644 + +package org.chromium.components.adblock; + ++import androidx.test.InstrumentationRegistry; ++import androidx.test.filters.LargeTest; ++ +import org.junit.Assert; ++import org.junit.Test; + -+import org.chromium.components.adblock.AdblockContentType; -+import org.chromium.components.adblock.AdblockCounters; -+import org.chromium.components.adblock.ResourceClassificationNotifier; ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CallbackHelper; ++import org.chromium.base.test.util.Feature; ++import org.chromium.base.test.util.IntegrationTest; ++import org.chromium.content_public.browser.BrowserContextHandle; ++import org.chromium.net.test.EmbeddedTestServer; + +import java.net.URL; ++import java.util.ArrayList; +import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CopyOnWriteArrayList; -+import java.util.concurrent.CountDownLatch; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; + -+public class TestAdBlockedObserver implements ResourceClassificationNotifier.AdBlockedObserver { -+ @Override -+ public void onAdAllowed(AdblockCounters.ResourceInfo info) { -+ allowedInfos.add(info); -+ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); -+ CheckAndCountDownLatch(Decision.ALLOWED, info.getRequestUrl().split("\\?")[0]); -+ } ++public abstract class FilteringConfigurationTestBase { ++ private final CallbackHelper mCallbackHelper = new CallbackHelper(); ++ private TestPagesHelperBase mHelper; ++ public FilteringConfiguration mConfigurationA; ++ public FilteringConfiguration mConfigurationB; ++ private EmbeddedTestServer mTestServer; ++ protected String mTestUrl; + -+ @Override -+ public void onAdBlocked(AdblockCounters.ResourceInfo info) { -+ blockedInfos.add(info); -+ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); -+ CheckAndCountDownLatch(Decision.BLOCKED, info.getRequestUrl().split("\\?")[0]); -+ } ++ protected abstract void loadTestUrl() throws Exception; + -+ @Override -+ public void onPageAllowed(AdblockCounters.ResourceInfo info) { -+ allowedPageInfos.add(info); -+ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); -+ } ++ protected abstract BrowserContextHandle getBrowserContext(); + -+ @Override -+ public void onPopupAllowed(AdblockCounters.ResourceInfo info) { -+ allowedPopupsInfos.add(info); -+ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ public void setUp(TestPagesHelperBase helper, String filePath) throws TimeoutException { ++ mHelper = helper; ++ mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); ++ mTestUrl = mTestServer.getURLWithHostName("test.org", filePath); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA = ++ FilteringConfiguration.createConfiguration("a", getBrowserContext()); ++ mConfigurationB = ++ FilteringConfiguration.createConfiguration("b", getBrowserContext()); ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); + } + -+ @Override -+ public void onPopupBlocked(AdblockCounters.ResourceInfo info) { -+ blockedPopupsInfos.add(info); -+ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ public void tearDown() { ++ mTestServer.stopAndDestroyServer(); + } + -+ public boolean isBlocked(String url) { -+ for (AdblockCounters.ResourceInfo info : blockedInfos) { -+ if (info.getRequestUrl().contains(url)) return true; ++ private static class TestConfigurationChangeObserver ++ implements FilteringConfiguration.ConfigurationChangeObserver { ++ public volatile boolean mOnEnabledStateChangedCalled; ++ public volatile boolean mOnFilterListsChanged; ++ public volatile boolean mOnAllowedDomainsChanged; ++ public volatile boolean mOnCustomFiltersChanged; ++ ++ public TestConfigurationChangeObserver() { ++ mOnEnabledStateChangedCalled = false; ++ mOnFilterListsChanged = false; ++ mOnAllowedDomainsChanged = false; ++ mOnCustomFiltersChanged = false; + } + -+ return false; -+ } ++ @Override ++ public void onEnabledStateChanged() { ++ mOnEnabledStateChangedCalled = true; ++ } + -+ public boolean isPopupBlocked(String url) { -+ for (AdblockCounters.ResourceInfo info : blockedPopupsInfos) { -+ if (info.getRequestUrl().contains(url)) return true; ++ @Override ++ public void onFilterListsChanged() { ++ mOnFilterListsChanged = true; + } + -+ return false; -+ } ++ @Override ++ public void onAllowedDomainsChanged() { ++ mOnAllowedDomainsChanged = true; ++ } + -+ public int numBlockedByType(AdblockContentType type) { -+ int result = 0; -+ for (AdblockCounters.ResourceInfo info : blockedInfos) { -+ if (info.getAdblockContentType() == type) ++result; ++ @Override ++ public void onCustomFiltersChanged() { ++ mOnCustomFiltersChanged = true; + } -+ return result; + } + -+ public int numBlockedPopups() { -+ return blockedPopupsInfos.size(); -+ } ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void addingAllowedDomains() throws Exception { ++ final List allowedDomainsA = new ArrayList<>(); ++ final List allowedDomainsB = new ArrayList<>(); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addAllowedDomain("foobar.com"); ++ mConfigurationA.addAllowedDomain("domain.com/path/to/page.html"); ++ mConfigurationA.addAllowedDomain("domain.com/duplicate.html"); ++ allowedDomainsA.addAll(mConfigurationA.getAllowedDomains()); ++ ++ mConfigurationB.addAllowedDomain("https://scheme.com/path.html"); ++ mConfigurationB.addAllowedDomain("https://second.com"); ++ mConfigurationB.removeAllowedDomain("https://second.com"); ++ mConfigurationB.addAllowedDomain("gibberish"); ++ allowedDomainsB.addAll(mConfigurationB.getAllowedDomains()); ++ ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ // We expect to see a sorted collection of domains (not URLs) without duplicates. ++ ArrayList expectedAllowedDomainsA = new ArrayList(); ++ expectedAllowedDomainsA.add("domain.com"); ++ expectedAllowedDomainsA.add("foobar.com"); ++ Assert.assertEquals(expectedAllowedDomainsA, allowedDomainsA); + -+ public boolean isAllowed(String url) { -+ for (AdblockCounters.ResourceInfo info : allowedInfos) { -+ if (info.getRequestUrl().contains(url)) return true; -+ } ++ // We expect not to see second.com because it was removed after being added. ++ ArrayList expectedAllowedDomainsB = new ArrayList(); ++ expectedAllowedDomainsB.add("scheme.com"); ++ expectedAllowedDomainsB.add("www.gibberish.com"); ++ Assert.assertEquals(expectedAllowedDomainsB, allowedDomainsB); ++ } + -+ return false; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void addingCustomFilters() throws Exception { ++ final List customFiltersA = new ArrayList<>(); ++ final List customFiltersB = new ArrayList<>(); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addCustomFilter("foobar.com"); ++ mConfigurationA.addCustomFilter("foobar.com"); ++ mConfigurationA.addCustomFilter("abc"); ++ customFiltersA.addAll(mConfigurationA.getCustomFilters()); ++ ++ mConfigurationB.addCustomFilter("https://scheme.com/path.html"); ++ mConfigurationB.addCustomFilter("https://second.com"); ++ mConfigurationB.removeCustomFilter("https://second.com"); ++ customFiltersB.addAll(mConfigurationB.getCustomFilters()); ++ ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ // We expect to see a collection of custom filters without duplicates. ++ // The order represents order of addition. ++ ArrayList expectedCustomFiltersA = new ArrayList(); ++ expectedCustomFiltersA.add("foobar.com"); ++ expectedCustomFiltersA.add("abc"); ++ Assert.assertEquals(expectedCustomFiltersA, customFiltersA); ++ ++ // We expect not to see https://second.com because it was removed after being added. ++ ArrayList expectedCustomFiltersB = new ArrayList(); ++ expectedCustomFiltersB.add("https://scheme.com/path.html"); ++ Assert.assertEquals(expectedCustomFiltersB, customFiltersB); + } + -+ public boolean isPageAllowed(String url) { -+ for (AdblockCounters.ResourceInfo info : allowedPageInfos) { -+ if (info.getRequestUrl().contains(url)) return true; -+ } ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void addingFilterLists() throws Exception { ++ final URL filterList1 = new URL("http://filters.com/list1.txt"); ++ final URL filterList2 = new URL("http://filters.com/list2.txt"); ++ final List filterListsA = new ArrayList(); ++ final List filterListsB = new ArrayList(); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addFilterList(filterList1); ++ mConfigurationA.addFilterList(filterList2); ++ mConfigurationA.addFilterList(filterList1); ++ filterListsA.addAll(mConfigurationA.getFilterLists()); ++ ++ mConfigurationB.addFilterList(filterList1); ++ mConfigurationB.addFilterList(filterList2); ++ mConfigurationB.removeFilterList(filterList1); ++ filterListsB.addAll(mConfigurationB.getFilterLists()); ++ ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ ArrayList expectedFilterListsA = new ArrayList(); ++ expectedFilterListsA.add(filterList1); ++ expectedFilterListsA.add(filterList2); ++ Assert.assertEquals(expectedFilterListsA, filterListsA); ++ ++ ArrayList expectedFilterListsB = new ArrayList(); ++ expectedFilterListsB.add(filterList2); ++ Assert.assertEquals(expectedFilterListsB, filterListsB); ++ } + -+ return false; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void aliasedConfigurations() throws Exception { ++ final URL filterList1 = new URL("http://filters.com/list1.txt"); ++ final URL filterList2 = new URL("http://filters.com/list2.txt"); ++ final List filterListsA = new ArrayList(); ++ final List filterListsB = new ArrayList(); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ // Create a new FilteringConfiguration with a name of one that ++ // already exist. ++ FilteringConfiguration aliasedConfiguration = ++ FilteringConfiguration.createConfiguration("a", getBrowserContext()); ++ ++ // We add filter lists only to the original configuration instance. ++ mConfigurationA.addFilterList(filterList1); ++ mConfigurationA.addFilterList(filterList2); ++ ++ // We check what filter lists are present in the original and in the aliased ++ // instance. ++ filterListsA.addAll(mConfigurationA.getFilterLists()); ++ filterListsB.addAll(aliasedConfiguration.getFilterLists()); ++ ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ ArrayList expectedFilterLists = new ArrayList(); ++ expectedFilterLists.add(filterList1); ++ expectedFilterLists.add(filterList2); ++ Assert.assertEquals(expectedFilterLists, filterListsA); ++ Assert.assertEquals(expectedFilterLists, filterListsB); + } + -+ public boolean isPopupAllowed(String url) { -+ for (AdblockCounters.ResourceInfo info : allowedPopupsInfos) { -+ if (info.getRequestUrl().contains(url)) return true; -+ } ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void configurationChangeObserverNotified() throws Exception { ++ final URL filterList1 = new URL("http://filters.com/list1.txt"); ++ final TestConfigurationChangeObserver observer = new TestConfigurationChangeObserver(); ++ Assert.assertFalse(observer.mOnEnabledStateChangedCalled); ++ Assert.assertFalse(observer.mOnAllowedDomainsChanged); ++ Assert.assertFalse(observer.mOnCustomFiltersChanged); ++ Assert.assertFalse(observer.mOnFilterListsChanged); ++ ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ // We'll create an aliased instance which will receive notifications triggered ++ // by ++ // changes made to the original instance. ++ FilteringConfiguration aliasedConfiguration = ++ FilteringConfiguration.createConfiguration("a", getBrowserContext()); ++ aliasedConfiguration.addObserver(observer); ++ ++ mConfigurationA.addFilterList(filterList1); ++ mConfigurationA.addAllowedDomain("test.com"); ++ mConfigurationA.addCustomFilter("test.com"); ++ mConfigurationA.setEnabled(false); ++ ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ Assert.assertTrue(observer.mOnEnabledStateChangedCalled); ++ Assert.assertTrue(observer.mOnAllowedDomainsChanged); ++ Assert.assertTrue(observer.mOnCustomFiltersChanged); ++ Assert.assertTrue(observer.mOnFilterListsChanged); ++ } + -+ return false; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void resourceBlockedByFilter() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addCustomFilter("resource.png"); ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ TestVerificationUtils.expectResourceBlocked(mHelper, "subresource"); + } + -+ public int numAllowedByType(AdblockContentType type) { -+ int result = 0; -+ for (AdblockCounters.ResourceInfo info : allowedInfos) { -+ if (info.getAdblockContentType() == type) ++result; -+ } -+ return result; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void resourceAllowedByFilter() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addCustomFilter("resource.png"); ++ // Allowing filter for the mocked test.org domain that mTestServer hosts. ++ mConfigurationA.addCustomFilter("@@test.org$document"); ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ TestVerificationUtils.expectResourceShown(mHelper, "subresource"); + } + -+ public int numAllowedPopups() { -+ return allowedPopupsInfos.size(); ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void resourceAllowedByAllowedDomain() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfigurationA.addCustomFilter("resource.png"); ++ mConfigurationA.addAllowedDomain("test.org"); ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ TestVerificationUtils.expectResourceShown(mHelper, "subresource"); + } + -+ public void setExpectedSubscriptionUrl(URL url) { -+ mTestSubscriptionUrl = url; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void resourceBlockedBySecondConfiguration() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ // ConfigurationA allows the resource. ++ // Allowing rules take precedence within a FilteringConfiguration. ++ mConfigurationA.addCustomFilter("resource.png"); ++ mConfigurationA.addAllowedDomain("test.org"); ++ // But ConfigurationB blocks the resource. ++ // Blocking takes precedence across FilteringConfigurations. ++ mConfigurationB.addCustomFilter("resource.png"); ++ mCallbackHelper.notifyCalled(); ++ }); ++ mCallbackHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ TestVerificationUtils.expectResourceBlocked(mHelper, "subresource"); + } + -+ private String getExpectedSubscriptionUrl() { -+ if (mTestSubscriptionUrl != null) return mTestSubscriptionUrl.toString(); -+ return "adblock:custom"; ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void noBlockingWithoutFilters() throws Exception { ++ loadTestUrl(); ++ TestVerificationUtils.expectResourceShown(mHelper, "subresource"); + } + -+ private enum Decision { ALLOWED, BLOCKED } ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void createAndRemoveConfiguration() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ // Check initial state. ++ FilteringConfiguration.removeConfiguration("adblock", getBrowserContext()); ++ ArrayList expectedConfigurations = ++ new ArrayList(); ++ expectedConfigurations.add(mConfigurationA); ++ expectedConfigurations.add(mConfigurationB); ++ Assert.assertEquals( ++ expectedConfigurations, ++ FilteringConfiguration.getConfigurations(getBrowserContext())); ++ // Add new configuration (twice) and check. ++ FilteringConfiguration configurationC = ++ FilteringConfiguration.createConfiguration("c", getBrowserContext()); ++ FilteringConfiguration configurationC2 = ++ FilteringConfiguration.createConfiguration("c", getBrowserContext()); ++ // Confirm this is the same reference. ++ Assert.assertEquals(configurationC, configurationC2); ++ expectedConfigurations.add(configurationC); ++ Assert.assertEquals( ++ expectedConfigurations, ++ FilteringConfiguration.getConfigurations(getBrowserContext())); ++ // Remove configuration "c" twice (2nd attempt has no effect) and check. ++ FilteringConfiguration.removeConfiguration("c", getBrowserContext()); ++ FilteringConfiguration.removeConfiguration("c", getBrowserContext()); ++ expectedConfigurations.remove(configurationC); ++ Assert.assertEquals( ++ expectedConfigurations, ++ FilteringConfiguration.getConfigurations(getBrowserContext())); ++ }); ++ } + -+ // We either countDown() our latch for every filtered resource if there are no -+ // specific expectations set (expectedAllowed == null && expectedBlocked == null), -+ // or we countDown() only when all expectations have been met so when: -+ // (expectedAllowed.isNullOrEmpty() && expectedBlocked.isNullOrEmpty()). -+ private void CheckAndCountDownLatch(final Decision decision, final String url) { -+ if (countDownLatch != null) { -+ if (expectedBlocked == null && expectedAllowed == null) { -+ countDownLatch.countDown(); -+ } else { -+ if (decision == Decision.BLOCKED) { -+ if (expectedBlocked != null) { -+ expectedBlocked.remove(url); -+ } -+ } else { -+ if (expectedAllowed != null) { -+ expectedAllowed.remove(url); ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void cannotUseRemovedConfiguration() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ FilteringConfiguration configurationC = ++ FilteringConfiguration.createConfiguration("c", getBrowserContext()); ++ configurationC.setEnabled(false); ++ Assert.assertFalse(configurationC.isEnabled()); ++ FilteringConfiguration.removeConfiguration("c", getBrowserContext()); ++ try { ++ configurationC.setEnabled(true); ++ Assert.fail(); ++ } catch (final IllegalStateException e) { ++ // Expected + } -+ } -+ boolean expectationsMet = (expectedAllowed == null || expectedAllowed.isEmpty()) -+ && (expectedBlocked == null || expectedBlocked.isEmpty()); -+ if (expectationsMet) { -+ countDownLatch.countDown(); -+ } -+ } -+ } ++ // Recreate. ++ configurationC = ++ FilteringConfiguration.createConfiguration("c", getBrowserContext()); ++ Assert.assertTrue(configurationC.isEnabled()); ++ configurationC.setEnabled(false); ++ Assert.assertFalse(configurationC.isEnabled()); ++ }); + } -+ -+ private URL mTestSubscriptionUrl; -+ public List blockedInfos = new CopyOnWriteArrayList<>(); -+ public List allowedInfos = new CopyOnWriteArrayList<>(); -+ public List allowedPageInfos = new CopyOnWriteArrayList<>(); -+ public List blockedPopupsInfos = new CopyOnWriteArrayList<>(); -+ public List allowedPopupsInfos = new CopyOnWriteArrayList<>(); -+ CountDownLatch countDownLatch; -+ Set expectedAllowed; -+ Set expectedBlocked; -+}; ++} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCircumventionTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCircumventionTestBase.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCircumventionTestBase.java -@@ -0,0 +1,64 @@ +@@ -0,0 +1,65 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2647,8 +2666,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testVerifyCircumventionInlineStyleNotImportant() throws Exception { -+ mHelper.loadUrl(TestPagesHelperBase.CIRCUMVENTION_TESTPAGES_TESTCASES_ROOT -+ + "inline-style-important"); ++ mHelper.loadUrl( ++ TestPagesHelperBase.CIRCUMVENTION_TESTPAGES_TESTCASES_ROOT ++ + "inline-style-important"); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "div"); + } + @@ -2656,19 +2676,19 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testVerifyCircumventionAnonymousFrameDocumentWrite() throws Exception { -+ mHelper.loadUrl(TestPagesHelperBase.CIRCUMVENTION_TESTPAGES_TESTCASES_ROOT -+ + "anoniframe-documentwrite"); ++ mHelper.loadUrl( ++ TestPagesHelperBase.CIRCUMVENTION_TESTPAGES_TESTCASES_ROOT ++ + "anoniframe-documentwrite"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "span[data-expectedresult='fail']"); -+ String numHidden = JavaScriptUtils.executeJavaScriptAndWaitForResult( -+ mHelper.getWebContents(), -+ "var hiddenCount = 0;" -+ + "var elements = document.getElementById(\"write\").contentWindow" -+ + ".document.getElementsByTagName(\"span\");" -+ + "for (let i = 0; i < elements.length; ++i) {" -+ + " if (window.getComputedStyle(elements[i]).display == \"none\") " -+ + "++hiddenCount;" -+ + "}" -+ + "hiddenCount;"); ++ String numHidden = ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), ++ "var hiddenCount = 0;var elements =" ++ + " document.getElementById(\"write\").contentWindow" ++ + ".document.getElementsByTagName(\"span\");for (let i = 0; i <" ++ + " elements.length; ++i) { if" ++ + " (window.getComputedStyle(elements[i]).display == \"none\") " ++ + "++hiddenCount;}hiddenCount;"); + Assert.assertEquals("1", numHidden); + } +} @@ -2676,7 +2696,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesCspTestBase.java -@@ -0,0 +1,108 @@ +@@ -0,0 +1,86 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2707,13 +2727,13 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testCspAllSites() throws Exception { -+ mHelper.addCustomFilter("*$csp=script-src 'none'"); + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "csp_all"); + TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "img[id='all-sites-fail-1']"); + } @@ -2722,10 +2742,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testCspSpecificSite() throws Exception { -+ mHelper.addCustomFilter(String.format("||%s^$csp=script-src https://%s/lib/utils.js", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "csp_specific"); -+ TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "img[id='specific-site-fail-1']", ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, ++ 0, ++ "img[id='specific-site-fail-1']", + TestVerificationUtils.IncludeSubframes.NO); + } + @@ -2733,12 +2754,13 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testCspSpecificSiteFrameSrc() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("||%s^$csp=frame-src 'self'", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "csp_specific"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div[id='sub-frame-error']", TestVerificationUtils.IncludeSubframes.NO); -+ TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "div[id='sub-frame-error-details']", ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, ++ 0, ++ "div[id='sub-frame-error-details']", + TestVerificationUtils.IncludeSubframes.NO); + } + @@ -2746,17 +2768,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testCspException() throws Exception { -+ // Blocking filter: -+ mHelper.addCustomFilter(String.format("||%s^$csp=script-src https://%s/lib/utils.js", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ // Resource loaded by JS was blocked -+ mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "csp"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 0, "div[id='unblock-javascript'] > img"); -+ -+ // Allowing filter: -+ mHelper.addCustomFilter(String.format("@@||%s^$csp", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ // Resource loaded by JS was allowed + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "csp"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 1, "div[id='unblock-javascript'] > img"); @@ -2766,19 +2777,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testCspGenericBlockException() throws Exception { -+ // Blocking filter: -+ mHelper.addCustomFilter(String.format("||%s^$csp=script-src https://%s/lib/utils.js", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ // Resource loaded by JS was blocked -+ mHelper.loadUrl( -+ TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "csp_genericblock"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 0, "div[id='genericblock-javascript'] > img"); -+ -+ // Allowing filter: -+ mHelper.addCustomFilter( -+ String.format("@@||%s^$genericblock", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ // Resource loaded by JS was allowed + mHelper.loadUrl( + TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "csp_genericblock"); + TestVerificationUtils.verifyDisplayedCount( @@ -2789,7 +2787,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideEmuInvTestBase.java -@@ -0,0 +1,135 @@ +@@ -0,0 +1,116 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2818,20 +2816,18 @@ new file mode 100644 +public abstract class TestPagesElemhideEmuInvTestBase { + public static final String ELEMENT_HIDING_EMULATION_TESTPAGES_URL = + TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT -+ + "element-hiding-emulation-inversion"; ++ + "element-hiding-emulation-inversion"; + private TestPagesHelperBase mHelper; + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotAbpProperties() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#.ehei-properties:not(:-abp-properties(width: 238px))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='basic-not-abp-properties-usage-fail']"); @@ -2841,9 +2837,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotAbpHas() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#.ehei-has:not(:-abp-has(span.ehei-has-not-hide))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='basic-not-abp-has-usage-fail']"); @@ -2853,9 +2846,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotAbpContains() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#.ehei-contains:not(span:-abp-contains(example-content))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "span[id='basic-not-abp-contains-usage-fail']"); @@ -2865,9 +2855,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotChained() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#.ehei-chained-parent:not(:-abp-has(> div:-abp-properties(width: 198px)))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='chained-extended-selectors-with-not-selector-fail-1']"); @@ -2877,8 +2864,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotCaseIsensitive() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#?#.ehei-case:not(:-abp-properties(WiDtH: 209px))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='case-insensitive-extended-selectors-with-not-selector-fail']"); @@ -2888,10 +2873,10 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotWildcard() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#?#.ehei-wildcard:not(:-abp-properties(cursor:*))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, + "div[id='wildcard-in-extended-selector-combined-with-not-selector-fail']"); + } + @@ -2899,9 +2884,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotRegexAbpProperties() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#.ehei-regex:not(:-abp-properties(/width: 11[1-5]px;/))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='regular-expression-in-not-abp-properties-fail-1']"); @@ -2914,9 +2896,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuNotRegexAbpContains() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#.ehei-contains-regex:not(span:-abp-contains(/example-contentregex\\d/))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "span[id='regular-expression-in-not-abp-contains-fail-1']"); @@ -2929,7 +2908,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideEmuTestBase.java -@@ -0,0 +1,183 @@ +@@ -0,0 +1,161 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2964,14 +2943,13 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicAbpProperties() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#div:-abp-properties(width: 213px)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='basic-abp-properties-usage-fail-1']"); @@ -2981,8 +2959,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicAbpHas() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#div:-abp-has(>div>span.ehe-abp-has)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='basic-abp-has-usage-fail-1']"); + } @@ -2991,19 +2967,14 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicHas() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#div:has(>div>span.ehe-has)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); -+ // "Basic :has() usage" are duplicated on testpage. -+ TestVerificationUtils.verifyHiddenCount(mHelper, 2, "div[id='basic-has-usage-fail-1']"); ++ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='basic-has-usage-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicAbpContains() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#?#span:-abp-contains(ehe-contains-target)", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "span[id='basic-abp-contains-usage-fail-1']"); @@ -3013,9 +2984,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicXpath() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#span:xpath(//*[@id=\"basic-xpath-usage-fail\"])", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "span[id='basic-xpath-usage-fail']"); + } @@ -3024,8 +2992,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersBasicHasText() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#span:has-text(ehe-has-text)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "span[id='basic-has-text-usage-fail-1']"); @@ -3035,9 +3001,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersChainedExtendedSelectors() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#div:-abp-has(> div:-abp-properties(width: 214px))", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='chained-extended-selectors-fail-1']"); @@ -3047,8 +3010,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersCaseInsensitiveExtendedSelectors() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#div:-abp-properties(WiDtH: 215px)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='case-insensitive-extended-selectors-fail-1']"); @@ -3058,8 +3019,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersWildcardInExtendedSelector() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#?#div:-abp-properties(cursor:*)", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='wildcard-in-extended-selector-fail-1']"); @@ -3069,8 +3028,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersRegularExpressionInAbpProperties() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#?#div:-abp-properties(/width: 12[1-5]px;/)", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='regular-expression-in-abp-properties-fail-1']"); @@ -3085,9 +3042,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersRegularExpressionInAbpContains() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#?#div > div:-abp-contains(/ehe-containsregex\\d/)", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='regular-expression-in-abp-contains-fail-1']"); @@ -3100,24 +3054,27 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideEmuFiltersException() throws Exception { -+ // Add a blocking filter, verify element hidden. -+ mHelper.addCustomFilter( -+ String.format("%s##.testcase-ehe", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrl(ELEMENT_HIDING_EMULATION_EXCEPTIONS_TESTPAGES_URL); -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='exception-usage-pass-1']"); -+ -+ // Add exception filter, verify element no longer hidden. -+ mHelper.addCustomFilter( -+ String.format("%s#@#.testcase-ehe", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EMULATION_EXCEPTIONS_TESTPAGES_URL); + TestVerificationUtils.verifyDisplayedCount(mHelper, 1, "div[id='exception-usage-pass-1']"); + } ++ ++ // DPD-2543 ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testElemHideEmuFiltersBasicAbpPropertiesWithUsupportedFilter() throws Exception { ++ // Add broken ElemHideEmu filter ++ mHelper.addCustomFilter("abptestpages.org#?#div:-unsupported-selector(width: 213px)"); ++ mHelper.loadUrl(ELEMENT_HIDING_EMULATION_TESTPAGES_URL); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div[id='basic-abp-properties-usage-fail-1']"); ++ } +} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideTestBase.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesElemhideTestBase.java -@@ -0,0 +1,204 @@ +@@ -0,0 +1,159 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -3152,13 +3109,13 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersIdSelector() throws Exception { -+ mHelper.addCustomFilter(String.format("%s###eh-id", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='eh-id']"); + } @@ -3167,8 +3124,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersIdSelectorDoubleCurlyBraces() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s##div[id='{{eh-id}}']", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='{{eh-id}}']"); + } @@ -3177,8 +3132,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersClassSelector() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s##.eh-class", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[class='eh-class']"); + } @@ -3187,8 +3140,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersDescendantSelector() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##.testcase-area > .eh-descendant", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[class='eh-descendant']"); + } @@ -3197,8 +3148,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersSiblingSelector() throws Exception { -+ mHelper.addCustomFilter(String.format("%s##.testcase-examplecontent + .eh-sibling", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[class='eh-sibling']"); + } @@ -3207,8 +3156,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersAttributeSelector1() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##div[height=\"100\"][width=\"100\"]", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='attribute-selector-1-fail-1']"); @@ -3218,8 +3165,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersAttributeSelector2() throws Exception { -+ mHelper.addCustomFilter(String.format("%s##div[href=\"http://testcase-attribute.com/\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='attribute-selector-2-fail-1']"); @@ -3229,8 +3174,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersAttributeSelector3() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##div[style=\"width: 200px;\"]", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='attribute-selector-3-fail-1']"); @@ -3240,8 +3183,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersStartsWithSelector1() throws Exception { -+ mHelper.addCustomFilter(String.format("%s##div[href^=\"http://testcase-startswith.com/\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='starts-with-selector-1-fail-1']"); @@ -3251,8 +3192,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersStartsWithSelector2() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##div[style^=\"width: 201px;\"]", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='starts-with-selector-2-fail-1']"); @@ -3262,8 +3201,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersEndsWithSelector1() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##div[style$=\"width: 202px;\"]", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div[id='ends-with-selector-1-fail-1']"); @@ -3273,8 +3210,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersContains() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s##div[style*=\"width: 203px;\"]", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='contains-fail-1']"); + } @@ -3284,19 +3219,7 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersBasicException() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s##.ex-elemhide", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/elemhide/basic/*", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EXCEPTIONS_TESTPAGES_URL); -+ // No exceptions added yet, both objects should be blocked. -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[id='basic-usage-fail-1']"); -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='basic-usage-pass-1']"); -+ // Add exception filter and reload. -+ mHelper.addCustomFilter(String.format( -+ "@@%s/en/exceptions/elemhide$elemhide", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrl(ELEMENT_HIDING_EXCEPTIONS_TESTPAGES_URL); -+ // Image should remain blocked, div should be unblocked. + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[id='basic-usage-fail-1']"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 1, "div[id='basic-usage-area'] > div[id='basic-usage-pass-1']"); @@ -3306,17 +3229,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testElemHideFiltersIframeException() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s##.targ-elemhide", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/elemhide/iframe/*.png", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrl(ELEMENT_HIDING_EXCEPTIONS_TESTPAGES_URL); -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[id='iframe-fail-1']"); -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='iframe-pass-1']"); -+ -+ // Add exception filter and reload. -+ mHelper.addCustomFilter(String.format( -+ "@@%s/en/exceptions/elemhide$elemhide", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(ELEMENT_HIDING_EXCEPTIONS_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[id='iframe-fail-1']"); + TestVerificationUtils.verifyDisplayedCount(mHelper, 1, "div[id='iframe-pass-1']"); @@ -3326,7 +3238,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesExceptionTestBase.java -@@ -0,0 +1,174 @@ +@@ -0,0 +1,190 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -3352,7 +3264,6 @@ new file mode 100644 +import org.junit.Test; + +import org.chromium.base.test.util.Feature; -+import org.chromium.components.adblock.AdblockContentType; +import org.chromium.content_public.browser.test.util.JavaScriptUtils; + +import java.util.HashSet; @@ -3372,18 +3283,22 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testVerifyImageExceptions() throws Exception { -+ final String subdomainImage = String.format( -+ "https://allowed.subdomain.%s/testfiles/image_exception/subdomain.png", -+ TestPagesHelperBase.TESTPAGES_DOMAIN); ++ final String subdomainImage = ++ String.format( ++ "https://allowed.subdomain.%s/testfiles/image_exception/subdomain.png", ++ TestPagesHelperBase.TESTPAGES_DOMAIN); + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "image"); + Assert.assertEquals(2, mHelper.numAllowed()); -+ Assert.assertEquals(2, mHelper.numAllowedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertEquals(2, mHelper.numAllowedByType(ContentType.CONTENT_TYPE_IMAGE)); + Assert.assertTrue(mHelper.isAllowed(subdomainImage)); -+ Assert.assertTrue(mHelper.isAllowed( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "image_exception/image.png")); ++ Assert.assertTrue( ++ mHelper.isAllowed( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "image_exception/image.png")); + TestVerificationUtils.verifyDisplayedCount(mHelper, 2, "img"); -+ String numImages = JavaScriptUtils.executeJavaScriptAndWaitForResult( -+ mHelper.getWebContents(), "document.getElementsByTagName(\"img\").length;"); ++ String numImages = ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), "document.getElementsByTagName(\"img\").length;"); + Assert.assertEquals("2", numImages); + } + @@ -3393,11 +3308,14 @@ new file mode 100644 + public void testVerifySubdocumentException() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "subdocument"); + Assert.assertEquals(1, mHelper.numAllowed()); -+ Assert.assertTrue(mHelper.isAllowed(TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT -+ + "subdocument_exception/subdocument.html")); ++ Assert.assertTrue( ++ mHelper.isAllowed( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "subdocument_exception/subdocument.html")); + TestVerificationUtils.verifyGreenBackground(mHelper, "exception-target"); -+ String numFrames = JavaScriptUtils.executeJavaScriptAndWaitForResult( -+ mHelper.getWebContents(), "window.frames.length;"); ++ String numFrames = ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), "window.frames.length;"); + Assert.assertEquals("1", numFrames); + TestVerificationUtils.verifyDisplayedCount(mHelper, 1, "iframe"); + } @@ -3408,12 +3326,16 @@ new file mode 100644 + public void testVerifyScriptException() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "script"); + Assert.assertEquals(1, mHelper.numAllowed()); -+ Assert.assertTrue(mHelper.isAllowed( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "script_exception/script.js")); ++ Assert.assertTrue( ++ mHelper.isAllowed( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "script_exception/script.js")); + TestVerificationUtils.verifyGreenBackground(mHelper, "script-target"); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "script_exception/image.png")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "script_exception/image.png")); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[data-expectedresult='fail']"); + } + @@ -3431,8 +3353,10 @@ new file mode 100644 + Assert.assertEquals(1, mHelper.numAllowed()); + Assert.assertTrue(mHelper.isAllowed(allowedUrl)); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "stylesheet_exception/image.png")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "stylesheet_exception/image.png")); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img"); + TestVerificationUtils.verifyGreenBackground(mHelper, "exception-target"); + } @@ -3458,9 +3382,11 @@ new file mode 100644 + public void testVerifyGenericBlockException() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "genericblock"); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "genericblock/specific.png")); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "genericblock/specific.png")); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); + TestVerificationUtils.verifyDisplayedCount(mHelper, 1, "img[data-expectedresult='pass']"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img[data-expectedresult='fail']"); + } @@ -3479,10 +3405,12 @@ new file mode 100644 + @Feature({"adblock"}) + public void testVerifyDocumentException() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "document"); -+ Assert.assertTrue(mHelper.isAllowed( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "document/image.png")); -+ Assert.assertTrue(mHelper.isPageAllowed( -+ TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "document")); ++ Assert.assertTrue( ++ mHelper.isAllowed( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "document/image.png")); ++ Assert.assertTrue( ++ mHelper.isPageAllowed( ++ TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "document")); + TestVerificationUtils.verifyDisplayedCount(mHelper, 1, "div[data-expectedresult='pass']"); + } + @@ -3491,13 +3419,13 @@ new file mode 100644 + @Feature({"adblock"}) + public void testVerifyWebSocketException() throws Exception { + final String wssUrl = -+ String.format("wss://%s/websocket", TestPagesHelperBase.TESTPAGES_DOMAIN); ++ String.format("wss://%s/exception_websocket", TestPagesHelperBase.TESTPAGES_DOMAIN); + final CountDownLatch countDownLatch = + mHelper.setOnAdMatchedExpectations(null, new HashSet<>(List.of(wssUrl))); + mHelper.loadUrl(TestPagesHelperBase.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "websocket"); + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); -+ Assert.assertEquals(1, mHelper.numAllowedByType(AdblockContentType.CONTENT_TYPE_WEBSOCKET)); ++ Assert.assertEquals(1, mHelper.numAllowedByType(ContentType.CONTENT_TYPE_WEBSOCKET)); + Assert.assertTrue(mHelper.isAllowed(wssUrl)); + } +} @@ -3505,7 +3433,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesFilterTestBase.java -@@ -0,0 +1,254 @@ +@@ -0,0 +1,277 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -3532,7 +3460,6 @@ new file mode 100644 + +import org.chromium.base.test.util.DisabledTest; +import org.chromium.base.test.util.Feature; -+import org.chromium.components.adblock.AdblockContentType; +import org.chromium.content_public.browser.test.util.DOMUtils; +import org.chromium.content_public.browser.test.util.JavaScriptUtils; + @@ -3554,23 +3481,28 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testBlockingFilters() throws Exception { -+ final CountDownLatch countDownLatch = mHelper.setOnAdMatchedExpectations( -+ new HashSet<>(Arrays.asList( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "blocking/full-path.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT -+ + "blocking/partial-path/partial-path.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT -+ + "blocking/wildcard/1/wildcard.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT -+ + "blocking/wildcard/2/wildcard.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "blocking/dynamic.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "blocking/subdomain.png")), -+ null); ++ final CountDownLatch countDownLatch = ++ mHelper.setOnAdMatchedExpectations( ++ new HashSet<>( ++ Arrays.asList( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/full-path.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/partial-path/partial-path.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/wildcard/1/wildcard.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/wildcard/2/wildcard.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/dynamic.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "blocking/subdomain.png")), ++ null); + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "blocking"); + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); + Assert.assertEquals(6, mHelper.numBlocked()); -+ Assert.assertEquals(6, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertEquals(6, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); + TestVerificationUtils.verifyHiddenCount(mHelper, 6, "img[data-expectedresult='fail']"); + } + @@ -3580,12 +3512,14 @@ new file mode 100644 + public void testVerifyScriptFilters() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "script"); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_SCRIPT)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "script/script.js")); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_SCRIPT)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "script/script.js")); + + String childCount = -+ JavaScriptUtils.executeJavaScriptAndWaitForResult(mHelper.getWebContents(), ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), + "document.getElementById(\"script-target\").childElementCount"); + Assert.assertEquals("1", childCount); + } @@ -3594,20 +3528,26 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testVerifyImageFilters() throws Exception { -+ final CountDownLatch countDownLatch = mHelper.setOnAdMatchedExpectations( -+ new HashSet<>(Arrays.asList( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "image/static/static.png", -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT -+ + "image/dynamic/dynamic.png")), -+ null); ++ final CountDownLatch countDownLatch = ++ mHelper.setOnAdMatchedExpectations( ++ new HashSet<>( ++ Arrays.asList( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "image/static/static.png", ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "image/dynamic/dynamic.png")), ++ null); + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "image"); + countDownLatch.await(10, TimeUnit.SECONDS); + Assert.assertEquals(2, mHelper.numBlocked()); -+ Assert.assertEquals(2, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "image/static/static.png")); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "image/dynamic/dynamic.png")); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "image/static/static.png")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "image/dynamic/dynamic.png")); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "img[data-expectedresult='fail']"); + } + @@ -3623,8 +3563,7 @@ new file mode 100644 + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals( -+ 1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_STYLESHEET)); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_STYLESHEET)); + Assert.assertTrue(mHelper.isBlocked(blockedUrl)); + String value = DOMUtils.getNodeContents(mHelper.getWebContents(), "stylesheet-target"); + Assert.assertEquals("Passed. Stylesheet was blocked.", value); @@ -3642,8 +3581,7 @@ new file mode 100644 + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals( -+ 1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_XMLHTTPREQUEST)); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_XMLHTTPREQUEST)); + Assert.assertTrue(mHelper.isBlocked(blockedUrl)); + } + @@ -3653,12 +3591,16 @@ new file mode 100644 + public void testVerifySubdocumentFilters() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "subdocument"); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals( -+ 1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_SUBDOCUMENT)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "subdocument/subdocument.html")); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_SUBDOCUMENT)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "subdocument/subdocument.html")); + // Do not search for iframe within the site's iframes. -+ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "iframe[data-expectedresult='fail']", ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "iframe[data-expectedresult='fail']", + TestVerificationUtils.IncludeSubframes.NO); + } + @@ -3669,14 +3611,17 @@ new file mode 100644 + public void testVerifyRewrite() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "rewrite"); + Assert.assertEquals(3, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_SCRIPT)); -+ Assert.assertEquals(2, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_MEDIA)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/audio.mp3")); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/video.mp4")); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/script.js")); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_SCRIPT)); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_MEDIA)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/audio.mp3")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/video.mp4")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "rewrite/script.js")); + } + + @Test @@ -3685,7 +3630,7 @@ new file mode 100644 + public void testVerifyMatchCaseFilter() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "match-case"); + Assert.assertEquals(2, mHelper.numBlocked()); -+ Assert.assertEquals(2, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "img[data-expectedresult='fail']"); + } + @@ -3695,7 +3640,7 @@ new file mode 100644 + public void testVerifyThirdPartyFilter() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "third-party"); + Assert.assertEquals(2, mHelper.numBlocked()); -+ Assert.assertEquals(2, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "img[data-expectedresult='fail']"); + TestVerificationUtils.verifyDisplayedCount(mHelper, 2, "img[data-expectedresult='pass']"); + } @@ -3706,9 +3651,10 @@ new file mode 100644 + public void testVerifyOtherFilter() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "other"); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_OTHER)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "other/image.png")); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_OTHER)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "other/image.png")); + } + + @Test @@ -3717,11 +3663,14 @@ new file mode 100644 + public void testVerifyDomainFilter() throws Exception { + mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "domain"); + Assert.assertEquals(2, mHelper.numBlocked()); -+ Assert.assertEquals(2, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "domain/static/target/image.png")); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "domain/dynamic/image.png")); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT ++ + "domain/static/target/image.png")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "domain/dynamic/image.png")); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "img[data-expectedresult='fail']"); + } + @@ -3735,7 +3684,8 @@ new file mode 100644 + final CountDownLatch countDownLatch = new CountDownLatch(1); + mHelper.setOnAdMatchedLatch(countDownLatch); + // Trigger ping action -+ JavaScriptUtils.executeJavaScriptAndWaitForResult(mHelper.getWebContents(), ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), + "document.getElementById(\"script-ping-trigger\").click()"); + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); @@ -3752,7 +3702,8 @@ new file mode 100644 + final CountDownLatch countDownLatch = new CountDownLatch(1); + mHelper.setOnAdMatchedLatch(countDownLatch); + // Trigger ping action -+ JavaScriptUtils.executeJavaScriptAndWaitForResult(mHelper.getWebContents(), ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ mHelper.getWebContents(), + "document.getElementsByClassName(\"testcase-trigger\")[0].click()"); + // Wait with 10 seconds max timeout + countDownLatch.await(10, TimeUnit.SECONDS); @@ -3764,7 +3715,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesHeaderFilterTestBase.java -@@ -0,0 +1,125 @@ +@@ -0,0 +1,102 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -3790,7 +3741,6 @@ new file mode 100644 +import org.junit.Test; + +import org.chromium.base.test.util.Feature; -+import org.chromium.components.adblock.AdblockContentType; + +public abstract class TestPagesHeaderFilterTestBase { + public static final String HEADER_TESTPAGES_URL = @@ -3801,20 +3751,20 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testHeaderFilterScript() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("||%s/testfiles/header/$header=content-type=application/javascript", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(HEADER_TESTPAGES_URL); -+ Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_SCRIPT)); -+ Assert.assertTrue(mHelper.isBlocked(String.format( -+ "https://%s/testfiles/header/script.js", TestPagesHelperBase.TESTPAGES_DOMAIN))); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_SCRIPT)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ String.format( ++ "https://%s/testfiles/header/script.js", ++ TestPagesHelperBase.TESTPAGES_DOMAIN))); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#functionproperty-target > div[data-expectedresult='fail']"); + } @@ -3822,29 +3772,20 @@ new file mode 100644 + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testHeaderFilterImage() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("||%s/testfiles/header/image.png$header=content-type=image/png", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ public void testHeaderFilterImagePlusImageAndComma() throws Exception { + mHelper.loadUrl(HEADER_TESTPAGES_URL); -+ Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); -+ Assert.assertTrue(mHelper.isBlocked(String.format( -+ "https://%s/testfiles/header/image.png", TestPagesHelperBase.TESTPAGES_DOMAIN))); ++ Assert.assertEquals(2, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ String.format( ++ "https://%s/testfiles/header/image.png", ++ TestPagesHelperBase.TESTPAGES_DOMAIN))); + TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "img[id='image-fail-1']"); -+ } -+ -+ @Test -+ @LargeTest -+ @Feature({"adblock"}) -+ public void testHeaderFilterImageAndComma() throws Exception { -+ mHelper.addCustomFilter(String.format("||%s/testfiles/header/image2.png$header=date=\\x2c", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrl(HEADER_TESTPAGES_URL); -+ Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); -+ Assert.assertTrue(mHelper.isBlocked(String.format( -+ "https://%s/testfiles/header/image2.png", TestPagesHelperBase.TESTPAGES_DOMAIN))); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ String.format( ++ "https://%s/testfiles/header/image2.png", ++ TestPagesHelperBase.TESTPAGES_DOMAIN))); + TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "img[id='comma-fail-1']"); + } + @@ -3852,40 +3793,27 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHeaderFilterStylesheet() throws Exception { -+ mHelper.addCustomFilter(String.format("||%s/testfiles/header/$header=content-type=text/css", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(HEADER_TESTPAGES_URL); -+ Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals( -+ 1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_STYLESHEET)); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_STYLESHEET)); + Assert.assertTrue( -+ mHelper.isBlocked(String.format("https://%s/testfiles/header/stylesheet.css", -+ TestPagesHelperBase.TESTPAGES_DOMAIN))); ++ mHelper.isBlocked( ++ String.format( ++ "https://%s/testfiles/header/stylesheet.css", ++ TestPagesHelperBase.TESTPAGES_DOMAIN))); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testHeaderFilterException() throws Exception { -+ // Add blocking filter, expect blocked image -+ mHelper.addCustomFilter( -+ String.format("||%s/testfiles/header_exception/$header=content-type=image/png", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrl(HEADER_EXCEPTIONS_TESTPAGES_URL); -+ Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 0, "img[id='image-header-exception-pass-1']"); -+ -+ // Add exception filter, expect image allowed -+ mHelper.addCustomFilter(String.format( -+ "@@%s/testfiles/header_exception/$header", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(HEADER_EXCEPTIONS_TESTPAGES_URL); + Assert.assertEquals(1, mHelper.numAllowed()); -+ Assert.assertEquals(1, mHelper.numAllowedByType(AdblockContentType.CONTENT_TYPE_IMAGE)); ++ Assert.assertEquals(1, mHelper.numAllowedByType(ContentType.CONTENT_TYPE_IMAGE)); + Assert.assertTrue( -+ mHelper.isAllowed(String.format("https://%s/testfiles/header_exception/image.png", -+ TestPagesHelperBase.TESTPAGES_DOMAIN))); ++ mHelper.isAllowed( ++ String.format( ++ "https://%s/testfiles/header_exception/image.png", ++ TestPagesHelperBase.TESTPAGES_DOMAIN))); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 1, "img[id='image-header-exception-pass-1']"); + } @@ -3894,7 +3822,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesHelperBase.java -@@ -0,0 +1,180 @@ +@@ -0,0 +1,230 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -3917,11 +3845,10 @@ new file mode 100644 +import org.junit.Assert; + +import org.chromium.base.Log; ++import org.chromium.base.ThreadUtils; +import org.chromium.base.test.util.CallbackHelper; -+import org.chromium.components.adblock.AdblockContentType; -+import org.chromium.components.adblock.AdblockController; ++import org.chromium.content_public.browser.BrowserContextHandle; +import org.chromium.content_public.browser.WebContents; -+import org.chromium.content_public.browser.test.util.TestThreadUtils; + +import java.net.MalformedURLException; +import java.net.URL; @@ -3930,7 +3857,8 @@ new file mode 100644 +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + -+public abstract class TestPagesHelperBase { ++public abstract class TestPagesHelperBase ++ implements FilteringConfiguration.ConfigurationChangeObserver { + public static final String TESTPAGES_DOMAIN = "abptestpages.org"; + public static final String TESTPAGES_BASE_URL = "https://" + TESTPAGES_DOMAIN; + public static final String TESTPAGES_TESTCASES_ROOT = TESTPAGES_BASE_URL + "/en/"; @@ -3941,27 +3869,31 @@ new file mode 100644 + public static final String CIRCUMVENTION_TESTPAGES_TESTCASES_ROOT = + TESTPAGES_TESTCASES_ROOT + "circumvention/"; + public static final String SITEKEY_TESTPAGES_TESTCASES_ROOT = -+ EXCEPTION_TESTPAGES_TESTCASES_ROOT + "sitekey"; ++ EXCEPTION_TESTPAGES_TESTCASES_ROOT + "sitekey_mv2"; + public static final String SNIPPETS_TESTPAGES_TESTCASES_ROOT = + TESTPAGES_TESTCASES_ROOT + "snippets/"; + public static final String TESTPAGES_RESOURCES_ROOT = TESTPAGES_BASE_URL + "/testfiles/"; + public static final String TESTPAGES_SUBSCRIPTION = -+ TESTPAGES_TESTCASES_ROOT + "/abp-testcase-subscription.txt"; ++ TESTPAGES_TESTCASES_ROOT + "abp-testcase-subscription.txt"; + public static final int TEST_TIMEOUT_SEC = 30; + ++ private FilteringConfiguration mAdblockFilteringConfiguration; ++ private ResourceClassificationNotifier mResourceClassificationNotifier; ++ + private URL mTestSubscriptionUrl; + private final CallbackHelper mHelper = new CallbackHelper(); -+ private final TestAdBlockedObserver mObserver = new TestAdBlockedObserver(); ++ private final TestResourceFilteringObserver mObserver = new TestResourceFilteringObserver(); + private final TestSubscriptionUpdatedObserver mSubscriptionUpdateObserver = + new TestSubscriptionUpdatedObserver(); + + private class TestSubscriptionUpdatedObserver -+ implements AdblockController.SubscriptionUpdateObserver { ++ implements FilteringConfiguration.SubscriptionUpdateObserver { + @Override + public void onSubscriptionDownloaded(final URL url) { + if (mTestSubscriptionUrl == null) return; + if (url.toString().contains(mTestSubscriptionUrl.toString())) { -+ Log.d("TestSubscriptionUpdatedObserver", ++ Log.d( ++ "TestSubscriptionUpdatedObserver", + "Notify subscription updated: " + url.toString()); + mHelper.notifyCalled(); + } @@ -3969,23 +3901,49 @@ new file mode 100644 + } + + public void setUp() { -+ TestThreadUtils.runOnUiThreadBlocking(() -> { -+ AdblockController.getInstance().addOnAdBlockedObserver(mObserver); -+ AdblockController.getInstance().addSubscriptionUpdateObserver( -+ mSubscriptionUpdateObserver); -+ }); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mResourceClassificationNotifier = ++ ResourceClassificationNotifier.getInstance(getBrowserContext()); ++ mResourceClassificationNotifier.addResourceFilteringObserver(mObserver); ++ // Remove "adblock" configuration to simply drop all default filter lists ++ FilteringConfiguration.removeConfiguration("adblock", getBrowserContext()); ++ mAdblockFilteringConfiguration = ++ FilteringConfiguration.createConfiguration( ++ "adblock", getBrowserContext()); ++ Assert.assertEquals(0, mAdblockFilteringConfiguration.getFilterLists().size()); ++ mAdblockFilteringConfiguration.addObserver(this); ++ mAdblockFilteringConfiguration.addSubscriptionUpdateObserver( ++ mSubscriptionUpdateObserver); ++ }); ++ } ++ ++ @Override ++ public void onEnabledStateChanged() {} ++ ++ @Override ++ public void onFilterListsChanged() {} ++ ++ @Override ++ public void onAllowedDomainsChanged() {} ++ ++ @Override ++ public void onCustomFiltersChanged() { ++ mHelper.notifyCalled(); + } + + public void addFilterList(final String filterListUrl) { ++ mTestSubscriptionUrl = null; + try { + mTestSubscriptionUrl = new URL(filterListUrl); + mObserver.setExpectedSubscriptionUrl(mTestSubscriptionUrl); + } catch (MalformedURLException ignored) { + } + Assert.assertNotNull("Test subscription url", mTestSubscriptionUrl); -+ TestThreadUtils.runOnUiThreadBlocking(() -> { -+ AdblockController.getInstance().installSubscription(mTestSubscriptionUrl); -+ }); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mAdblockFilteringConfiguration.addFilterList(mTestSubscriptionUrl); ++ }); + try { + mHelper.waitForCallback(0, 1, TEST_TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (TimeoutException e) { @@ -3995,16 +3953,30 @@ new file mode 100644 + } + + public void addCustomFilter(final String filter) { -+ TestThreadUtils.runOnUiThreadBlocking( -+ () -> { AdblockController.getInstance().addCustomFilter(filter); }); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mAdblockFilteringConfiguration.addCustomFilter(filter); ++ }); ++ try { ++ mHelper.waitForCallback(0, 1, TEST_TIMEOUT_SEC, TimeUnit.SECONDS); ++ } catch (TimeoutException e) { ++ Assert.assertEquals( ++ "Test custom filter was properly added", "Failed to add test custom filter"); ++ } + } + + public void tearDown() { -+ TestThreadUtils.runOnUiThreadBlocking(() -> { -+ AdblockController.getInstance().removeSubscriptionUpdateObserver( -+ mSubscriptionUpdateObserver); -+ AdblockController.getInstance().removeOnAdBlockedObserver(mObserver); -+ }); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ if (mAdblockFilteringConfiguration != null) { ++ mAdblockFilteringConfiguration.removeSubscriptionUpdateObserver( ++ mSubscriptionUpdateObserver); ++ mAdblockFilteringConfiguration.removeObserver(this); ++ } ++ if (mResourceClassificationNotifier != null) { ++ mResourceClassificationNotifier.removeResourceFilteringObserver(mObserver); ++ } ++ }); + } + + // Note: Use either setOnAdMatchedLatch XOR setOnAdMatchedExpectations @@ -4033,7 +4005,7 @@ new file mode 100644 + return mObserver.isPopupBlocked(url); + } + -+ public int numBlockedByType(final AdblockContentType type) { ++ public int numBlockedByType(final ContentType type) { + return mObserver.numBlockedByType(type); + } + @@ -4041,7 +4013,7 @@ new file mode 100644 + return mObserver.numBlockedPopups(); + } + -+ public int numAllowedByType(final AdblockContentType type) { ++ public int numAllowedByType(final ContentType type) { + return mObserver.numAllowedByType(type); + } + @@ -4061,9 +4033,15 @@ new file mode 100644 + return mObserver.isPopupAllowed(url); + } + ++ public void loadUrlWaitForContent(final String url) throws Exception { ++ loadUrl(url); ++ TestVerificationUtils.verifyCondition( ++ this, "document.getElementsByClassName('testcase-waiting-content').length == 0"); ++ } ++ + public abstract void loadUrl(final String url) throws Exception; + -+ public abstract void loadUrlWaitForContent(final String url) throws Exception; ++ public abstract BrowserContextHandle getBrowserContext(); + + public abstract WebContents getWebContents(); + @@ -4075,11 +4053,209 @@ new file mode 100644 + return mObserver.allowedInfos.size(); + } +} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesInlineCssTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesInlineCssTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesInlineCssTestBase.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.Test; ++ ++import org.chromium.base.test.util.Feature; ++ ++import java.util.concurrent.TimeoutException; ++ ++public abstract class TestPagesInlineCssTestBase { ++ public static final String INLINE_CSS_TESTPAGES_URL = ++ TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "inline-css"; ++ private TestPagesHelperBase mHelper; ++ ++ protected void setUp(TestPagesHelperBase helper) { ++ mHelper = helper; ++ } ++ ++ private void verifyIsRed(final String elemId) throws TimeoutException { ++ TestVerificationUtils.expectResourceStyleProperty( ++ mHelper, elemId, "backgroundColor", "rgb(199, 13, 44)"); ++ } ++ ++ private void verifyIsGreen(final String elemId) throws TimeoutException { ++ TestVerificationUtils.expectResourceStyleProperty( ++ mHelper, elemId, "backgroundColor", "rgb(13, 199, 75)"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testInlineCssWithEhSelector() throws Exception { ++ final String elemId = "inline-css-id"; ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL); ++ verifyIsRed(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL); ++ verifyIsGreen(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testInlineCssWithEheSelector() throws Exception { ++ final String elemId = "basic-abp-properties-usage-with-inline-css-fail-1"; ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL + "-extended"); ++ verifyIsRed(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL + "-extended"); ++ verifyIsGreen(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testInlineCssWithEheSelectorInversion() throws Exception { ++ final String elemId = "basic-not-abp-properties-usage-with-inline-css-fail"; ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL + "-extended-inversion"); ++ verifyIsRed(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(INLINE_CSS_TESTPAGES_URL + "-extended-inversion"); ++ verifyIsGreen(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testInlineCssOnDOMMutation() throws Exception { ++ final String elemId = "span-inline-css"; ++ mHelper.loadUrlWaitForContent(INLINE_CSS_TESTPAGES_URL + "-on-DOM-mutation"); ++ verifyIsRed(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrlWaitForContent(INLINE_CSS_TESTPAGES_URL + "-on-DOM-mutation"); ++ verifyIsGreen(elemId); ++ } ++} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRemoveTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRemoveTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRemoveTestBase.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.Test; ++ ++import org.chromium.base.test.util.Feature; ++ ++import java.util.concurrent.TimeoutException; ++ ++public abstract class TestPagesRemoveTestBase { ++ public static final String REMOVE_TESTPAGES_URL = ++ TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "remove"; ++ private TestPagesHelperBase mHelper; ++ ++ protected void setUp(TestPagesHelperBase helper) { ++ mHelper = helper; ++ } ++ ++ private void verifyResourcePresent(final String elemId) throws TimeoutException { ++ TestVerificationUtils.verifyCondition( ++ mHelper, "document.getElementById('" + elemId + "') != null"); ++ } ++ ++ private void verifyResourceRemoved(final String elemId) throws TimeoutException { ++ TestVerificationUtils.verifyCondition( ++ mHelper, "document.getElementById('" + elemId + "') == null"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testRemoveWithEhSelector() throws Exception { ++ final String elemId = "remove-id"; ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL); ++ verifyResourcePresent(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL); ++ verifyResourceRemoved(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testRemoveWithEheSelector() throws Exception { ++ final String elemId = "basic-abp-properties-usage-with-remove-fail-1"; ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL + "-extended"); ++ verifyResourcePresent(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL + "-extended"); ++ verifyResourceRemoved(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testRemoveWithEheSelectorInversion() throws Exception { ++ final String elemId = "basic-not-abp-properties-usage-with-remove-fail"; ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL + "-extended-inversion"); ++ verifyResourcePresent(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrl(REMOVE_TESTPAGES_URL + "-extended-inversion"); ++ verifyResourceRemoved(elemId); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testRemoveOnDOMMutation() throws Exception { ++ final String elemId = "input-remove"; ++ mHelper.loadUrlWaitForContent(REMOVE_TESTPAGES_URL + "-on-DOM-mutation"); ++ verifyResourcePresent(elemId); ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ mHelper.loadUrlWaitForContent(REMOVE_TESTPAGES_URL + "-on-DOM-mutation"); ++ verifyResourceRemoved(elemId); ++ } ++} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRewriteTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRewriteTestBase.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesRewriteTestBase.java -@@ -0,0 +1,153 @@ +@@ -0,0 +1,175 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -4101,11 +4277,9 @@ new file mode 100644 + +import androidx.test.filters.LargeTest; + -+import org.junit.Assert; +import org.junit.Test; + +import org.chromium.base.test.util.Feature; -+import org.chromium.content_public.browser.test.util.JavaScriptUtils; + +public abstract class TestPagesRewriteTestBase { + public static final String REWRITE_TEST_URL = @@ -4120,9 +4294,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteScript() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.js$rewrite=abp-resource:blank-js,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.js$rewrite=abp-resource:blank-js,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifyDisplayedCount(mHelper, 0, "div[id='script-fail-1']"); + } @@ -4131,9 +4307,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteStylesheet() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.css$rewrite=abp-resource:blank-css,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.css$rewrite=abp-resource:blank-css,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifyGreenBackground(mHelper, "stylesheet-target"); + } @@ -4142,9 +4320,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteSubdocument() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.html$rewrite=abp-resource:blank-html,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.html$rewrite=abp-resource:blank-html,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "subdocument-target"); + } @@ -4153,9 +4333,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteText() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.txt$rewrite=abp-resource:blank-text,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.txt$rewrite=abp-resource:blank-text,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "text-status"); + } @@ -4164,9 +4346,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteGif() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/1x1.gif$rewrite=abp-resource:1x1-transparent-gif,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/1x1.gif$rewrite=abp-resource:1x1-transparent-gif,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "1x1-target"); + } @@ -4175,9 +4359,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewrite2x2Png() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/2x2.png$rewrite=abp-resource:2x2-transparent-png,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/2x2.png$rewrite=abp-resource:2x2-transparent-png,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "2x2-target"); + } @@ -4186,9 +4372,11 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewrite3x2Png() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/3x2.png$rewrite=abp-resource:3x2-transparent-png,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/3x2.png$rewrite=abp-resource:3x2-transparent-png,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "3x2-target"); + } @@ -4197,10 +4385,12 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewrite32x32Png() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/32x32.png$rewrite=abp-resource:32x32-transparent-png," -+ + "domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/32x32.png$rewrite=abp-resource:32x32-transparent-png," ++ + "domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); + TestVerificationUtils.verifySelfTestPass(mHelper, "32x32-target"); + } @@ -4209,35 +4399,119 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testRewriteAudio() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.mp3$rewrite=abp-resource:blank-mp3,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.mp3$rewrite=abp-resource:blank-mp3,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); -+ String value = JavaScriptUtils.executeJavaScriptAndWaitForResult(mHelper.getWebContents(), -+ "document.getElementById('audio-area').lastChild.getAttribute" -+ + "('data-expectedresult')"); -+ Assert.assertEquals("\"pass\"", value); ++ final String js_verification_code = ++ "document.getElementById('audio-area') && " ++ + "document.getElementById('audio-area').lastChild && " ++ + "document.getElementById('audio-area').lastChild.getAttribute" ++ + "('data-expectedresult') == \"pass\""; ++ TestVerificationUtils.verifyCondition(mHelper, js_verification_code); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testRewriteVideo() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/rewrite/*.mp4$rewrite=abp-resource:blank-mp4,domain=%s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ mHelper.addCustomFilter( ++ String.format( ++ "||%s/testfiles/rewrite/*.mp4$rewrite=abp-resource:blank-mp4,domain=%s", ++ TestPagesHelperBase.TESTPAGES_DOMAIN, ++ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrl(REWRITE_TEST_URL); -+ String value = JavaScriptUtils.executeJavaScriptAndWaitForResult(mHelper.getWebContents(), -+ "document.getElementById('video-area').lastChild.getAttribute" -+ + "('data-expectedresult')"); -+ Assert.assertEquals("\"pass\"", value); ++ final String js_verification_code = ++ "document.getElementById('video-area') && " ++ + "document.getElementById('video-area').lastChild && " ++ + "document.getElementById('video-area').lastChild.getAttribute" ++ + "('data-expectedresult') == \"pass\""; ++ TestVerificationUtils.verifyCondition(mHelper, js_verification_code); ++ } ++} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesServiceWorkersTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesServiceWorkersTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesServiceWorkersTestBase.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.Test; ++ ++import org.chromium.base.test.util.Feature; ++ ++public abstract class TestPagesServiceWorkersTestBase { ++ public static final String SERVICE_WORKERS_TEST_URL = ++ TestPagesHelperBase.TESTPAGES_TESTCASES_ROOT + "service-worker/"; ++ private TestPagesHelperBase mHelper; ++ ++ protected void setUp(TestPagesHelperBase helper) { ++ mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testBlockingWorker() throws Exception { ++ mHelper.loadUrlWaitForContent(SERVICE_WORKERS_TEST_URL + "worker-itself"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#blocked-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testAllowingWorker() throws Exception { ++ mHelper.loadUrlWaitForContent(SERVICE_WORKERS_TEST_URL + "worker-itself"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#allowed-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testBlockingFromWorker() throws Exception { ++ mHelper.loadUrlWaitForContent(SERVICE_WORKERS_TEST_URL + "worker-subresource"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#blocked-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testAllowingFromWorker() throws Exception { ++ mHelper.loadUrlWaitForContent(SERVICE_WORKERS_TEST_URL + "worker-subresource"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#allowed-target > div[data-expectedresult='pass']"); + } +} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSiteKeyTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSiteKeyTestBase.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSiteKeyTestBase.java -@@ -0,0 +1,58 @@ +@@ -0,0 +1,51 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -4269,29 +4543,22 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testVerifySitekeyException() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#@#[data-adblockkey]", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter( -+ String.format("%s##.testcase-sitekey-eh", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/sitekey/outofframe.png", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter(String.format( -+ "||%s/testfiles/sitekey/inframe.png", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.addCustomFilter( -+ "@@$document,sitekey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANGtTstne7e8MbmDHDiMFkGbcuBgXmiVesGOG3gtYeM1EkrzVhBjGUvKXYE4GLFwqty3v5MuWWbvItUWBTYoVVsCAwEAAQ"); + mHelper.loadUrl(TestPagesHelperBase.SITEKEY_TESTPAGES_TESTCASES_ROOT); + Assert.assertEquals(1, mHelper.numBlocked()); -+ Assert.assertTrue(mHelper.isBlocked( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "sitekey/outofframe.png")); ++ Assert.assertTrue( ++ mHelper.isBlocked( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "sitekey/outofframe.png")); + Assert.assertEquals(1, mHelper.numAllowed()); -+ Assert.assertTrue(mHelper.isAllowed( -+ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "sitekey/inframe.png")); ++ Assert.assertTrue( ++ mHelper.isAllowed( ++ TestPagesHelperBase.TESTPAGES_RESOURCES_ROOT + "sitekey/inframe.png")); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "img"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div"); + } @@ -4300,7 +4567,7 @@ diff --git a/components/adblock/android/javatests/src/org/chromium/components/ad new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesSnippetsTestBase.java -@@ -0,0 +1,562 @@ +@@ -0,0 +1,560 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -4324,6 +4591,7 @@ new file mode 100644 + +import org.junit.Test; + ++import org.chromium.base.test.util.DisabledTest; +import org.chromium.base.test.util.Feature; + +public abstract class TestPagesSnippetsTestBase { @@ -4331,16 +4599,16 @@ new file mode 100644 + + protected void setUp(TestPagesHelperBase helper) { + mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); + } + + @Test + @LargeTest + @Feature({"adblock"}) + public void testAbortCurrentInlineScriptBasic() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-current-inline-script console.group", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-current-inline-script"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-current-inline-script"); + // All "Abort" snippets cancel creation of the target div, so it won't be hidden - it will + // not exist in DOM. Therefore we verify it's not displayed instead of verifying it's + // hidden. @@ -4352,11 +4620,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortCurrentInlineScriptSearch() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#abort-current-inline-script console.info acis-search", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-current-inline-script"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-current-inline-script"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#search-target > div[data-expectedresult='fail']"); + } @@ -4365,11 +4631,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortCurrentInlineScriptRegex() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#abort-current-inline-script console.warn '/acis-regex[1-2]/'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-current-inline-script"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-current-inline-script"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#regex-target > div[data-expectedresult='fail']"); + } @@ -4378,8 +4642,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyReadBasic() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-property-read aoprb", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-read"); + TestVerificationUtils.verifyDisplayedCount( @@ -4390,8 +4652,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyReadSubProperty() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-property-read aopr.sp", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-read"); + TestVerificationUtils.verifyDisplayedCount( @@ -4402,8 +4662,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyReadFunctionProperty() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-property-read aoprf.fp", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-read"); + TestVerificationUtils.verifyDisplayedCount( @@ -4414,8 +4672,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyWriteBasic() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-property-write window.aopwb", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-write"); + TestVerificationUtils.verifyDisplayedCount( @@ -4426,8 +4682,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyWriteSubProperty() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-property-write window.aopwsp", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-write"); + TestVerificationUtils.verifyDisplayedCount( @@ -4438,8 +4692,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnPropertyWriteFunctionProperty() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-property-write aopwf.fp", TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "abort-on-property-write"); + TestVerificationUtils.verifyDisplayedCount( @@ -4450,10 +4702,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyReadBasic() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#abort-on-iframe-property-read aoiprb", TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-read"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-read"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#basic-target > div[data-expectedresult='fail']"); + } @@ -4462,10 +4713,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyReadSubProperty() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-iframe-property-read aoipr.sp", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-read"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-read"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#subproperty-target > div[data-expectedresult='fail']"); + } @@ -4474,10 +4724,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyReadMultipleProperties() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-iframe-property-read aoipr1 aoipr2", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-read"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-read"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#multipleproperties-target > div[data-expectedresult='fail']"); + } @@ -4486,10 +4735,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyWriteBasic() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-iframe-property-write aoipwb", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-write"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-write"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#basic-target > div[data-expectedresult='fail']"); + } @@ -4498,10 +4746,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyWriteSubProperty() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-iframe-property-write aoipw.sp", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-write"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-write"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#subproperty-target > div[data-expectedresult='fail']"); + } @@ -4510,10 +4757,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testAbortOnIframePropertyWriteMultipleProperties() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#abort-on-iframe-property-write aoipw1 aoipw2", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "abort-on-iframe-property-write"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "abort-on-iframe-property-write"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 0, "div#multipleproperties-target > div[data-expectedresult='fail']"); + } @@ -4522,8 +4768,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsStatic() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains 'hic-basic-static' p[id]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "p#hic-static-id"); @@ -4533,8 +4777,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsDynamic() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains 'hic-basic-dynamic' p[id]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "p#hic-dynamic-id"); @@ -4544,8 +4786,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsSearch() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains 'hic-search' p[id] .target", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div#search2-target > p.target"); @@ -4557,8 +4797,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsRegex() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains /hic-regex-[2-3]/ p[id]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains"); + // "hic-regex-1" does not match regex, should remain displayed. @@ -4573,8 +4811,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsFrame() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains hidden span#frame-target", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "span#frame-target"); @@ -4584,12 +4820,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsAndMatchesStyleStatic() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-contains-and-matches-style hicamss div[id] span.label /./ 'display:" -+ + " inline;'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-contains-and-matches-style"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-contains-and-matches-style"); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div#static-usage-area > div[data-expectedresult='fail']"); + TestVerificationUtils.verifyDisplayedCount( @@ -4600,12 +4833,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsAndMatchesStyleDynamic() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-contains-and-matches-style hicamsd div[id] span.label /./ 'display:" -+ + " inline;'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-contains-and-matches-style"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-contains-and-matches-style"); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div#dynamic-target > div[data-expectedresult='fail']"); + TestVerificationUtils.verifyDisplayedCount( @@ -4616,10 +4846,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsImage() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-contains-image " -+ + "/^89504e470d0a1a0a0000000d4948445200000064000000640802/ " -+ + "div[shouldhide] div", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-contains-image"); + TestVerificationUtils.verifyHiddenCount(mHelper, 2, "div[data-expectedresult='fail']"); @@ -4629,12 +4855,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsVisibleTextBasicUsage() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-contains-visible-text Sponsored-hicvt-basic '#parent-basic > " -+ + ".article' '#parent-basic > .article .label'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-contains-visible-text"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-contains-visible-text"); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div#parent-basic > div[data-expectedresult='fail']"); + } @@ -4643,12 +4866,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfContainsVisibleTextContentUsage() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-contains-visible-text Sponsored-hicvt-content '#parent-content > " -+ + ".article' '#parent-content > .article .label'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-contains-visible-text"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-contains-visible-text"); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div#parent-content > div[data-expectedresult='fail']"); + } @@ -4657,12 +4877,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfHasAndMatchesStyleBasicUsage() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-has-and-matches-style a[href=\"#basic-target-ad\"] div[id] span" -+ + ".label /./ 'display: inline;'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-has-and-matches-style"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-has-and-matches-style"); + TestVerificationUtils.verifyHiddenCount( + mHelper, 1, "div#basic-target > div[data-expectedresult='fail']"); + } @@ -4671,12 +4888,9 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfHasAndMatchesStyleLegitElements() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-has-and-matches-style a[href=\"#comments-target-ad\"] div[id] span" -+ + ".label ';' /\\\\bdisplay:\\ inline\\;/", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "hide-if-has-and-matches-style"); ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-has-and-matches-style"); + TestVerificationUtils.verifyDisplayedCount( + mHelper, 1, "div#comments-target > div[data-expectedresult='pass']"); + } @@ -4685,9 +4899,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfLabeledBy() throws Exception { -+ mHelper.addCustomFilter(String.format( -+ "%s#$#hide-if-labelled-by 'Label' '#hilb-target [aria-labelledby]' '#hilb-target'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-labelled-by"); + TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[data-expectedresult='fail']"); @@ -4698,8 +4909,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfMatchesXPathBasicStaticUsage() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-matches-xpath //*[@id=\"isnfnv\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); + TestVerificationUtils.verifyHiddenCount( @@ -4712,8 +4921,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfMatchesXPath3BasicStaticUsage() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-matches-xpath3 //*[@id=\"isnfnv\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); + TestVerificationUtils.verifyHiddenCount( @@ -4726,9 +4933,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfMatchesXPathClassUsage() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#hide-if-matches-xpath //*[@class=\"to-be-hidden\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); + TestVerificationUtils.verifyHiddenCount( @@ -4741,9 +4945,6 @@ new file mode 100644 + @LargeTest + @Feature({"adblock"}) + public void testHideIfMatchesXPath3ClassUsage() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#hide-if-matches-xpath3 //*[@class=\"to-be-hidden\"]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); + mHelper.loadUrlWaitForContent( + TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); + TestVerificationUtils.verifyHiddenCount( @@ -4755,119 +4956,363 @@ new file mode 100644 + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testHideIfMatchesXPathIdStartsWith() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#hide-if-matches-xpath //div[starts-with(@id,\"fail\")]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); ++ public void testHideIfMatchesXPathIdStartsWith() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='fail']"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesXPath3IdStartsWith() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='fail']"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesXPath3Regex() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath3"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div#hide-if-class-matches-regex-area > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesXPath3NormalizeAndJoinStrings() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath3"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div#normalize-and-join-strings-area > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesXPath3CastToNumber() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath3"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div#cast-to-number-area > div[data-expectedresult='fail']"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#cast-to-number-area > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesComputedXPathClassChangesDynamically() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-matches-computed-xpath"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div#hide-when-class-changes-dynamically-based-on-a-string-found-in-another-element-area" ++ + " > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfMatchesComputedXPathClassMatchesRegex() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "hide-if-matches-computed-xpath"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div#hide-when-class-matches-regex-group-area > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfShadowContainsBasicUsage() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-shadow-contains"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div#basic-target > p[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testHideIfShadowContainsRegexUsage() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-shadow-contains"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 2, "div#regex-target > div[data-expectedresult='fail']"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#regex-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testJsonPrune() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "json-prune?delay=100"); ++ // The object does not get hidden, it no longer exists in the DOM. ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 0, "div#testcase-area > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testOverridePropertyRead() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "override-property-read"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div#basic-target > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testStripFetchQueryParameterBasicUsage() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "strip-fetch-query-parameter"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 0, "div#basic-target > div[data-expectedresult='fail']"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 1, "div#basic-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testStripFetchQueryParameterOtherUsage() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT ++ + "strip-fetch-query-parameter"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 2, "div#other-target > div[data-expectedresult='pass']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testSimulateMouseEvent() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "simulate-mouse-event"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 0, "div#simulate-mouse-event-area > div[data-expectedresult='fail']"); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ @DisabledTest(message = "DPD-2832") ++ public void testSkipVideo() throws Exception { ++ mHelper.loadUrlWaitForContent( ++ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "skip-video"); ++ TestVerificationUtils.verifyDisplayedCount( ++ mHelper, 0, "div#skip-video-area > div[data-expectedresult='fail']"); ++ } ++} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java +@@ -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 . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.Assert; ++import org.junit.Test; ++ ++import org.chromium.base.test.util.Feature; ++ ++import java.util.HashSet; ++import java.util.List; ++import java.util.concurrent.CountDownLatch; ++import java.util.concurrent.TimeUnit; ++ ++public abstract class TestPagesWebsocketTestBase { ++ private TestPagesHelperBase mHelper; ++ ++ protected void setUp(TestPagesHelperBase helper) { ++ mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testVerifyWebsocketFilter() throws Exception { ++ final String wssUrl = ++ String.format("wss://%s/websocket", TestPagesHelperBase.TESTPAGES_DOMAIN); ++ final CountDownLatch countDownLatch = ++ mHelper.setOnAdMatchedExpectations(new HashSet<>(List.of(wssUrl)), null); ++ mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "websocket"); ++ // Wait with 10 seconds max timeout ++ countDownLatch.await(10, TimeUnit.SECONDS); ++ Assert.assertEquals(1, mHelper.numBlockedByType(ContentType.CONTENT_TYPE_WEBSOCKET)); ++ Assert.assertTrue(mHelper.isBlocked(wssUrl)); ++ } ++} +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWildcardDomainTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWildcardDomainTestBase.java +new file mode 100644 +--- /dev/null ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWildcardDomainTestBase.java +@@ -0,0 +1,116 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++package org.chromium.components.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.Test; ++ ++import org.chromium.base.test.util.Feature; ++ ++public abstract class TestPagesWildcardDomainTestBase { ++ ++ public static final String WILDCARD_DOMAIN_TESTPAGES_URL = ++ "https://subdomain.abptestpages.org/en/filters/wildcard-domain"; ++ ++ private TestPagesHelperBase mHelper; ++ ++ protected void setUp(TestPagesHelperBase helper) { ++ mHelper = helper; ++ mHelper.addFilterList(TestPagesHelperBase.TESTPAGES_SUBSCRIPTION); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testWildcardDomainContainsWithWildcards() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( -+ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='fail']"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='pass']"); ++ mHelper, 1, "div[id='element-hiding-contains-with-wildcards-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testHideIfMatchesXPath3IdStartsWith() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#hide-if-matches-xpath3 //div[starts-with(@id,\"fail\")]", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-matches-xpath"); -+ TestVerificationUtils.verifyHiddenCount( -+ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='fail']"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 1, "div#hide-if-id-starts-with-area > div[data-expectedresult='pass']"); ++ public void testWildcardDomainId() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); ++ TestVerificationUtils.verifyHiddenCount(mHelper, 1, "div[id='eh-id']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testHideIfShadowContainsBasicUsage() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#hide-if-shadow-contains 'hisc-basic' p", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-shadow-contains"); ++ public void testWildcardDomainAttributeSelector() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( -+ mHelper, 1, "div#basic-target > p[data-expectedresult='fail']"); ++ mHelper, 1, "div[id='element-hiding-attribute-selector-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testHideIfShadowContainsRegexUsage() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#hide-if-shadow-contains '/hisc-regex[1-2]/' div", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "hide-if-shadow-contains"); ++ public void testWildcardDomainAbpProperties() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( -+ mHelper, 2, "div#regex-target > div[data-expectedresult='fail']"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 1, "div#regex-target > div[data-expectedresult='pass']"); ++ mHelper, 1, "div[id='element-hiding-emulation-basic-abp-properties-usage-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testJsonPrune() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#json-prune 'data-expectedresult jsonprune aria-label'", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "json-prune?delay=100"); -+ // The object does not get hidden, it no longer exists in the DOM. -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 0, "div#testcase-area > div[data-expectedresult='fail']"); ++ public void testWildcardDomainAbpHas() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, 1, "div[id='element-hiding-emulation-basic-abp-has-usage-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testOverridePropertyRead() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#override-property-read overridePropertyRead.fp false", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent( -+ TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT + "override-property-read"); ++ public void testWildcardDomainChainedExtendedSelectors() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); + TestVerificationUtils.verifyHiddenCount( -+ mHelper, 1, "div#basic-target > div[data-expectedresult='fail']"); ++ mHelper, 1, "div[id='element-hiding-emulation-chained-extended-selectors-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testStripFetchQueryParameterBasicUsage() throws Exception { -+ mHelper.addCustomFilter(String.format("%s#$#strip-fetch-query-parameter basicBlocked %s", -+ TestPagesHelperBase.TESTPAGES_DOMAIN, TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "strip-fetch-query-parameter"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 0, "div#basic-target > div[data-expectedresult='fail']"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 1, "div#basic-target > div[data-expectedresult='pass']"); ++ public void testWildcardDomainExtendedSelectorAndDomain() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div[id='element-hide-emulation-wildcard-in-extended-selector-and-wildcard-in-domain-fail-1']"); + } + + @Test + @LargeTest + @Feature({"adblock"}) -+ public void testStripFetchQueryParameterOtherUsage() throws Exception { -+ mHelper.addCustomFilter( -+ String.format("%s#$#strip-fetch-query-parameter otherAllowed2 other-domain", -+ TestPagesHelperBase.TESTPAGES_DOMAIN)); -+ mHelper.loadUrlWaitForContent(TestPagesHelperBase.SNIPPETS_TESTPAGES_TESTCASES_ROOT -+ + "strip-fetch-query-parameter"); -+ TestVerificationUtils.verifyDisplayedCount( -+ mHelper, 2, "div#other-target > div[data-expectedresult='pass']"); ++ public void testWildcardDomainRegularExpression() throws Exception { ++ mHelper.loadUrl(WILDCARD_DOMAIN_TESTPAGES_URL); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div[id='element-hiding-emulation-regular-expression-in-abp-contains-fail-1']"); ++ TestVerificationUtils.verifyHiddenCount( ++ mHelper, ++ 1, ++ "div[id='element-hiding-emulation-regular-expression-in-abp-contains-fail-2']"); + } +} -diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java +diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestResourceFilteringObserver.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestResourceFilteringObserver.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestPagesWebsocketTestBase.java -@@ -0,0 +1,56 @@ ++++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestResourceFilteringObserver.java +@@ -0,0 +1,180 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -4887,48 +5332,172 @@ new file mode 100644 + +package org.chromium.components.adblock; + -+import androidx.test.filters.LargeTest; -+ +import org.junit.Assert; -+import org.junit.Test; -+ -+import org.chromium.base.test.util.Feature; -+import org.chromium.components.adblock.AdblockContentType; + -+import java.util.HashSet; ++import java.net.URL; +import java.util.List; ++import java.util.Set; ++import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; -+import java.util.concurrent.TimeUnit; + -+public abstract class TestPagesWebsocketTestBase { -+ private TestPagesHelperBase mHelper; ++public class TestResourceFilteringObserver ++ implements ResourceClassificationNotifier.ResourceFilteringObserver { ++ @Override ++ public void onRequestAllowed(ResourceFilteringCounters.ResourceInfo info) { ++ allowedInfos.add(info); ++ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ CheckAndCountDownLatch(Decision.ALLOWED, info.getRequestUrl().split("\\?")[0]); ++ } + -+ protected void setUp(TestPagesHelperBase helper) { -+ mHelper = helper; -+ mHelper.addCustomFilter( -+ String.format("$websocket,domain=%s", TestPagesHelperBase.TESTPAGES_DOMAIN)); ++ @Override ++ public void onRequestBlocked(ResourceFilteringCounters.ResourceInfo info) { ++ blockedInfos.add(info); ++ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ CheckAndCountDownLatch(Decision.BLOCKED, info.getRequestUrl().split("\\?")[0]); + } + -+ @Test -+ @LargeTest -+ @Feature({"adblock"}) -+ public void testVerifyWebsocketFilter() throws Exception { -+ final String wssUrl = -+ String.format("wss://%s/websocket", TestPagesHelperBase.TESTPAGES_DOMAIN); -+ final CountDownLatch countDownLatch = -+ mHelper.setOnAdMatchedExpectations(new HashSet<>(List.of(wssUrl)), null); -+ mHelper.loadUrl(TestPagesHelperBase.FILTER_TESTPAGES_TESTCASES_ROOT + "websocket"); -+ // Wait with 10 seconds max timeout -+ countDownLatch.await(10, TimeUnit.SECONDS); -+ Assert.assertEquals(1, mHelper.numBlockedByType(AdblockContentType.CONTENT_TYPE_WEBSOCKET)); -+ Assert.assertTrue(mHelper.isBlocked(wssUrl)); ++ @Override ++ public void onPageAllowed(ResourceFilteringCounters.ResourceInfo info) { ++ allowedPageInfos.add(info); ++ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ } ++ ++ @Override ++ public void onPopupAllowed(ResourceFilteringCounters.ResourceInfo info) { ++ allowedPopupsInfos.add(info); ++ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ } ++ ++ @Override ++ public void onPopupBlocked(ResourceFilteringCounters.ResourceInfo info) { ++ blockedPopupsInfos.add(info); ++ Assert.assertEquals(info.getSubscription(), getExpectedSubscriptionUrl()); ++ } ++ ++ public boolean isBlocked(String url) { ++ for (ResourceFilteringCounters.ResourceInfo info : blockedInfos) { ++ if (info.getRequestUrl().contains(url)) return true; ++ } ++ ++ return false; ++ } ++ ++ public boolean isPopupBlocked(String url) { ++ for (ResourceFilteringCounters.ResourceInfo info : blockedPopupsInfos) { ++ if (info.getRequestUrl().contains(url)) return true; ++ } ++ ++ return false; ++ } ++ ++ public int numBlockedByType(ContentType type) { ++ int result = 0; ++ for (ResourceFilteringCounters.ResourceInfo info : blockedInfos) { ++ if (info.getContentType() == type) ++result; ++ } ++ return result; ++ } ++ ++ public int numBlockedPopups() { ++ return blockedPopupsInfos.size(); ++ } ++ ++ public boolean isAllowed(String url) { ++ for (ResourceFilteringCounters.ResourceInfo info : allowedInfos) { ++ if (info.getRequestUrl().contains(url)) return true; ++ } ++ ++ return false; ++ } ++ ++ public boolean isPageAllowed(String url) { ++ for (ResourceFilteringCounters.ResourceInfo info : allowedPageInfos) { ++ if (info.getRequestUrl().contains(url)) return true; ++ } ++ ++ return false; ++ } ++ ++ public boolean isPopupAllowed(String url) { ++ for (ResourceFilteringCounters.ResourceInfo info : allowedPopupsInfos) { ++ if (info.getRequestUrl().contains(url)) return true; ++ } ++ ++ return false; ++ } ++ ++ public int numAllowedByType(ContentType type) { ++ int result = 0; ++ for (ResourceFilteringCounters.ResourceInfo info : allowedInfos) { ++ if (info.getContentType() == type) ++result; ++ } ++ return result; ++ } ++ ++ public int numAllowedPopups() { ++ return allowedPopupsInfos.size(); ++ } ++ ++ public void setExpectedSubscriptionUrl(URL url) { ++ mTestSubscriptionUrl = url; ++ } ++ ++ private String getExpectedSubscriptionUrl() { ++ if (mTestSubscriptionUrl != null) return mTestSubscriptionUrl.toString(); ++ return "adblock:custom"; ++ } ++ ++ private enum Decision { ++ ALLOWED, ++ BLOCKED ++ } ++ ++ // We either countDown() our latch for every filtered resource if there are no ++ // specific expectations set (expectedAllowed == null && expectedBlocked == null), ++ // or we countDown() only when all expectations have been met so when: ++ // (expectedAllowed.isNullOrEmpty() && expectedBlocked.isNullOrEmpty()). ++ private void CheckAndCountDownLatch(final Decision decision, final String url) { ++ if (countDownLatch != null) { ++ if (expectedBlocked == null && expectedAllowed == null) { ++ countDownLatch.countDown(); ++ } else { ++ if (decision == Decision.BLOCKED) { ++ if (expectedBlocked != null) { ++ expectedBlocked.remove(url); ++ } ++ } else { ++ if (expectedAllowed != null) { ++ expectedAllowed.remove(url); ++ } ++ } ++ boolean expectationsMet = ++ (expectedAllowed == null || expectedAllowed.isEmpty()) ++ && (expectedBlocked == null || expectedBlocked.isEmpty()); ++ if (expectationsMet) { ++ countDownLatch.countDown(); ++ } ++ } ++ } + } ++ ++ private URL mTestSubscriptionUrl; ++ public List blockedInfos = new CopyOnWriteArrayList<>(); ++ public List allowedInfos = new CopyOnWriteArrayList<>(); ++ public List allowedPageInfos = ++ new CopyOnWriteArrayList<>(); ++ public List blockedPopupsInfos = ++ new CopyOnWriteArrayList<>(); ++ public List allowedPopupsInfos = ++ new CopyOnWriteArrayList<>(); ++ CountDownLatch countDownLatch; ++ Set expectedAllowed; ++ Set expectedBlocked; +} diff --git a/components/adblock/android/javatests/src/org/chromium/components/adblock/TestVerificationUtils.java b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestVerificationUtils.java new file mode 100644 --- /dev/null +++ b/components/adblock/android/javatests/src/org/chromium/components/adblock/TestVerificationUtils.java -@@ -0,0 +1,163 @@ +@@ -0,0 +1,220 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -4963,92 +5532,108 @@ new file mode 100644 + + private static final String STATUS_OK = "\"OK\""; + -+ private static final String MATCHES_HIDDEN_FUNCTION = "let matches = function(element) {" -+ + " return window.getComputedStyle(element).display == \"none\";" -+ + "}"; ++ private static final String MATCHES_HIDDEN_FUNCTION = ++ "let matches = function(element) {" ++ + " return window.getComputedStyle(element).display == \"none\";" ++ + "}"; + -+ private static final String MATCHES_DISPLAYED_FUNCTION = "let matches = function(element) {" -+ + " return window.getComputedStyle(element).display != \"none\";" -+ + "}"; ++ private static final String MATCHES_DISPLAYED_FUNCTION = ++ "let matches = function(element) {" ++ + " return window.getComputedStyle(element).display != \"none\";" ++ + "}"; + + private static final String COUNT_ELEMENT_FUNCTION = + "let countElements = function(selector, includeSubframes) {" -+ + " let count = 0;" -+ + " for (let element of document.querySelectorAll(selector)) {" -+ + " if (matches(element))" -+ + " ++count;" -+ + " }" -+ + " if (includeSubframes) {" -+ + " for (let frame of document.querySelectorAll(\"iframe\")) {" -+ + " for (let element of frame.contentWindow.document.body" -+ + ".querySelectorAll(selector)) {" -+ + " if (matches(element))" -+ + " ++count;" -+ + " }" -+ + " }" -+ + " }" -+ + " return count;" -+ + "}"; -+ -+ private static final String WAIT_FOR_COUNT_FUNCTION_WRAPPER = "(function () {" -+ + "%s\n" // matches() definition placeholder -+ + "%s\n" // countElements() definition placeholder -+ + "%s\n" // WAIT_FUNCTION placeholder which calls countElements() as a predicate -+ + "}());"; -+ -+ // Poll every 100 ms until condition is met or 4 seconds timeout occurs ++ + " let count = 0;" ++ + " for (let element of document.querySelectorAll(selector)) {" ++ + " if (matches(element))" ++ + " ++count;" ++ + " }" ++ + " if (includeSubframes) {" ++ + " for (let frame of document.querySelectorAll(\"iframe\")) {" ++ + " for (let element of frame.contentWindow.document.body" ++ + ".querySelectorAll(selector)) {" ++ + " if (matches(element))" ++ + " ++count;" ++ + " }" ++ + " }" ++ + " }" ++ + " return count;" ++ + "}"; ++ ++ // Poll every 100 ms until condition is met or 3 seconds timeout occurs + // Internal timeout of JavaScriptUtils.runJavascriptWithAsyncResult() is 5 seconds + // so our wait timeout needs to be shorter. -+ private static final String WAIT_FUNCTION = "function waitWithTimeout() {" -+ + " return new Promise(resolve => {" -+ + " let repeat = 40;" -+ + " const id = setInterval(() => {" -+ + " --repeat;" -+ + " if (%s) {" // predicate placeholder -+ + " clearInterval(id);" -+ + " resolve('OK');" -+ + " } else if (repeat == 0) {" -+ + " clearInterval(id);" -+ + " resolve('Timeout');" -+ + " }" -+ + " }, 100);" -+ + " });" -+ + "};" -+ + "waitWithTimeout().then((result) => { domAutomationController.send(result); });"; -+ -+ private static void verifyMatchesCount(final TestPagesHelperBase helper, final int num, -+ final String matchesFunction, final String selector, IncludeSubframes includeSubframes) ++ private static final String WAIT_FUNCTION = ++ "function waitWithTimeout() {" ++ + " return new Promise(resolve => {" ++ + " let repeat = 30;" ++ + " const id = setInterval(() => {" ++ + " --repeat;" ++ + " if (%s) {" // predicate placeholder ++ + " clearInterval(id);" ++ + " resolve('OK');" ++ + " } else if (repeat == 0) {" ++ + " clearInterval(id);" ++ + " resolve('Timeout');" ++ + " }" ++ + " }, 100);" ++ + " });" ++ + "};" ++ + "waitWithTimeout().then((r) => { domAutomationController.send(r);});"; ++ ++ private static void verifyMatchesCount( ++ final TestPagesHelperBase helper, ++ final int num, ++ final String matchesFunction, ++ final String selector, ++ IncludeSubframes includeSubframes) + throws TimeoutException { + final String boolIncludeSubframes = + includeSubframes == IncludeSubframes.YES ? "true" : "false"; -+ final String predicate = String.format(Locale.getDefault(), -+ "countElements(\"%s\", %s) == %d", selector, boolIncludeSubframes, num); ++ final String predicate = ++ String.format( ++ Locale.getDefault(), ++ "countElements(\"%s\", %s) == %d", ++ selector, ++ boolIncludeSubframes, ++ num); + final String waitFunction = String.format(WAIT_FUNCTION, predicate); -+ final String js = String.format(WAIT_FOR_COUNT_FUNCTION_WRAPPER, matchesFunction, -+ COUNT_ELEMENT_FUNCTION, waitFunction); ++ final String js = ++ String.format( ++ "(function () {%s\n%s\n%s\n}());", ++ matchesFunction, COUNT_ELEMENT_FUNCTION, waitFunction); + final String result = + JavaScriptUtils.runJavascriptWithAsyncResult(helper.getWebContents(), js); + Assert.assertEquals(STATUS_OK, result); + } + -+ public static void verifyHiddenCount(final TestPagesHelperBase helper, final int num, -+ final String selector) throws TimeoutException { ++ public static void verifyHiddenCount( ++ final TestPagesHelperBase helper, final int num, final String selector) ++ throws TimeoutException { + verifyHiddenCount(helper, num, selector, IncludeSubframes.YES); + } + -+ public static void verifyHiddenCount(final TestPagesHelperBase helper, final int num, -+ final String selector, final IncludeSubframes includeSubframes) ++ public static void verifyHiddenCount( ++ final TestPagesHelperBase helper, ++ final int num, ++ final String selector, ++ final IncludeSubframes includeSubframes) + throws TimeoutException { + verifyMatchesCount(helper, num, MATCHES_HIDDEN_FUNCTION, selector, includeSubframes); + } + -+ public static void verifyDisplayedCount(final TestPagesHelperBase helper, final int num, -+ final String selector) throws TimeoutException { ++ public static void verifyDisplayedCount( ++ final TestPagesHelperBase helper, final int num, final String selector) ++ throws TimeoutException { + verifyDisplayedCount(helper, num, selector, IncludeSubframes.YES); + } + -+ public static void verifyDisplayedCount(final TestPagesHelperBase helper, final int num, -+ final String selector, final IncludeSubframes includeSubframes) ++ public static void verifyDisplayedCount( ++ final TestPagesHelperBase helper, ++ final int num, ++ final String selector, ++ final IncludeSubframes includeSubframes) + throws TimeoutException { + verifyMatchesCount(helper, num, MATCHES_DISPLAYED_FUNCTION, selector, includeSubframes); + } @@ -5056,15 +5641,21 @@ new file mode 100644 + public static void verifyCondition(final TestPagesHelperBase helper, final String predicate) + throws TimeoutException { + final String waitFunction = String.format(WAIT_FUNCTION, predicate); -+ Assert.assertEquals(STATUS_OK, ++ Assert.assertEquals( ++ STATUS_OK, + JavaScriptUtils.runJavascriptWithAsyncResult( + helper.getWebContents(), waitFunction)); + } + + public static void verifyGreenBackground(final TestPagesHelperBase helper, final String elemId) + throws TimeoutException { -+ verifyCondition(helper, -+ "window.getComputedStyle(document.getElementById('" + elemId ++ verifyCondition( ++ helper, ++ "document.getElementById('" ++ + elemId ++ + "') && " ++ + "window.getComputedStyle(document.getElementById('" ++ + elemId + + "')).backgroundColor == 'rgb(13, 199, 75)'"); + } + @@ -5073,29 +5664,64 @@ new file mode 100644 + // checks for rewrite filters replaces content proper way. + public static void verifySelfTestPass(final TestPagesHelperBase helper, final String elemId) + throws TimeoutException { -+ verifyCondition(helper, -+ "document.getElementById('" + elemId ++ verifyCondition( ++ helper, ++ "document.getElementById('" ++ + elemId ++ + "') && " ++ + "document.getElementById('" ++ + elemId + + "').getAttribute('data-expectedresult') == 'pass'"); + } + + public static void expectResourceBlocked(final TestPagesHelperBase helper, final String elemId) + throws TimeoutException { -+ verifyCondition(helper, -+ "window.getComputedStyle(document.getElementById('" + elemId ++ verifyCondition( ++ helper, ++ "document.getElementById('" ++ + elemId ++ + "') && " ++ + "window.getComputedStyle(document.getElementById('" ++ + elemId + + "')).display == 'none'"); + } + + public static void expectResourceShown(final TestPagesHelperBase helper, final String elemId) + throws TimeoutException { -+ verifyCondition(helper, -+ "window.getComputedStyle(document.getElementById('" + elemId ++ verifyCondition( ++ helper, ++ "document.getElementById('" ++ + elemId ++ + "') && " ++ + "window.getComputedStyle(document.getElementById('" ++ + elemId + + "')).display == 'inline'"); + } ++ ++ public static void expectResourceStyleProperty( ++ final TestPagesHelperBase helper, ++ final String elemId, ++ final String property, ++ final String value) ++ throws TimeoutException { ++ verifyCondition( ++ helper, ++ "document.getElementById('" ++ + elemId ++ + "') && " ++ + "window.getComputedStyle(document.getElementById('" ++ + elemId ++ + "'))['" ++ + property ++ + "'] == '" ++ + value ++ + "'"); ++ } +} -diff --git a/components/adblock/android/resource_classification_notifier_bindings.cc b/components/adblock/android/resource_classification_notifier_bindings.cc +diff --git a/components/adblock/android/resource_classification_notifier_android.cc b/components/adblock/android/resource_classification_notifier_android.cc new file mode 100644 --- /dev/null -+++ b/components/adblock/android/resource_classification_notifier_bindings.cc ++++ b/components/adblock/android/resource_classification_notifier_android.cc @@ -0,0 +1,163 @@ +/* + * This file is part of eyeo Chromium SDK, @@ -5114,51 +5740,46 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#include "components/adblock/android/resource_classification_notifier_bindings.h" -+ -+#include -+#include ++#include "components/adblock/android/resource_classification_notifier_android.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" -+#include "components/adblock/android/java_bindings_getters.h" ++#include "base/android/jni_weak_ref.h" +#include "components/adblock/android/jni_headers/ResourceClassificationNotifier_jni.h" -+#include "components/adblock/content/browser/resource_classification_runner.h" ++#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h" +#include "components/adblock/core/common/adblock_utils.h" -+ -+namespace adblock { ++#include "content/public/browser/android/browser_context_handle.h" ++#include "content/public/browser/render_process_host.h" ++#include "content/public/browser/web_contents.h" + +using base::android::AttachCurrentThread; -+using base::android::CheckException; -+using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; -+using base::android::GetClass; +using base::android::JavaParamRef; -+using base::android::JavaRef; -+using base::android::MethodID; -+using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; -+using base::android::ToJavaArrayOfObjects; +using base::android::ToJavaArrayOfStrings; + -+ResourceClassificationNotifierBindings::ResourceClassificationNotifierBindings( -+ ResourceClassificationRunner* classification_runner) -+ : classification_runner_(classification_runner) { -+ classification_runner_->AddObserver(this); -+} ++namespace adblock { + -+ResourceClassificationNotifierBindings:: -+ ~ResourceClassificationNotifierBindings() { -+ classification_runner_->RemoveObserver(this); ++ResourceClassificationNotifierAndroid::ResourceClassificationNotifierAndroid( ++ JNIEnv* env, ++ const JavaParamRef& jcontroller, ++ ResourceClassificationRunner* classification_runner) ++ : classification_runner_(classification_runner), ++ java_weak_controller_(env, jcontroller) { ++ if (classification_runner_) { ++ classification_runner_->AddObserver(this); ++ } +} + -+void ResourceClassificationNotifierBindings::Bind( -+ JavaObjectWeakGlobalRef resource_classifier_java) { -+ bound_counterpart_ = resource_classifier_java; ++ResourceClassificationNotifierAndroid:: ++ ~ResourceClassificationNotifierAndroid() { ++ if (classification_runner_) { ++ classification_runner_->RemoveObserver(this); ++ } +} + -+void ResourceClassificationNotifierBindings::OnAdMatched( ++void ResourceClassificationNotifierAndroid::OnRequestMatched( + const GURL& url, + FilterMatchResult result, + const std::vector& parent_frame_urls, @@ -5174,26 +5795,26 @@ new file mode 100644 + DVLOG(3) << "[eyeo] Ad matched " << url << "(type: " << content_type + << (was_blocked ? ", blocked" : ", allowed") << ")"; + JNIEnv* env = AttachCurrentThread(); -+ -+ ScopedJavaLocalRef obj = bound_counterpart_.get(env); -+ if (obj.is_null()) { -+ return; ++ auto java_controller = java_weak_controller_.get(env); ++ if (!java_controller.is_null()) { ++ ScopedJavaLocalRef j_url = ++ ConvertUTF8ToJavaString(env, url.spec()); ++ ScopedJavaLocalRef j_parents = ToJavaArrayOfStrings( ++ env, adblock::utils::ConvertURLs(parent_frame_urls)); ++ ScopedJavaLocalRef j_subscription = ++ ConvertUTF8ToJavaString(env, subscription.spec()); ++ ScopedJavaLocalRef j_configuration = ++ ConvertUTF8ToJavaString(env, configuration_name); ++ const content::GlobalRenderFrameHostId rfh_id = ++ render_frame_host->GetGlobalId(); ++ Java_ResourceClassificationNotifier_requestMatchedCallback( ++ env, java_controller, j_url, was_blocked, j_parents, j_subscription, ++ j_configuration, static_cast(content_type), rfh_id.child_id, ++ rfh_id.frame_routing_id); + } -+ -+ ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, url.spec()); -+ ScopedJavaLocalRef j_parents = -+ ToJavaArrayOfStrings(env, adblock::utils::ConvertURLs(parent_frame_urls)); -+ ScopedJavaLocalRef j_subscription = -+ ConvertUTF8ToJavaString(env, subscription.spec()); -+ ScopedJavaLocalRef j_configuration = -+ ConvertUTF8ToJavaString(env, configuration_name); -+ int tab_id = adblock::GetTabId(render_frame_host); -+ Java_ResourceClassificationNotifier_adMatchedCallback( -+ env, obj, j_url, was_blocked, j_parents, j_subscription, j_configuration, -+ static_cast(content_type), tab_id); +} + -+void ResourceClassificationNotifierBindings::OnPageAllowed( ++void ResourceClassificationNotifierAndroid::OnPageAllowed( + const GURL& url, + content::RenderFrameHost* render_frame_host, + const GURL& subscription, @@ -5202,23 +5823,23 @@ new file mode 100644 + DCHECK(render_frame_host); + DVLOG(3) << "[eyeo] Page allowed " << url; + JNIEnv* env = AttachCurrentThread(); -+ -+ ScopedJavaLocalRef obj = bound_counterpart_.get(env); -+ if (obj.is_null()) { -+ return; ++ auto java_controller = java_weak_controller_.get(env); ++ if (!java_controller.is_null()) { ++ ScopedJavaLocalRef j_url = ++ ConvertUTF8ToJavaString(env, url.spec()); ++ ScopedJavaLocalRef j_subscription = ++ ConvertUTF8ToJavaString(env, subscription.spec()); ++ ScopedJavaLocalRef j_configuration = ++ ConvertUTF8ToJavaString(env, configuration_name); ++ const content::GlobalRenderFrameHostId rfh_id = ++ render_frame_host->GetGlobalId(); ++ Java_ResourceClassificationNotifier_pageAllowedCallback( ++ env, java_controller, j_url, j_subscription, j_configuration, ++ rfh_id.child_id, rfh_id.frame_routing_id); + } -+ -+ ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, url.spec()); -+ ScopedJavaLocalRef j_subscription = -+ ConvertUTF8ToJavaString(env, subscription.spec()); -+ ScopedJavaLocalRef j_configuration = -+ ConvertUTF8ToJavaString(env, configuration_name); -+ int tab_id = adblock::GetTabId(render_frame_host); -+ Java_ResourceClassificationNotifier_pageAllowedCallback( -+ env, obj, j_url, j_subscription, j_configuration, tab_id); +} + -+void ResourceClassificationNotifierBindings::OnPopupMatched( ++void ResourceClassificationNotifierAndroid::OnPopupMatched( + const GURL& url, + FilterMatchResult result, + const GURL& opener_url, @@ -5233,38 +5854,43 @@ new file mode 100644 + DVLOG(3) << "[eyeo] Popup matched " << url + << (was_blocked ? ", blocked" : ", allowed"); + JNIEnv* env = AttachCurrentThread(); -+ -+ ScopedJavaLocalRef obj = bound_counterpart_.get(env); -+ if (obj.is_null()) { -+ return; ++ auto java_controller = java_weak_controller_.get(env); ++ if (!java_controller.is_null()) { ++ ScopedJavaLocalRef j_url = ++ ConvertUTF8ToJavaString(env, url.spec()); ++ ScopedJavaLocalRef j_opener = ++ ConvertUTF8ToJavaString(env, opener_url.spec()); ++ ScopedJavaLocalRef j_subscription = ++ ConvertUTF8ToJavaString(env, subscription.spec()); ++ ScopedJavaLocalRef j_configuration = ++ ConvertUTF8ToJavaString(env, configuration_name); ++ const content::GlobalRenderFrameHostId rfh_id = ++ render_frame_host->GetGlobalId(); ++ Java_ResourceClassificationNotifier_popupMatchedCallback( ++ env, java_controller, j_url, was_blocked, j_opener, j_subscription, ++ j_configuration, rfh_id.child_id, rfh_id.frame_routing_id); + } -+ -+ ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, url.spec()); -+ ScopedJavaLocalRef j_opener = -+ ConvertUTF8ToJavaString(env, opener_url.spec()); -+ ScopedJavaLocalRef j_subscription = -+ ConvertUTF8ToJavaString(env, subscription.spec()); -+ ScopedJavaLocalRef j_configuration = -+ ConvertUTF8ToJavaString(env, configuration_name); -+ int tab_id = adblock::GetTabId(render_frame_host); -+ Java_ResourceClassificationNotifier_popupMatchedCallback( -+ env, obj, j_url, was_blocked, j_opener, j_subscription, j_configuration, -+ tab_id); +} + +} // namespace adblock + -+static void JNI_ResourceClassificationNotifier_Bind( ++static jlong JNI_ResourceClassificationNotifier_Create( + JNIEnv* env, -+ const base::android::JavaParamRef& caller) { -+ auto& bindings = adblock::GetResourceClassificationNotifierBindings(); -+ bindings.Bind(JavaObjectWeakGlobalRef(env, caller)); ++ const JavaParamRef& jcontroller, ++ const base::android::JavaParamRef& jbrowser_context_handle) { ++ DCHECK(!jcontroller.is_null()); ++ DCHECK(!jbrowser_context_handle.is_null()); ++ return reinterpret_cast( ++ new adblock::ResourceClassificationNotifierAndroid( ++ env, std::move(jcontroller), ++ adblock::ResourceClassificationRunnerFactory::GetForBrowserContext( ++ content::BrowserContextFromJavaHandle(jbrowser_context_handle)))); +} -diff --git a/components/adblock/android/resource_classification_notifier_bindings.h b/components/adblock/android/resource_classification_notifier_bindings.h +diff --git a/components/adblock/android/resource_classification_notifier_android.h b/components/adblock/android/resource_classification_notifier_android.h new file mode 100644 --- /dev/null -+++ b/components/adblock/android/resource_classification_notifier_bindings.h -@@ -0,0 +1,69 @@ ++++ b/components/adblock/android/resource_classification_notifier_android.h +@@ -0,0 +1,72 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -5282,38 +5908,35 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+#ifndef COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_H_ -+#define COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_H_ ++#ifndef COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_ANDROID_H_ ++#define COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_ANDROID_H_ + -+#include -+#include ++#include "base/android/jni_android.h" +#include "base/android/jni_weak_ref.h" -+#include "base/memory/raw_ptr.h" -+#include "base/sequence_checker.h" +#include "components/adblock/content/browser/resource_classification_runner.h" -+#include "components/keyed_service/core/keyed_service.h" -+#include "components/prefs/pref_service.h" ++#include "content/public/browser/browser_context.h" ++ ++class FilterMatchResult; + +namespace adblock { + -+class ResourceClassificationNotifierBindings -+ : public KeyedService, -+ public ResourceClassificationRunner::Observer { ++class ResourceClassificationNotifierAndroid ++ : public ResourceClassificationRunner::Observer { + public: -+ explicit ResourceClassificationNotifierBindings( ++ ResourceClassificationNotifierAndroid( ++ JNIEnv* env, ++ const base::android::JavaParamRef& jcontroller, + ResourceClassificationRunner* classification_runner); -+ ~ResourceClassificationNotifierBindings() override; -+ -+ void Bind(JavaObjectWeakGlobalRef resource_classifier_java); ++ ~ResourceClassificationNotifierAndroid() override; + + // ResourceClassificationRunner::Observer -+ void OnAdMatched(const GURL& url, -+ FilterMatchResult match_result, -+ const std::vector& parent_frame_urls, -+ ContentType content_type, -+ content::RenderFrameHost* render_frame_host, -+ const GURL& subscription, -+ const std::string& configuration_name) override; ++ void OnRequestMatched(const GURL& url, ++ FilterMatchResult match_result, ++ const std::vector& 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, @@ -5328,10 +5951,16 @@ new file mode 100644 + private: + SEQUENCE_CHECKER(sequence_checker_); + raw_ptr classification_runner_; -+ JavaObjectWeakGlobalRef bound_counterpart_; ++ ++ // Direct reference to ResourceClassificationNotifier java class. Kept for as ++ // long as this instance of ResourceClassificationNotifierAndroid lives: ++ // until corresponding Profile gets destroyed. Destruction of Profile triggers ++ // destruction of both C++ ResourceClassificationNotifierAndroid and Java ++ // ResourceClassificationNotifier objects. ++ const JavaObjectWeakGlobalRef java_weak_controller_; +}; + +} // namespace adblock + -+#endif // COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_BINDINGS_H_ ++#endif // COMPONENTS_ADBLOCK_ANDROID_RESOURCE_CLASSIFICATION_NOTIFIER_ANDROID_H_ -- diff --git a/build/cromite_patches/eyeo-beta-118.0.5993.48-android_settings.patch b/build/cromite_patches/eyeo-133.0.6943.49-android_settings.patch similarity index 75% rename from build/cromite_patches/eyeo-beta-118.0.5993.48-android_settings.patch rename to build/cromite_patches/eyeo-133.0.6943.49-android_settings.patch index c06560c3ee5ffbb6eb652b2d762c6e340d76b2ad..9d7ae27b35a54efa3025ca6de3482ce44a7afc64 100644 --- a/build/cromite_patches/eyeo-beta-118.0.5993.48-android_settings.patch +++ b/build/cromite_patches/eyeo-133.0.6943.49-android_settings.patch @@ -1,20 +1,17 @@ From: chromium-sdk -Date: Thu, 12 Oct 2023 14:46:08 +0200 +Date: Fri, 14 Feb 2025 08:26:40 +0100 Subject: eyeo Browser Ad filtering Solution: Android Settings UI Module -Based on Chromium 118.0.5993.48 +Based on Chromium 133.0.6943.49 Pre-requisites: eyeo Browser Ad filtering Solution: Base Module and Android API Module --- chrome/android/BUILD.gn | 8 + .../android/java/res/xml/main_preferences.xml | 8 + - chrome/browser/adblock/android/BUILD.gn | 10 + - .../adblock/AdblockFilterFragmentTest.java | 63 +++++ - .../adblock/AdblockPopupInfoBarTest.java | 118 +++++++++ - .../adblock/AdblockPopupMessageTest.java | 103 ++++++++ - components/adblock/android/BUILD.gn | 67 +++++ - components/adblock/android/README.md | 13 + - .../adblock/android/adblock_strings.grd | 238 ++++++++++++++++++ + .../java/res/xml/main_preferences_legacy.xml | 8 + + chrome/browser/adblock/android/BUILD.gn | 85 ++++++ + chrome/browser/adblock/android/README.md | 13 + + .../adblock/android/adblock_strings.grd | 244 ++++++++++++++++++ .../fragment_adblock_custom_item_add.png | Bin 0 -> 490 bytes .../fragment_adblock_custom_item_remove.png | Bin 0 -> 296 bytes .../fragment_adblock_custom_item_add.png | Bin 0 -> 321 bytes @@ -35,15 +32,16 @@ Pre-requisites: eyeo Browser Ad filtering Solution: Base Module and Android API .../layout/adblock_custom_item_settings.xml | 65 +++++ .../layout/adblock_filter_lists_list_item.xml | 36 +++ .../java/res/xml/adblock_more_options.xml | 34 +++ - .../java/res/xml/adblock_preferences.xml | 56 +++++ - .../AdblockAllowedDomainsFragment.java | 77 ++++++ - .../AdblockCustomFilterListsFragment.java | 118 +++++++++ - .../AdblockCustomFiltersFragment.java | 76 ++++++ - .../settings/AdblockCustomItemFragment.java | 166 ++++++++++++ - .../settings/AdblockFilterListsAdapter.java | 115 +++++++++ - .../settings/AdblockFilterListsFragment.java | 54 ++++ + .../java/res/xml/adblock_preferences.xml | 63 +++++ + .../AdblockAllowedDomainsFragment.java | 79 ++++++ + .../AdblockCustomFilterListsFragment.java | 125 +++++++++ + .../AdblockCustomFiltersFragment.java | 80 ++++++ + .../settings/AdblockCustomItemFragment.java | 174 +++++++++++++ + .../settings/AdblockFilterListsAdapter.java | 112 ++++++++ + .../settings/AdblockFilterListsFragment.java | 57 ++++ .../settings/AdblockMoreOptionsFragment.java | 34 +++ - .../settings/AdblockSettingsFragment.java | 141 +++++++++++ + .../settings/AdblockSettingsFragment.java | 162 ++++++++++++ + .../adblock/AdblockFilterFragmentTest.java | 129 +++++++++ .../translations/adblock_strings_af.xtb | 4 + .../translations/adblock_strings_am.xtb | 13 + .../translations/adblock_strings_ar.xtb | 13 + @@ -124,142 +122,140 @@ Pre-requisites: eyeo Browser Ad filtering Solution: Base Module and Android API .../translations/adblock_strings_zh-HK.xtb | 4 + .../translations/adblock_strings_zh-TW.xtb | 13 + .../translations/adblock_strings_zu.xtb | 4 + - 118 files changed, 2441 insertions(+) + 116 files changed, 2357 insertions(+) + create mode 100644 chrome/browser/adblock/android/README.md + create mode 100644 chrome/browser/adblock/android/adblock_strings.grd + create mode 100644 chrome/browser/adblock/android/java/res/drawable-hdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-hdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-mdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-mdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-hdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-hdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-mdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-mdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-xhdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-xhdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-xxhdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-night-xxhdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-xhdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-xhdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-xxhdpi/fragment_adblock_custom_item_add.png + create mode 100644 chrome/browser/adblock/android/java/res/drawable-xxhdpi/fragment_adblock_custom_item_remove.png + create mode 100644 chrome/browser/adblock/android/java/res/layout/adblock_custom_item.xml + create mode 100644 chrome/browser/adblock/android/java/res/layout/adblock_custom_item_settings.xml + create mode 100644 chrome/browser/adblock/android/java/res/layout/adblock_filter_lists_list_item.xml + create mode 100644 chrome/browser/adblock/android/java/res/xml/adblock_more_options.xml + create mode 100644 chrome/browser/adblock/android/java/res/xml/adblock_preferences.xml + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockMoreOptionsFragment.java + create mode 100644 chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockFilterFragmentTest.java - create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupInfoBarTest.java - create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java - create mode 100644 components/adblock/android/README.md - create mode 100644 components/adblock/android/adblock_strings.grd - create mode 100644 components/adblock/android/java/res/drawable-hdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-hdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-mdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-mdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-night-hdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-night-hdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-night-mdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-night-mdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-night-xhdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-night-xhdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-night-xxhdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-night-xxhdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-xhdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-xhdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/drawable-xxhdpi/fragment_adblock_custom_item_add.png - create mode 100644 components/adblock/android/java/res/drawable-xxhdpi/fragment_adblock_custom_item_remove.png - create mode 100644 components/adblock/android/java/res/layout/adblock_custom_item.xml - create mode 100644 components/adblock/android/java/res/layout/adblock_custom_item_settings.xml - create mode 100644 components/adblock/android/java/res/layout/adblock_filter_lists_list_item.xml - create mode 100644 components/adblock/android/java/res/xml/adblock_more_options.xml - create mode 100644 components/adblock/android/java/res/xml/adblock_preferences.xml - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockMoreOptionsFragment.java - create mode 100644 components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java - create mode 100644 components/adblock/android/translations/adblock_strings_af.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_am.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ar.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_as.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_az.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_be.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_bg.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_bn.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_bs.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ca.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_cs.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_da.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_de.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_el.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_en-GB.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_es-419.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_es.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_et.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_eu.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_fa.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_fi.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_fil.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_fr-CA.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_fr.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_gl.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_gu.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_hi.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_hr.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_hu.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_hy.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_id.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_is.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_it.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_iw.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ja.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ka.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_kk.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_km.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_kn.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ko.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ky.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_lo.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_lt.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_lv.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_mk.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ml.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_mn.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_mr.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ms.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_my.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ne.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_nl.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_no.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_or.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_pa.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_pl.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_pt-BR.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_pt-PT.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ro.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ru.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_si.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sk.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sl.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sq.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sr-Latn.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sr.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sv.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_sw.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ta.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_te.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_th.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_tr.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_uk.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_ur.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_uz.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_vi.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_zh-CN.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_zh-HK.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_zh-TW.xtb - create mode 100644 components/adblock/android/translations/adblock_strings_zu.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_af.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_am.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ar.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_as.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_az.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_be.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_bg.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_bn.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_bs.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ca.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_cs.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_da.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_de.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_el.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_en-GB.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_es-419.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_es.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_et.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_eu.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_fa.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_fi.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_fil.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_fr-CA.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_fr.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_gl.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_gu.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_hi.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_hr.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_hu.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_hy.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_id.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_is.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_it.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_iw.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ja.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ka.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_kk.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_km.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_kn.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ko.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ky.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_lo.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_lt.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_lv.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_mk.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ml.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_mn.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_mr.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ms.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_my.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ne.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_nl.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_no.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_or.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_pa.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_pl.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_pt-BR.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_pt-PT.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ro.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ru.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_si.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sk.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sl.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sq.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sr-Latn.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sr.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sv.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_sw.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ta.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_te.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_th.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_tr.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_uk.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_ur.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_uz.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_vi.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_zh-CN.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_zh-HK.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_zh-TW.xtb + create mode 100644 chrome/browser/adblock/android/translations/adblock_strings_zu.xtb diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn -@@ -285,6 +285,10 @@ if (current_toolchain == default_toolchain) { - "//third_party/androidx:androidx_preference_preference_java", - ] +@@ -292,6 +292,10 @@ if (current_toolchain == default_toolchain) { + deps += [ "//chrome/browser:screen_capture_java_resources" ] + } + ### Android UI module start -+ deps += [ "//components/adblock/android:java_ui_resources" ] -+ ### Android UI module end ++ deps += [ "//chrome/browser/adblock/android:java_ui_resources" ] + - if (plus_addresses_use_internal_android_resources) { - deps += [ "//clank/components/plus_addresses:java_resources" ] - } else { -@@ -774,6 +778,10 @@ if (current_toolchain == default_toolchain) { ++ ### Android UI module end + } + + android_resources("java_overlay_resources") { +@@ -776,6 +780,10 @@ if (current_toolchain == default_toolchain) { "//url/mojom:url_mojom_origin_java", ] + ### Android UI module start -+ deps += [ "//components/adblock/android:adblock_ui_java" ] ++ deps += [ "//chrome/browser/adblock/android:adblock_ui_java" ] + ### Android UI module end + deps += feed_deps @@ -278,378 +274,105 @@ diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/j --> - android:key="accessibility" - android:order="24" - android:title="@string/prefs_accessibility"/> +@@ -96,6 +99,11 @@ for the previous order (main_preferences_legacy). --> + android:key="autofill_options" + android:order="16" + android:title="@string/autofill_options_title" /> + + + + + + android:key="ui_theme" + android:order="18" + android:title="@string/theme_settings" /> ++ - . -+ */ -+ -+package org.chromium.chrome.browser.adblock; -+ -+import androidx.test.filters.SmallTest; -+ -+import org.junit.Assert; -+import org.junit.Before; -+import org.junit.Rule; -+import org.junit.Test; -+import org.junit.runner.RunWith; -+ -+import org.chromium.base.test.util.ApplicationTestUtils; -+import org.chromium.base.test.util.CommandLineFlags; -+import org.chromium.base.test.util.Feature; -+import org.chromium.chrome.browser.flags.ChromeSwitches; -+import org.chromium.chrome.browser.settings.SettingsActivityTestRule; -+import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -+import org.chromium.chrome.test.ChromeTabbedActivityTestRule; -+import org.chromium.components.adblock.settings.AdblockFilterListsFragment; -+ -+@RunWith(ChromeJUnit4ClassRunner.class) -+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) -+public class AdblockFilterFragmentTest { -+ @Rule -+ public final ChromeTabbedActivityTestRule mActivityTestRule = -+ new ChromeTabbedActivityTestRule(); -+ @Rule -+ public SettingsActivityTestRule mTestRule = -+ new SettingsActivityTestRule<>(AdblockFilterListsFragment.class); -+ -+ @Before -+ public void setUp() throws Exception { -+ mActivityTestRule.startMainActivityOnBlankPage(); -+ mTestRule.startSettingsActivity(); -+ ApplicationTestUtils.finishActivity(mTestRule.getActivity()); -+ } ++ deps += [ ":adblock_ui_java" ] + -+ @Test -+ @SmallTest -+ @Feature({"adblock"}) -+ public void testLanguageFiltersNotEmpty() { -+ mTestRule.startSettingsActivity(); -+ AdblockFilterListsFragment fragment = mTestRule.getFragment(); -+ Assert.assertNotNull(fragment); -+ Assert.assertNotEquals(0, fragment.getListView().getCount()); -+ } -+} -diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupInfoBarTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupInfoBarTest.java -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupInfoBarTest.java -@@ -0,0 +1,118 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+package org.chromium.chrome.browser.adblock; -+ -+import androidx.test.InstrumentationRegistry; -+import androidx.test.filters.MediumTest; -+ -+import org.hamcrest.Matchers; -+import org.junit.After; -+import org.junit.Assert; -+import org.junit.Before; -+import org.junit.Rule; -+import org.junit.Test; -+import org.junit.runner.RunWith; -+ -+import org.chromium.base.task.PostTask; -+import org.chromium.base.task.TaskTraits; -+import org.chromium.base.test.util.CommandLineFlags; -+import org.chromium.base.test.util.Criteria; -+import org.chromium.base.test.util.CriteriaHelper; -+import org.chromium.base.test.util.Feature; -+import org.chromium.chrome.browser.flags.ChromeFeatureList; -+import org.chromium.chrome.browser.flags.ChromeSwitches; -+import org.chromium.chrome.browser.infobar.InfoBarIdentifier; -+import org.chromium.chrome.browser.tabmodel.TabModelSelector; -+import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -+import org.chromium.chrome.test.ChromeTabbedActivityTestRule; -+import org.chromium.chrome.test.util.browser.Features.DisableFeatures; -+import org.chromium.components.infobars.InfoBar; -+import org.chromium.net.test.EmbeddedTestServer; -+ -+// NOTE: Messages feature had to be disabled when testing infobars -+// https://gitlab.com/eyeo/adblockplus/abpchromium/-/commit/a617caaa5692630f73d967a55d86bf52cc491ad5 -+@RunWith(ChromeJUnit4ClassRunner.class) -+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) -+@DisableFeatures({ChromeFeatureList.MESSAGES_FOR_ANDROID_INFRASTRUCTURE}) -+public class AdblockPopupInfoBarTest { -+ @Rule -+ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); -+ -+ private static final String POPUP_HTML_PATH = "/chrome/test/data/android/popup_test.html"; -+ -+ private String mPopupHtmlUrl; -+ private EmbeddedTestServer mTestServer; -+ -+ @Before -+ public void setUp() throws Exception { -+ // Create a new temporary instance to ensure the Class is loaded. Otherwise we will get a -+ // ClassNotFoundException when trying to instantiate during startup. -+ mActivityTestRule.startMainActivityOnBlankPage(); -+ -+ PostTask.runOrPostTask( -+ TaskTraits.UI_DEFAULT, () -> Assert.assertEquals(0, getNumInfobarsShowing())); -+ -+ mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); -+ mPopupHtmlUrl = mTestServer.getURL(POPUP_HTML_PATH); -+ } -+ -+ @After -+ public void tearDown() { -+ mTestServer.stopAndDestroyServer(); -+ } -+ -+ private int getNumInfobarsShowing() { -+ return mActivityTestRule.getInfoBars().size(); -+ } -+ -+ public int getTabCount() { -+ final TabModelSelector tabModelSelector = -+ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); -+ Assert.assertNotNull(tabModelSelector); -+ return tabModelSelector.getTotalTabCount(); -+ } -+ -+ @Test -+ @MediumTest -+ @Feature({"adblock"}) -+ public void popUpBlockedInfoBarVisibleWhenAbpEnabled() throws InterruptedException { -+ mActivityTestRule.loadUrl(mPopupHtmlUrl); -+ Assert.assertEquals(1, getTabCount()); -+ CriteriaHelper.pollUiThread(() -> { -+ Criteria.checkThat(getNumInfobarsShowing(), Matchers.is(1)); -+ InfoBar frontInfoBar = mActivityTestRule.getInfoBars().get(0); -+ Criteria.checkThat("Invalid infobar type shown", frontInfoBar.getInfoBarIdentifier(), -+ Matchers.is(InfoBarIdentifier.POPUP_BLOCKED_INFOBAR_DELEGATE_MOBILE)); -+ }); -+ } -+ -+ @Test -+ @MediumTest -+ @CommandLineFlags.Add({"disable-adblock"}) -+ @Feature({"adblock"}) -+ public void popUpBlockedInfoBarVisibleWhenAbpDisabled() { -+ mActivityTestRule.loadUrl(mPopupHtmlUrl); -+ Assert.assertEquals(1, getTabCount()); -+ CriteriaHelper.pollUiThread(() -> { -+ Criteria.checkThat(getNumInfobarsShowing(), Matchers.is(1)); -+ InfoBar frontInfoBar = mActivityTestRule.getInfoBars().get(0); -+ Criteria.checkThat("Invalid infobar type shown", frontInfoBar.getInfoBarIdentifier(), -+ Matchers.is(InfoBarIdentifier.POPUP_BLOCKED_INFOBAR_DELEGATE_MOBILE)); -+ }); -+ } ++ ### Android UI module end +} -diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java -new file mode 100644 ---- /dev/null -+++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java -@@ -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 . -+ */ -+ -+package org.chromium.chrome.browser.adblock; + -+import androidx.test.InstrumentationRegistry; -+import androidx.test.filters.MediumTest; -+ -+import org.hamcrest.Matchers; -+import org.junit.After; -+import org.junit.Assert; -+import org.junit.Before; -+import org.junit.Rule; -+import org.junit.Test; -+import org.junit.runner.RunWith; -+ -+import org.chromium.base.test.util.CommandLineFlags; -+import org.chromium.base.test.util.Criteria; -+import org.chromium.base.test.util.CriteriaHelper; -+import org.chromium.base.test.util.Feature; -+import org.chromium.chrome.browser.flags.ChromeSwitches; -+import org.chromium.chrome.browser.tabmodel.TabModelSelector; -+import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -+import org.chromium.chrome.test.ChromeTabbedActivityTestRule; -+import org.chromium.components.messages.MessagesTestHelper; -+import org.chromium.net.test.EmbeddedTestServer; -+ -+@RunWith(ChromeJUnit4ClassRunner.class) -+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) -+public class AdblockPopupMessageTest { -+ @Rule -+ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); -+ -+ private static final String POPUP_HTML_PATH = "/chrome/test/data/android/popup_test.html"; -+ -+ private String mPopupHtmlUrl; -+ private EmbeddedTestServer mTestServer; -+ -+ @Before -+ public void setUp() throws Exception { -+ // Create a new temporary instance to ensure the Class is loaded. Otherwise we will get a -+ // ClassNotFoundException when trying to instantiate during startup. -+ mActivityTestRule.startMainActivityOnBlankPage(); -+ -+ mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); -+ mPopupHtmlUrl = mTestServer.getURL(POPUP_HTML_PATH); -+ } -+ -+ @After -+ public void tearDown() { -+ mTestServer.stopAndDestroyServer(); -+ } -+ -+ public int getTabCount() { -+ final TabModelSelector tabModelSelector = -+ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); -+ Assert.assertNotNull(tabModelSelector); -+ return tabModelSelector.getTotalTabCount(); -+ } -+ -+ @Test -+ @MediumTest -+ @Feature({"adblock"}) -+ public void popUpBlockedMessageVisibleWhenAbpEnabled() throws InterruptedException { -+ mActivityTestRule.loadUrl(mPopupHtmlUrl); -+ Assert.assertEquals(1, getTabCount()); -+ -+ CriteriaHelper.pollUiThread(() -> { -+ Criteria.checkThat(MessagesTestHelper.getMessageCount( -+ mActivityTestRule.getActivity().getWindowAndroid()), -+ Matchers.is(1)); -+ }); -+ } -+ -+ @Test -+ @MediumTest -+ @CommandLineFlags.Add({"disable-adblock"}) -+ @Feature({"adblock"}) -+ public void popUpBlockedMessageVisibleWhenAbpDisabled() { -+ mActivityTestRule.loadUrl(mPopupHtmlUrl); -+ Assert.assertEquals(1, getTabCount()); -+ -+ CriteriaHelper.pollUiThread(() -> { -+ Criteria.checkThat(MessagesTestHelper.getMessageCount( -+ mActivityTestRule.getActivity().getWindowAndroid()), -+ Matchers.is(1)); -+ }); -+ } -+} -diff --git a/components/adblock/android/BUILD.gn b/components/adblock/android/BUILD.gn ---- a/components/adblock/android/BUILD.gn -+++ b/components/adblock/android/BUILD.gn -@@ -66,6 +66,73 @@ android_library("adblock_controller_java") { - resources_package = "org.chromium.components.adblock.controller" - } - +### Android UI module start +android_library("adblock_ui_java") { + sources = [ -+ "java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockFilterListsFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockMoreOptionsFragment.java", -+ "java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockMoreOptionsFragment.java", ++ "java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java", + ] + + deps = [ -+ ":adblock_controller_java", + ":java_ui_resources", + "//base:base_java", + "//build/android:build_java", + "//chrome/browser/preferences:java", + "//chrome/browser/profiles/android:java", ++ "//components/adblock/android:adblock_controller_java", + "//components/browser_ui/settings/android:java", + "//components/prefs/android:java", + "//components/user_prefs/android:java", + "//content/public/android:content_full_java", + "//third_party/androidx:androidx_fragment_fragment_java", + "//third_party/androidx:androidx_preference_preference_java", ++ "//third_party/jni_zero:jni_zero_java", + ] + -+ resources_package = "org.chromium.components.adblock" ++ resources_package = "org.chromium.chrome.browser.adblock" +} + +android_resources("java_ui_resources") { @@ -686,15 +409,12 @@ diff --git a/components/adblock/android/BUILD.gn b/components/adblock/android/BU + process_file_template( + android_bundle_locales_as_resources, + [ "values-{{source_name_part}}/adblock_strings.xml" ]) -+} + } +### Android UI module end - - android_library("adblock_java_tests_base") { - testonly = true -diff --git a/components/adblock/android/README.md b/components/adblock/android/README.md +diff --git a/chrome/browser/adblock/android/README.md b/chrome/browser/adblock/android/README.md new file mode 100644 --- /dev/null -+++ b/components/adblock/android/README.md ++++ b/chrome/browser/adblock/android/README.md @@ -0,0 +1,13 @@ +# Translations HOWTO + @@ -709,11 +429,11 @@ new file mode 100644 +will produce output `grit_xmb_output.xml` containing mappings between textual +IDs in [grdp file](adblock_strings.grd) (`message name` value) and numerical IDs +in [xtb files](translations) (`translation id` value). -diff --git a/components/adblock/android/adblock_strings.grd b/components/adblock/android/adblock_strings.grd +diff --git a/chrome/browser/adblock/android/adblock_strings.grd b/chrome/browser/adblock/android/adblock_strings.grd new file mode 100644 --- /dev/null -+++ b/components/adblock/android/adblock_strings.grd -@@ -0,0 +1,238 @@ ++++ b/chrome/browser/adblock/android/adblock_strings.grd +@@ -0,0 +1,244 @@ + + + + ++ ++ ++ + + + + + + -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java -@@ -0,0 +1,77 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java +@@ -0,0 +1,79 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1630,14 +1363,13 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + -+import android.app.Activity; +import android.os.Bundle; -+import android.view.View; + ++import org.chromium.chrome.browser.adblock.R; ++import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; + +import java.util.List; + @@ -1652,7 +1384,8 @@ new file mode 100644 + + @Override + protected List getItems() { -+ return AdblockController.getInstance().getAllowedDomains(); ++ return AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .getAllowedDomains(); + } + + @Override @@ -1682,19 +1415,21 @@ new file mode 100644 + + @Override + protected void addItemImpl(String domain) { -+ AdblockController.getInstance().addAllowedDomain(domain); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .addAllowedDomain(domain); + } + + @Override + protected void removeItemImpl(String domain) { -+ AdblockController.getInstance().removeAllowedDomain(domain); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .removeAllowedDomain(domain); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFilterListsFragment.java -@@ -0,0 +1,118 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFilterListsFragment.java +@@ -0,0 +1,125 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1712,17 +1447,17 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + -+import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.webkit.URLUtil; +import android.widget.Toast; + ++import org.chromium.chrome.browser.adblock.R; ++import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; + +import java.net.MalformedURLException; +import java.net.URL; @@ -1731,6 +1466,7 @@ new file mode 100644 + +public class AdblockCustomFilterListsFragment extends AdblockCustomItemFragment { + private static final String TAG = AdblockCustomFilterListsFragment.class.getSimpleName(); ++ + public AdblockCustomFilterListsFragment() {} + + @Override @@ -1742,17 +1478,21 @@ new file mode 100644 + @Override + protected List getItems() { + final List installed = -+ AdblockController.getInstance().getInstalledSubscriptions(); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .getInstalledSubscriptions(); + final List recommended = -+ AdblockController.getInstance().getRecommendedSubscriptions(); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .getRecommendedSubscriptions(); + final List customStrings = new ArrayList(); + for (final AdblockController.Subscription subscription : installed) { + if (recommended.contains(subscription)) { -+ continue; ++ continue; + } + // FIXME(kzlomek): Remove this after DPD-1613 -+ if (subscription.url().toString().equals( -+ "https://easylist-downloads.adblockplus.org/exceptionrules.txt")) { ++ if (subscription ++ .url() ++ .toString() ++ .equals("https://easylist-downloads.adblockplus.org/exceptionrules.txt")) { + continue; + } + customStrings.add(subscription.url().toString()); @@ -1797,7 +1537,8 @@ new file mode 100644 + return; + } + try { -+ AdblockController.getInstance().installSubscription(new URL(URLUtil.guessUrl(url))); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .installSubscription(new URL(URLUtil.guessUrl(url))); + } catch (MalformedURLException ex) { + Toast.makeText(getActivity(), "Error parsing url", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Error parsing url: " + url); @@ -1807,17 +1548,18 @@ new file mode 100644 + @Override + protected void removeItemImpl(String url) { + try { -+ AdblockController.getInstance().uninstallSubscription(new URL(URLUtil.guessUrl(url))); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .uninstallSubscription(new URL(URLUtil.guessUrl(url))); + } catch (MalformedURLException ex) { + Log.e(TAG, "Error parsing url: " + url); + } + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomFiltersFragment.java -@@ -0,0 +1,76 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomFiltersFragment.java +@@ -0,0 +1,80 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1835,13 +1577,13 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + -+import android.app.Activity; +import android.os.Bundle; + ++import org.chromium.chrome.browser.adblock.R; ++import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; + +import java.util.List; + @@ -1851,12 +1593,14 @@ new file mode 100644 + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); -+ getActivity().setTitle(getString(R.string.fragment_adblock_more_options_custom_filters_title)); ++ getActivity() ++ .setTitle(getString(R.string.fragment_adblock_more_options_custom_filters_title)); + } + + @Override + protected List getItems() { -+ return AdblockController.getInstance().getCustomFilters(); ++ return AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .getCustomFilters(); + } + + @Override @@ -1886,19 +1630,21 @@ new file mode 100644 + + @Override + protected void addItemImpl(String value) { -+ AdblockController.getInstance().addCustomFilter(value); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .addCustomFilter(value); + } + + @Override + protected void removeItemImpl(String value) { -+ AdblockController.getInstance().removeCustomFilter(value); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .removeCustomFilter(value); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockCustomItemFragment.java -@@ -0,0 +1,166 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockCustomItemFragment.java +@@ -0,0 +1,174 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -1916,7 +1662,7 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + +import android.os.Bundle; +import android.view.LayoutInflater; @@ -1930,9 +1676,8 @@ new file mode 100644 + +import androidx.preference.PreferenceFragmentCompat; + -+import org.chromium.components.adblock.R; ++import org.chromium.chrome.browser.adblock.R; + -+import java.util.ArrayList; +import java.util.List; + +public abstract class AdblockCustomItemFragment extends PreferenceFragmentCompat { @@ -1974,12 +1719,19 @@ new file mode 100644 + } + + protected abstract void addItemImpl(String item); ++ + protected abstract void removeItemImpl(String item); ++ + protected abstract List getItems(); ++ + protected abstract String getCustomItemTextViewText(); ++ + protected abstract String getCustomItemTextViewContentDescription(); ++ + protected abstract String getCustomItemAddButtonContentDescription(); ++ + protected abstract String getCustomItemRemoveButtonContentDescription(); ++ + protected abstract String getCustomItemEditTextHint(); + + // Holder for listview items @@ -1995,14 +1747,15 @@ new file mode 100644 + } + } + -+ private View.OnClickListener removeItemClickListener = new View.OnClickListener() { -+ @Override -+ public void onClick(View v) { -+ String item = (String) v.getTag(); -+ removeItemImpl(item); -+ mAdapter.notifyDataSetChanged(); -+ } -+ }; ++ private View.OnClickListener removeItemClickListener = ++ new View.OnClickListener() { ++ @Override ++ public void onClick(View v) { ++ String item = (String) v.getTag(); ++ removeItemImpl(item); ++ mAdapter.notifyDataSetChanged(); ++ } ++ }; + + private class Adapter extends BaseAdapter { + @Override @@ -2039,16 +1792,17 @@ new file mode 100644 + } + + private void initControls() { -+ mAddButton.setOnClickListener(new View.OnClickListener() { -+ @Override -+ public void onClick(View v) { -+ String preparedItem = prepareItem(mItem.getText().toString()); -+ -+ if (preparedItem != null && preparedItem.length() > 0) { -+ addItem(preparedItem); -+ } -+ } -+ }); ++ mAddButton.setOnClickListener( ++ new View.OnClickListener() { ++ @Override ++ public void onClick(View v) { ++ String preparedItem = prepareItem(mItem.getText().toString()); ++ ++ if (preparedItem != null && preparedItem.length() > 0) { ++ addItem(preparedItem); ++ } ++ } ++ }); + + mAdapter = new Adapter(); + mListView.setAdapter(mAdapter); @@ -2065,11 +1819,11 @@ new file mode 100644 + mItem.clearFocus(); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsAdapter.java -@@ -0,0 +1,115 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsAdapter.java +@@ -0,0 +1,112 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2087,7 +1841,7 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + +import android.content.Context; +import android.view.LayoutInflater; @@ -2098,21 +1852,17 @@ new file mode 100644 +import android.widget.CheckBox; +import android.widget.TextView; + ++import org.chromium.chrome.browser.adblock.R; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; -+import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference; + +import java.net.URL; -+import java.util.ArrayList; +import java.util.List; + +public class AdblockFilterListsAdapter extends BaseAdapter implements OnClickListener { + private final LayoutInflater mLayoutInflater; -+ private final Context mContext; + private final AdblockController mController; + + public AdblockFilterListsAdapter(Context context, AdblockController controller) { -+ this.mContext = context; + mLayoutInflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.mController = controller; @@ -2154,13 +1904,16 @@ new file mode 100644 + final List subscriptions = + mController.getInstalledSubscriptions(); + boolean subscribed = false; ++ boolean autoinstalled = false; + for (final AdblockController.Subscription subscription : subscriptions) { + if (subscription.url().equals(item.url())) { + subscribed = true; ++ autoinstalled = subscription.autoinstalled(); + break; + } + } + checkBox.setChecked(subscribed); ++ checkBox.setEnabled(!autoinstalled); + checkBox.setContentDescription(item.title() + "filer list item checkbox"); + + TextView description = view.findViewById(R.id.name); @@ -2172,8 +1925,6 @@ new file mode 100644 + @Override + public void onClick(View view) { + URL url = (URL) view.getTag(); -+ TextView description = view.findViewById(R.id.name); -+ String title = description.getText().toString(); + + CheckBox checkBox = view.findViewById(R.id.checkbox); + if (checkBox.isChecked()) { @@ -2185,11 +1936,11 @@ new file mode 100644 + notifyDataSetChanged(); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockFilterListsFragment.java -@@ -0,0 +1,54 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockFilterListsFragment.java +@@ -0,0 +1,57 @@ +/* + * This file is part of eyeo Chromium SDK, + * Copyright (C) 2006-present eyeo GmbH @@ -2207,7 +1958,7 @@ new file mode 100644 + * along with eyeo Chromium SDK. If not, see . + */ + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + +import android.os.Bundle; +import android.view.View; @@ -2215,8 +1966,9 @@ new file mode 100644 + +import androidx.fragment.app.ListFragment; + ++import org.chromium.chrome.browser.adblock.R; ++import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; + +public class AdblockFilterListsFragment extends ListFragment { + private AdblockFilterListsAdapter mFilterListsAdapter; @@ -2226,7 +1978,9 @@ new file mode 100644 + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.fragment_adblock_settings_filter_lists_title); + mFilterListsAdapter = -+ new AdblockFilterListsAdapter(getActivity(), AdblockController.getInstance()); ++ new AdblockFilterListsAdapter( ++ getActivity(), ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile())); + setListAdapter(mFilterListsAdapter); + } + @@ -2244,10 +1998,10 @@ new file mode 100644 + mFilterListsAdapter.start(); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockMoreOptionsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockMoreOptionsFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockMoreOptionsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockMoreOptionsFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockMoreOptionsFragment.java ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockMoreOptionsFragment.java @@ -0,0 +1,34 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH @@ -2261,13 +2015,13 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + -+import org.chromium.components.adblock.R; ++import org.chromium.chrome.browser.adblock.R; + +public class AdblockMoreOptionsFragment extends PreferenceFragmentCompat { + public AdblockMoreOptionsFragment() {} @@ -2283,11 +2037,11 @@ new file mode 100644 + getActivity().setTitle(R.string.fragment_adblock_more_options_custom_filter_lists_title); + } +} -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java new file mode 100644 --- /dev/null -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockSettingsFragment.java -@@ -0,0 +1,141 @@ ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockSettingsFragment.java +@@ -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 @@ -2300,7 +2054,7 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+package org.chromium.components.adblock.settings; ++package org.chromium.chrome.browser.adblock.settings; + +import android.os.Bundle; + @@ -2308,17 +2062,18 @@ new file mode 100644 +import androidx.preference.PreferenceFragmentCompat; + +import org.chromium.build.BuildConfig; ++import org.chromium.chrome.browser.adblock.R; +import org.chromium.chrome.browser.preferences.Pref; -+import org.chromium.chrome.browser.profiles.Profile; ++import org.chromium.chrome.browser.profiles.ProfileManager; +import org.chromium.components.adblock.AdblockController; -+import org.chromium.components.adblock.R; +import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; +import org.chromium.components.user_prefs.UserPrefs; + -+public class AdblockSettingsFragment -+ extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { ++public class AdblockSettingsFragment extends PreferenceFragmentCompat ++ implements Preference.OnPreferenceChangeListener { + private ChromeSwitchPreference mAdblockEnabled; + private ChromeSwitchPreference mAcceptableAdsEnabled; ++ private ChromeSwitchPreference mAutoInstalledEnabled; + private Preference mFilterLists; + private Preference mAllowedDomains; + private Preference mMoreOptions; @@ -2328,6 +2083,8 @@ new file mode 100644 + "fragment_adblock_settings_filter_lists_key"; + private static final String SETTINGS_AA_ENABLED_KEY = + "fragment_adblock_settings_aa_enabled_key"; ++ private static final String SETTINGS_AUTO_INSTALL_ENABLED_KEY = ++ "fragment_adblock_settings_auto_install_enabled_key"; + private static final String SETTINGS_ALLOWED_DOMAINS_KEY = + "fragment_adblock_settings_allowed_domains_key"; + private static final String SETTINGS_MORE_OPTIONS_KEY = @@ -2342,40 +2099,52 @@ new file mode 100644 + mAdblockEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_ENABLED_KEY); + mFilterLists = findPreference(SETTINGS_FILTER_LISTS_KEY); + mAcceptableAdsEnabled = (ChromeSwitchPreference) findPreference(SETTINGS_AA_ENABLED_KEY); ++ mAutoInstalledEnabled = ++ (ChromeSwitchPreference) findPreference(SETTINGS_AUTO_INSTALL_ENABLED_KEY); + mAllowedDomains = findPreference(SETTINGS_ALLOWED_DOMAINS_KEY); + mMoreOptions = findPreference(SETTINGS_MORE_OPTIONS_KEY); + } + + private boolean areMoreOptionsEnabled() { -+ return UserPrefs.get(Profile.getLastUsedRegularProfile()) ++ return UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) + .getBoolean(Pref.ADBLOCK_MORE_OPTIONS_ENABLED); + } + + private void applyAdblockEnabled(boolean enabledValue) { + mFilterLists.setEnabled(enabledValue); + mAcceptableAdsEnabled.setEnabled(enabledValue); ++ mAutoInstalledEnabled.setEnabled(enabledValue); + mAllowedDomains.setEnabled(enabledValue); + mMoreOptions.setEnabled(enabledValue); + mMoreOptions.setVisible(areMoreOptionsEnabled()); + } + + private void synchronizePreferences() { -+ boolean enabled = AdblockController.getInstance().isEnabled(); ++ boolean enabled = ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .isEnabled(); + mAdblockEnabled.setChecked(enabled); + mAdblockEnabled.setOnPreferenceChangeListener(this); + applyAdblockEnabled(enabled); + -+ mAcceptableAdsEnabled.setChecked(AdblockController.getInstance().isAcceptableAdsEnabled()); ++ mAcceptableAdsEnabled.setChecked( ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .isAcceptableAdsEnabled()); + mAcceptableAdsEnabled.setOnPreferenceChangeListener(this); ++ ++ mAutoInstalledEnabled.setChecked( ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .isAutoInstallEnabled()); ++ mAutoInstalledEnabled.setOnPreferenceChangeListener(this); + } + + private void maybeEnableMoreOptions() { + long now = System.currentTimeMillis(); + /* Chromium does not have info about build type in its BuildConfig. -+ We'd have patch it and add - which sounds like an overkill for this -+ where ENABLE_ASSERTS is pretty close and equivalent unless DCHECKs are -+ always on. -+ enable_java_asserts = is_java_debug || dcheck_always_on */ ++ We'd have patch it and add - which sounds like an overkill for this ++ where ENABLE_ASSERTS is pretty close and equivalent unless DCHECKs are ++ always on. ++ enable_java_asserts = is_java_debug || dcheck_always_on */ + if (BuildConfig.ENABLE_ASSERTS + || mOnOffTogleTimestamp + ON_OFF_TOGGLE_COUNT_TIME_WINDOW_MS >= now) { + ++mOnOffClickCount; @@ -2385,7 +2154,7 @@ new file mode 100644 + + mOnOffTogleTimestamp = now; + if (mOnOffClickCount >= ON_OFF_TOGGLE_COUNT_TO_ENABLE_MORE_OPTIONS) { -+ UserPrefs.get(Profile.getLastUsedRegularProfile()) ++ UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) + .setBoolean(Pref.ADBLOCK_MORE_OPTIONS_ENABLED, true); + } + } @@ -2411,14 +2180,20 @@ new file mode 100644 + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference.getKey().equals(SETTINGS_ENABLED_KEY)) { -+ AdblockController.getInstance().setEnabled((Boolean) newValue); ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .setEnabled((Boolean) newValue); + + maybeEnableMoreOptions(); + + applyAdblockEnabled((Boolean) newValue); ++ } else if (preference.getKey().equals(SETTINGS_AA_ENABLED_KEY)) { ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .setAcceptableAdsEnabled((Boolean) newValue); + } else { -+ assert preference.getKey().equals(SETTINGS_AA_ENABLED_KEY); -+ AdblockController.getInstance().setAcceptableAdsEnabled((Boolean) newValue); ++ assert preference.getKey().equals(SETTINGS_AUTO_INSTALL_ENABLED_KEY); ++ ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .setAutoInstallEnabled((Boolean) newValue); + } + return true; + } @@ -2429,19 +2204,153 @@ new file mode 100644 + synchronizePreferences(); + } +} -diff --git a/components/adblock/android/translations/adblock_strings_af.xtb b/components/adblock/android/translations/adblock_strings_af.xtb +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockFilterFragmentTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockFilterFragmentTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockFilterFragmentTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import static org.mockito.Mockito.when; ++ ++import android.util.Pair; ++import android.view.View; ++import android.widget.CheckBox; ++ ++import androidx.test.filters.SmallTest; ++ ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.mockito.Mock; ++import org.mockito.MockitoAnnotations; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.ApplicationTestUtils; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.Feature; ++import org.chromium.chrome.browser.adblock.settings.AdblockFilterListsFragment; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.settings.SettingsActivityTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockController; ++import org.chromium.components.adblock.AdblockController.Subscription; ++ ++import java.net.MalformedURLException; ++import java.net.URL; ++import java.util.Arrays; ++import java.util.HashMap; ++import java.util.Map; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) ++public class AdblockFilterFragmentTest { ++ @Mock private AdblockController mAdblockControllerMock; ++ ++ @Rule ++ public final ChromeTabbedActivityTestRule mActivityTestRule = ++ new ChromeTabbedActivityTestRule(); ++ ++ @Rule ++ public SettingsActivityTestRule mTestRule = ++ new SettingsActivityTestRule<>(AdblockFilterListsFragment.class); ++ ++ @Before ++ public void setUp() throws Exception { ++ MockitoAnnotations.initMocks(this); ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ mTestRule.startSettingsActivity(); ++ ApplicationTestUtils.finishActivity(mTestRule.getActivity()); ++ } ++ ++ @Test ++ @SmallTest ++ @Feature({"adblock"}) ++ public void testLanguageFiltersNotEmpty() { ++ mTestRule.startSettingsActivity(); ++ final AdblockFilterListsFragment fragment = mTestRule.getFragment(); ++ Assert.assertNotNull(fragment); ++ Assert.assertNotEquals(0, fragment.getListView().getCount()); ++ } ++ ++ @Test ++ @SmallTest ++ @Feature({"adblock"}) ++ public void testAutoInstalledFilterListsCheckbox() throws MalformedURLException { ++ AdblockController.setInstanceForTesting(mAdblockControllerMock); ++ final String autoInstalledUrl = "https://auto_installed.com"; ++ final String manuallyInstalledUrl = "https://manually_installed.com"; ++ final String recommendedUrl = "https://some_recommendation.com"; ++ final Subscription autoInstalled = ++ new Subscription(new URL(autoInstalledUrl), "auto", "", new String[] {}, true); ++ final Subscription manuallyInstalled = ++ new Subscription( ++ new URL(manuallyInstalledUrl), "manual", "", new String[] {}, false); ++ final Subscription recommended = ++ new Subscription( ++ new URL(recommendedUrl), "recommended", "", new String[] {}, false); ++ when(mAdblockControllerMock.getRecommendedSubscriptions()) ++ .thenReturn(Arrays.asList(autoInstalled, manuallyInstalled, recommended)); ++ when(mAdblockControllerMock.getInstalledSubscriptions()) ++ .thenReturn(Arrays.asList(autoInstalled, manuallyInstalled)); ++ mTestRule.startSettingsActivity(); ++ final AdblockFilterListsFragment fragment = mTestRule.getFragment(); ++ Assert.assertNotNull(fragment); ++ Assert.assertEquals(3, fragment.getListView().getCount()); ++ ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ // Map: filter list URL => Pair(checkbox marked, checkbox enabled) ++ final Map> expectations = new HashMap<>(); ++ expectations.put(autoInstalledUrl, new Pair<>(true, false)); ++ expectations.put(manuallyInstalledUrl, new Pair<>(true, true)); ++ expectations.put(recommendedUrl, new Pair<>(false, true)); ++ for (int i = 0; i < fragment.getListView().getCount(); i++) { ++ final View view = fragment.getListView().getChildAt(i); ++ final CheckBox checkBox = view.findViewById(R.id.checkbox); ++ Assert.assertEquals( ++ expectations.get(view.getTag().toString()).first, ++ checkBox.isChecked()); ++ Assert.assertEquals( ++ expectations.get(view.getTag().toString()).second, ++ checkBox.isEnabled()); ++ } ++ }); ++ } ++} +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_af.xtb b/chrome/browser/adblock/android/translations/adblock_strings_af.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_af.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_af.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_am.xtb b/components/adblock/android/translations/adblock_strings_am.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_am.xtb b/chrome/browser/adblock/android/translations/adblock_strings_am.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_am.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_am.xtb @@ -0,0 +1,13 @@ + + @@ -2456,10 +2365,10 @@ new file mode 100644 +ኣርስዎ የሚፈልጉት የንግድ ማስታወቂያ የሚያሳዩት የድር ጣቢያራስዎ መጨመር ይችላሉ። +Domain (ዱመይን) መጨመር ይችላሉ። + -diff --git a/components/adblock/android/translations/adblock_strings_ar.xtb b/components/adblock/android/translations/adblock_strings_ar.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ar.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ar.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ar.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ar.xtb @@ -0,0 +1,13 @@ + + @@ -2474,37 +2383,37 @@ new file mode 100644 +ادعم المواقع الإلكترونية المفضلة لديك بإضافتها إلى هذه القائمة. وفي حالة إضافتها، قد تشاهد إعلانات عليها +أدخل عنوان الموقع (URL) + -diff --git a/components/adblock/android/translations/adblock_strings_as.xtb b/components/adblock/android/translations/adblock_strings_as.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_as.xtb b/chrome/browser/adblock/android/translations/adblock_strings_as.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_as.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_as.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_az.xtb b/components/adblock/android/translations/adblock_strings_az.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_az.xtb b/chrome/browser/adblock/android/translations/adblock_strings_az.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_az.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_az.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_be.xtb b/components/adblock/android/translations/adblock_strings_be.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_be.xtb b/chrome/browser/adblock/android/translations/adblock_strings_be.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_be.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_be.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_bg.xtb b/components/adblock/android/translations/adblock_strings_bg.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_bg.xtb b/chrome/browser/adblock/android/translations/adblock_strings_bg.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_bg.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_bg.xtb @@ -0,0 +1,16 @@ + + @@ -2522,10 +2431,10 @@ new file mode 100644 +Изчиств. и на данните от тези сайтове и прил.? +Изключване + -diff --git a/components/adblock/android/translations/adblock_strings_bn.xtb b/components/adblock/android/translations/adblock_strings_bn.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_bn.xtb b/chrome/browser/adblock/android/translations/adblock_strings_bn.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_bn.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_bn.xtb @@ -0,0 +1,13 @@ + + @@ -2540,10 +2449,10 @@ new file mode 100644 +এই তালিকায় সেই ওয়েবসাইটগুলি যোগ করুন যাতে বিজ্ঞাপন দেখতে চান +ডোমেন যোগ করুন + -diff --git a/components/adblock/android/translations/adblock_strings_bs.xtb b/components/adblock/android/translations/adblock_strings_bs.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_bs.xtb b/chrome/browser/adblock/android/translations/adblock_strings_bs.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_bs.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_bs.xtb @@ -0,0 +1,13 @@ + + @@ -2558,10 +2467,10 @@ new file mode 100644 +Na ovaj popis dodajte webstranice sa kojih želite vidjeti oglase +ডDodaj domen + -diff --git a/components/adblock/android/translations/adblock_strings_ca.xtb b/components/adblock/android/translations/adblock_strings_ca.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ca.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ca.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ca.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ca.xtb @@ -0,0 +1,13 @@ + + @@ -2576,10 +2485,10 @@ new file mode 100644 +Afegiu a aquesta llista tots els llocs web en què voleu veure anuncis +Afegeix un domini + -diff --git a/components/adblock/android/translations/adblock_strings_cs.xtb b/components/adblock/android/translations/adblock_strings_cs.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_cs.xtb b/chrome/browser/adblock/android/translations/adblock_strings_cs.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_cs.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_cs.xtb @@ -0,0 +1,13 @@ + + @@ -2594,10 +2503,10 @@ new file mode 100644 +Na tento seznam lze přidávat webové stránky, na kterých chcete vidět reklamy +Přidat doménu + -diff --git a/components/adblock/android/translations/adblock_strings_da.xtb b/components/adblock/android/translations/adblock_strings_da.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_da.xtb b/chrome/browser/adblock/android/translations/adblock_strings_da.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_da.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_da.xtb @@ -0,0 +1,13 @@ + + @@ -2612,10 +2521,10 @@ new file mode 100644 +Tilføj hjemmesider, du ønsker at se reklamer på, til denne liste +Tilføj domæne + -diff --git a/components/adblock/android/translations/adblock_strings_de.xtb b/components/adblock/android/translations/adblock_strings_de.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_de.xtb b/chrome/browser/adblock/android/translations/adblock_strings_de.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_de.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_de.xtb @@ -0,0 +1,13 @@ + + @@ -2630,10 +2539,10 @@ new file mode 100644 +Unterstütze deine liebsten Webseiten und füge sie zu dieser Liste hinzu, um bei deren Besuch Werbung zuzulassen. +URL hinzufügen + -diff --git a/components/adblock/android/translations/adblock_strings_el.xtb b/components/adblock/android/translations/adblock_strings_el.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_el.xtb b/chrome/browser/adblock/android/translations/adblock_strings_el.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_el.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_el.xtb @@ -0,0 +1,13 @@ + + @@ -2648,10 +2557,10 @@ new file mode 100644 +Υποστηρίξτε τους αγαπημένους σας ιστοτόπους και προσθέστε τους σε αυτή τη λίστα. Μπορεί να δείτε διαφημίσεις στους ιστοτόπους αυτούς. +Εισάγετε διεύθυνση URL + -diff --git a/components/adblock/android/translations/adblock_strings_en-GB.xtb b/components/adblock/android/translations/adblock_strings_en-GB.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_en-GB.xtb b/chrome/browser/adblock/android/translations/adblock_strings_en-GB.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_en-GB.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_en-GB.xtb @@ -0,0 +1,13 @@ + + @@ -2666,10 +2575,10 @@ new file mode 100644 +Support your favorite websites by adding them to this list. You might see ads on them. +Enter URL + -diff --git a/components/adblock/android/translations/adblock_strings_es-419.xtb b/components/adblock/android/translations/adblock_strings_es-419.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_es-419.xtb b/chrome/browser/adblock/android/translations/adblock_strings_es-419.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_es-419.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_es-419.xtb @@ -0,0 +1,13 @@ + + @@ -2684,10 +2593,10 @@ new file mode 100644 +Ayuda a tus sitios favoritos añadiéndolos a esta lista. Al hacerlo, puede que veas anuncios en ellos. +Introducir URL + -diff --git a/components/adblock/android/translations/adblock_strings_es.xtb b/components/adblock/android/translations/adblock_strings_es.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_es.xtb b/chrome/browser/adblock/android/translations/adblock_strings_es.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_es.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_es.xtb @@ -0,0 +1,13 @@ + + @@ -2702,10 +2611,10 @@ new file mode 100644 +Ayuda a tus sitios favoritos añadiéndolos a esta lista. Al hacerlo, puede que veas anuncios en ellos. +Introducir URL + -diff --git a/components/adblock/android/translations/adblock_strings_et.xtb b/components/adblock/android/translations/adblock_strings_et.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_et.xtb b/chrome/browser/adblock/android/translations/adblock_strings_et.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_et.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_et.xtb @@ -0,0 +1,13 @@ + + @@ -2720,19 +2629,19 @@ new file mode 100644 +Lisage sellesse loendisse veebisaidid, kus soovite reklaame näha +Lisa domeenid + -diff --git a/components/adblock/android/translations/adblock_strings_eu.xtb b/components/adblock/android/translations/adblock_strings_eu.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_eu.xtb b/chrome/browser/adblock/android/translations/adblock_strings_eu.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_eu.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_eu.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_fa.xtb b/components/adblock/android/translations/adblock_strings_fa.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_fa.xtb b/chrome/browser/adblock/android/translations/adblock_strings_fa.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_fa.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_fa.xtb @@ -0,0 +1,13 @@ + + @@ -2747,10 +2656,10 @@ new file mode 100644 +وب +افزودن دامنه + -diff --git a/components/adblock/android/translations/adblock_strings_fi.xtb b/components/adblock/android/translations/adblock_strings_fi.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_fi.xtb b/chrome/browser/adblock/android/translations/adblock_strings_fi.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_fi.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_fi.xtb @@ -0,0 +1,13 @@ + + @@ -2765,10 +2674,10 @@ new file mode 100644 +Lisää tähän luetteloon verkkosivustot, joilla haluat nähdä mainoksia +Lisää toimialue + -diff --git a/components/adblock/android/translations/adblock_strings_fil.xtb b/components/adblock/android/translations/adblock_strings_fil.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_fil.xtb b/chrome/browser/adblock/android/translations/adblock_strings_fil.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_fil.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_fil.xtb @@ -0,0 +1,13 @@ + + @@ -2783,10 +2692,10 @@ new file mode 100644 +Magdagdag ng mga website na gusto mong makitaan ng mga ad sa listahang ito +Magdagdag ng domain + -diff --git a/components/adblock/android/translations/adblock_strings_fr-CA.xtb b/components/adblock/android/translations/adblock_strings_fr-CA.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_fr-CA.xtb b/chrome/browser/adblock/android/translations/adblock_strings_fr-CA.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_fr-CA.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_fr-CA.xtb @@ -0,0 +1,13 @@ + + @@ -2801,10 +2710,10 @@ new file mode 100644 +Soutenez vos sites Internet préférés en les ajoutant à cette liste. Vous y verrez peut-être des publicités. +Entrez l'URL + -diff --git a/components/adblock/android/translations/adblock_strings_fr.xtb b/components/adblock/android/translations/adblock_strings_fr.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_fr.xtb b/chrome/browser/adblock/android/translations/adblock_strings_fr.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_fr.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_fr.xtb @@ -0,0 +1,13 @@ + + @@ -2819,28 +2728,28 @@ new file mode 100644 +Soutenez vos sites Internet préférés en les ajoutant à cette liste. Vous y verrez peut-être des publicités. +Entrez l'URL + -diff --git a/components/adblock/android/translations/adblock_strings_gl.xtb b/components/adblock/android/translations/adblock_strings_gl.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_gl.xtb b/chrome/browser/adblock/android/translations/adblock_strings_gl.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_gl.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_gl.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_gu.xtb b/components/adblock/android/translations/adblock_strings_gu.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_gu.xtb b/chrome/browser/adblock/android/translations/adblock_strings_gu.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_gu.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_gu.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_hi.xtb b/components/adblock/android/translations/adblock_strings_hi.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_hi.xtb b/chrome/browser/adblock/android/translations/adblock_strings_hi.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_hi.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_hi.xtb @@ -0,0 +1,13 @@ + + @@ -2855,10 +2764,10 @@ new file mode 100644 +उन वेबसाइट्स को जोड़ें, जिन्हें आप इस सूची में विज्ञापन देखना चाहते हैं +डोमेन जोड़ें + -diff --git a/components/adblock/android/translations/adblock_strings_hr.xtb b/components/adblock/android/translations/adblock_strings_hr.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_hr.xtb b/chrome/browser/adblock/android/translations/adblock_strings_hr.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_hr.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_hr.xtb @@ -0,0 +1,13 @@ + + @@ -2873,10 +2782,10 @@ new file mode 100644 +Na ovaj popis dodajte web-mjesta s kojih želite vidjeti reklame +Dodaj domen + -diff --git a/components/adblock/android/translations/adblock_strings_hu.xtb b/components/adblock/android/translations/adblock_strings_hu.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_hu.xtb b/chrome/browser/adblock/android/translations/adblock_strings_hu.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_hu.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_hu.xtb @@ -0,0 +1,13 @@ + + @@ -2891,19 +2800,19 @@ new file mode 100644 +Támogassa kedvenc weboldalait e listához történő hozzáadással. Előfordulhat, hogy hirdetéseket is láthat rajtuk. +URL-cím megadása + -diff --git a/components/adblock/android/translations/adblock_strings_hy.xtb b/components/adblock/android/translations/adblock_strings_hy.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_hy.xtb b/chrome/browser/adblock/android/translations/adblock_strings_hy.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_hy.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_hy.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_id.xtb b/components/adblock/android/translations/adblock_strings_id.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_id.xtb b/chrome/browser/adblock/android/translations/adblock_strings_id.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_id.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_id.xtb @@ -0,0 +1,13 @@ + + @@ -2918,19 +2827,19 @@ new file mode 100644 +Tambah situs web yang ingin Anda lihat iklan di dalamnya ke daftar ini +Tambah domain + -diff --git a/components/adblock/android/translations/adblock_strings_is.xtb b/components/adblock/android/translations/adblock_strings_is.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_is.xtb b/chrome/browser/adblock/android/translations/adblock_strings_is.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_is.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_is.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_it.xtb b/components/adblock/android/translations/adblock_strings_it.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_it.xtb b/chrome/browser/adblock/android/translations/adblock_strings_it.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_it.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_it.xtb @@ -0,0 +1,13 @@ + + @@ -2945,10 +2854,10 @@ new file mode 100644 +Sostieni i tuoi siti web preferiti aggiungendoli a questo elenco. È possibile che su questi siti web appaiano delle pubblicità. +Inserisci URL + -diff --git a/components/adblock/android/translations/adblock_strings_iw.xtb b/components/adblock/android/translations/adblock_strings_iw.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_iw.xtb b/chrome/browser/adblock/android/translations/adblock_strings_iw.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_iw.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_iw.xtb @@ -0,0 +1,13 @@ + + @@ -2963,10 +2872,10 @@ new file mode 100644 +לרשימה הזו הוסף אתרים שבהם תרצה לראות פרסומות +הוסף דומיין + -diff --git a/components/adblock/android/translations/adblock_strings_ja.xtb b/components/adblock/android/translations/adblock_strings_ja.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ja.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ja.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ja.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ja.xtb @@ -0,0 +1,13 @@ + + @@ -2981,19 +2890,19 @@ new file mode 100644 +このリストにお気に入りのウェブサイトを追加して支援しましょう。広告が表示される場合があります。 +URL を入力してください + -diff --git a/components/adblock/android/translations/adblock_strings_ka.xtb b/components/adblock/android/translations/adblock_strings_ka.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ka.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ka.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ka.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ka.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_kk.xtb b/components/adblock/android/translations/adblock_strings_kk.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_kk.xtb b/chrome/browser/adblock/android/translations/adblock_strings_kk.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_kk.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_kk.xtb @@ -0,0 +1,13 @@ + + @@ -3008,10 +2917,10 @@ new file mode 100644 +Осы тізімге жарнамалар көруді қалайтын веб-сайттарды қосу +Домен қосу + -diff --git a/components/adblock/android/translations/adblock_strings_km.xtb b/components/adblock/android/translations/adblock_strings_km.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_km.xtb b/chrome/browser/adblock/android/translations/adblock_strings_km.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_km.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_km.xtb @@ -0,0 +1,13 @@ + + @@ -3026,19 +2935,19 @@ new file mode 100644 +បន្ថែម +បន្ថែមដូមែន + -diff --git a/components/adblock/android/translations/adblock_strings_kn.xtb b/components/adblock/android/translations/adblock_strings_kn.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_kn.xtb b/chrome/browser/adblock/android/translations/adblock_strings_kn.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_kn.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_kn.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_ko.xtb b/components/adblock/android/translations/adblock_strings_ko.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ko.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ko.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ko.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ko.xtb @@ -0,0 +1,13 @@ + + @@ -3053,28 +2962,28 @@ new file mode 100644 +이 목록에 즐겨 찾는 웹 사이트를 추가하여 지원할 수 있습니다. 광고가 표시될 수 있습니다. +URL 입력 + -diff --git a/components/adblock/android/translations/adblock_strings_ky.xtb b/components/adblock/android/translations/adblock_strings_ky.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ky.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ky.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ky.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ky.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_lo.xtb b/components/adblock/android/translations/adblock_strings_lo.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_lo.xtb b/chrome/browser/adblock/android/translations/adblock_strings_lo.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_lo.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_lo.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_lt.xtb b/components/adblock/android/translations/adblock_strings_lt.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_lt.xtb b/chrome/browser/adblock/android/translations/adblock_strings_lt.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_lt.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_lt.xtb @@ -0,0 +1,13 @@ + + @@ -3089,10 +2998,10 @@ new file mode 100644 +Į šį sąrašą pridėkite svetaines, kurių skelbimus norite matyti +Pridėti domeną + -diff --git a/components/adblock/android/translations/adblock_strings_lv.xtb b/components/adblock/android/translations/adblock_strings_lv.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_lv.xtb b/chrome/browser/adblock/android/translations/adblock_strings_lv.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_lv.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_lv.xtb @@ -0,0 +1,13 @@ + + @@ -3107,10 +3016,10 @@ new file mode 100644 +Pievienojiet šim sarakstam tīmekļa vietnes, kurās vēlaties redzēt reklāmas +Pievienot domēnu + -diff --git a/components/adblock/android/translations/adblock_strings_mk.xtb b/components/adblock/android/translations/adblock_strings_mk.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_mk.xtb b/chrome/browser/adblock/android/translations/adblock_strings_mk.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_mk.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_mk.xtb @@ -0,0 +1,13 @@ + + @@ -3125,37 +3034,37 @@ new file mode 100644 +На оваа листа додај веб сајтови на коишто сакаш да видиш реклами +Додај домен + -diff --git a/components/adblock/android/translations/adblock_strings_ml.xtb b/components/adblock/android/translations/adblock_strings_ml.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ml.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ml.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ml.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ml.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_mn.xtb b/components/adblock/android/translations/adblock_strings_mn.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_mn.xtb b/chrome/browser/adblock/android/translations/adblock_strings_mn.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_mn.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_mn.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_mr.xtb b/components/adblock/android/translations/adblock_strings_mr.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_mr.xtb b/chrome/browser/adblock/android/translations/adblock_strings_mr.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_mr.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_mr.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_ms.xtb b/components/adblock/android/translations/adblock_strings_ms.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ms.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ms.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ms.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ms.xtb @@ -0,0 +1,13 @@ + + @@ -3170,10 +3079,10 @@ new file mode 100644 +Tambah laman web yang anda ingin lihat iklan pada senarai ini +Tambaha domain + -diff --git a/components/adblock/android/translations/adblock_strings_my.xtb b/components/adblock/android/translations/adblock_strings_my.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_my.xtb b/chrome/browser/adblock/android/translations/adblock_strings_my.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_my.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_my.xtb @@ -0,0 +1,13 @@ + + @@ -3188,19 +3097,19 @@ new file mode 100644 +ဤစာရင္းတြင္ ေၾကာ္ျငာမ်ားကိုျမင္ခ်င္သည့္ ဝဘ္ဆိုက္မ်ားကိုထည့္သြင္းပါ +ডဒိုမိန္းကိုထည့္ပါ + -diff --git a/components/adblock/android/translations/adblock_strings_ne.xtb b/components/adblock/android/translations/adblock_strings_ne.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ne.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ne.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ne.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ne.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_nl.xtb b/components/adblock/android/translations/adblock_strings_nl.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_nl.xtb b/chrome/browser/adblock/android/translations/adblock_strings_nl.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_nl.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_nl.xtb @@ -0,0 +1,13 @@ + + @@ -3215,37 +3124,37 @@ new file mode 100644 +Ondersteun uw favoriete websites door ze aan deze lijst toe te voegen. Er kunnen advertenties op worden weergegeven. +Voer URL in + -diff --git a/components/adblock/android/translations/adblock_strings_no.xtb b/components/adblock/android/translations/adblock_strings_no.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_no.xtb b/chrome/browser/adblock/android/translations/adblock_strings_no.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_no.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_no.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_or.xtb b/components/adblock/android/translations/adblock_strings_or.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_or.xtb b/chrome/browser/adblock/android/translations/adblock_strings_or.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_or.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_or.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_pa.xtb b/components/adblock/android/translations/adblock_strings_pa.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_pa.xtb b/chrome/browser/adblock/android/translations/adblock_strings_pa.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_pa.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_pa.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_pl.xtb b/components/adblock/android/translations/adblock_strings_pl.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_pl.xtb b/chrome/browser/adblock/android/translations/adblock_strings_pl.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_pl.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_pl.xtb @@ -0,0 +1,13 @@ + + @@ -3260,10 +3169,10 @@ new file mode 100644 +Wspieraj swoje ulubione witryny, dodając je do tej listy. Reklamy będą na nich wyświetlane. +Wprowadź URL + -diff --git a/components/adblock/android/translations/adblock_strings_pt-BR.xtb b/components/adblock/android/translations/adblock_strings_pt-BR.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_pt-BR.xtb b/chrome/browser/adblock/android/translations/adblock_strings_pt-BR.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_pt-BR.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_pt-BR.xtb @@ -0,0 +1,13 @@ + + @@ -3278,10 +3187,10 @@ new file mode 100644 +Para apoiar os seus sites favoritos, adicione-os a essa lista. Você poderá ver anúncios neles. +Inserir URL + -diff --git a/components/adblock/android/translations/adblock_strings_pt-PT.xtb b/components/adblock/android/translations/adblock_strings_pt-PT.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_pt-PT.xtb b/chrome/browser/adblock/android/translations/adblock_strings_pt-PT.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_pt-PT.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_pt-PT.xtb @@ -0,0 +1,13 @@ + + @@ -3296,10 +3205,10 @@ new file mode 100644 +Adicione os sites Web dos quais pretende ver anúncios a esta lista +Adicionar domínio + -diff --git a/components/adblock/android/translations/adblock_strings_ro.xtb b/components/adblock/android/translations/adblock_strings_ro.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ro.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ro.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ro.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ro.xtb @@ -0,0 +1,13 @@ + + @@ -3314,10 +3223,10 @@ new file mode 100644 +Adăugați website-uri pe care doriți vedeți reclame la această listă +Adăugare domeniu + -diff --git a/components/adblock/android/translations/adblock_strings_ru.xtb b/components/adblock/android/translations/adblock_strings_ru.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ru.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ru.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ru.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ru.xtb @@ -0,0 +1,13 @@ + + @@ -3332,19 +3241,19 @@ new file mode 100644 +Поддержите ваши любимые сайты, добавив их в этот список. На этих сайтах вы можете увидеть рекламу. +Ввести URL-адрес + -diff --git a/components/adblock/android/translations/adblock_strings_si.xtb b/components/adblock/android/translations/adblock_strings_si.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_si.xtb b/chrome/browser/adblock/android/translations/adblock_strings_si.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_si.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_si.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_sk.xtb b/components/adblock/android/translations/adblock_strings_sk.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sk.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sk.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sk.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sk.xtb @@ -0,0 +1,13 @@ + + @@ -3359,10 +3268,10 @@ new file mode 100644 +Pridajte webové lokality, ktoré chcete vidieť na tomto zozname +Pridať doménu + -diff --git a/components/adblock/android/translations/adblock_strings_sl.xtb b/components/adblock/android/translations/adblock_strings_sl.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sl.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sl.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sl.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sl.xtb @@ -0,0 +1,13 @@ + + @@ -3377,28 +3286,28 @@ new file mode 100644 +Dodajte spletne strani, na katerih želite videti oglase, na ta seznam +Dodaj domeno + -diff --git a/components/adblock/android/translations/adblock_strings_sq.xtb b/components/adblock/android/translations/adblock_strings_sq.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sq.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sq.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sq.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sq.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_sr-Latn.xtb b/components/adblock/android/translations/adblock_strings_sr-Latn.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sr-Latn.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sr-Latn.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sr-Latn.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sr-Latn.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_sr.xtb b/components/adblock/android/translations/adblock_strings_sr.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sr.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sr.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sr.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sr.xtb @@ -0,0 +1,13 @@ + + @@ -3413,10 +3322,10 @@ new file mode 100644 +Na ovu listu dodajte veb lokacije sa kojih želite da vidite reklame +Dodaj domen + -diff --git a/components/adblock/android/translations/adblock_strings_sv.xtb b/components/adblock/android/translations/adblock_strings_sv.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sv.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sv.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sv.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sv.xtb @@ -0,0 +1,13 @@ + + @@ -3431,10 +3340,10 @@ new file mode 100644 +Lägg till webbplatser där du vill se annonser i den här listan +Lägg till domän + -diff --git a/components/adblock/android/translations/adblock_strings_sw.xtb b/components/adblock/android/translations/adblock_strings_sw.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_sw.xtb b/chrome/browser/adblock/android/translations/adblock_strings_sw.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_sw.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_sw.xtb @@ -0,0 +1,13 @@ + + @@ -3449,28 +3358,28 @@ new file mode 100644 +Ongeza tovuti ambazo kwazo ungependa kuona ads kwenye orodha hii +Ongeza domeini + -diff --git a/components/adblock/android/translations/adblock_strings_ta.xtb b/components/adblock/android/translations/adblock_strings_ta.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ta.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ta.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ta.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ta.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_te.xtb b/components/adblock/android/translations/adblock_strings_te.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_te.xtb b/chrome/browser/adblock/android/translations/adblock_strings_te.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_te.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_te.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_th.xtb b/components/adblock/android/translations/adblock_strings_th.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_th.xtb b/chrome/browser/adblock/android/translations/adblock_strings_th.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_th.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_th.xtb @@ -0,0 +1,13 @@ + + @@ -3485,10 +3394,10 @@ new file mode 100644 +เพิ่มเว็บไซต์ที่คุณต้องการเห็นโฆษณาบนรายการนี้ +เพิ่มโดเมน + -diff --git a/components/adblock/android/translations/adblock_strings_tr.xtb b/components/adblock/android/translations/adblock_strings_tr.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_tr.xtb b/chrome/browser/adblock/android/translations/adblock_strings_tr.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_tr.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_tr.xtb @@ -0,0 +1,13 @@ + + @@ -3503,10 +3412,10 @@ new file mode 100644 +En sevdiğiniz web sitelerini bu listeye ekleyerek destekleyin. Bu sitelerde reklamlar görebilirsiniz. +URL gir + -diff --git a/components/adblock/android/translations/adblock_strings_uk.xtb b/components/adblock/android/translations/adblock_strings_uk.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_uk.xtb b/chrome/browser/adblock/android/translations/adblock_strings_uk.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_uk.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_uk.xtb @@ -0,0 +1,13 @@ + + @@ -3521,10 +3430,10 @@ new file mode 100644 +Додайте веб-сайти у білий список, щоб дозволити відображення реклами +Додати домен + -diff --git a/components/adblock/android/translations/adblock_strings_ur.xtb b/components/adblock/android/translations/adblock_strings_ur.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_ur.xtb b/chrome/browser/adblock/android/translations/adblock_strings_ur.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_ur.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_ur.xtb @@ -0,0 +1,13 @@ + + @@ -3539,19 +3448,19 @@ new file mode 100644 +اس فہرست میں ویب سائٹوں کا اضافہ کریں آپ جن کے اشتہارات دیکھنا چاہتے ہیں +ڈومین کا اضافہ کریں + -diff --git a/components/adblock/android/translations/adblock_strings_uz.xtb b/components/adblock/android/translations/adblock_strings_uz.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_uz.xtb b/chrome/browser/adblock/android/translations/adblock_strings_uz.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_uz.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_uz.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_vi.xtb b/components/adblock/android/translations/adblock_strings_vi.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_vi.xtb b/chrome/browser/adblock/android/translations/adblock_strings_vi.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_vi.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_vi.xtb @@ -0,0 +1,13 @@ + + @@ -3566,10 +3475,10 @@ new file mode 100644 +Thêm các trang web mà bạn muốn xem quảng cáo vào danh sách này +Thêm tên miền + -diff --git a/components/adblock/android/translations/adblock_strings_zh-CN.xtb b/components/adblock/android/translations/adblock_strings_zh-CN.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_zh-CN.xtb b/chrome/browser/adblock/android/translations/adblock_strings_zh-CN.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_zh-CN.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_zh-CN.xtb @@ -0,0 +1,13 @@ + + @@ -3584,19 +3493,19 @@ new file mode 100644 +支持您最喜爱的网站,将其添加到此列表。您可能在这些网站上看到广告。 +输入 URL + -diff --git a/components/adblock/android/translations/adblock_strings_zh-HK.xtb b/components/adblock/android/translations/adblock_strings_zh-HK.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_zh-HK.xtb b/chrome/browser/adblock/android/translations/adblock_strings_zh-HK.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_zh-HK.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_zh-HK.xtb @@ -0,0 +1,4 @@ + + + + -diff --git a/components/adblock/android/translations/adblock_strings_zh-TW.xtb b/components/adblock/android/translations/adblock_strings_zh-TW.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_zh-TW.xtb b/chrome/browser/adblock/android/translations/adblock_strings_zh-TW.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_zh-TW.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_zh-TW.xtb @@ -0,0 +1,13 @@ + + @@ -3611,10 +3520,10 @@ new file mode 100644 +將您想要在其上查看廣告的網站新增至此清單 +新增網域 + -diff --git a/components/adblock/android/translations/adblock_strings_zu.xtb b/components/adblock/android/translations/adblock_strings_zu.xtb +diff --git a/chrome/browser/adblock/android/translations/adblock_strings_zu.xtb b/chrome/browser/adblock/android/translations/adblock_strings_zu.xtb new file mode 100644 --- /dev/null -+++ b/components/adblock/android/translations/adblock_strings_zu.xtb ++++ b/chrome/browser/adblock/android/translations/adblock_strings_zu.xtb @@ -0,0 +1,4 @@ + + diff --git a/build/cromite_patches/eyeo-133.0.6943.49-base.patch b/build/cromite_patches/eyeo-133.0.6943.49-base.patch new file mode 100644 index 0000000000000000000000000000000000000000..3ab554e10b9a70550ab6b2b3c2daea5ececd0a59 --- /dev/null +++ b/build/cromite_patches/eyeo-133.0.6943.49-base.patch @@ -0,0 +1,51872 @@ +From: chromium-sdk +Date: Fri, 14 Feb 2025 08:26:31 +0100 +Subject: eyeo Browser Ad filtering Solution: Base Module + +Based on Chromium 133.0.6943.49 +--- + BUILD.gn | 4 + + DEPS | 12 + + LICENSE | 15 + + README.md | 12 + + base/threading/thread_restrictions.h | 9 + + base/trace_event/builtin_categories.h | 1 + + components/BUILD.gn | 10 + + 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 | 68 + + .../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 +- + 362 files changed, 48854 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/BUILD.gn b/BUILD.gn +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -1,6 +1,9 @@ + # 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. + + # This is the root build file for GN. GN will start processing by loading this + # file, and recursively load all dependencies until all dependencies are either +@@ -440,6 +443,7 @@ group("gn_all") { + "//chrome/browser/android/examples/partner_browser_customizations_provider:partner_browser_customizations_example_apk", + "//content/shell/android:content_shell_test_apk", + ] ++ + } + + if (enable_chrome_android_internal) { +diff --git a/DEPS b/DEPS +--- a/DEPS ++++ b/DEPS +@@ -168,6 +168,7 @@ vars = { + # flag is set True. + 'checkout_wpr_archives': False, + ++ + # By default, do not check out WebKit for iOS, as it is not needed unless + # running against ToT WebKit rather than system WebKit. This can be overridden + # e.g. with custom_vars. +@@ -262,6 +263,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', +@@ -502,6 +504,11 @@ vars = { + # and whatever else without interference from each other. + 'llvm_libc_revision': 'a02de4d0d992b110c8b180fdec91258e7b60265f', + ++ # 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': '7f8b68f91ca8b192375f5e71cd81fb3ed9650ef3', +@@ -1460,6 +1467,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' + '@' + '441c86221443f48e818335d51f84cf1880c35aa4', + }, +@@ -1589,6 +1600,7 @@ deps = { + 'url': Var('chromium_git') + '/external/github.com/google/Accessibility-Test-Framework-for-Android.git' + '@' + '4a764c690353ea136c82f1a696a70bf38d1ef5fe', + }, + ++ + 'src/third_party/android_build_tools/protoc/cipd': { + 'packages': [ + { +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 . +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 BrowserDataBackMigrator; + class LoginEventRecorder; +@@ -660,6 +667,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 +@@ -96,6 +96,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") +@@ -177,6 +180,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", +@@ -893,6 +897,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", +@@ -909,6 +914,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", +@@ -1010,6 +1016,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", +@@ -1219,6 +1226,9 @@ if (use_blink) { + + deps = [ + "//base", ++ "//components/adblock/core/classifier:perf_tests", ++ "//components/adblock/core/converter:perf_tests", ++ "//components/adblock/core/subscription:perf_tests", + "//components/attribution_reporting", + "//components/bookmarks/test", + "//components/discardable_memory/common", +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 . ++ ++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 (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. ++ 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. ++ ++ ++ Copyright (C) -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 . ++ ++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: ++ ++ Copyright (C) -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 ++. ++ ++ 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 ++. +\ 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 . ++ ++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 . ++ ++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 . ++ */ ++ ++#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 AdblockContentBrowserClient : public ContentBrowserClientBase { ++ public: ++ template ++ explicit AdblockContentBrowserClient(Args&&... args) ++ : ContentBrowserClientBase(std::forward(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& user_agent, ++ mojo::PendingRemote ++ 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 navigation_id, ++ ukm::SourceIdObj ukm_source_id, ++ network::URLLoaderFactoryBuilder& factory_builder, ++ mojo::PendingRemote* ++ header_client, ++ bool* bypass_redirect_checks, ++ bool* disable_secure_dns, ++ network::mojom::URLLoaderFactoryOverridePtr* factory_override, ++ scoped_refptr navigation_response_task_runner) ++ override; ++ ++ void RegisterBrowserInterfaceBindersForFrame( ++ content::RenderFrameHost* render_frame_host, ++ mojo::BinderMapWithContext* 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& user_agent, ++ mojo::PendingRemote ++ handshake_client, ++ adblock::FilterMatchResult result); ++ ++ base::WeakPtrFactory> ++ weak_factory_{this}; ++ ++#if BUILDFLAG(ENABLE_EXTENSIONS) ++ static bool force_adblock_proxy_for_testing_; ++#endif ++}; ++ ++#if BUILDFLAG(ENABLE_EXTENSIONS) ++// static ++template ++bool AdblockContentBrowserClient< ++ ContentBrowserClientBase>::force_adblock_proxy_for_testing_ = false; ++ ++// static ++template ++void AdblockContentBrowserClient< ++ ContentBrowserClientBase>::ForceAdblockProxyForTesting() { ++ force_adblock_proxy_for_testing_ = true; ++} ++#endif ++ ++template ++bool AdblockContentBrowserClient:: ++ WillInterceptWebSocket(content::RenderFrameHost* frame) { ++ if (frame && IsFilteringNeeded(GetBrowserContext(frame))) { ++ return true; ++ } ++ return ContentBrowserClientBase::WillInterceptWebSocket(frame); ++} ++ ++template ++void AdblockContentBrowserClient::CreateWebSocket( ++ content::RenderFrameHost* frame, ++ content::ContentBrowserClient::WebSocketFactory factory, ++ const GURL& url, ++ const net::SiteForCookies& site_for_cookies, ++ const absl::optional& user_agent, ++ mojo::PendingRemote ++ 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 ++void AdblockContentBrowserClient:: ++ RegisterBrowserInterfaceBindersForFrame( ++ content::RenderFrameHost* render_frame_host, ++ mojo::BinderMapWithContext* map) { ++ ContentBrowserClientBase::RegisterBrowserInterfaceBindersForFrame( ++ render_frame_host, map); ++ content::RegisterWebUIControllerInterfaceBinder< ++ ::mojom::adblock_internals::AdblockInternalsPageHandler, ++ adblock::AdblockInternalsUI>(map); ++} ++ ++template ++void AdblockContentBrowserClient:: ++ 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 navigation_id, ++ ukm::SourceIdObj ukm_source_id, ++ network::URLLoaderFactoryBuilder& factory_builder, ++ mojo::PendingRemote* ++ header_client, ++ bool* bypass_redirect_checks, ++ bool* disable_secure_dns, ++ network::mojom::URLLoaderFactoryOverridePtr* factory_override, ++ scoped_refptr ++ 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 ++void AdblockContentBrowserClient:: ++ OnWebSocketFilterCheckCompleted( ++ content::GlobalRenderFrameHostId render_frame_host_id, ++ content::ContentBrowserClient::WebSocketFactory factory, ++ const GURL& url, ++ const net::SiteForCookies& site_for_cookies, ++ const absl::optional& user_agent, ++ mojo::PendingRemote ++ 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 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 ++bool AdblockContentBrowserClient::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 . ++ */ ++ ++#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 receiver, ++ mojo::PendingRemote target_factory, ++ const std::string& user_agent, ++ bool use_test_loader) { ++ const void* const kAdblockContextUserDataKey = &kAdblockContextUserDataKey; ++ auto* self = static_cast( ++ 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( ++ 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( ++ 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTEXT_DATA_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTEXT_DATA_H_ ++ ++#include ++#include ++#include ++ ++#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 receiver, ++ mojo::PendingRemote target_factory, ++ const std::string& user_agent, ++ bool use_test_loader); ++ ++ private: ++ AdblockContextData(); ++ ++ void RemoveProxy(AdblockURLLoaderFactory* proxy); ++ ++ std::set, base::UniquePtrComparator> ++ proxies_; ++ ++ base::WeakPtrFactory 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 . ++ */ ++#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; ++ ++} // 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 . ++ */ ++ ++#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 ++ 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 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 . ++ */ ++ ++#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 topic_provider_content); ++ bool IsSubscribedToTestpagesFL() const; ++ ++ raw_ptr context_; ++ mojo::Receiver ++ 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,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 . \ ++ */ ++ ++#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( ++ base::span(kAdblockInternalsResources, kAdblockInternalsResourcesSize)); ++ source->SetDefaultResource(IDR_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_HTML); ++} ++ ++AdblockInternalsUI::~AdblockInternalsUI() = default; ++ ++WEB_UI_CONTROLLER_TYPE_IMPL(AdblockInternalsUI) ++ ++void AdblockInternalsUI::BindInterface( ++ mojo::PendingReceiver ++ receiver) { ++ handler_ = std::make_unique( ++ 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 . ++ */ ++ ++#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 ++ 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 . ++ */ ++ ++#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 loader_receiver, ++ int32_t request_id, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ mojo::PendingRemote client, ++ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation); ++ ++ void FollowRedirect( ++ const std::vector& removed_headers, ++ const net::HttpRequestHeaders& modified_headers, ++ const net::HttpRequestHeaders& modified_cors_exempt_headers, ++ const absl::optional& 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 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; ++ using CheckRewriteFilterMatchCallback = ++ base::OnceCallback&)>; ++ ++ void OnBindingsClosed(); ++ void OnClientDisconnected(); ++ void Start(uint32_t options, ++ network::ResourceRequest request, ++ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, ++ mojo::PendingReceiver target_loader, ++ mojo::PendingRemote proxy_client, ++ const absl::optional& rewrite); ++ void OnRequestFilterMatchResult( ++ ::mojo::PendingReceiver loader, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ ::mojo::PendingRemote 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 cached_metadata, ++ FilterMatchResult result, ++ network::mojom::ParsedHeadersPtr parsed_headers); ++ void OnRequestError(int error_code); ++ void CheckFilterMatch(CheckFilterMatchCallback callback); ++ void ProcessResponseHeaders( ++ const scoped_refptr& headers, ++ ProcessResponseHeadersCallback callback); ++ void CheckRewriteFilterMatch(CheckRewriteFilterMatchCallback callback); ++ void OnRequestUrlClassified(CheckFilterMatchCallback callback, ++ FilterMatchResult result); ++ void OnResponseHeadersClassified( ++ const scoped_refptr& 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&)> callback, ++ const absl::optional& 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 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 target_client_; ++ mojo::Receiver loader_receiver_; ++ base::RepeatingCallback ++ 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 target_loader_; ++ mojo::Receiver client_receiver_{this}; ++ base::WeakPtrFactory ++ weak_factory_{this}; ++}; ++ ++AdblockURLLoaderFactory::InProgressRequest::InProgressRequest( ++ AdblockURLLoaderFactory* factory, ++ mojo::PendingReceiver loader_receiver, ++ int32_t request_id, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ mojo::PendingRemote 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& removed_headers, ++ const net::HttpRequestHeaders& modified_headers, ++ const net::HttpRequestHeaders& modified_cors_exempt_headers, ++ const absl::optional& 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 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& 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 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& 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{}); ++ 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& 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&)> callback, ++ const absl::optional& 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 target_loader, ++ mojo::PendingRemote proxy_client, ++ const absl::optional& 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::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 target_loader, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ ::mojo::PendingRemote 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 receiver, ++ mojo::PendingRemote 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 loader, ++ int32_t request_id, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ ::mojo::PendingRemote client, ++ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { ++ requests_.insert(std::make_unique( ++ this, std::move(loader), request_id, options, request, std::move(client), ++ traffic_annotation)); ++} ++ ++void AdblockURLLoaderFactory::Clone( ++ ::mojo::PendingReceiver 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 . ++ */ ++ ++#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 ++ subscription_service = nullptr; ++ raw_ptr ++ resource_classifier = nullptr; ++ raw_ptr element_hider = nullptr; ++ raw_ptr sitekey_storage = ++ nullptr; ++ raw_ptr ++ csp_injector = nullptr; ++}; ++ ++// Processing network requests and responses. ++class AdblockURLLoaderFactory : public network::mojom::URLLoaderFactory { ++ public: ++ using DisconnectCallback = base::OnceCallback; ++ ++ AdblockURLLoaderFactory( ++ AdblockURLLoaderFactoryConfig config, ++ RequestInitiator request_initiator, ++ mojo::PendingReceiver receiver, ++ mojo::PendingRemote 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 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 proxy_receivers_; ++ std::set, base::UniquePtrComparator> ++ requests_; ++ mojo::Remote 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 . ++ */ ++ ++#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 buffer; ++ // url::EncodeURIComponent(base.c_str(), base.size(), &buffer); ++ // std::string encoded(buffer.data(), buffer.length()); ++ VLOG(2) << "[eyeo] Encoded payload: " << encoded; ++ url::RawCanonOutputT 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 GetCommandElements(const GURL& url) { ++ std::vector 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 (FilteringConfiguration::*getter)() const, ++ void (FilteringConfiguration::*action)(const std::string&)) { ++ for (const auto& item : (configuration->*getter)()) { ++ (configuration->*action)(item); ++ } ++} ++ ++std::vector List( ++ FilteringConfiguration* configuration, ++ std::vector (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 ListSubscriptions( ++ FilteringConfiguration* configuration) { ++ std::vector 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 receiver, ++ mojo::PendingRemote 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 loader, ++ int32_t request_id, ++ uint32_t options, ++ const network::ResourceRequest& request, ++ ::mojo::PendingRemote 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 client) const { ++ auto response_head = network::mojom::URLResponseHead::New(); ++ response_head->mime_type = "text/plain"; ++ mojo::Remote 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(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 (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 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 . ++ */ ++ ++#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 receiver, ++ mojo::PendingRemote 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 client) const; ++ raw_ptr subscription_service_; ++ raw_ptr 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 . ++ */ ++ ++#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::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 ++AdblockWebUIControllerFactory::CreateWebUIControllerForURL( ++ content::WebUI* web_ui, ++ const GURL& url) { ++ if (url.host() == adblock::kChromeUIAdblockInternalsHost) { ++ return std::make_unique(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 . ++ */ ++ ++#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 CreateWebUIControllerForURL( ++ content::WebUI* web_ui, ++ const GURL& url) override; ++ ++ private: ++ friend struct base::DefaultSingletonTraits; ++ ++ 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 . ++ */ ++ ++#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 frame_hierarchy_builder, ++ base::RepeatingCallback navigation_counter) ++ : content::WebContentsObserver(web_contents), ++ content::WebContentsUserData(*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(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 . ++ */ ++ ++#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 { ++ public: ++ AdblockWebContentObserver( ++ content::WebContents* web_contents, ++ SubscriptionService* subscription_service, ++ ElementHider* element_hider, ++ SitekeyStorage* sitekey_storage, ++ std::unique_ptr frame_hierarchy_builder, ++ base::RepeatingCallback ++ 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; ++ WEB_CONTENTS_USER_DATA_KEY_DECL(); ++ ++ raw_ptr subscription_service_; ++ raw_ptr element_hider_; ++ raw_ptr sitekey_storage_; ++ ++ std::unique_ptr frame_hierarchy_builder_; ++ base::RepeatingCallback 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_ ++ ++#include ++ ++#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; ++ ++// 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& 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 . ++ */ ++ ++#include "components/adblock/content/browser/content_security_policy_injector_impl.h" ++ ++#include ++ ++#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 GetCspInjections( ++ const SubscriptionService::Snapshot subscription_collections, ++ const GURL request_url, ++ const std::vector frame_hierarchy_chain) { ++ TRACE_EVENT1("eyeo", "GetCspInjection", "url", request_url.spec()); ++ std::set 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 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& 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 headers, ++ InsertContentSecurityPolicyHeadersCallback callback, ++ std::set 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_ ++ ++#include ++#include ++ ++#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 frame_hierarchy_builder); ++ ++ ~ContentSecurityPolicyInjectorImpl() final; ++ ++ void InsertContentSecurityPolicyHeadersIfApplicable( ++ const GURL& request_url, ++ const RequestInitiator& request_initiator, ++ const scoped_refptr& headers, ++ InsertContentSecurityPolicyHeadersCallback callback) final; ++ ++ private: ++ void OnCspInjectionsSearchFinished( ++ const GURL request_url, ++ const scoped_refptr headers, ++ InsertContentSecurityPolicyHeadersCallback callback, ++ std::set csp_injections); ++ ++ SEQUENCE_CHECKER(sequence_checker_); ++ raw_ptr subscription_service_; ++ std::unique_ptr frame_hierarchy_builder_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_ ++ ++#include ++#include ++ ++#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 frame_hierarchy, ++ content::RenderFrameHost* render_frame_host, ++ SiteKey sitekey, ++ base::OnceCallback 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 . ++ */ ++ ++#include "components/adblock/content/browser/element_hider_impl.h" ++ ++#include ++ ++#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 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 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 ++ 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 frame_hierarchy, ++ content::RenderFrameHost* render_frame_host, ++ SiteKey sitekey, ++ base::OnceCallback 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_ ++ ++#include ++ ++#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 frame_hierarchy, ++ content::RenderFrameHost* render_frame_host, ++ SiteKey sitekey, ++ base::OnceCallback 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 subscription_service_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#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(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 . ++ */ ++ ++#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 { ++ 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 . ++ */ ++ ++#include "components/adblock/content/browser/eyeo_page_info.h" ++ ++namespace adblock { ++ ++PAGE_USER_DATA_KEY_IMPL(EyeoPageInfo); ++ ++EyeoPageInfo::EyeoPageInfo(content::Page& page) ++ : content::PageUserData(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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_PAGE_INFO_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_EYEO_PAGE_INFO_H_ ++ ++#include ++ ++#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 { ++ 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 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/adblock_request_throttle_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++ ++// static ++AdblockRequestThrottleFactory* AdblockRequestThrottleFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++AdblockRequestThrottleFactory::AdblockRequestThrottleFactory() ++ : BrowserContextKeyedServiceFactory( ++ "AdblockRequestThrottle", ++ BrowserContextDependencyManager::GetInstance()) {} ++ ++AdblockRequestThrottleFactory::~AdblockRequestThrottleFactory() = default; ++ ++std::unique_ptr ++AdblockRequestThrottleFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ auto throttle = std::make_unique(); ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/adblock_telemetry_service_factory.h" ++ ++#include ++ ++#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 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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++ ++// static ++AdblockTelemetryServiceFactory* AdblockTelemetryServiceFactory::GetInstance() { ++ static base::NoDestructor 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 ++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 url_loader_factory = ++ context->GetDefaultStoragePartition() ++ ->GetURLLoaderFactoryForBrowserProcess(); ++ auto* subscription_service = ++ SubscriptionServiceFactory::GetForBrowserContext(context); ++ auto telemetry_service = std::make_unique( ++ subscription_service, url_loader_factory, ++ AdblockRequestThrottleFactory::GetForBrowserContext(context), ++ GetCheckInterval()); ++ telemetry_service->AddTopicProvider( ++ std::make_unique( ++ user_prefs::UserPrefs::Get(context), subscription_service, ++ ActivepingTelemetryTopicProvider::DefaultBaseUrl(), ++ ActivepingTelemetryTopicProvider::DefaultAuthToken(), ++ std::make_unique( ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/content_security_policy_injector_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++ContentSecurityPolicyInjectorFactory* ++ContentSecurityPolicyInjectorFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++ContentSecurityPolicyInjectorFactory::ContentSecurityPolicyInjectorFactory() ++ : BrowserContextKeyedServiceFactory( ++ "ContentSecurityPolicyInjector", ++ BrowserContextDependencyManager::GetInstance()) { ++ DependsOn(SubscriptionServiceFactory::GetInstance()); ++} ++ ++ContentSecurityPolicyInjectorFactory::~ContentSecurityPolicyInjectorFactory() = ++ default; ++ ++std::unique_ptr ++ContentSecurityPolicyInjectorFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique( ++ SubscriptionServiceFactory::GetForBrowserContext(context), ++ std::make_unique()); ++} ++ ++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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/element_hider_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++ElementHiderFactory* ElementHiderFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++ElementHiderFactory::ElementHiderFactory() ++ : BrowserContextKeyedServiceFactory( ++ "ElementHider", ++ BrowserContextDependencyManager::GetInstance()) { ++ DependsOn(adblock::SubscriptionServiceFactory::GetInstance()); ++} ++ ++ElementHiderFactory::~ElementHiderFactory() = default; ++ ++std::unique_ptr ++ElementHiderFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique( ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/embedding_utils.h" ++ ++#include ++ ++#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 . ++ */ ++ ++#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 ++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(), 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/resource_classification_runner_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++ResourceClassificationRunnerFactory* ++ResourceClassificationRunnerFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++ResourceClassificationRunnerFactory::ResourceClassificationRunnerFactory() ++ : BrowserContextKeyedServiceFactory( ++ "ResourceClassificationRunner", ++ BrowserContextDependencyManager::GetInstance()) { ++ DependsOn(SitekeyStorageFactory::GetInstance()); ++} ++ ++ResourceClassificationRunnerFactory::~ResourceClassificationRunnerFactory() = ++ default; ++ ++std::unique_ptr ++ResourceClassificationRunnerFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique( ++ base::MakeRefCounted(), ++ std::make_unique(), ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/session_stats_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++ ++// static ++SessionStatsFactory* SessionStatsFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++SessionStatsFactory::SessionStatsFactory() ++ : BrowserContextKeyedServiceFactory( ++ "SessionStats", ++ BrowserContextDependencyManager::GetInstance()) { ++ DependsOn(ResourceClassificationRunnerFactory::GetInstance()); ++} ++ ++SessionStatsFactory::~SessionStatsFactory() = default; ++ ++std::unique_ptr ++SessionStatsFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique( ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/sitekey_storage_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++SitekeyStorageFactory* SitekeyStorageFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++SitekeyStorageFactory::SitekeyStorageFactory() ++ : BrowserContextKeyedServiceFactory( ++ "SitekeyStorage", ++ BrowserContextDependencyManager::GetInstance()) {} ++ ++SitekeyStorageFactory::~SitekeyStorageFactory() = default; ++ ++std::unique_ptr ++SitekeyStorageFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique(); ++} ++ ++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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/subscription_persistent_metadata_factory.h" ++ ++#include ++ ++#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( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++SubscriptionPersistentMetadataFactory* ++SubscriptionPersistentMetadataFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++SubscriptionPersistentMetadataFactory::SubscriptionPersistentMetadataFactory() ++ : BrowserContextKeyedServiceFactory( ++ "AdblockSubscriptionPersistentMetadata", ++ BrowserContextDependencyManager::GetInstance()) {} ++SubscriptionPersistentMetadataFactory:: ++ ~SubscriptionPersistentMetadataFactory() = default; ++ ++std::unique_ptr ++SubscriptionPersistentMetadataFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ return std::make_unique( ++ 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 . ++ */ ++ ++#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() override; ++ ++ // BrowserContextKeyedServiceFactory: ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/factories/subscription_service_factory.h" ++ ++#include ++#include ++#include ++#include ++ ++#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 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 MakeSubscriptionRequest( ++ content::BrowserContext* context) { ++ scoped_refptr url_loader_factory = ++ context->GetDefaultStoragePartition() ++ ->GetURLLoaderFactoryForBrowserProcess(); ++ AdblockRequestThrottle* request_throttle = ++ AdblockRequestThrottleFactory::GetForBrowserContext(context); ++ return std::make_unique( ++ &kRetryBackoffPolicy, url_loader_factory, request_throttle); ++} ++ ++ConversionResult ConvertFilterFile( ++ const scoped_refptr& 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 MakeTaskScheduler() { ++ return std::make_unique(GetUpdateCheckInterval()); ++} ++ ++std::unique_ptr ++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( ++ context->GetPath().AppendASCII(storage_dir), ++ std::make_unique(prefs, ++ CurrentSchemaVersion()), ++ persistent_metadata); ++ ++ auto downloader = std::make_unique( ++ base::BindRepeating(&MakeSubscriptionRequest, context), ++ const_cast(conversion_executors), ++ persistent_metadata); ++ ++ std::unique_ptr recommended_installer = ++ nullptr; ++ if (configuration->GetName() == kAdblockFilteringConfigurationName) { ++ recommended_installer = ++ std::make_unique( ++ prefs, configuration, persistent_metadata, ++ base::BindRepeating(&MakeSubscriptionRequest, context)); ++ } ++ ++ auto maintainer = std::make_unique( ++ configuration, std::move(storage), std::move(downloader), ++ std::move(recommended_installer), ++ std::make_unique(), ++ MakeTaskScheduler(), ++ const_cast(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& 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& 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& 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( ++ 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 ++std::vector MigrateItemsFromList(PrefService* pref_service, ++ const std::string& pref_name) { ++ std::vector 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 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& adblock_filtering_configuration) { ++ auto enable_value = ++ MigrateBoolFromPrefs(prefs, common::prefs::kEnableAdblockLegacy); ++ auto aa_value = ++ MigrateBoolFromPrefs(prefs, common::prefs::kEnableAcceptableAdsLegacy); ++ auto subscriptions = MigrateItemsFromList( ++ prefs, common::prefs::kAdblockSubscriptionsLegacy); ++ auto custom_subscriptions = MigrateItemsFromList( ++ prefs, common::prefs::kAdblockCustomSubscriptionsLegacy); ++ auto domains = MigrateItemsFromList( ++ prefs, common::prefs::kAdblockAllowedDomainsLegacy); ++ auto filters = MigrateItemsFromList( ++ 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( ++ 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(); ++ DependsOn(SubscriptionPersistentMetadataFactory::GetInstance()); ++} ++ ++SubscriptionServiceFactory::~SubscriptionServiceFactory() = default; ++ ++// static ++SubscriptionService* SubscriptionServiceFactory::GetForBrowserContext( ++ content::BrowserContext* context) { ++ return static_cast( ++ GetInstance()->GetServiceForBrowserContext(context, true)); ++} ++// static ++SubscriptionServiceFactory* SubscriptionServiceFactory::GetInstance() { ++ static base::NoDestructor instance; ++ return instance.get(); ++} ++ ++std::unique_ptr ++SubscriptionServiceFactory::BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const { ++ auto* prefs = GetPrefs(context); ++ auto subscription_service = std::make_unique( ++ prefs, ++ base::BindRepeating(&MakeFilterConfigurationMaintainer, context, prefs, ++ GetSubscriptionPersistentMetadata(context), ++ static_cast(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 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 ++SubscriptionServiceFactory::ConvertCustomFilters( ++ const std::vector& filters) const { ++ auto raw_data = ++ flatbuffer_converter_->Convert(filters, CustomFiltersUrl(), true); ++ return base::MakeRefCounted( ++ std::move(raw_data), Subscription::InstallationState::Installed, ++ base::Time()); ++} ++ ++void SubscriptionServiceFactory::ConvertFilterListFile( ++ const GURL& subscription_url, ++ const base::FilePath& path, ++ base::OnceCallback 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 . ++ */ ++ ++#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 ConvertCustomFilters( ++ const std::vector& filters) const override; ++ void ConvertFilterListFile( ++ const GURL& subscription_url, ++ const base::FilePath& path, ++ base::OnceCallback) 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 BuildServiceInstanceForBrowserContext( ++ content::BrowserContext* context) const override; ++ ++ private: ++ friend class base::NoDestructor; ++ ++ // BrowserContextKeyedServiceFactory: ++ content::BrowserContext* GetBrowserContextToUse( ++ content::BrowserContext* context) const override; ++ void RegisterProfilePrefs( ++ user_prefs::PrefRegistrySyncable* registry) override; ++ ++ scoped_refptr 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 . ++ */ ++ ++#include "components/adblock/content/browser/frame_hierarchy_builder.h" ++ ++#include ++#include ++ ++#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 BuildFrameHierarchyForRenderFrameHost( ++ content::RenderFrameHost* frame_host) { ++ std::vector 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 BuildFrameHierarchyForRequestInitiator( ++ const GURL& initiator_url) { ++ std::vector frame_hierarchy; ++ if (IsValidForFrameHierarchy(initiator_url)) { ++ frame_hierarchy.emplace_back(initiator_url); ++ } ++ return frame_hierarchy; ++} ++ ++} // namespace ++ ++FrameHierarchyBuilder::FrameHierarchyBuilder() = default; ++ ++FrameHierarchyBuilder::~FrameHierarchyBuilder() = default; ++ ++std::vector 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_ ++ ++#include ++ ++#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 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 . ++ */ ++ ++#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(*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 . ++ */ ++ ++#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 { ++ 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 . ++ ++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 . ++ ++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 . ++ */ ++ ++#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 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& 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 ++CountNavigationsCallback() { ++ return base::BindRepeating(&RegisterNavigationWithLastUsedPageViewStats); ++} ++ ++base::RepeatingCallback ++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 . ++ */ ++ ++#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& 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 classification_runner_; ++ raw_ptr prefs_; ++ const GURL endpoint_url_; ++ std::map> ++ parked_metrics_before_main_navigation_; ++ base::WeakPtrFactory 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 ++CountNavigationsCallback(); ++base::RepeatingCallback ++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 . ++ */ ++ ++#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(initiator_); ++} ++ ++content::RenderFrameHost* RequestInitiator::GetRenderFrameHost() const { ++ DCHECK(IsFrame()); ++ return content::RenderFrameHost::FromID( ++ absl::get(initiator_)); ++} ++ ++const GURL& RequestInitiator::GetInitiatorUrl() const { ++ DCHECK(!IsFrame()); ++ return absl::get(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 . ++ */ ++ ++#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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_ ++ ++#include ++#include ++ ++#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& 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& headers, ++ CheckFilterMatchCallback callback) = 0; ++ virtual void CheckRewriteFilterMatch( ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL& request_url, ++ const RequestInitiator& request_initiator, ++ base::OnceCallback&)> 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 . ++ */ ++ ++#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 resource_classifier, ++ std::unique_ptr 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& frame_hierarchy, ++ content::GlobalRenderFrameHostId render_frame_host_id, ++ absl::optional 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& resource_classifier, ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL request_url, ++ const std::vector 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 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& 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(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(), 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& 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 resource_classifier, ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL response_url, ++ const std::vector frame_hierarchy, ++ ContentType adblock_resource_type, ++ const scoped_refptr 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&)> 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_ ++ ++#include ++ ++#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 resource_classifier, ++ std::unique_ptr 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& headers, ++ CheckFilterMatchCallback callback) final; ++ void CheckRewriteFilterMatch( ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL& request_url, ++ const RequestInitiator& request_initiator, ++ base::OnceCallback&)> result) final; ++ ++ private: ++ struct CheckResourceFilterMatchResult { ++ FilterMatchResult status; ++ GURL subscription; ++ std::string configuration_name; ++ }; ++ ++ static CheckResourceFilterMatchResult CheckRequestFilterMatchInternal( ++ const scoped_refptr& resource_classifier, ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL request_url, ++ const std::vector frame_hierarchy, ++ ContentType adblock_resource_type, ++ const SiteKey sitekey); ++ ++ void OnCheckResourceFilterMatchComplete( ++ const GURL request_url, ++ const std::vector frame_hierarchy, ++ ContentType adblock_resource_type, ++ const RequestInitiator& request_initiator, ++ CheckFilterMatchCallback callback, ++ const CheckResourceFilterMatchResult result); ++ ++ void OnCheckPopupFilterMatchComplete( ++ const GURL& popup_url, ++ const std::vector& frame_hierarchy, ++ content::GlobalRenderFrameHostId render_frame_host_id, ++ absl::optional callback, ++ const ResourceClassifier::ClassificationResult& result); ++ ++ static CheckResourceFilterMatchResult CheckResponseFilterMatchInternal( ++ const scoped_refptr resource_classifier, ++ SubscriptionService::Snapshot subscription_collections, ++ const GURL response_url, ++ const std::vector frame_hierarchy, ++ ContentType adblock_resource_type, ++ const scoped_refptr response_headers); ++ ++ void NotifyResourceMatched(const GURL& url, ++ FilterMatchResult result, ++ const std::vector& 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&)> callback, ++ absl::optional 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 resource_classifier_; ++ std::unique_ptr frame_hierarchy_builder_; ++ raw_ptr sitekey_storage_; ++ base::ObserverList observers_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#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 SessionStatsImpl::GetSessionAllowedResourcesCount() const { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ return allowed_map_; ++} ++ ++std::map 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& 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 . ++ */ ++ ++#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 GetSessionAllowedResourcesCount() const final; ++ ++ std::map GetSessionBlockedResourcesCount() const final; ++ ++ // ResourceClassificationRunner::Observer: ++ void OnRequestMatched(const GURL& url, ++ FilterMatchResult match_result, ++ const std::vector& 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 classification_runner_; ++ std::map allowed_map_; ++ std::map 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 . ++ */ ++ ++#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> { ++ 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 subscriptions = {DefaultSubscriptionUrl()}; ++ if (AcceptableAdsEnabled()) { ++ subscriptions.emplace_back(AcceptableAdsUrl()); ++ } ++ auto waiter = GetSubscriptionInstalledWaiter(); ++ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions)); ++ } ++ ++ virtual std::unique_ptr RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (request.GetURL().path() == AcceptableAdsUrl().path()) { ++ std::unique_ptr 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 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"( ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr 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"( ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr 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 . ++ */ ++ ++#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& 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 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& 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 . ++ */ ++ ++#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& 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 blocked_ads_notifications; ++ std::vector allowed_ads_notifications; ++ std::vector blocked_popups_notifications; ++ std::vector allowed_popups_notifications; ++ std::vector allowed_pages_notifications; ++}; ++ ++class SubscriptionInstalledWaiter ++ : public SubscriptionService::SubscriptionObserver { ++ public: ++ explicit SubscriptionInstalledWaiter( ++ SubscriptionService* subscription_service); ++ ++ ~SubscriptionInstalledWaiter() override; ++ ++ void WaitUntilSubscriptionsInstalled(std::vector subscriptions); ++ ++ void OnSubscriptionInstalled(const GURL& subscription_url) override; ++ ++ protected: ++ raw_ptr subscription_service_; ++ base::RunLoop run_loop_; ++ std::vector 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& 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 . ++ */ ++ ++#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(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 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 . ++ */ ++ ++#include ++ ++#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 . ++ */ ++ ++#include ++ ++#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 invalid_urls = { ++ GURL{"https://adblocktest.data"}, GURL{"https://adblock.testdata"}, ++ GURL{"https://adblock.test.data.eyeo"}, GURL{"https://test.data.eyeo"}}; ++ std::vector 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 . ++ */ ++ ++#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 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 default_lists_; ++ bool finish_condition_met_ = false; ++ base::RepeatingClosure quit_closure_; ++}; ++ ++class AdblockEnabledFilterListDownloadTest ++ : public AdblockFilterListDownloadTestBase { ++ public: ++ std::unique_ptr 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 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 { ++ 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 { ++ public: ++ using LocaleToEasylistPath = std::pair>; ++ AdblockLocaleFilterListDownloadTest() : locale_data_(GetLocale(GetParam())) { ++ setenv("LANGUAGE", locale_data_.first.c_str(), 1); ++ } ++ ++ std::unique_ptr 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{ ++ "/liste_ar.txt", "/liste_fr.txt"}); ++ case Country::Bulgaria: ++ return std::make_pair("bg_BG", ++ std::vector{"/bulgarian_list.txt"}); ++ case Country::China: ++ return std::make_pair("zh_CN", ++ std::vector{"/easylistchina.txt"}); ++ case Country::Czech: ++ return std::make_pair( ++ "cs_CZ", std::vector{"/easylistczechslovak.txt"}); ++ case Country::France: ++ return std::make_pair("fr_FR", ++ std::vector{"/liste_fr.txt"}); ++ case Country::Germany: ++ return std::make_pair("de_DE", ++ std::vector{"/easylistgermany.txt"}); ++ case Country::Hungary: ++ return std::make_pair("hu_HU", ++ std::vector{"/hufilter.txt"}); ++ case Country::India: ++ return std::make_pair("ml_IN", ++ std::vector{"/indianlist.txt"}); ++ case Country::Indonesia: ++ return std::make_pair("id_ID", ++ std::vector{"/abpindo.txt"}); ++ case Country::Israel: ++ return std::make_pair("he_IL", ++ std::vector{"/israellist.txt"}); ++ case Country::Italy: ++ return std::make_pair("it_IT", ++ std::vector{"/easylistitaly.txt"}); ++ case Country::Japan: ++ return std::make_pair( ++ "ja_JP", std::vector{"/japanese-filters.txt"}); ++ case Country::Korea: ++ return std::make_pair("ko_KR", ++ std::vector{"/koreanlist.txt"}); ++ case Country::Latvia: ++ return std::make_pair("lv_LV", ++ std::vector{"/latvianlist.txt"}); ++ case Country::Lithuania: ++ return std::make_pair( ++ "lt_LT", std::vector{"/easylistlithuania.txt"}); ++ case Country::Netherlands: ++ return std::make_pair("nl_NL", ++ std::vector{"/easylistdutch.txt"}); ++ case Country::Norway: ++ return std::make_pair( ++ "no_NO", ++ std::vector{"/dandelion_sprouts_nordic_filters.txt"}); ++ case Country::Poland: ++ return std::make_pair("pl_PL", ++ std::vector{"/easylistpolish.txt"}); ++ case Country::Portugal: ++ return std::make_pair( ++ "pt_PT", std::vector{"/easylistportuguese.txt"}); ++ case Country::Romania: ++ return std::make_pair("ro_RO", std::vector{"/rolist.txt"}); ++ case Country::Russia: ++ return std::make_pair("ru_RU", ++ std::vector{"/ruadlist.txt"}); ++ case Country::Spain: ++ return std::make_pair("es_ES", ++ std::vector{"/easylistspanish.txt"}); ++ case Country::Thailand: ++ return std::make_pair("th_TH", ++ std::vector{"/global-filters.txt"}); ++ case Country::Turkey: ++ return std::make_pair("tr_TR", ++ std::vector{"/turkish-filters.txt"}); ++ case Country::Vietnam: ++ return std::make_pair("vi_VN", std::vector{"/abpvn.txt"}); ++ default: ++ return std::make_pair("en_US", ++ std::vector{"/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 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 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 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 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 configuration_; ++ base::RepeatingClosure filter_list_removed_closure_; ++}; ++ ++IN_PROC_BROWSER_TEST_F(AdblockGeolocatedFilterListsTest, ++ CheckRecommendationsChange) { ++ std::vector 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 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 . ++ */ ++ ++#include ++ ++#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 RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (request.GetURL() == AcceptableAdsUrl()) { ++ std::unique_ptr 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 MakeConfiguration( ++ std::string name) { ++ return std::make_unique(GetPrefs(), ++ std::move(name)); ++ } ++ ++ void InstallFilteringConfiguration( ++ std::unique_ptr 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 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 . ++ */ ++ ++#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 . ++ */ ++ ++#include ++#include ++#include ++ ++#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 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 RespondWithContent( ++ std::string_view content, ++ std::string_view content_type) { ++ auto http_response = ++ std::make_unique(); ++ 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 RedirectToPage( ++ const std::string& redirect_to_target) { ++ auto http_response = ++ std::make_unique(); ++ http_response->set_code(net::HTTP_MOVED_PERMANENTLY); ++ http_response->AddCustomHeader("Location", redirect_to_target); ++ return http_response; ++ } ++ ++ std::unique_ptr CreateTelemetryResponse() { ++ auto http_response = ++ std::make_unique(); ++ 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 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 mock_websites_; ++ absl::optional 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ ++ ++ )", ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ // The iframe loads an image that is blocked by easylist but allowlisted by ++ // AA. ++ RegisterHtmlContent("/iframe.html", R"( ++ ++ ++ Test iframe ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ ++ ++ )"); ++ // The iframe loads an image that is blocked by easylist but allowlisted by ++ // AA. ++ RegisterHtmlContent("/iframe.html", R"( ++ ++ ++ Test iframe ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ RegisterHtmlContent("/iframe.html", R"( ++ ++ ++ Test iframe ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ RegisterHtmlContent("/iframe.html", R"( ++ ++ ++ Test iframe ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ RegisterHtmlContent("/iframe.html", R"( ++ ++ ++ Test iframe ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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"( ++ ++ ++ Test page ++ ++ ++ ++ )"); ++ RegisterHtmlContent("/after_page.html", R"( ++ ++ ++ Test page ++ ++ ++ ++ ++ ++ )"); ++ 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 . ++ */ ++ ++#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 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 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 . ++ */ ++ ++#include ++ ++#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 RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (request.relative_url == "/requested_path") { ++ std::unique_ptr 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 . ++ */ ++ ++#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 RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (base::StartsWith(request.relative_url, "/test_page.html")) { ++ static constexpr char kMainFrame[] = ++ R"( ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr 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"( ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr 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 key_original( ++ crypto::RSAPrivateKey::Create(1024)); ++ std::vector priv_key; ++ EXPECT_TRUE(key_original->ExportPrivateKey(&priv_key)); ++ std::vector pub_key; ++ EXPECT_TRUE(key_original->ExportPublicKey(&pub_key)); ++ std::unique_ptr key( ++ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(priv_key)); ++ std::unique_ptr signer( ++ crypto::SignatureCreator::Create(key.get(), ++ crypto::SignatureCreator::SHA1)); ++ EXPECT_TRUE(signer.get()); ++ EXPECT_TRUE(signer->Update( ++ reinterpret_cast(sitekey_encryption_input.c_str()), ++ sitekey_encryption_input.size())); ++ std::vector 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 . ++ */ ++ ++#include ++ ++#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 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 . ++ */ ++ ++#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::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 ++ 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(); ++ 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 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 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 . ++ */ ++ ++#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& parsed) { ++ return GetPayload(parsed)->Find("first_ping"); ++ } ++ ++ base::Value* GetLastPing(absl::optional& parsed) { ++ return GetPayload(parsed)->Find("last_ping"); ++ } ++ ++ base::Value* GetLastPingTag(absl::optional& parsed) { ++ return GetPayload(parsed)->Find("last_ping_tag"); ++ } ++ ++ base::Value* GetPreviousLastPing(absl::optional& 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 CreateResponse( ++ const std::string& token) { ++ auto http_response = ++ std::make_unique(); ++ 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 RequestHandler( ++ const net::test_server::HttpRequest& request) = 0; ++ ++ private: ++ base::Value::Dict* GetPayload(absl::optional& 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 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 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 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 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 . ++ */ ++ ++#include ++ ++#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 { ++ 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 . ++ */ ++ ++#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 redirect_url = std::nullopt; ++ scoped_refptr headers = ++ base::MakeRefCounted(""); ++}; ++ ++class AdblockURLLoaderFactoryTest ++ : public content::RenderViewHostTestHarness, ++ public testing::WithParamInterface { ++ 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 GetUrlsForCurrentFlow() { ++ std::vector 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(); ++ request->url = flow.url; ++ request->destination = flow.destination; ++ ConfigureSubscriptionService(); ++ ++ loader_ = network::SimpleURLLoader::Create(std::move(request), ++ TRAFFIC_ANNOTATION_FOR_TESTS); ++ mojo::Remote 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::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 response_body) {} ++ ++ void ConfigureSubscriptionService() { ++ EXPECT_CALL(subscription_service_, GetCurrentSnapshot()) ++ .WillRepeatedly([]() { ++ adblock::SubscriptionService::Snapshot snapshot; ++ auto collection = ++ std::make_unique(); ++ // 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&)> 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 loader_; ++ std::unique_ptr adblock_factory_; ++ network::TestURLLoaderFactory test_factory_; ++ mojo::Receiver 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 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 . ++ */ ++ ++#include ++#include ++ ++#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 HandleFileRequest( ++ const net::test_server::HttpRequest& request) { ++ auto response = ++ net::test_server::HandleFileRequest(temp_dir_.GetPath(), request); ++ if (response) { ++ auto* basic = ++ static_cast(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& 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 blocked_ads_notifications_; ++ std::vector 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 ++ ++ ++ ++ ++ ++ ++ ++


++ ++
++ Testpages Filterlist:  ++ ++
++ ++
++ ++

++
++
++
+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 .
++
++import {getRequiredElement} from 'chrome://resources/js/util.js';
++import {AdblockInternalsPageHandler} from './adblock_internals.mojom-webui.js';
++
++async function debugInfo(): Promise {
++  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 .
++
++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 .
++ */
++
++#include "components/adblock/core/activeping_telemetry_topic_provider.h"
++
++#include 
++
++#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 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 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 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 .
++ */
++
++#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 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 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 pref_service_;
++  raw_ptr subscription_service_;
++  const GURL base_url_;
++  const std::string auth_token_;
++  std::unique_ptr 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 .
++ */
++
++#include "components/adblock/core/adblock_telemetry_service.h"
++
++#include 
++
++#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 topic_provider,
++      scoped_refptr 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& 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();
++    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 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 topic_provider_;
++  scoped_refptr url_loader_factory_;
++  raw_ptr request_throttle_;
++  std::unique_ptr url_loader_;
++  base::WeakPtrFactory weak_ptr_factory_{this};
++};
++
++AdblockTelemetryService::AdblockTelemetryService(
++    SubscriptionService* subscription_service,
++    scoped_refptr 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 topic_provider) {
++  ongoing_conversations_.push_back(std::make_unique(
++      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(
++      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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_
++#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_
++
++#include 
++#include 
++#include 
++
++#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;
++    using DebugInfoCallback = base::OnceCallback;
++    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 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 url_loader_factory,
++      AdblockRequestThrottle* request_throttle,
++      base::TimeDelta check_interval);
++  ~AdblockTelemetryService() override;
++  using TopicProvidersDebugInfoCallback =
++      base::OnceCallback)>;
++
++  // Add all required topic providers before calling Start().
++  void AddTopicProvider(std::unique_ptr 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 subscription_service_;
++  scoped_refptr url_loader_factory_;
++  raw_ptr request_throttle_;
++  base::TimeDelta check_interval_;
++
++  class Conversation;
++  std::vector> 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 .
++
++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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_
++#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_
++
++#include 
++
++#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 {
++ 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& 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& opener_frame_hierarchy,
++      const SiteKey& sitekey) const = 0;
++
++  virtual ClassificationResult ClassifyResponse(
++      const SubscriptionService::Snapshot subscription_collections,
++      const GURL& request_url,
++      const std::vector& frame_hierarchy,
++      ContentType content_type,
++      const scoped_refptr& response_headers)
++      const = 0;
++
++  virtual absl::optional CheckRewrite(
++      const SubscriptionService::Snapshot subscription_collections,
++      const GURL& request_url,
++      const std::vector& frame_hierarchy) const = 0;
++
++ protected:
++  friend class base::RefCountedThreadSafe;
++  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 .
++ */
++
++#include "components/adblock/core/classifier/resource_classifier_impl.h"
++
++#include 
++
++#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 IsHeaderFilterOverruled(
++    std::string_view blocking_header_filter,
++    std::set& 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& 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& 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& 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 frame_hierarchy,
++    const scoped_refptr& headers,
++    std::set blocking_filters,
++    std::set 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& frame_hierarchy,
++    ContentType content_type,
++    const scoped_refptr& 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 CheckRewriteWithSingleCollection(
++    const SubscriptionCollection& subscription_collection,
++    const GURL& request_url,
++    const std::vector& 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{*blocking_rewrites.begin()});
++  }
++
++  // Change set to vector to call algorithm
++  std::vector 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{*blocking_rewrites_v.begin()});
++}
++
++}  // namespace
++
++ResourceClassifierImpl::~ResourceClassifierImpl() = default;
++
++ClassificationResult ResourceClassifierImpl::ClassifyRequest(
++    const SubscriptionService::Snapshot subscription_collections,
++    const GURL& request_url,
++    const std::vector& 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& 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& frame_hierarchy,
++    ContentType content_type,
++    const scoped_refptr& 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 ResourceClassifierImpl::CheckRewrite(
++    const SubscriptionService::Snapshot subscription_collections,
++    const GURL& request_url,
++    const std::vector& 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_
++#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_
++
++#include 
++
++#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& frame_hierarchy,
++      ContentType content_type,
++      const SiteKey& sitekey) const final;
++
++  ClassificationResult ClassifyPopup(
++      const SubscriptionService::Snapshot& subscription_collections,
++      const GURL& popup_url,
++      const std::vector& opener_frame_hierarchy,
++      const SiteKey& sitekey) const final;
++
++  ClassificationResult ClassifyResponse(
++      const SubscriptionService::Snapshot subscription_collections,
++      const GURL& request_url,
++      const std::vector& frame_hierarchy,
++      ContentType content_type,
++      const scoped_refptr& response_headers)
++      const final;
++
++  absl::optional CheckRewrite(
++      const SubscriptionService::Snapshot subscription_collections,
++      const GURL& request_url,
++      const std::vector& frame_hierarchy) const final;
++
++ protected:
++  friend class base::RefCountedThreadSafe;
++  ~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 .
++ */
++
++#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 .
++ */
++
++#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&,
++               ContentType,
++               const SiteKey&),
++              (override, const));
++  MOCK_METHOD(ClassificationResult,
++              ClassifyPopup,
++              (const SubscriptionService::Snapshot&,
++               const GURL&,
++               const std::vector&,
++               const SiteKey&),
++              (override, const));
++  MOCK_METHOD(ClassificationResult,
++              ClassifyResponse,
++              (const SubscriptionService::Snapshot,
++               const GURL&,
++               const std::vector&,
++               ContentType,
++               const scoped_refptr&),
++              (override, const));
++  MOCK_METHOD(absl::optional,
++              CheckRewrite,
++              (const SubscriptionService::Snapshot,
++               const GURL&,
++               const std::vector&),
++              (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 .
++ */
++
++#include "components/adblock/core/classifier/resource_classifier_impl.h"
++
++#include 
++
++#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();
++    mock_snapshot_ = std::make_unique();
++    mock_snapshot_->push_back(std::make_unique());
++    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(
++        mock_snapshot_->front().get());
++  }
++
++  void FindBySubresourceFilterReturns(const absl::optional& return_value,
++                                      FilterCategory category) {
++    EXPECT_CALL(mock_subscription_collection(),
++                FindBySubresourceFilter(_, _, _, _, category))
++        .WillOnce(Return(return_value));
++  }
++
++  void FindBySpecialFilterReturns(const absl::optional& return_value,
++                                  SpecialFilterType type) {
++    EXPECT_CALL(mock_subscription_collection(),
++                FindBySpecialFilter(type, _, _, _))
++        .WillOnce(Return(return_value));
++  }
++
++  void FindByAllowFilterReturns(const absl::optional& return_value) {
++    EXPECT_CALL(mock_subscription_collection(), FindByAllowFilter(_, _, _, _))
++        .WillOnce(Return(return_value));
++  }
++
++  void FindByPopupFilterReturns(const absl::optional& return_value,
++                                FilterCategory category) {
++    EXPECT_CALL(mock_subscription_collection(),
++                FindByPopupFilter(_, _, _, category))
++        .WillOnce(Return(return_value));
++  }
++
++  void GetHeaderFiltersReturns(const std::set& 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& 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 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 kAllowingHeaderFilters = {
++      {"allowing_filter", kSourceUrl}};
++  const std::set kBlockingHeaderFilters = {
++      {"blocking_filter", kSourceUrl}};
++  const std::set kDomainSpecificHeaderFilters = {
++      {"domain_specific_filter", kSourceUrl}};
++  const std::string kConfigurationName = "test_configuration";
++  std::unique_ptr mock_snapshot_;
++  scoped_refptr 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(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(kSourceUrl),
++                                 FilterCategory::Blocking);
++
++  // Since there's a blocking filter, subscriptions are queried for allowing
++  // filters
++  FindByAllowFilterReturns(absl::optional(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(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(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(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(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(kSourceUrl),
++                             SpecialFilterType::Genericblock);
++
++  // And there is a domain specific filter.
++  FindBySubresourceFilterReturns(absl::optional(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(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(kSourceUrl),
++                           FilterCategory::Blocking);
++  FindByPopupFilterReturns(absl::optional(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(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(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(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(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(kSourceUrl),
++                                 FilterCategory::Blocking);
++  FindByAllowFilterReturns(absl::optional(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();
++  EXPECT_CALL(*mock_subscription_collection,
++              FindBySubresourceFilter(_, _, _, _, FilterCategory::Blocking))
++      .WillOnce(Return(absl::optional(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(kSourceUrl),
++                                 FilterCategory::Blocking);
++  FindByAllowFilterReturns(absl::nullopt);
++  FindBySpecialFilterReturns(absl::nullopt, SpecialFilterType::Genericblock);
++
++  // Other configuration should not be called
++  auto mock_subscription_collection =
++      std::make_unique();
++  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(kSourceUrl),
++                                 FilterCategory::Blocking);
++  FindByAllowFilterReturns(absl::optional(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(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(kSourceUrl),
++                             SpecialFilterType::Genericblock);
++  GetRewriteFiltersReturns({"about::blank/domain_specific"},
++                           FilterCategory::DomainSpecificBlocking);
++
++  EXPECT_EQ(absl::optional{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(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();
++  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 .
++ */
++
++#include 
++#include 
++#include 
++
++#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();
++    converter_ = base::MakeRefCounted();
++  }
++
++  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> 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(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> state,
++      int cycles = BenchmarkRepetitions()) {
++    std::set csp_injections;
++    auto sub_collection =
++        std::make_unique(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> state) {
++    auto sub_collection =
++        std::make_unique(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& DefaultFrameHierarchy() const {
++    static const std::vector 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 classifier_;
++  scoped_refptr converter_;
++};
++
++// Uses real filter lists
++class AdblockResourceClassifierPerfTestFull
++    : public AdblockResourceClassifierPerfTestBase {
++ public:
++  std::vector> CreateState(
++      std::initializer_list filenames) {
++    std::vector> 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>(
++          converter_result));
++      state.push_back(base::MakeRefCounted(
++          std::move(
++              absl::get>(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(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> CreateStateFromFilters(
++      std::vector filters) {
++    auto converter_result =
++        converter_->Convert(std::move(filters), CustomFiltersUrl(), false);
++    DCHECK(converter_result);
++    std::vector> state;
++    state.push_back(base::MakeRefCounted(
++        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{"example.com###selector"});
++  MeasureElemhideGenerationTime(GURL("https://example.com/frame.html"),
++                                std::move(state));
++}
++
++TEST_F(AdblockResourceClassifierPerfTestSimple,
++       NoWildcardMultipleDomainsElemhideMatch) {
++  auto state = CreateStateFromFilters(std::vector{
++      "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{"example.*###selector"});
++  MeasureElemhideGenerationTime(GURL("https://example.com/frame.html"),
++                                std::move(state));
++}
++
++TEST_F(AdblockResourceClassifierPerfTestSimple,
++       NoWildcardDomainElemhideNoMatch) {
++  auto state = CreateStateFromFilters(
++      std::vector{"example.com###selector"});
++  MeasureElemhideGenerationTime(GURL("https://wrong.com/frame.html"),
++                                std::move(state));
++}
++
++TEST_F(AdblockResourceClassifierPerfTestSimple,
++       NoWildcardMultipleDomainsElemhideNoMatch) {
++  auto state = CreateStateFromFilters(std::vector{
++      "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{"example.*###selector"});
++  MeasureElemhideGenerationTime(GURL("https://wrong.com/frame.html"),
++                                std::move(state));
++}
++
++TEST_F(AdblockResourceClassifierPerfTestSimple, NoWildcardDomainUrlMatch) {
++  auto state = CreateStateFromFilters(
++      std::vector{"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{"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{"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{"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{"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{"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 .
++
++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 .
++ */
++
++#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,";
++
++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 OverrideEyeoFilteringDisabledByDefault(bool val) {
++  return base::AutoReset(&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 OverrideAcceptableAdsDisabledByDefault(bool val) {
++  return base::AutoReset(&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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_
++
++#include 
++
++#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 OverrideEyeoFilteringDisabledByDefault(bool val);
++
++// Override result of IsAcceptableAdsDisabledByDefault() for the
++// duration of the returned AutoReset's lifetime. Used for testing.
++base::AutoReset 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 .
++ */
++
++#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 GetPrefs() {
++  static std::vector 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_
++
++#include 
++#include 
++
++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 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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 ConvertURLs(const std::vector& input) {
++  std::vector 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 MakeFlatbufferDataFromResourceBundle(
++    int resource_id) {
++  return std::make_unique(
++      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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_
++
++#include 
++#include 
++#include 
++
++#include "components/adblock/core/common/flatbuffer_data.h"
++#include "url/gurl.h"
++
++namespace adblock::utils {
++
++std::vector ConvertURLs(const std::vector& input);
++
++// Creates a FlatbufferData object that holds data from the ResourceBundle
++
++std::unique_ptr 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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_APP_INFO_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_APP_INFO_H_
++
++#include 
++
++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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_
++
++#include 
++
++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 .
++ */
++
++#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 memory,
++                             absl::optional 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(data_.data());
++}
++
++size_t InMemoryFlatbufferData::size() const {
++  return data_.size();
++}
++
++const base::span 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();
++  if (!file_->Initialize(path_)) {
++    file_.reset();
++  }
++}
++
++MemoryMappedFlatbufferData::~MemoryMappedFlatbufferData() {
++  const auto path_to_remove =
++      permanently_remove_path_.load()
++          ? absl::optional(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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_
++
++#include 
++#include 
++#include 
++
++#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 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 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 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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_
++
++#include 
++
++#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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_
++
++#include 
++
++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 .
++ */
++
++#include "components/adblock/core/common/regex_filter_pattern.h"
++
++namespace adblock {
++
++absl::optional 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_
++
++#include 
++
++#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 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 .
++ */
++
++#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;
++
++}
++
++#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 .
++ */
++
++#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 .
++ */
++
++#include "components/adblock/core/common/task_scheduler_impl.h"
++
++#include 
++#include 
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_IMPL_H_
++#define COMPONENTS_ADBLOCK_CORE_COMMON_TASK_SCHEDULER_IMPL_H_
++
++#include 
++#include 
++
++#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 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 .
++ */
++
++#include "components/adblock/core/common/flatbuffer_data.h"
++
++#include 
++#include 
++
++#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(test_file);
++  auto span = std::string_view(reinterpret_cast(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(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(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 .
++ */
++
++#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 .
++ */
++
++#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 {
++ 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 .
++ */
++
++#include "components/adblock/core/common/task_scheduler_impl.h"
++
++#include 
++#include 
++#include 
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 .
++
++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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_
++#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_
++
++#include 
++
++#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 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 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 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 .
++ */
++
++#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
++
++#include 
++#include 
++
++#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 
++std::vector GetFromList(const base::Value::Dict& configuration,
++                           std::string_view key) {
++  DCHECK(configuration.FindList(key));  // see SetDefaultValuesIfNeeded().
++  const auto* list = configuration.FindList(key);
++  std::vector 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 PersistentFilteringConfiguration::GetFilterLists() const {
++  return GetFromList(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 PersistentFilteringConfiguration::GetAllowedDomains()
++    const {
++  return GetFromList(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 PersistentFilteringConfiguration::GetCustomFilters()
++    const {
++  return GetFromList(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>
++PersistentFilteringConfiguration::GetPersistedConfigurations(
++    PrefService* pref_service) {
++  std::vector> 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(
++        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 .
++ */
++
++#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 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 GetAllowedDomains() const final;
++
++  void AddCustomFilter(const std::string& filter) final;
++  void RemoveCustomFilter(const std::string& filter) final;
++  std::vector GetCustomFilters() const final;
++
++  static std::vector>
++  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 pref_service_;
++  std::string name_;
++  base::ObserverList 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 .
++ */
++
++#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 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 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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_FAKE_FILTERING_CONFIGURATION_H_
++#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_TEST_FAKE_FILTERING_CONFIGURATION_H_
++
++#include 
++#include 
++
++#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 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 GetAllowedDomains() const override;
++
++  void AddCustomFilter(const std::string& filter) override;
++  void RemoveCustomFilter(const std::string& filter) override;
++  std::vector GetCustomFilters() const override;
++
++  raw_ptr observer = nullptr;
++  std::string name;
++  bool is_enabled = true;
++  std::vector filter_lists;
++  std::vector allowed_domains;
++  std::vector 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 .
++ */
++
++#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 .
++ */
++
++#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 {
++ 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, 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,
++              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,
++              GetCustomFilters,
++              (),
++              (const, override));
++
++  base::ObserverList 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 .
++ */
++
++#include "components/adblock/core/configuration/persistent_filtering_configuration.h"
++
++#include 
++
++#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 {
++ 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(
++        &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 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 .
++
++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 .
++ */
++
++#include 
++#include 
++
++#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();
++  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(converter_result)) {
++    LOG(ERROR) << "[eyeo] "
++               << absl::get(converter_result);
++    return false;
++  }
++
++  if (absl::holds_alternative(converter_result)) {
++    LOG(ERROR) << "[eyeo] Filter list redirects. Won't convert";
++    return false;
++  }
++
++  if (!base::WriteFile(
++          output_path,
++          std::string_view(
++              reinterpret_cast(
++                  absl::get>(
++                      converter_result)
++                      ->data()),
++              absl::get>(
++                  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 .
++ */
++
++#include "components/adblock/core/converter/flatbuffer_converter.h"
++
++#include 
++#include 
++#include 
++
++#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 FlatbufferConverter::Convert(
++    const std::vector& 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_
++
++#include 
++#include 
++
++#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;
++// Conversion can yield valid FlatbufferData, a redirect URL or an error:
++using ConversionResult =
++    absl::variant, GURL, ConversionError>;
++
++class FlatbufferSerializer;
++class FlatbufferConverter
++    : public base::RefCountedThreadSafe {
++ public:
++  FlatbufferConverter();
++
++  ConversionResult Convert(std::istream& filter_stream,
++                           GURL subscription_url,
++                           bool allow_privileged);
++  std::unique_ptr Convert(
++      const std::vector& filters,
++      GURL subscription_url,
++      bool allow_privileged);
++
++ protected:
++  friend class base::RefCountedThreadSafe;
++  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 .
++
++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 .
++ */
++
++#include "components/adblock/core/converter/parser/content_filter.h"
++
++#include "base/logging.h"
++
++namespace adblock {
++
++static constexpr char kDomainSeparator[] = ",";
++
++// static
++absl::optional 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_
++
++#include 
++#include 
++
++#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 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 .
++ */
++
++#include "components/adblock/core/converter/parser/domain_option.h"
++
++#include 
++
++#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& 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& 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 exclude_domains(domains.begin(),
++                                           first_include_domain_it);
++  std::vector 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& DomainOption::GetExcludeDomains() const {
++  return exclude_domains_;
++}
++
++const std::vector& 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 exclude_domains,
++                           std::vector 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_
++
++#include 
++#include 
++#include 
++
++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& GetExcludeDomains() const;
++  const std::vector& GetIncludeDomains() const;
++
++  void RemoveDomainsWithNoSubdomain();
++  bool UnrestrictedByDomain() const;
++
++ private:
++  DomainOption(std::vector exclude_domains,
++               std::vector include_domains);
++
++  static bool HasSubdomainOrLocalhost(std::string_view domain);
++
++  std::vector exclude_domains_;
++  std::vector 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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_
++
++#include 
++
++#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 .
++ */
++
++#include "components/adblock/core/converter/parser/metadata.h"
++
++#include 
++
++#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::FromStream(std::istream& filter_stream) {
++  static re2::RE2 comment_re("^!\\s*(.*?)\\s*:\\s*(.*)");
++
++  std::string homepage;
++  std::string title;
++  std::string version;
++  absl::optional 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 "
++               ".].";
++    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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_
++
++#include 
++#include 
++
++#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 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 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 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 .
++ */
++
++#include "components/adblock/core/converter/parser/snippet_filter.h"
++
++#include "base/logging.h"
++
++namespace adblock {
++
++static constexpr char kDomainSeparator[] = ",";
++
++// static
++absl::optional 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_
++
++#include 
++
++#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 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 .
++ */
++
++#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 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& 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& 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_
++
++#include 
++#include 
++#include 
++
++namespace adblock {
++
++class SnippetTokenizer {
++ public:
++  using SnippetScript = std::vector>;
++
++  static SnippetScript Tokenize(std::string_view input);
++
++ private:
++  static void AddEscapeChar(std::string& token, char ch);
++  static void AddArgument(std::vector& arguments,
++                          std::string& token,
++                          bool quotes_just_closed);
++  static void AddFunctionCall(SnippetScript& script,
++                              std::vector& 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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#include "components/adblock/core/converter/parser/url_filter.h"
++
++#include 
++
++#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& 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::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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_
++
++#include 
++
++#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 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 .
++ */
++
++#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;
++
++static constexpr char kDomainOrSitekeySeparator[] = "|";
++static constexpr char kInverseSymbol = '~';
++
++// static
++absl::optional 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 rewrite;
++  DomainOption domains;
++  SiteKeys sitekeys;
++  absl::optional csp;
++  absl::optional headers;
++  uint32_t content_types = 0;
++  std::set 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::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::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 rewrite,
++    const DomainOption domains,
++    const SiteKeys sitekeys,
++    const absl::optional csp,
++    const absl::optional headers,
++    const std::set 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_
++
++#include 
++#include 
++#include 
++
++#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 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& Rewrite() const {
++    return rewrite_;
++  }
++  inline const DomainOption& Domains() const { return domains_; }
++  inline const std::vector& Sitekeys() const { return sitekeys_; }
++  inline const absl::optional& Csp() const { return csp_; }
++  inline const absl::optional& Headers() const { return headers_; }
++  inline uint32_t ContentTypes() const { return content_types_; }
++  inline const std::set& 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 rewrite,
++                   DomainOption domains,
++                   std::vector sitekeys,
++                   absl::optional csp,
++                   absl::optional headers,
++                   std::set exception_types);
++
++  static absl::optional ParseRewrite(
++      const std::string& rewrite_value);
++  static std::vector ParseSitekeys(const std::string& sitekey_value);
++  static bool IsValidCsp(const std::string& csp_value);
++  static void ParseHeaders(std::string& headers_value);
++  static absl::optional 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 rewrite_;
++  DomainOption domains_;
++  std::vector sitekeys_;
++  absl::optional csp_;
++  absl::optional headers_;
++  std::set 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 .
++
++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 .
++ */
++
++#include "components/adblock/core/converter/serializer/filter_keyword_extractor.h"
++
++#include 
++#include 
++
++#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 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,
++                             ¤t_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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_
++
++#include 
++#include 
++
++#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 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 .
++ */
++
++#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 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
++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(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> 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(),
++      options.Csp().has_value()
++          ? builder_.CreateSharedString(options.Csp().value())
++          : flatbuffers::Offset(),
++      options.Headers().has_value()
++          ? builder_.CreateSharedString(options.Headers().value())
++          : flatbuffers::Offset());
++
++  const absl::optional keyword_pattern =
++      ExtractRegexFilterFromPattern(url_filter.pattern).has_value()
++          ? absl::optional()
++          : 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 pattern_text,
++    flatbuffers::Offset filter) {
++  const auto keyword =
++      pattern_text ? FindCandidateKeyword(index, *pattern_text) : "";
++  index[keyword].push_back(filter);
++}
++
++void FlatbufferSerializer::AddElemhideFilterForDomains(
++    ElemhideIndex& index,
++    const std::vector& include_domains,
++    flatbuffers::Offset 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& include_domains,
++    flatbuffers::Offset 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& include_domains,
++    flatbuffers::Offset 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& domains,
++    flatbuffers::Offset filter) const {
++  for (const auto& domain : domains) {
++    index[domain].push_back(filter);
++  }
++}
++
++flatbuffers::Offset<
++    flatbuffers::Vector>>
++FlatbufferSerializer::CreateVectorOfSharedStrings(
++    const std::vector& strings) {
++  std::vector> 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>>
++FlatbufferSerializer::CreateVectorOfSharedStringsFromSitekeys(
++    const std::vector& sitekeys) {
++  std::vector> 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>>
++FlatbufferSerializer::WriteUrlFilterIndex(const UrlFilterIndex& index) {
++  std::vector> 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>>
++FlatbufferSerializer::WriteElemhideFilterIndex(const ElemhideIndex& index) {
++  std::vector> 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>>
++FlatbufferSerializer::WriteRemoveFilterIndex(const RemoveFilterIndex& index) {
++  std::vector> 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>>
++FlatbufferSerializer::WriteInlineCssFilterIndex(
++    const InlineCssFilterIndex& index) {
++  std::vector> 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>>
++FlatbufferSerializer::WriteSnippetFilterIndex(const SnippetIndex& index) {
++  std::vector> 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::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
++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
++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
++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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_
++
++#include 
++#include 
++#include 
++#include 
++#include 
++#include 
++
++#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 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>>;
++  using ElemhideIndex = std::unordered_map<
++      std::string,
++      std::vector>>;
++  using SnippetIndex =
++      std::map>>;
++  using RemoveFilterIndex =
++      std::map>>;
++  using InlineCssFilterIndex =
++      std::map>>;
++
++  void AddUrlFilterToIndex(UrlFilterIndex& index,
++                           absl::optional pattern_text,
++                           flatbuffers::Offset filter);
++  void AddElemhideFilterForDomains(
++      ElemhideIndex& index,
++      const std::vector& include_domains,
++      flatbuffers::Offset filter) const;
++  void AddRemoveFilterForDomains(
++      RemoveFilterIndex& index,
++      const std::vector& include_domains,
++      flatbuffers::Offset filter) const;
++  void AddInlineCssFilterForDomains(
++      InlineCssFilterIndex& index,
++      const std::vector& include_domains,
++      flatbuffers::Offset filter) const;
++  void AddSnippetFilterForDomains(
++      SnippetIndex& index,
++      const std::vector& domains,
++      flatbuffers::Offset filter) const;
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  CreateVectorOfSharedStrings(const std::vector& strings);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  CreateVectorOfSharedStringsFromSitekeys(const std::vector& sitekeys);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  WriteUrlFilterIndex(const UrlFilterIndex& index);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  WriteElemhideFilterIndex(const ElemhideIndex& index);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  WriteRemoveFilterIndex(const RemoveFilterIndex& index);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  WriteInlineCssFilterIndex(const InlineCssFilterIndex& index);
++
++  flatbuffers::Offset<
++      flatbuffers::Vector>>
++  WriteSnippetFilterIndex(const SnippetIndex& index);
++
++  std::string FindCandidateKeyword(UrlFilterIndex& index,
++                                   std::string_view value);
++
++  flatbuffers::Offset CreateElemHideFilter(
++      const ContentFilter& content_filter);
++  flatbuffers::Offset CreateRemoveFilter(
++      const ContentFilter& content_filter);
++  flatbuffers::Offset 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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_
++#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_
++
++#include 
++
++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 .
++ */
++
++#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 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 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 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 .
++ */
++
++#include 
++
++#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();
++  }
++  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>(buffer));
++    reporter.AddResult(kMetricRuntime,
++                       static_cast(timer.Elapsed().InMilliseconds()));
++  }
++
++ private:
++  scoped_refptr converter_ =
++      base::MakeRefCounted();
++};
++
++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 .
++ */
++
++#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 .
++ */
++
++#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 .
++
++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 .
++ */
++
++#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 .
++ */
++
++#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 .
++ */
++
++#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 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 .
++ */
++
++#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 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 .
++ */
++
++#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 url_loader_factory,
++    AdblockRequestThrottle* request_throttle)
++    : backoff_entry_(std::make_unique(backoff_policy)),
++      url_loader_factory_(url_loader_factory),
++      request_throttle_(request_throttle),
++      retry_timer_(std::make_unique()),
++      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();
++  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 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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_IMPL_H_
++#define COMPONENTS_ADBLOCK_CORE_NET_ADBLOCK_RESOURCE_REQUEST_IMPL_H_
++
++#include 
++#include 
++
++#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 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 headers);
++  const char* MethodToString() const;
++
++  SEQUENCE_CHECKER(sequence_checker_);
++  std::unique_ptr backoff_entry_;
++  scoped_refptr url_loader_factory_;
++  raw_ptr request_throttle_;
++  GURL url_;
++  Method method_ = Method::GET;
++  RetryPolicy retry_policy_;
++  ResponseCallback response_callback_;
++  std::unique_ptr loader_;
++  std::unique_ptr retry_timer_;
++  size_t number_of_redirects_;
++  base::WeakPtrFactory 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 .
++ */
++
++#include "components/adblock/core/net/adblock_request_throttle_impl.h"
++
++#include 
++#include 
++
++#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
++      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 .
++ */
++
++#include "components/adblock/core/net/adblock_resource_request_impl.h"
++
++#include 
++#include 
++
++#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 {
++ public:
++  void SetUp() final {
++    SetOnline();
++    ongoing_request_ = std::make_unique(
++        &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
++      test_shared_url_loader_factory_{
++          base::MakeRefCounted(
++              &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 ongoing_request_;
++};
++
++TEST_P(AdblockResourceRequestImplTest,
++       RequestDeferredUntilConnectionAvailable) {
++  SetOffline();
++  base::MockCallback
++      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
++      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
++      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 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
++      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
++      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
++      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 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
++      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
++      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
++      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 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
++      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
++      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 .
++ */
++
++#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 .
++ */
++
++#ifndef COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_REQUEST_THROTTLE_H_
++#define COMPONENTS_ADBLOCK_CORE_NET_TEST_MOCK_ADBLOCK_REQUEST_THROTTLE_H_
++
++#include 
++
++#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 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 .
++ */
++
++#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 .
++ */
++
++#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 {
++ 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 .
++
++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 @@
++
++
++
++  
++    
++        
++        
++    
++    
++  
++  
++    
++      
++      
++      
++      
++      
++      
++      
++      
++    
++  
++
+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 .
++ */
++
++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 .
++ */
++
++// {{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 .
++ */
++
++"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.}
++ *    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
++        // 
Hello
++ // 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 . ++ ++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 . ++ ++# 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 . ++ */ ++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 . ++ */ ++ ++// Generated by chrome/tools/build/sha256_file.py ++// !! DO NOT EDIT !! ++ ++#include ++ ++#include ++ ++#ifndef GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_ ++#define GEN_COMPONENTS_ADBLOCK_CORE_HASH_SCHEMA_HASH_H_ ++ ++extern const std::array 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_ ++#define COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_ ++ ++#include ++ ++#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 GetSessionAllowedResourcesCount() const = 0; ++ ++ virtual std::map 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_ ++#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_ ++ ++#include ++ ++#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& headers, ++ const std::string& user_agent) = 0; ++ ++ virtual absl::optional> FindSiteKeyForAnyUrl( ++ const std::vector& 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 . ++ */ ++ ++#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& 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& 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> ++SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_ ++ ++#include ++ ++#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& headers, ++ const std::string& user_agent) final; ++ ++ absl::optional> FindSiteKeyForAnyUrl( ++ const std::vector& 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 url_to_sitekey_map_; ++ base::WeakPtrFactory 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 . ++ ++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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_ ++ ++#include ++#include ++ ++#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 ConvertCustomFilters( ++ const std::vector& filters) const = 0; ++ // Asynchronous ++ virtual void ConvertFilterListFile( ++ const GURL& subscription_url, ++ const base::FilePath& path, ++ base::OnceCallback 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 . ++ */ ++ ++#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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_ ++ ++#include ++ ++#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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_ ++ ++#include ++#include ++ ++#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 GetSubscriptionCollection() ++ const = 0; ++ ++ // Allows inspecting what Subscriptions are currently in use. This includes ++ // ongoing downloads, preloaded subscriptions and installed subscriptions. ++ virtual std::vector> 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 . ++ */ ++ ++#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() final = default; ++ GURL url_; ++}; ++ ++FilteringConfigurationMaintainerImpl::FilteringConfigurationMaintainerImpl( ++ FilteringConfiguration* configuration, ++ std::unique_ptr storage, ++ std::unique_ptr downloader, ++ std::unique_ptr recommended_installer, ++ std::unique_ptr ++ preloaded_subscription_provider, ++ std::unique_ptr 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 ++FilteringConfigurationMaintainerImpl::GetSubscriptionCollection() const { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ std::vector> 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( ++ state, configuration_->GetName()); ++} ++ ++std::vector> ++FilteringConfigurationMaintainerImpl::GetCurrentSubscriptions() const { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ // Result will contain the currently installed subscriptions: ++ std::vector> 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 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 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> 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, 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(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 ongoing_installation, ++ std::unique_ptr 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 ongoing_installation, ++ scoped_refptr 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 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 FilteringConfigurationMaintainerImpl::GetReadySubscriptions() ++ const { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ std::vector result; ++ result.reserve(current_state_.size()); ++ std::ranges::transform(current_state_, std::back_inserter(result), ++ &Subscription::GetSourceUrl); ++ return result; ++} ++ ++std::vector ++FilteringConfigurationMaintainerImpl::GetPendingSubscriptions() const { ++ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++ std::vector 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 . ++ */ ++ ++#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; ++ FilteringConfigurationMaintainerImpl( ++ FilteringConfiguration* configuration, ++ std::unique_ptr storage, ++ std::unique_ptr downloader, ++ std::unique_ptr recommended_installer, ++ std::unique_ptr ++ preloaded_subscription_provider, ++ std::unique_ptr subscription_updater, ++ ConversionExecutors* conversion_executor, ++ SubscriptionPersistentMetadata* persistent_metadata, ++ SubscriptionUpdatedCallback subscription_updated_callback); ++ ~FilteringConfigurationMaintainerImpl() override; ++ ++ std::unique_ptr GetSubscriptionCollection() ++ const final; ++ ++ std::vector> 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> loaded_subscriptions); ++ void RemoveDuplicateSubscriptions(); ++ void RunUpdateCheck(); ++ void DownloadAndInstallSubscription(const GURL& subscription_url); ++ void OnSubscriptionDataAvailable( ++ scoped_refptr ongoing_installation, ++ std::unique_ptr raw_data); ++ void SubscriptionAddedToStorage( ++ scoped_refptr ongoing_installation, ++ scoped_refptr 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 GetReadySubscriptions() const; ++ std::vector GetPendingSubscriptions() const; ++ ++ SEQUENCE_CHECKER(sequence_checker_); ++ StorageStatus status_ = StorageStatus::Uninitialized; ++ raw_ptr configuration_; ++ std::unique_ptr storage_; ++ std::unique_ptr downloader_; ++ std::unique_ptr recommended_installer_; ++ std::unique_ptr ++ preloaded_subscription_provider_; ++ std::unique_ptr subscription_updater_; ++ raw_ptr 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 persistent_metadata_; ++ SubscriptionUpdatedCallback subscription_updated_callback_; ++ std::set> ongoing_installations_; ++ std::vector> current_state_; ++ scoped_refptr custom_filters_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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; ++ using Selectors = std::vector; ++ using SelectorsWithCss = std::vector; ++ 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 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& results) const = 0; ++ // Find all rewrite filters matching category. ++ virtual std::set 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& results) const = 0; ++ ++ virtual std::vector 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() 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 . ++ */ ++ ++#include "components/adblock/core/subscription/installed_subscription_impl.h" ++ ++#include ++#include ++#include ++#include ++ ++#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>* 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 ++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 ++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& 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 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()) { ++ 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& 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 InstalledSubscriptionImpl::FindRewriteFilters( ++ const GURL& url, ++ const std::string& document_domain, ++ FilterCategory category) const { ++ std::set 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& 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( ++ index_->elemhide_exception()->LookupByKey(""), domain); ++ result.elemhide_selectors = ++ GetSelectorsForDomain( ++ index_->elemhide()->LookupByKey(""), domain); ++ } ++ ++ DomainSplitter domain_splitter(domain); ++ while (auto subdomain = domain_splitter.FindNextSubdomain()) { ++ auto specific_exceptions = ++ GetSelectorsForDomain( ++ 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( ++ 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( ++ 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( ++ 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( ++ 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 InstalledSubscriptionImpl::FindInternal( ++ const UrlFilterIndex* index, ++ const GURL& url, ++ absl::optional 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 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 content_type, ++ const std::string& document_domain, ++ const std::string& sitekey, ++ FilterCategory category, ++ bool is_third_party_request, ++ FindStrategy strategy, ++ std::vector& 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 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 ++InstalledSubscriptionImpl::MatchSnippets( ++ const std::string& document_domain) const { ++ std::vector 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_ ++ ++#include ++#include ++#include ++#include ++ ++#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 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& results) const final; ++ std::set 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& results) const final; ++ ++ ContentFiltersData GetElemhideData(const GURL& url, ++ bool domain_specific) const final; ++ ContentFiltersData GetElemhideEmulationData(const GURL& url) const final; ++ ++ std::vector MatchSnippets( ++ const std::string& document_domain) const final; ++ ++ void MarkForPermanentRemoval() final; ++ ++ private: ++ friend class base::RefCountedThreadSafe; ++ ~InstalledSubscriptionImpl() final; ++ enum class FindStrategy { ++ FindFirst, ++ FindAll, ++ }; ++ ++ using UrlFilterIndex = ++ flatbuffers::Vector>; ++ using Domains = flatbuffers::Vector>; ++ // Finds the first filter in |category| that matches the remaining parameters. ++ // Finds all filters in category that matchers the remaining parameters. ++ std::vector FindInternal( ++ const UrlFilterIndex* index, ++ const GURL& url, ++ absl::optional 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 content_type, ++ const std::string& document_domain, ++ const std::string& sitekey, ++ FilterCategory category, ++ bool is_third_party_request, ++ FindStrategy strategy, ++ std::vector& out_results) const; ++ bool CandidateFilterViable(const flat::UrlFilter* candidate, ++ absl::optional 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 buffer_; ++ const InstallationState installation_state_; ++ const base::Time installation_time_; ++ raw_ptr index_ = nullptr; ++ const std::unique_ptr 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 . ++ */ ++ ++#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 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 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{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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_ ++ ++#include ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_ ++ ++#include ++ ++#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 installed_subscriptions, ++ std::vector 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> ++ 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 . ++ */ ++ ++#include "components/adblock/core/subscription/preloaded_subscription_provider_impl.h" ++ ++#include ++ ++#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& 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& installed_subscriptions, ++ const std::vector& 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( ++ 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 subscription() const { ++ return subscription_; ++ } ++ ++ void Reset() { subscription_.reset(); } ++ ++ private: ++ PreloadedSubscriptionInfo info_; ++ scoped_refptr subscription_; ++}; ++ ++PreloadedSubscriptionProviderImpl::~PreloadedSubscriptionProviderImpl() = ++ default; ++PreloadedSubscriptionProviderImpl::PreloadedSubscriptionProviderImpl() { ++ for (const auto& info : config::GetPreloadedSubscriptionConfiguration()) { ++ providers_.emplace_back(info); ++ } ++} ++ ++void PreloadedSubscriptionProviderImpl::UpdateSubscriptions( ++ std::vector installed_subscriptions, ++ std::vector pending_subscriptions) { ++ installed_subscriptions_ = std::move(installed_subscriptions); ++ pending_subscriptions_ = std::move(pending_subscriptions); ++ UpdateSubscriptionsInternal(); ++} ++ ++std::vector> ++PreloadedSubscriptionProviderImpl::GetCurrentPreloadedSubscriptions() const { ++ std::vector> 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_ ++ ++#include ++ ++#include "components/adblock/core/subscription/preloaded_subscription_provider.h" ++ ++namespace adblock { ++ ++class PreloadedSubscriptionProviderImpl final ++ : public PreloadedSubscriptionProvider { ++ public: ++ ~PreloadedSubscriptionProviderImpl() final; ++ PreloadedSubscriptionProviderImpl(); ++ ++ void UpdateSubscriptions(std::vector installed_subscriptions, ++ std::vector pending_subscriptions) final; ++ ++ std::vector> ++ GetCurrentPreloadedSubscriptions() const final; ++ ++ private: ++ void UpdateSubscriptionsInternal(); ++ ++ class SingleSubscriptionProvider; ++ std::vector installed_subscriptions_; ++ std::vector pending_subscriptions_; ++ std::vector 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_H_ ++ ++#include ++ ++#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&)>; ++ ++ 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 . ++ */ ++ ++#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 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& 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_INSTALLER_IMPL_H_ ++ ++#include ++ ++#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()>; ++ ++ 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 headers); ++ void OnRecommendedSubscriptionsParsed( ++ const std::vector& recommended_subscriptions); ++ ++ SEQUENCE_CHECKER(sequence_checker_); ++ raw_ptr pref_service_; ++ raw_ptr configuration_; ++ raw_ptr persistent_metadata_; ++ ResourceRequestMaker request_maker_; ++ RecommendedSubscriptionsParsedCallback ++ on_recommended_subscriptions_available_; ++ std::unique_ptr resource_request_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#include "components/adblock/core/subscription/recommended_subscription_parser.h" ++ ++#include ++ ++#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 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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_PARSER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_RECOMMENDED_SUBSCRIPTION_PARSER_H_ ++ ++#include ++ ++#include "base/files/file_path.h" ++#include "url/gurl.h" ++ ++namespace adblock { ++ ++class RecommendedSubscriptionParser { ++ public: ++ static std::vector 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 . ++ */ ++ ++#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 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 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::StringPiece(regular_expression.data(), regular_expression.size()), ++ options); ++ if (!prebuilt_re2->ok()) { ++ return nullptr; ++ } ++ return prebuilt_re2; ++} ++ ++std::unique_ptr 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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_ ++ ++#include ++#include ++ ++#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>; ++ void PreBuildPatternsFrom(const UrlFilterIndex* index); ++ std::unique_ptr BuildRe2Expression( ++ std::string_view regular_expression, ++ bool case_sensitive); ++ std::unique_ptr BuildIcuExpression( ++ std::string_view regular_expression, ++ bool case_sensitive); ++ int CacheSize() const; ++ ++ using CacheKey = std::tuple; ++ std::map> re2_cache_; ++ std::map> 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 . ++ */ ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_ ++ ++#include ++ ++#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 { ++ 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; ++ 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_ ++ ++#include ++#include ++#include ++ ++#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 FindBySubresourceFilter( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey, ++ FilterCategory category) const = 0; ++ virtual absl::optional FindByPopupFilter( ++ const GURL& popup_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey, ++ FilterCategory category) const = 0; ++ virtual absl::optional FindByAllowFilter( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey) const = 0; ++ virtual absl::optional FindBySpecialFilter( ++ SpecialFilterType filter_type, ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey) const = 0; ++ ++ virtual InstalledSubscription::ContentFiltersData GetElementHideData( ++ const GURL& frame_url, ++ const std::vector& 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& frame_hierarchy) const = 0; ++ ++ virtual std::set GetCspInjections( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy) const = 0; ++ ++ virtual std::set GetRewriteFilters( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ FilterCategory category) const = 0; ++ ++ virtual std::set GetHeaderFilters( ++ const GURL& request_url, ++ const std::vector& 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_collection_impl.h" ++ ++#include ++#include ++#include ++ ++namespace adblock { ++namespace { ++ ++std::string DocumentDomain(const GURL& request_url, ++ const std::vector& 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 subscription, ++ SpecialFilterType filter_type, ++ const std::vector& 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 subscription, ++ const GURL& request_url, ++ const std::vector& 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 subscription, ++ SpecialFilterType filter_type, ++ const GURL& request_url, ++ const std::vector& 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> 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 SubscriptionCollectionImpl::FindBySubresourceFilter( ++ const GURL& request_url, ++ const std::vector& 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 SubscriptionCollectionImpl::FindByPopupFilter( ++ const GURL& popup_url, ++ const std::vector& 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 SubscriptionCollectionImpl::FindByAllowFilter( ++ const GURL& request_url, ++ const std::vector& 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 SubscriptionCollectionImpl::FindBySpecialFilter( ++ SpecialFilterType filter_type, ++ const GURL& request_url, ++ const std::vector& 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& 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& 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 SubscriptionCollectionImpl::GetCspInjections( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy) const { ++ std::set blocking_filters{}; ++ std::set 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 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 SubscriptionCollectionImpl::GetRewriteFilters( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ FilterCategory category) const { ++ std::set 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 SubscriptionCollectionImpl::GetHeaderFilters( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ FilterCategory category) const { ++ std::set 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_ ++ ++#include ++#include ++ ++#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> 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 FindBySubresourceFilter( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey, ++ FilterCategory category) const final; ++ ++ absl::optional FindByPopupFilter( ++ const GURL& popup_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey, ++ FilterCategory category) const final; ++ ++ absl::optional FindByAllowFilter( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey) const final; ++ ++ absl::optional FindBySpecialFilter( ++ SpecialFilterType filter_type, ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey) const final; ++ ++ InstalledSubscription::ContentFiltersData GetElementHideData( ++ const GURL& frame_url, ++ const std::vector& 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& frame_hierarchy) const final; ++ std::set GetCspInjections( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy) const final; ++ std::set GetRewriteFilters( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ FilterCategory category) const final; ++ ++ std::set GetHeaderFilters( ++ const GURL& request_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ FilterCategory category) const final; ++ ++ private: ++ std::vector> 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_config.h" ++ ++#include ++ ++#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 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& 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 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& ++config::GetPreloadedSubscriptionConfiguration() { ++ static const std::vector 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& config::MaybeSplitCombinedAdblockList( ++ const GURL& filter_list) { ++ static std::vector EMPTY_VALUE; ++ static std::string_view VALUE_EASYLIST_EN = "easylist.txt"; ++ static std::map> ++ 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_ ++ ++#include ++#include ++ ++#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 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 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& 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& ++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& 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_ ++ ++#include ++ ++#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)>; ++ ++ // For head requests we only need the parsed version as result ++ using HeadRequestCallback = base::OnceCallback; ++ 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_downloader_impl.h" ++ ++#include ++#include ++#include ++#include ++ ++#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{}(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(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(*ongoing_ping_)).Run(""); ++ } ++ ++ ongoing_ping_ = HeadRequest{request_maker_.Run(), std::move(on_finished)}; ++ std::get(*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 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(*ongoing_ping_)) ++ .Run(std::move(version)); ++ ongoing_ping_.reset(); ++} ++ ++void SubscriptionDownloaderImpl::OnDownloadFinished( ++ const GURL& subscription_url, ++ base::FilePath downloaded_file, ++ scoped_refptr 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(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>( ++ converter_result)) { ++ VLOG(1) << "[eyeo] Finished converting " << subscription_url ++ << " successfully"; ++ std::move(std::get(download_it->second)) ++ .Run(std::move( ++ absl::get>(converter_result))); ++ ongoing_downloads_.erase(download_it); ++ } else if (absl::holds_alternative(converter_result)) { ++ const GURL& redirect_url = absl::get(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(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(redirected_download_it->second) ++ ->Redirect(redirect_url, ++ BuildSubscriptionQueryParams(redirect_url, ++ persistent_metadata_, false)); ++ } ++ } else { ++ persistent_metadata_->IncrementDownloadErrorCount(subscription_url); ++ AbortWithWarning(download_it, ++ *absl::get(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(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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_ ++ ++#include ++#include ++#include ++ ++#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()>; ++ ++ 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; ++ // Represents subscription downloads in progress. ++ using OngoingDownload = ++ std::tuple; ++ using OngoingDownloads = std::map; ++ 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; ++ ++ bool IsUrlAllowed(const GURL& subscription_url) const; ++ bool IsConnectionAllowed() const; ++ void OnDownloadFinished(const GURL& subscription_url, ++ base::FilePath downloaded_file, ++ scoped_refptr headers); ++ void OnHeadersOnlyDownloaded(const GURL& subscription_url, ++ base::FilePath downloaded_file, ++ scoped_refptr 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 conversion_executor_; ++ raw_ptr persistent_metadata_; ++ OngoingDownloads ongoing_downloads_; ++ absl::optional ongoing_ping_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_ ++ ++#include ++ ++#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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h" ++ ++#include ++ ++#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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_ ++ ++#include ++ ++#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; ++ void UpdatePrefs(); ++ void LoadFromPrefs(); ++ ++ raw_ptr 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_ ++ ++#include ++#include ++ ++#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>)>; ++ // Loads subscriptions from a directory on disk and returns them via ++ // |on_loaded|. ++ virtual void LoadSubscriptions(LoadCallback on_loaded) = 0; ++ ++ using StoreCallback = ++ base::OnceCallback)>; ++ // 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 raw_data, ++ StoreCallback on_finished) = 0; ++ ++ // Removes |subscription|'s file from disk. ++ virtual void RemoveSubscription( ++ scoped_refptr 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h" ++ ++#include ++#include ++ ++#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 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 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(raw_data->data()), ++ raw_data->size()))) { ++ // Disk write failed. ++ return std::make_pair(nullptr, base::FilePath{}); ++ } ++ auto buffer = std::make_unique(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::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 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(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 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 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 loaded_buffers) { ++ std::vector> 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( ++ 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( ++ 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_ ++ ++#include ++ ++#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 validator, ++ SubscriptionPersistentMetadata* persistent_metadata); ++ ~SubscriptionPersistentStorageImpl() final; ++ ++ void LoadSubscriptions(LoadCallback on_loaded) final; ++ void StoreSubscription(std::unique_ptr raw_data, ++ StoreCallback on_finished) final; ++ void RemoveSubscription( ++ scoped_refptr subscription) final; ++ ++ private: ++ using SubscriptionFileMapping = ++ std::map, base::FilePath>; ++ using LoadedBuffer = ++ std::pair, base::FilePath>; ++ static LoadedBuffer WriteSubscription( ++ const base::FilePath& storage_dir, ++ std::unique_ptr raw_data, ++ SubscriptionValidator::StoreTrustedSignatureThreadSafeCallback ++ store_signature); ++ static std::vector ReadSubscriptionsFromDirectory( ++ const base::FilePath& storage_dir, ++ SubscriptionValidator::IsSignatureValidThreadSafeCallback ++ is_signature_valid); ++ void LoadComplete(LoadCallback on_initialized, ++ std::vector loaded_buffers); ++ void SubscriptionStored(StoreCallback on_finished, ++ LoadedBuffer storage_result); ++ ++ SEQUENCE_CHECKER(sequence_checker_); ++ base::FilePath base_storage_dir_; ++ std::unique_ptr validator_; ++ raw_ptr persistent_metadata_; ++ // Maps Subscriptions to files that they access. ++ SubscriptionFileMapping backing_file_mapping_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_ ++ ++#include ++#include ++#include ++ ++#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>; ++ 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> 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 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 ++ 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_service_impl.h" ++ ++#include ++#include ++#include ++#include ++ ++#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> ++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> result; ++ std::ranges::transform(urls, std::back_inserter(result), ++ [](const auto& url) { ++ return base::MakeRefCounted(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 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 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 ++SubscriptionServiceImpl::GetInstalledFilteringConfigurations() { ++ std::vector 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 ++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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_ ++ ++#include ++#include ++#include ++ ++#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; ++ // Used to create FilteringConfigurationMaintainers for newly installed ++ // FilteringConfigurations. ++ using FilteringConfigurationMaintainerFactory = ++ base::RepeatingCallback( ++ FilteringConfiguration* configuration, ++ SubscriptionUpdatedCallback observer)>; ++ using FilteringConfigurationCleaner = ++ base::RepeatingCallback; ++ explicit SubscriptionServiceImpl( ++ PrefService* pref_service, ++ FilteringConfigurationMaintainerFactory maintainer_factory, ++ FilteringConfigurationCleaner configuration_cleaner); ++ ~SubscriptionServiceImpl() final; ++ ++ // SubscriptionService: ++ std::vector> GetCurrentSubscriptions( ++ FilteringConfiguration* configuration) const final; ++ void InstallFilteringConfiguration( ++ std::unique_ptr configuration) final; ++ void UninstallFilteringConfiguration( ++ std::string_view configuration_name) final; ++ std::vector 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 MakeMaintainer( ++ FilteringConfiguration* configuration); ++ ++ SEQUENCE_CHECKER(sequence_checker_); ++ const raw_ptr pref_service_; ++ FilteringConfigurationMaintainerFactory maintainer_factory_; ++ FilteringConfigurationCleaner configuration_cleaner_; ++ using MaintainersCollection = ++ std::map, ++ std::unique_ptr>; ++ MaintainersCollection maintainers_; ++ base::ObserverList observers_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#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; ++ virtual IsSignatureValidThreadSafeCallback IsSignatureValid() const = 0; ++ ++ // Asynchronously persistently store the signature of |data| associated with ++ // |path|. ++ using StoreTrustedSignatureThreadSafeCallback = ++ base::OnceCallback; ++ virtual StoreTrustedSignatureThreadSafeCallback StoreTrustedSignature() = 0; ++ ++ // Asynchronously removes the signature of file |path| from persistent ++ // storage. ++ using RemoveStoredSignatureThreadSafeCallback = ++ base::OnceCallback; ++ 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 . ++ */ ++ ++#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 main_task_runner, ++ base::OnceCallback ++ 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 . ++ */ ++ ++#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 pref_service_; ++ base::WeakPtrFactory 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 . ++ */ ++ ++#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 . ++ */ ++ ++#include "components/adblock/core/subscription/filtering_configuration_maintainer_impl.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#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 { ++ public: ++ MOCK_METHOD(void, MockLoadSubscriptions, ()); ++ ++ void LoadSubscriptions(LoadCallback on_initialized) final { ++ on_initialized_ = std::move(on_initialized); ++ MockLoadSubscriptions(); ++ } ++ ++ void StoreSubscription(std::unique_ptr raw_data, ++ StoreCallback on_finished) final { ++ store_subscription_calls_.emplace_back(std::move(raw_data), ++ std::move(on_finished)); ++ } ++ void RemoveSubscription( ++ scoped_refptr subscription) final { ++ remove_subscription_calls_.push_back(std::move(subscription)); ++ } ++ ++ LoadCallback on_initialized_; ++ std::vector, StoreCallback>> ++ store_subscription_calls_; ++ std::vector> 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 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& results) const final {} ++ std::set 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& 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 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 { ++ public: ++ MOCK_METHOD(void, ++ UpdateSubscriptions, ++ (std::vector installed_subscriptions, ++ std::vector pending_subscriptions), ++ (override)); ++ MOCK_METHOD(std::vector>, ++ GetCurrentPreloadedSubscriptions, ++ (), ++ (override, const)); ++}; ++ ++class MockRecommendedSubscriptionInstaller ++ : public NiceMock { ++ public: ++ MOCK_METHOD(void, RunUpdateCheck, (), (override)); ++ MOCK_METHOD(void, RemoveAutoInstalledSubscriptions, (), (override)); ++}; ++ ++} // namespace ++ ++class AdblockFilteringConfigurationMaintainerImplTest : public testing::Test { ++ public: ++ void CreateTestee(std::vector> ++ demanded_subscriptions) { ++ filtering_configuration_ = std::make_unique(); ++ filtering_configuration_->name = "adblock"; ++ for (auto& sub : demanded_subscriptions) { ++ filtering_configuration_->AddFilterList(sub->GetSourceUrl()); ++ } ++ auto storage = std::make_unique(); ++ storage_ = storage.get(); ++ auto downloader = std::make_unique(); ++ downloader_ = downloader.get(); ++ auto recommended_subscription_installer = ++ std::make_unique(); ++ recommended_subscription_installer_ = ++ recommended_subscription_installer.get(); ++ auto preloaded_subscription_provider = ++ std::make_unique(); ++ preloaded_subscription_provider_ = preloaded_subscription_provider.get(); ++ auto updater = std::make_unique(); ++ updater_ = updater.get(); ++ ++ testee_ = std::make_unique( ++ 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> 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 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)> ++ callback) { ++ // The downloader responds by running the callback with a new ++ // buffer, simulating a successful download. ++ std::move(callback).Run(std::make_unique()); ++ }); ++ 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 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 filtering_configuration_; ++ raw_ptr storage_; ++ raw_ptr preloaded_subscription_provider_; ++ raw_ptr updater_; ++ raw_ptr downloader_; ++ raw_ptr ++ 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 testee_; ++}; ++ ++TEST_F(AdblockFilteringConfigurationMaintainerImplTest, Initialization) { ++ std::vector> initial_subscriptions = { ++ base::MakeRefCounted("fake_subscription1"), ++ base::MakeRefCounted("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("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("fake_subscription1"); ++ auto fake_subscription2 = ++ base::MakeRefCounted("fake_subscription2"); ++ auto fake_subscription3 = ++ base::MakeRefCounted("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("fake_subscription1"); ++ AddSubscription(fake_subscription1); ++ ++ // Take snapshot now. ++ auto snapshot = testee_->GetSubscriptionCollection(); ++ ++ // Add new subscription after snapshot. ++ auto fake_subscription2 = ++ base::MakeRefCounted("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("fake_subscription1"); ++ auto fake_subscription2 = ++ base::MakeRefCounted("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("expired_subscription"); ++ auto young_subscription = ++ base::MakeRefCounted("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())); ++ ++ // 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("expired_subscription"); ++ auto young_subscription = ++ base::MakeRefCounted("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()))); ++ ++ // 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("subscription"); ++ auto fake_subscription2 = ++ base::MakeRefCounted("unique_subscription"); ++ auto fake_subscription3 = ++ base::MakeRefCounted("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 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("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( ++ "fake_subscription1", Subscription::InstallationState::Preloaded); ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ GetCurrentPreloadedSubscriptions()) ++ .WillRepeatedly( ++ testing::Return(std::vector>{ ++ 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>{})); ++ 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()); ++ // 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("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>{})); ++ // 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()); ++ }); ++ ++ // 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("subscription"); ++ InitializeTesteeWithNoSubscriptions(); ++ AddSubscription(fake_subscription1); ++ ++ std::string filter = "test"; ++ std::vector filters = {filter}; ++ EXPECT_CALL(conversion_executor_, ConvertCustomFilters(filters)) ++ .WillOnce(testing::Return( ++ base::MakeRefCounted(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{custom_filter})) ++ .WillOnce(testing::Return( ++ base::MakeRefCounted(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{ ++ custom_filter, expected_allowing_filter})) ++ .WillOnce(testing::Return( ++ base::MakeRefCounted(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{custom_filter})) ++ .WillOnce(testing::Return( ++ base::MakeRefCounted(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("subscription"); ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ UpdateSubscriptions( ++ std::vector{}, ++ std::vector{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{first_subscription->GetSourceUrl()}, ++ std::vector{})); ++ ++ // Download completes. ++ EXPECT_CALL(observer_, Run(first_subscription->GetSourceUrl())); ++ std::move(download_completed_callback).Run(std::make_unique()); ++ std::move(storage_->store_subscription_calls_.back().second) ++ .Run(first_subscription); ++ ++ // Second subscription added. ++ auto second_subscription = ++ base::MakeRefCounted("subscription2"); ++ // Provider updated with both the old installed subscription and the new ++ // ongoing download. ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ UpdateSubscriptions( ++ std::vector{first_subscription->GetSourceUrl()}, ++ std::vector{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{first_subscription->GetSourceUrl(), ++ second_subscription->GetSourceUrl()}, ++ std::vector{})); ++ EXPECT_CALL(observer_, Run(second_subscription->GetSourceUrl())); ++ std::move(download_completed_callback).Run(std::make_unique()); ++ 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{second_subscription->GetSourceUrl()}, ++ std::vector{})); ++ 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("subscription"); ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ UpdateSubscriptions( ++ std::vector{}, ++ std::vector{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{}, std::vector{})); ++ ++ // 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("subscription"); ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ UpdateSubscriptions( ++ std::vector{}, ++ std::vector{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{}, std::vector{})) ++ .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()); ++} ++ ++TEST_F(AdblockFilteringConfigurationMaintainerImplTest, ++ PreloadedSubscriptionProviderConsultedForSnapshot) { ++ auto subscription_in_service = ++ base::MakeRefCounted("subscription_in_service"); ++ auto preloaded_subscription = base::MakeRefCounted( ++ "preloaded_subscription", Subscription::InstallationState::Preloaded); ++ InitializeTesteeWithNoSubscriptions(); ++ AddSubscription(subscription_in_service); ++ ++ EXPECT_CALL(*preloaded_subscription_provider_, ++ GetCurrentPreloadedSubscriptions()) ++ .WillOnce( ++ testing::Return(std::vector>{ ++ 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("exceptionrules.txt"); ++ auto easylist_subscription = ++ base::MakeRefCounted("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("temp_sub.txt"); ++ auto easylist_subscription = ++ base::MakeRefCounted("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 . ++ */ ++ ++#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 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 filters2; ++ subscriptions->FindCspFilters(GURL("https://test.org/resource.jpg"), ++ "test.com", FilterCategory::Blocking, filters2); ++ EXPECT_TRUE(filters2.empty()); ++ ++ // Allowing filter, not found. ++ std::set 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 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 filters2; ++ subscriptions->FindCspFilters(GURL("https://test.org/resource.jpg"), ++ "test.com", FilterCategory::Blocking, filters2); ++ EXPECT_TRUE(filters2.empty()); ++ ++ // Allowing filter, not found. ++ std::set 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 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 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 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 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 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 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 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 expected_blocking{"script-src 'self'"}; ++ ++ std::set 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 expected_allowing{"script-src 'first'", ++ "script-src 'second'"}; ++ ++ std::set 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 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 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 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 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 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 . ++ */ ++ ++#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({".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({"#ad_1"})); ++ EXPECT_EQ(FilterSelectors(selectors_2), ++ std::set({"#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( ++ {"#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({"#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({"#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({"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({"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({"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({ ++ "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({ ++ "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{"general"}); ++ ++ EXPECT_EQ(FilterSelectors(selectors_2), std::set({ ++ "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{"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{"#_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{"#_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{"#_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{"#_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{"#_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{".ad_box"}); ++ EXPECT_EQ(FilterSelectors(subscriptions->GetElemhideEmulationData( ++ GURL("https://example.com/"))), ++ std::set{".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{".ad_box"}); ++ EXPECT_EQ(FilterSelectors(selectors_3), ++ std::set{".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{".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( ++ {"#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({"#testcase-eh-id"})); ++} ++ ++enum class ContentFilterType { ++ kElemhide, ++ kElemhideEmulation, ++}; ++ ++class AdblockInstalledSubscriptionImplElemhideParametrizedTest ++ : public ::testing::WithParamInterface, ++ public AdblockInstalledSubscriptionImplElemhideTest { ++ protected: ++ std::string separator() const { ++ return GetParam() == ContentFilterType::kElemhide ? "##" : "#?#"; ++ } ++ ++ void ExpectSelectorsMatch(scoped_refptr subscription, ++ const std::string& url, ++ const std::set& 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({"#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(); ++ // .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({"#div"}); ++ const auto no_selectors = std::set(); ++ // 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 . ++ */ ++ ++#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 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 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 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 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 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 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 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 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 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 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 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 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 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 . ++ */ ++ ++#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h" ++ ++#include ++#include ++#include ++#include ++ ++#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 ConvertAndLoadVectorOfRules( ++ std::vector& rules, ++ GURL url = GURL(), ++ bool allow_privileged = false) { ++ auto raw_data = converter_->Convert(rules, url, allow_privileged); ++ ++ auto subscription = base::MakeRefCounted( ++ 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 rules = {}; ++ auto subscription = ConvertAndLoadVectorOfRules(rules); ++} ++ ++TEST_F(AdblockInstalledSubscriptionImplListConverterTest, ++ ConvertListOfRules_SingleRule) { ++ std::vector 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({"#ad"})); ++} ++ ++TEST_F(AdblockInstalledSubscriptionImplListConverterTest, ++ ConvertListOfRulesMultipleRules) { ++ std::vector 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({"#ad1"})); ++ EXPECT_EQ(FilterSelectors(selectors_2), ++ std::set({"#ad1", "#ad2"})); ++} ++ ++TEST_F(AdblockInstalledSubscriptionImplListConverterTest, ++ ConvertListOfRulesWithInvalidRules) { ++ std::vector 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({"#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 . ++ */ ++ ++#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& 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 span() const final { return {}; } ++ ++ std::unique_ptr 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(converter_result)); ++ EXPECT_EQ(absl::get(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(converter_result)); ++ EXPECT_EQ(absl::get(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( ++ std::make_unique(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(converter_); ++ EXPECT_CALL(*buffer, PermanentlyRemoveSourceOnDestruction()); ++ auto subscription = base::MakeRefCounted( ++ std::move(buffer), Subscription::InstallationState::Installed, ++ base::Time()); ++ subscription->MarkForPermanentRemoval(); ++} ++ ++TEST_F(AdblockInstalledSubscriptionImplMetadataTest, ++ NormalDestructionDoesNotTriggerSourceRemoval) { ++ auto buffer = std::make_unique(converter_); ++ EXPECT_CALL(*buffer, PermanentlyRemoveSourceOnDestruction()).Times(0); ++ auto subscription = base::MakeRefCounted( ++ 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 . ++ */ ++ ++#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,"); ++} ++ ++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 . ++ */ ++ ++#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 data) ++ : buffer_(std::move(data)), ++ index_(flat::GetSubscription(buffer_->data())) {} ++ const std::unique_ptr buffer_; ++ const raw_ptr 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>( ++ converter_result)) { ++ return FlatIndex(nullptr); ++ } ++ return FlatIndex(std::move( ++ absl::get>(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 . ++ */ ++ ++#include "components/adblock/core/subscription/test/installed_subscription_impl_test_base.h" ++ ++#include ++#include ++#include ++#include ++ ++#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()) {} ++ ++AdblockInstalledSubscriptionImplTestBase:: ++ ~AdblockInstalledSubscriptionImplTestBase() = default; ++ ++std::set ++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 filtered_selectors( ++ selectors.elemhide_selectors.begin(), selectors.elemhide_selectors.end()); ++ return filtered_selectors; ++} ++ ++scoped_refptr ++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>( ++ converter_result)) { ++ return {}; ++ } ++ return base::MakeRefCounted( ++ std::move(absl::get>(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 . ++ */ ++ ++#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 ++#include ++#include ++#include ++ ++#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 FilterSelectors( ++ InstalledSubscription::ContentFiltersData selectors); ++ ++ scoped_refptr ConvertAndLoadRules( ++ std::string rules, ++ GURL url = GURL(), ++ bool allow_privileged = false); ++ ++ public: ++ scoped_refptr 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 . ++ */ ++ ++#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 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( ++ 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 . ++ */ ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_LOAD_GZIPPED_TEST_FILE_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_LOAD_GZIPPED_TEST_FILE_H_ ++ ++#include ++#include ++ ++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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ public: ++ MockConversionExecutors(); ++ ~MockConversionExecutors() override; ++ MOCK_METHOD(scoped_refptr, ++ ConvertCustomFilters, ++ (const std::vector& filters), ++ (override, const)); ++ MOCK_METHOD(void, ++ ConvertFilterListFile, ++ (const GURL& subscription_url, ++ const base::FilePath& path, ++ base::OnceCallback), ++ (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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ public: ++ MockFilteringConfigurationMaintainer(); ++ ~MockFilteringConfigurationMaintainer() override; ++ MOCK_METHOD(std::vector>, ++ GetCurrentSubscriptions, ++ (), ++ (override, const)); ++ MOCK_METHOD(std::unique_ptr, ++ 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ 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& results), ++ (override, const)); ++ MOCK_METHOD(std::set, ++ 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& 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, ++ 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 . ++ */ ++ ++#include "components/adblock/core/subscription/test/mock_subscription.h" ++ ++namespace adblock { ++ ++MockSubscription::MockSubscription() = default; ++ ++MockSubscription::~MockSubscription() = default; ++ ++scoped_refptr MakeMockSubscription( ++ GURL url, ++ Subscription::InstallationState state) { ++ auto subscription = base::MakeRefCounted(); ++ 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 . ++ */ ++ ++#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 { ++ 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 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ public: ++ MockSubscriptionCollection(); ++ ~MockSubscriptionCollection() override; ++ MOCK_METHOD(const std::string&, ++ GetFilteringConfigurationName, ++ (), ++ (const, override)); ++ MOCK_METHOD(absl::optional, ++ FindBySubresourceFilter, ++ (const GURL& frame_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey, ++ FilterCategory category), ++ (const, override)); ++ MOCK_METHOD(absl::optional, ++ FindByPopupFilter, ++ (const GURL& popup_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey, ++ FilterCategory category), ++ (const, override)); ++ MOCK_METHOD(absl::optional, ++ FindByAllowFilter, ++ (const GURL& frame_url, ++ const std::vector& frame_hierarchy, ++ ContentType content_type, ++ const SiteKey& sitekey), ++ (const, override)); ++ MOCK_METHOD(absl::optional, ++ FindBySpecialFilter, ++ (SpecialFilterType filter_type, ++ const GURL& frame_url, ++ const std::vector& frame_hierarchy, ++ const SiteKey& sitekey), ++ (const, override)); ++ MOCK_METHOD(InstalledSubscription::ContentFiltersData, ++ GetElementHideData, ++ (const GURL& frame_url, ++ const std::vector& 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& frame_hierarchy), ++ (const, override)); ++ MOCK_METHOD(std::set, ++ GetCspInjections, ++ (const GURL& frame_url, const std::vector& frame_hierarchy), ++ (const, override)); ++ MOCK_METHOD(std::set, ++ GetRewriteFilters, ++ (const GURL& frame_url, ++ const std::vector& frame_hierarchy, ++ FilterCategory blocking_filter_category), ++ (const, override)); ++ MOCK_METHOD(std::set, ++ GetHeaderFilters, ++ (const GURL& frame_url, ++ const std::vector& 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ 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 . ++ */ ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_SERVICE_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_TEST_MOCK_SUBSCRIPTION_SERVICE_H_ ++ ++#include ++ ++#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 { ++ public: ++ MockSubscriptionService(); ++ ~MockSubscriptionService() override; ++ MOCK_METHOD(std::vector>, ++ GetCurrentSubscriptions, ++ (FilteringConfiguration*), ++ (override, const)); ++ MOCK_METHOD(Snapshot, GetCurrentSnapshot, (), (override, const)); ++ MOCK_METHOD(void, ++ InstallFilteringConfiguration, ++ (std::unique_ptr configuration), ++ (override)); ++ MOCK_METHOD(void, ++ UninstallFilteringConfiguration, ++ (std::string_view configuration_name), ++ (override)); ++ MOCK_METHOD(std::vector, ++ 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{&filtering_configuration_})); ++ } ++ ++ raw_ptr 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 . ++ */ ++#include "components/adblock/core/subscription/pattern_matcher.h" ++ ++#include ++#include ++#include ++#include ++ ++#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& 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 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(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 . ++ */ ++ ++#include "components/adblock/core/subscription/pattern_matcher.h" ++ ++#include ++#include ++ ++#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 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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( ++ &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 ++ request_maker_; ++ std::unique_ptr ++ 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(); ++ // ... 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(); ++ // ... 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(); ++ // ... 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(); ++ // ... 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(); ++ // ... 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 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 . ++ */ ++ ++#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& 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 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 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 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 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 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 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 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 . ++ */ ++#include "components/adblock/core/subscription/regex_matcher.h" ++ ++#include ++#include ++#include ++#include ++ ++#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& 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 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_collection_impl.h" ++ ++#include ++#include ++ ++#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(); ++ ++ const std::vector 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>{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(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // 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 expected_selectors{"div", "header", ++ "ad_content"}; ++ ++ SubscriptionCollectionImpl collection( ++ std::vector>{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(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // 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 expected_selectors{"a", "d", "e"}; ++ ++ SubscriptionCollectionImpl collection( ++ std::vector>{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(); ++ ++ InstalledSubscription::Snippet snippet; ++ snippet.command = "say"; ++ snippet.arguments = {"Hello"}; ++ EXPECT_CALL(*subscription, MatchSnippets("parent.com")) ++ .WillOnce(Return(std::vector{snippet})); ++ ++ SubscriptionCollectionImpl collection( ++ std::vector>{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(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_TRUE(!!collection.FindBySpecialFilter( ++ SpecialFilterType::Document, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, NoneHasAllowingDocumentFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_FALSE(!!collection.FindBySpecialFilter( ++ SpecialFilterType::Document, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, OneHasAllowingElementHideFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_TRUE(!!collection.FindBySpecialFilter( ++ SpecialFilterType::Elemhide, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ NoneHasAllowingElementHideFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_FALSE(!!collection.FindBySpecialFilter( ++ SpecialFilterType::Elemhide, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, OneHasGenericblockFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_TRUE(collection.FindBySpecialFilter( ++ SpecialFilterType::Genericblock, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, NoneHasGenericblockFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ 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>{sub1, sub2}, {}); ++ EXPECT_FALSE(collection.FindBySpecialFilter( ++ SpecialFilterType::Genericblock, kSourceUrl, {kParentAddress}, kSitekey)); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, CspBlockingFilterNotFound) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // 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>{sub1, sub2}, {}); ++ ++ // Empty result means no CSP injection necessary. ++ EXPECT_TRUE( ++ collection.GetCspInjections(kImageAddress, {kParentAddress}).empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, CspBlockingFilterFound) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // First subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& 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>{sub1, sub2}, {}); ++ ++ std::set filters = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(filters, testing::UnorderedElementsAre("block")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, MultipleCspBlockingFilterFound) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // First subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& res) { res.insert("first"); }))); ++ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& 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>{sub1, sub2}, {}); ++ ++ std::set filters = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(filters, testing::UnorderedElementsAre("first", "second")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ SameCspBlockingFilterFoundInMultipleSubs) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // First subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& res) { res.insert("block"); }))); ++ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& 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>{sub1, sub2}, {}); ++ ++ std::set filters = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(filters, testing::UnorderedElementsAre("block")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ CspBlockingFilterOverruledViaDocumentAllowingRule) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>( ++ testing::Invoke([](std::set& 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>{sub1}, {}); ++ ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_TRUE(results.empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ CspBlockingFilterOverruledViaMatchingAllowingCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>( ++ testing::Invoke([](std::set& 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& 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>{sub1}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_TRUE(results.empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ TwoCspBlockingFilterWithOneOverruledViaMatchingAllowingCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>( ++ testing::Invoke([](std::set& 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& 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>{sub1}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(results, testing::UnorderedElementsAre("first")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ TwoCspBlockingFilterOverruledViaMatchingAllowingCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& res) { res.insert("first"); }))); ++ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>(testing::Invoke( ++ [](std::set& 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& 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& 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>{sub1, sub2}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_TRUE(results.empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ CspBlockingFilterOverruledViaGenericAllowingCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>( ++ testing::Invoke([](std::set& 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& 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>{sub1}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_TRUE(results.empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ CspBlockingFilterNotOverruledViaMismatchedAllowingCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // Subscription contains a blocking CSP filter. ++ EXPECT_CALL(*sub1, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking, _)) ++ .WillOnce(testing::WithArgs<3>( ++ testing::Invoke([](std::set& 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& 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>{sub1}, {}); ++ ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(results, testing::UnorderedElementsAre("script-src 'none'")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, GenericBlockCspFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // 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& 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& res) { ++ res.insert("domain-block"); ++ }))); ++ ++ EXPECT_CALL(*sub2, FindCspFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::DomainSpecificBlocking, _)); ++ ++ // Finally, no CSP filter found. ++ SubscriptionCollectionImpl collection( ++ std::vector>{sub1, sub2}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_THAT(results, testing::UnorderedElementsAre("domain-block")); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, ++ GenericBlockCspFilterWithDomainAllowingFilter) { ++ auto sub1 = base::MakeRefCounted(); ++ ++ // 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& 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& 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& res) { ++ res.insert("domain-block"); ++ }))); ++ ++ // Finally, no CSP filter found. ++ SubscriptionCollectionImpl collection( ++ std::vector>{sub1}, {}); ++ std::set results = ++ collection.GetCspInjections(kImageAddress, {kParentAddress}); ++ EXPECT_TRUE(results.empty()); ++} ++ ++TEST_F(AdblockSubscriptionCollectionImplTest, RewriteBlockingFilterNotFound) { ++ auto sub1 = base::MakeRefCounted(); ++ auto sub2 = base::MakeRefCounted(); ++ ++ // There are no blocking filters found. ++ EXPECT_CALL(*sub1, FindRewriteFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking)) ++ .WillOnce(Return(std::set{})); ++ EXPECT_CALL(*sub2, FindRewriteFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking)) ++ .WillOnce(Return(std::set{})); ++ ++ // 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>{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(); ++ auto sub2 = base::MakeRefCounted(); ++ 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{redirect1})); ++ EXPECT_CALL(*sub2, FindRewriteFilters(kImageAddress, kParentAddress.host(), ++ FilterCategory::Blocking)) ++ .WillOnce(Return(std::set{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>{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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_downloader_impl.h" ++ ++#include ++#include ++ ++#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 span() const final { return {}; } ++}; ++ ++class AdblockSubscriptionDownloaderImplTest : public testing::Test { ++ public: ++ AdblockSubscriptionDownloaderImplTest() { ++ downloader_ = std::make_unique( ++ request_maker_.Get(), &conversion_executor_, &persistent_metadata_); ++ } ++ ++ void TestDateHeaderParsing(network::mojom::URLResponseHeadPtr header_response, ++ std::string_view expected_parsed_string) { ++ base::MockCallback ++ 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(); ++ 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 ++ request_maker_; ++ MockConversionExecutors conversion_executor_; ++ MockSubscriptionPersistentMetadata persistent_metadata_; ++ std::unique_ptr downloader_; ++ ++ const GURL kSubscriptionUrlHttps{"https://subscription.com/filterlist.txt"}; ++}; ++ ++TEST_F(AdblockSubscriptionDownloaderImplTest, ++ NoDownloadFromNotAllowedUrlScheme) { ++ base::MockCallback ++ 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(); ++ 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 ++ 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(); ++ 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 ++ 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(); ++ 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 cb) { ++ std::move(cb).Run(std::make_unique()); ++ }))); ++ // 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 ++ 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(); ++ 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 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 ++ 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(); ++ 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 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 ++ 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(); ++ 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 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(); ++ 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> ++ 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(); ++ 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(); ++ 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.h" ++ ++#include ++ ++#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 { ++ public: ++ AdblockSubscriptionPersistentMetadataImplTest() { ++ common::prefs::RegisterProfilePrefs(prefs_.registry()); ++ metadata_ = std::make_unique(&prefs_); ++ } ++ ++ void MaybeRecreateMetadata() { ++ if (GetParam() == PersistenceType::ValuesPersistInPrefs) { ++ // Recreate SubscriptionPersistentMetadataImpl to make sure it re-reads ++ // its state from prefs. ++ metadata_ = std::make_unique(&prefs_); ++ } else { ++ // Do nothing, verify SubscriptionPersistentMetadataImpl maintains a ++ // consistent state in memory/ ++ } ++ } ++ ++ protected: ++ TestingPrefServiceSimple prefs_; ++ std::unique_ptr 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h" ++ ++#include ++#include ++#include ++#include ++ ++#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(); ++ validator_ = validator.get(); ++ storage_ = std::make_unique( ++ 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 kEasylistFlatbuffer = ++ utils::MakeFlatbufferDataFromResourceBundle( ++ IDR_ADBLOCK_FLATBUFFER_EASYLIST); ++ const std::unique_ptr kExceptionrulesFlatbuffer = ++ utils::MakeFlatbufferDataFromResourceBundle( ++ IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES); ++ ++ base::test::TaskEnvironment task_environment_; ++ base::ScopedTempDir temp_dir_; ++ MockSubscriptionPersistentMetadata metadata_; ++ raw_ptr validator_; ++ std::unique_ptr 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 callback; ++ ++ // Save the argument passed to callback. ++ std::vector> 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 callback; ++ // The callback will be called with parsed Subscription. Save the argument. ++ scoped_refptr 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 callback; ++ scoped_refptr 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 callback; ++ std::vector> 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 . ++ */ ++ ++#include "components/adblock/core/subscription/subscription_service_impl.h" ++ ++#include ++ ++#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 { ++ public: ++ MOCK_METHOD(void, ++ OnSubscriptionInstalled, ++ (const GURL& subscription_url), ++ (override)); ++}; ++} // namespace ++ ++class AdblockSubscriptionServiceImplTest : public testing::Test { ++ public: ++ struct MaintainerFactoryCall { ++ raw_ptr input_configuration; ++ SubscriptionServiceImpl::SubscriptionUpdatedCallback input_update_callback; ++ raw_ptr 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 MockMakeMaintainer( ++ FilteringConfiguration* configuration, ++ SubscriptionServiceImpl::SubscriptionUpdatedCallback update_callback) { ++ auto maintainer = std::make_unique(); ++ 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& subscription) { ++ return (subscription->GetInstallationState() == ++ InstalledSubscription::InstallationState::Unknown) && ++ subscription->GetTitle().empty() && ++ subscription->GetCurrentVersion().empty(); ++ } ++ ++ std::vector maintainer_factory_calls_; ++ std::vector 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(); ++ 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{config_bare_ptr}); ++} ++ ++TEST_F(AdblockSubscriptionServiceImplTest, ++ InstallingEnabledConfigurationCreatesMaintainer) { ++ auto config = std::make_unique(); ++ 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{config_bare_ptr}); ++} ++ ++TEST_F(AdblockSubscriptionServiceImplTest, ++ UninstallingConfigurationRemovesData) { ++ auto config = std::make_unique(); ++ 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{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(); ++ 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(); ++ 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{config_bare_ptr}); ++} ++ ++TEST_F(AdblockSubscriptionServiceImplTest, ++ EnabledMaintainersConsultedForSubscriptions) { ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("1")); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("2")); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("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>{ ++ 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()); ++ ++ 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()); ++ ++ 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>{})); ++ 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("1")); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("2")); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("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(); ++ auto collection2 = std::make_unique(); ++ const std::vector 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::get)); ++} ++ ++TEST_F(AdblockSubscriptionServiceImplTest, ++ SubscriptionObserverNotifiedByMaintainerCallbacks) { ++ const GURL kUrl("https://test.com"); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("1")); ++ testee_.InstallFilteringConfiguration( ++ std::make_unique("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("test"); ++ auto* config1_bare_ptr = config1.get(); ++ testee_.InstallFilteringConfiguration(std::move(config1)); ++ auto config2 = std::make_unique("test"); ++ testee_.InstallFilteringConfiguration(std::move(config2)); ++ EXPECT_EQ(testee_.GetInstalledFilteringConfigurations(), ++ std::vector{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 . ++ */ ++ ++#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 MakeValidator( ++ const std::string& current_schema_version) { ++ return std::make_unique(&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 . ++ */ ++ ++#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 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 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 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 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 . ++ */ ++ ++#include "components/adblock/core/subscription/url_keyword_extractor.h" ++ ++#include ++#include ++ ++#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 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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_ ++#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_ ++ ++#include ++#include ++ ++#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 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 . ++ */ ++ ++#include "components/adblock/core/activeping_telemetry_topic_provider.h" ++ ++#include ++ ++#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 { ++ 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{AcceptableAdsUrl()} ++ : std::vector{})); ++ RecreateProvider(); ++ } ++ ++ bool AcceptableAdsEnabled() const { ++ return GetParam() == AcceptableAds::Enabled; ++ } ++ ++ void RecreateProvider() { ++ provider_ = std::make_unique( ++ &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 ++ 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 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 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(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(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(R"({"data":"1111"})")); ++} ++ ++TEST_P(AdblockActivepingTelemetryTopicProviderTest, ++ FirstRunFailedDueToInvalidJsonResponse) { ++ // Not a valid JSON: ++ ExpectFailureAndRetryForResponse( ++ std::make_unique(R"(rubbish data)")); ++} ++ ++TEST_P(AdblockActivepingTelemetryTopicProviderTest, SecondRunSucceeded) { ++ // Successful first server response: ++ provider_->ParseResponse( ++ std::make_unique(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(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(R"({"token":"Monday"})")); ++ provider_->ParseResponse( ++ std::make_unique(R"({"token":"Tuesday"})")); ++ provider_->ParseResponse( ++ std::make_unique(R"({"token":"Wednesday"})")); ++ provider_->ParseResponse( ++ std::make_unique(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(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 . ++ */ ++ ++#include "components/adblock/core/adblock_telemetry_service.h" ++ ++#include ++ ++#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 { ++ 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), (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( ++ &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(); ++ 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().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 ++ test_shared_url_loader_factory_{ ++ base::MakeRefCounted( ++ &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 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 ++ 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 . ++ */ ++ ++#include ++ ++#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 . ++ */ ++ ++#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 . ++ */ ++ ++#ifndef COMPONENTS_ADBLOCK_CORE_TEST_MOCK_SITEKEY_STORAGE_H_ ++#define COMPONENTS_ADBLOCK_CORE_TEST_MOCK_SITEKEY_STORAGE_H_ ++ ++#include ++#include ++ ++#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& headers, ++ const std::string& user_agent), ++ (override)); ++ ++ MOCK_METHOD((absl::optional>), ++ FindSiteKeyForAnyUrl, ++ (const std::vector& 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 . ++ */ ++ ++#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(); } ++ ++ 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(""); ++ 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 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(""); ++ ++ 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(""); ++ 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 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 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 . ++ ++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 +@@ -1212,7 +1216,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" + +@@ -3464,6 +3468,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_ +@@ -495,6 +499,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_ +@@ -552,6 +556,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" + +@@ -95,6 +99,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& 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_ +@@ -78,6 +82,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& + 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" + +@@ -30,7 +34,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/browser/shell_paths.h" + #include "content/shell/common/shell_content_client.h" + #include "content/shell/common/shell_switches.h" +@@ -446,7 +450,7 @@ ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() { + return browser_client_.get(); + } + #endif +- browser_client_ = std::make_unique(); ++ browser_client_ = std::make_unique(); + 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 . ++ */ ++ ++#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(); ++ ++ 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( ++ base::MakeRefCounted(), ++ base::MakeRefCounted( ++ 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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 . ++ */ ++ ++#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 { ++ 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 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 + origin_trials_controller_delegate_; + ++ std::unique_ptr 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 +@@ -453,6 +457,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_ + +@@ -269,6 +273,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; + +@@ -968,6 +972,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_ + +@@ -143,6 +147,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 +@@ -867,7 +871,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 +@@ -266,6 +266,56 @@ WebStyleSheetKey WebDocument::InsertStyleSheet( + return injection_key; + } + ++bool IsValidAbpRule(StyleRuleBase* rule) { ++ if (!rule->IsStyleRule()) { ++ return false; ++ } ++ const auto& props = blink::To(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(); ++ DCHECK(document); ++ ++ auto* parsed_sheet = MakeGarbageCollected( ++ MakeGarbageCollected(*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()->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_ +@@ -112,6 +116,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" +@@ -2414,7 +2418,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(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 +@@ -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 . ++ ++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 ++ ++ 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 ++ 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 . ++ ++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 . ++ ++# 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 +@@ -1109,6 +1113,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. + --> + + + + + +- ++ + + + +@@ -3196,6 +3200,8 @@ Called by update_extension_permission.py.--> + + + ++ ++ + + + +-- diff --git a/build/cromite_patches/eyeo-133.0.6943.49-chrome_integration.patch b/build/cromite_patches/eyeo-133.0.6943.49-chrome_integration.patch new file mode 100644 index 0000000000000000000000000000000000000000..270c03f025a4670b361d6c41ec74cf650e67baab --- /dev/null +++ b/build/cromite_patches/eyeo-133.0.6943.49-chrome_integration.patch @@ -0,0 +1,4110 @@ +From: chromium-sdk +Date: Fri, 14 Feb 2025 08:26:33 +0100 +Subject: eyeo Browser Ad filtering Solution: Chrome Integration Module + +Based on Chromium 133.0.6943.49 + +Pre-requisites: eyeo Browser Ad filtering Solution: Base Module +--- + chrome/app/chrome_main_delegate.cc | 7 +- + chrome/browser/BUILD.gn | 8 + + chrome/browser/adblock/README.md | 3 + + .../adblock_chrome_content_browser_client.cc | 27 + + .../adblock_chrome_content_browser_client.h | 31 ++ + ..._chrome_content_browser_client_unittest.cc | 204 ++++++++ + chrome/browser/adblock/android/BUILD.gn | 79 +++ + .../adblock/AdblockControllerTest.java | 52 ++ + .../adblock/AdblockPopupMessageTest.java | 113 +++++ + .../browser/adblock/CheckChromeFlagsTest.java | 65 +++ + .../browser/adblock/DefaultSettingsTest.java | 54 ++ + .../adblock/FilteringConfigurationTest.java | 79 +++ + .../ResourceClassificationNotifierTest.java | 153 ++++++ + .../adblock/TestPagesCircumventionTest.java | 56 +++ + .../browser/adblock/TestPagesCspTest.java | 56 +++ + .../adblock/TestPagesElemhideEmuInvTest.java | 53 ++ + .../adblock/TestPagesElemhideEmuTest.java | 53 ++ + .../adblock/TestPagesElemhideTest.java | 53 ++ + .../adblock/TestPagesExceptionTest.java | 114 +++++ + .../browser/adblock/TestPagesFilterTest.java | 113 +++++ + .../adblock/TestPagesHeaderFilterTest.java | 56 +++ + .../browser/adblock/TestPagesHelper.java | 144 ++++++ + .../adblock/TestPagesInlineCssTest.java | 53 ++ + .../browser/adblock/TestPagesRemoveTest.java | 53 ++ + .../browser/adblock/TestPagesRewriteTest.java | 56 +++ + .../adblock/TestPagesServiceWorkersTest.java | 56 +++ + .../browser/adblock/TestPagesSiteKeyTest.java | 56 +++ + .../adblock/TestPagesSnippetsTest.java | 56 +++ + .../adblock/TestPagesWebsocketTest.java | 56 +++ + .../adblock/TestPagesWildcardDomainTest.java | 56 +++ + ...ock_frame_hierarchy_builder_browsertest.cc | 474 ++++++++++++++++++ + .../test/adblock_multiple_tabs_browsertest.cc | 168 +++++++ + .../adblock/test/adblock_popup_browsertest.cc | 462 +++++++++++++++++ + chrome/browser/chrome_browser_main.cc | 7 + + .../client_hints/client_hints_browsertest.cc | 11 +- + chrome/browser/extensions/BUILD.gn | 5 + + chrome/browser/net/errorpage_browsertest.cc | 8 + + ..._page_load_metrics_observer_browsertest.cc | 7 +- + chrome/browser/preferences/BUILD.gn | 4 + + .../prefs/chrome_pref_service_factory.cc | 11 + + ...hrome_browser_main_extra_parts_profiles.cc | 22 + + .../profile_keyed_service_browsertest.cc | 18 + + chrome/browser/resources/BUILD.gn | 5 + + .../safe_browsing_blocking_page_test.cc | 7 +- + .../sessions/session_restore_browsertest.cc | 8 +- + ...subresource_filter_browser_test_harness.cc | 8 +- + chrome/browser/ui/BUILD.gn | 4 + + chrome/browser/ui/prefs/pref_watcher.cc | 13 + + chrome/browser/ui/tab_helpers.cc | 13 + + chrome/chrome_paks.gni | 5 + + chrome/common/BUILD.gn | 3 + + chrome/test/BUILD.gn | 14 + + chrome/test/base/in_process_browser_test.cc | 24 +- + 53 files changed, 3309 insertions(+), 7 deletions(-) + create mode 100644 chrome/browser/adblock/README.md + create mode 100644 chrome/browser/adblock/adblock_chrome_content_browser_client.cc + create mode 100644 chrome/browser/adblock/adblock_chrome_content_browser_client.h + create mode 100644 chrome/browser/adblock/adblock_chrome_content_browser_client_unittest.cc + create mode 100644 chrome/browser/adblock/android/BUILD.gn + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockControllerTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/CheckChromeFlagsTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/DefaultSettingsTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/FilteringConfigurationTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/ResourceClassificationNotifierTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCircumventionTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCspTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuInvTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesExceptionTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesFilterTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHeaderFilterTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHelper.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesInlineCssTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRemoveTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRewriteTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesServiceWorkersTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSiteKeyTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSnippetsTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWebsocketTest.java + create mode 100644 chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWildcardDomainTest.java + create mode 100644 chrome/browser/adblock/test/adblock_frame_hierarchy_builder_browsertest.cc + create mode 100644 chrome/browser/adblock/test/adblock_multiple_tabs_browsertest.cc + create mode 100644 chrome/browser/adblock/test/adblock_popup_browsertest.cc + +diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc +--- a/chrome/app/chrome_main_delegate.cc ++++ b/chrome/app/chrome_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. + + #ifdef UNSAFE_BUFFERS_BUILD + // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. +@@ -42,6 +46,7 @@ + #include "base/timer/timer.h" + #include "base/trace_event/trace_event_impl.h" + #include "build/build_config.h" ++#include "chrome/browser/adblock/adblock_chrome_content_browser_client.h" + #include "chrome/browser/buildflags.h" + #include "chrome/browser/chrome_content_browser_client.h" + #include "chrome/browser/chrome_resource_bundle_helper.h" +@@ -1630,7 +1635,7 @@ content::ContentClient* ChromeMainDelegate::CreateContentClient() { + content::ContentBrowserClient* + ChromeMainDelegate::CreateContentBrowserClient() { + chrome_content_browser_client_ = +- std::make_unique(); ++ std::make_unique(); + #if !BUILDFLAG(IS_ANDROID) + // Android does this in `ChromeMainDelegateAndroid::PreSandboxStartup`. + CHECK(sampling_profiler_); +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +--- a/chrome/browser/BUILD.gn ++++ b/chrome/browser/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/allocator/allocator.gni") + import("//build/buildflag_header.gni") +@@ -171,6 +174,8 @@ static_library("browser") { + "accessibility/page_colors_factory.h", + "accessibility/prefers_default_scrollbar_styles_prefs.cc", + "accessibility/prefers_default_scrollbar_styles_prefs.h", ++ "adblock/adblock_chrome_content_browser_client.cc", ++ "adblock/adblock_chrome_content_browser_client.h", + "affiliations/affiliation_service_factory.cc", + "affiliations/affiliation_service_factory.h", + "after_startup_task_utils.cc", +@@ -2024,6 +2029,7 @@ static_library("browser") { + "//chrome/common/notifications", + "//chrome/installer/util:with_no_strings", + "//chrome/services/speech/buildflags", ++ "//components/adblock/content:browser", + "//components/assist_ranker", + "//components/autofill/core/browser", + "//components/back_forward_cache", +@@ -3245,6 +3251,7 @@ static_library("browser") { + "touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h", + ] + ++ + deps += [ + ":delta_file_proto", + ":profile_token", +@@ -8313,6 +8320,7 @@ static_library("browser_public_dependencies") { + "//chrome/services/file_util/public/mojom", + "//components/account_id", + "//components/autofill/content/browser", ++ "//components/adblock/content:browser", + "//components/autofill/core/browser", + "//components/commerce/core/product_specifications:product_specifications", + "//components/compose/core/browser:mojo_bindings", +diff --git a/chrome/browser/adblock/README.md b/chrome/browser/adblock/README.md +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/README.md +@@ -0,0 +1,3 @@ ++This folder contains the OS-agnostic, Chrome-specific source code of eyeo Chromium SDK. ++ ++For the full documentation, refer to [components/adblock](/components/adblock). +diff --git a/chrome/browser/adblock/adblock_chrome_content_browser_client.cc b/chrome/browser/adblock/adblock_chrome_content_browser_client.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/adblock_chrome_content_browser_client.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 . ++ */ ++ ++#include "chrome/browser/adblock/adblock_chrome_content_browser_client.h" ++ ++#include "chrome/browser/profiles/profile.h" ++ ++content::BrowserContext* ++AdblockChromeContentBrowserClient::GetBrowserContextForEyeoFactories( ++ content::BrowserContext* current_browser_context) { ++ return Profile::FromBrowserContext(current_browser_context) ++ ->GetOriginalProfile(); ++} +diff --git a/chrome/browser/adblock/adblock_chrome_content_browser_client.h b/chrome/browser/adblock/adblock_chrome_content_browser_client.h +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/adblock_chrome_content_browser_client.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 . ++ */ ++ ++#ifndef CHROME_BROWSER_ADBLOCK_ADBLOCK_CHROME_CONTENT_BROWSER_CLIENT_H_ ++#define CHROME_BROWSER_ADBLOCK_ADBLOCK_CHROME_CONTENT_BROWSER_CLIENT_H_ ++ ++#include "chrome/browser/chrome_content_browser_client.h" ++#include "components/adblock/content/browser/adblock_content_browser_client.h" ++ ++class AdblockChromeContentBrowserClient ++ : public adblock::AdblockContentBrowserClient { ++ private: ++ content::BrowserContext* GetBrowserContextForEyeoFactories( ++ content::BrowserContext* current_browser_context) override; ++}; ++ ++#endif // CHROME_BROWSER_ADBLOCK_ADBLOCK_CHROME_CONTENT_BROWSER_CLIENT_H_ +diff --git a/chrome/browser/adblock/adblock_chrome_content_browser_client_unittest.cc b/chrome/browser/adblock/adblock_chrome_content_browser_client_unittest.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/adblock_chrome_content_browser_client_unittest.cc +@@ -0,0 +1,204 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++#include "chrome/browser/adblock/adblock_chrome_content_browser_client.h" ++ ++#include "base/callback_list.h" ++#include "base/memory/raw_ptr.h" ++#include "base/run_loop.h" ++#include "base/test/gmock_move_support.h" ++#include "base/test/mock_callback.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/test/base/chrome_render_view_host_test_harness.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/mock_resource_classification_runner.h" ++#include "components/adblock/core/common/adblock_prefs.h" ++#include "components/adblock/core/common/content_type.h" ++#include "components/adblock/core/subscription/installed_subscription.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/keyed_service/core/keyed_service.h" ++#include "content/public/browser/content_browser_client.h" ++#include "content/public/test/mock_render_process_host.h" ++#include "content/public/test/test_renderer_host.h" ++#include "gmock/gmock.h" ++#include "services/network/public/mojom/network_context.mojom.h" ++#include "services/network/public/mojom/websocket.mojom.h" ++#include "testing/gtest/include/gtest/gtest.h" ++ ++using testing::_; ++using testing::Ref; ++using testing::Return; ++ ++namespace adblock { ++ ++class AdblockChromeContentBrowserClientUnitTest ++ : public ChromeRenderViewHostTestHarness { ++ public: ++ TestingProfile::TestingFactories GetTestingFactories() const override { ++ return {TestingProfile::TestingFactory{ ++ SubscriptionServiceFactory::GetInstance(), ++ base::BindRepeating([](content::BrowserContext* bc) ++ -> std::unique_ptr { ++ return std::make_unique(); ++ })}, ++ TestingProfile::TestingFactory{ ++ ResourceClassificationRunnerFactory::GetInstance(), ++ base::BindRepeating([](content::BrowserContext* bc) ++ -> std::unique_ptr { ++ return std::make_unique(); ++ })}}; ++ } ++ ++ void SetUp() override { ++ ChromeRenderViewHostTestHarness::SetUp(); ++ ++ subscription_service_ = static_cast( ++ SubscriptionServiceFactory::GetForBrowserContext(profile())); ++ resource_classification_runner_ = ++ static_cast( ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ profile())); ++ } ++ ++ void TearDown() override { ++ subscription_service_ = nullptr; ++ resource_classification_runner_ = nullptr; ++ ChromeRenderViewHostTestHarness::TearDown(); ++ } ++ ++ raw_ptr subscription_service_; ++ raw_ptr resource_classification_runner_; ++}; ++ ++TEST_F(AdblockChromeContentBrowserClientUnitTest, ++ WillInterceptWebSocketWhenFilteringEnabled) { ++ AdblockChromeContentBrowserClient content_client; ++ subscription_service_->WillRequireFiltering(true); ++ EXPECT_TRUE(content_client.WillInterceptWebSocket(main_rfh())); ++} ++ ++TEST_F(AdblockChromeContentBrowserClientUnitTest, ++ WillNotInterceptWebSocketWhenFilteringDisabled) { ++ AdblockChromeContentBrowserClient content_client; ++ subscription_service_->WillRequireFiltering(false); ++ EXPECT_FALSE(content_client.WillInterceptWebSocket(main_rfh())); ++} ++ ++TEST_F(AdblockChromeContentBrowserClientUnitTest, ++ RenderFrameHostDiesBeforeClassificationFinished) { ++ const auto kSocketUrl = GURL("wss://domain.com/test"); ++ subscription_service_->WillRequireFiltering(true); ++ EXPECT_CALL(*subscription_service_, GetCurrentSnapshot()).WillOnce([]() { ++ SubscriptionService::Snapshot snapshot; ++ snapshot.push_back(std::make_unique()); ++ return snapshot; ++ }); ++ CheckFilterMatchCallback classification_callback; ++ EXPECT_CALL(*resource_classification_runner_, ++ CheckRequestFilterMatch(_, kSocketUrl, ContentType::Websocket, ++ RequestInitiator(main_rfh()), _)) ++ .WillOnce(MoveArg<4>(&classification_callback)); ++ ++ AdblockChromeContentBrowserClient content_client; ++ base::MockCallback ++ web_socket_factory; ++ // The web_socket_factory callback will never be called because the ++ // associated RenderFrameHost will be dead. ++ EXPECT_CALL(web_socket_factory, Run(_, _, _, _, _)).Times(0); ++ ++ const net::SiteForCookies site_for_cookies; ++ content_client.CreateWebSocket(main_rfh(), web_socket_factory.Get(), ++ kSocketUrl, site_for_cookies, absl::nullopt, ++ {}); ++ // Tab is closed. ++ DeleteContents(); ++ ++ // Classification finishes now. It will not trigger a call to ++ // |web_socket_factory| because the RFH is dead. ++ std::move(classification_callback).Run(FilterMatchResult::kBlockRule); ++ ++ task_environment()->RunUntilIdle(); ++} ++ ++TEST_F(AdblockChromeContentBrowserClientUnitTest, WebSocketAllowed) { ++ subscription_service_->WillRequireFiltering(true); ++ const auto kSocketUrl = GURL("wss://domain.com/test"); ++ EXPECT_CALL(*subscription_service_, GetCurrentSnapshot()).WillOnce([]() { ++ SubscriptionService::Snapshot snapshot; ++ snapshot.push_back(std::make_unique()); ++ return snapshot; ++ }); ++ CheckFilterMatchCallback classification_callback; ++ EXPECT_CALL(*resource_classification_runner_, ++ CheckRequestFilterMatch(_, kSocketUrl, ContentType::Websocket, ++ RequestInitiator(main_rfh()), _)) ++ .WillOnce(MoveArg<4>(&classification_callback)); ++ ++ AdblockChromeContentBrowserClient content_client; ++ base::MockCallback ++ web_socket_factory; ++ // The web_socket_factory callback will be called to let the web socket ++ // continue connecting. ++ EXPECT_CALL(web_socket_factory, Run(kSocketUrl, _, _, _, _)); ++ ++ const net::SiteForCookies site_for_cookies; ++ content_client.CreateWebSocket(main_rfh(), web_socket_factory.Get(), ++ kSocketUrl, site_for_cookies, absl::nullopt, ++ {}); ++ ++ // Classification finishes now. It will trigger a call to |web_socket_factory| ++ std::move(classification_callback).Run(FilterMatchResult::kAllowRule); ++ ++ task_environment()->RunUntilIdle(); ++} ++ ++TEST_F(AdblockChromeContentBrowserClientUnitTest, WebSocketBlocked) { ++ subscription_service_->WillRequireFiltering(true); ++ const auto kSocketUrl = GURL("wss://domain.com/test"); ++ EXPECT_CALL(*subscription_service_, GetCurrentSnapshot()).WillOnce([]() { ++ SubscriptionService::Snapshot snapshot; ++ snapshot.push_back(std::make_unique()); ++ return snapshot; ++ }); ++ CheckFilterMatchCallback classification_callback; ++ EXPECT_CALL(*resource_classification_runner_, ++ CheckRequestFilterMatch(_, kSocketUrl, ContentType::Websocket, ++ RequestInitiator(main_rfh()), _)) ++ .WillOnce(MoveArg<4>(&classification_callback)); ++ ++ AdblockChromeContentBrowserClient content_client; ++ base::MockCallback ++ web_socket_factory; ++ // The web_socket_factory callback will not be called as to disallow ++ // connection. ++ EXPECT_CALL(web_socket_factory, Run(kSocketUrl, _, _, _, _)).Times(0); ++ ++ const net::SiteForCookies site_for_cookies; ++ content_client.CreateWebSocket(main_rfh(), web_socket_factory.Get(), ++ kSocketUrl, site_for_cookies, absl::nullopt, ++ {}); ++ ++ // Classification finishes now. ++ std::move(classification_callback).Run(FilterMatchResult::kBlockRule); ++ ++ task_environment()->RunUntilIdle(); ++} ++ ++} // namespace adblock +diff --git a/chrome/browser/adblock/android/BUILD.gn b/chrome/browser/adblock/android/BUILD.gn +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/BUILD.gn +@@ -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 . ++ ++import("//build/config/android/rules.gni") ++import("//build/config/locales.gni") ++import("//third_party/jni_zero/jni_zero.gni") ++import("//tools/grit/grit_rule.gni") ++ ++android_library("adblock_java_tests") { ++ testonly = true ++ ++ sources = [ ++ "javatests/src/org/chromium/chrome/browser/adblock/AdblockControllerTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/CheckChromeFlagsTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/DefaultSettingsTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/FilteringConfigurationTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/ResourceClassificationNotifierTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesCircumventionTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesCspTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuInvTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesExceptionTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesFilterTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesHeaderFilterTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesHelper.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesInlineCssTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesRemoveTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesRewriteTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesServiceWorkersTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesSiteKeyTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesSnippetsTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesWebsocketTest.java", ++ "javatests/src/org/chromium/chrome/browser/adblock/TestPagesWildcardDomainTest.java", ++ ] ++ ++ deps = [] ++ ++ ++ deps += [ ++ "//base:base_java", ++ "//base:base_java_test_support", ++ "//chrome/android:chrome_java", ++ "//chrome/browser/flags:java", ++ "//chrome/browser/profiles/android:java", ++ "//chrome/browser/settings:test_support_java", ++ "//chrome/browser/tab:java", ++ "//chrome/browser/tabmodel:java", ++ "//chrome/test/android:chrome_java_integration_test_support", ++ "//chrome/test/android:chrome_java_test_support_common", ++ "//components/adblock/android:adblock_controller_java", ++ "//components/adblock/android:adblock_java_tests_base", ++ "//components/infobars/android:java", ++ "//components/infobars/core:infobar_enums_java", ++ "//components/messages/android/test:test_support_java", ++ "//content/public/android:content_full_java", ++ "//content/public/android:content_main_dex_java", ++ "//content/public/test/android:content_java_test_support", ++ "//net/android:net_java_test_support", ++ "//third_party/androidx:androidx_fragment_fragment_java", ++ "//third_party/androidx:androidx_test_monitor_java", ++ "//third_party/androidx:androidx_test_runner_java", ++ "//third_party/hamcrest:hamcrest_core_java", ++ "//third_party/hamcrest:hamcrest_library_java", ++ "//third_party/junit:junit", ++ "//ui/android:ui_no_recycler_view_java", ++ "//url:gurl_java", ++ ] ++ ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockControllerTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockControllerTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockControllerTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockController; ++import org.chromium.components.adblock.AdblockControllerTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) ++public class AdblockControllerTest extends AdblockControllerTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ @Before ++ public void setUp() { ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mAdblockController = ++ AdblockController.getInstance( ++ ProfileManager.getLastUsedRegularProfile()); ++ }); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/AdblockPopupMessageTest.java +@@ -0,0 +1,113 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import androidx.test.InstrumentationRegistry; ++import androidx.test.filters.MediumTest; ++ ++import org.junit.After; ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.CriteriaHelper; ++import org.chromium.base.test.util.Feature; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.tabmodel.TabModelSelector; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.messages.MessageDispatcher; ++import org.chromium.components.messages.MessageDispatcherProvider; ++import org.chromium.components.messages.MessageIdentifier; ++import org.chromium.components.messages.MessageStateHandler; ++import org.chromium.components.messages.MessagesTestHelper; ++import org.chromium.net.test.EmbeddedTestServer; ++ ++import java.util.List; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) ++public class AdblockPopupMessageTest { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private static final String POPUP_HTML_PATH = "/chrome/test/data/android/popup_test.html"; ++ ++ private String mPopupHtmlUrl; ++ private EmbeddedTestServer mTestServer; ++ ++ private void isPopupMessageShown(boolean visible) { ++ CriteriaHelper.pollUiThread( ++ () -> { ++ final MessageDispatcher messageDispatcher = ++ MessageDispatcherProvider.from( ++ mActivityTestRule.getActivity().getWindowAndroid()); ++ assert messageDispatcher != null; ++ final List messages = ++ MessagesTestHelper.getEnqueuedMessages( ++ messageDispatcher, MessageIdentifier.POPUP_BLOCKED); ++ assert (visible ? !messages.isEmpty() : messages.isEmpty()); ++ }); ++ } ++ ++ @Before ++ public void setUp() throws Exception { ++ // Create a new temporary instance to ensure the Class is loaded. Otherwise we will get a ++ // ClassNotFoundException when trying to instantiate during startup. ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ ++ mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); ++ mPopupHtmlUrl = mTestServer.getURL(POPUP_HTML_PATH); ++ } ++ ++ @After ++ public void tearDown() { ++ mTestServer.stopAndDestroyServer(); ++ } ++ ++ public int getTabCount() { ++ final TabModelSelector tabModelSelector = ++ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); ++ Assert.assertNotNull(tabModelSelector); ++ return tabModelSelector.getTotalTabCount(); ++ } ++ ++ @Test ++ @MediumTest ++ @Feature({"adblock"}) ++ public void popUpBlockedMessageVisibleWhenAbpEnabled() { ++ isPopupMessageShown(false); ++ mActivityTestRule.loadUrl(mPopupHtmlUrl); ++ Assert.assertEquals(1, getTabCount()); ++ isPopupMessageShown(true); ++ } ++ ++ @Test ++ @MediumTest ++ @CommandLineFlags.Add({"disable-adblock"}) ++ @Feature({"adblock"}) ++ public void popUpBlockedMessageVisibleWhenAbpDisabled() { ++ isPopupMessageShown(false); ++ mActivityTestRule.loadUrl(mPopupHtmlUrl); ++ Assert.assertEquals(1, getTabCount()); ++ isPopupMessageShown(true); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/CheckChromeFlagsTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/CheckChromeFlagsTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/CheckChromeFlagsTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.Feature; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.TestVerificationUtils; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) ++public class CheckChromeFlagsTest { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++ ++ public static final String CHROME_FLAGS_URL = "chrome://flags/"; ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ public void testCheckChromeFlagsDoesntCrash() throws Exception { ++ mHelper.loadUrl(CHROME_FLAGS_URL); ++ TestVerificationUtils.verifyCondition(mHelper, "document.title == 'Experiments'"); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/DefaultSettingsTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/DefaultSettingsTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/DefaultSettingsTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.DefaultSettingsTestBase; ++import org.chromium.content_public.browser.BrowserContextHandle; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) ++public class DefaultSettingsTest extends DefaultSettingsTestBase { ++ @Override ++ protected BrowserContextHandle getBrowserContext() { ++ return ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ return ProfileManager.getLastUsedRegularProfile(); ++ }); ++ } ++ ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ @Before ++ public void setUp() { ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/FilteringConfigurationTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/FilteringConfigurationTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/FilteringConfigurationTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.FilteringConfigurationTestBase; ++import org.chromium.content_public.browser.BrowserContextHandle; ++import org.chromium.content_public.common.ContentSwitches; ++ ++import java.util.concurrent.TimeoutException; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1", ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class FilteringConfigurationTest extends FilteringConfigurationTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Override ++ protected void loadTestUrl() throws Exception { ++ mActivityTestRule.loadUrl(mTestUrl, 5); ++ } ++ ++ @Override ++ protected BrowserContextHandle getBrowserContext() { ++ return ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ return ProfileManager.getLastUsedRegularProfile(); ++ }); ++ } ++ ++ @Before ++ public void setUp() throws TimeoutException { ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ mHelper.setActivityTestRule(mActivityTestRule); ++ super.setUp(mHelper, "/components/test/data/adblock/innermost_frame.html"); ++ } ++ ++ @After ++ @Override ++ public void tearDown() { ++ mHelper.tearDown(); ++ super.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/ResourceClassificationNotifierTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/ResourceClassificationNotifierTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/ResourceClassificationNotifierTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import androidx.test.InstrumentationRegistry; ++import androidx.test.filters.LargeTest; ++ ++import org.junit.After; ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CallbackHelper; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.Feature; ++import org.chromium.base.test.util.IntegrationTest; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockController; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.FilteringConfiguration; ++import org.chromium.components.adblock.ResourceClassificationNotifier; ++import org.chromium.components.adblock.TestResourceFilteringObserver; ++import org.chromium.content_public.common.ContentSwitches; ++import org.chromium.net.test.EmbeddedTestServer; ++ ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1", ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class ResourceClassificationNotifierTest { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final CallbackHelper mHelper = new CallbackHelper(); ++ public FilteringConfiguration mConfiguration; ++ public TestResourceFilteringObserver mResourceFilteringObserver = ++ new TestResourceFilteringObserver(); ++ ++ private EmbeddedTestServer mTestServer; ++ private String mTestUrl; ++ ++ public void loadTestUrl() { ++ mActivityTestRule.loadUrl(mTestUrl, 5); ++ } ++ ++ @Before ++ public void setUp() throws TimeoutException { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfiguration = ++ FilteringConfiguration.createConfiguration( ++ "a", ProfileManager.getLastUsedRegularProfile()); ++ ResourceClassificationNotifier.getInstance( ++ ProfileManager.getLastUsedRegularProfile()) ++ .addResourceFilteringObserver(mResourceFilteringObserver); ++ mHelper.notifyCalled(); ++ }); ++ mHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); ++ mTestUrl = ++ mTestServer.getURLWithHostName( ++ "test.org", "/components/test/data/adblock/innermost_frame.html"); ++ } ++ ++ @After ++ public void tearDown() { ++ mTestServer.stopAndDestroyServer(); ++ } ++ ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void noNotificationWithoutBlocking() throws Exception { ++ loadTestUrl(); ++ ++ Assert.assertTrue(mResourceFilteringObserver.blockedInfos.isEmpty()); ++ Assert.assertTrue(mResourceFilteringObserver.allowedInfos.isEmpty()); ++ Assert.assertTrue(mResourceFilteringObserver.allowedPageInfos.isEmpty()); ++ } ++ ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void resourceBlockedByFilter() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfiguration.addCustomFilter("resource.png"); ++ mHelper.notifyCalled(); ++ }); ++ mHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ // Observer was notified about the blocking ++ Assert.assertTrue(mResourceFilteringObserver.isBlocked("resource.png")); ++ Assert.assertTrue(mResourceFilteringObserver.allowedInfos.isEmpty()); ++ Assert.assertTrue(mResourceFilteringObserver.allowedPageInfos.isEmpty()); ++ } ++ ++ @Test ++ @IntegrationTest ++ @LargeTest ++ @Feature({"adblock"}) ++ public void pageAllowed() throws Exception { ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> { ++ mConfiguration.addCustomFilter("resource.png"); ++ mConfiguration.addAllowedDomain("test.org"); ++ // In general "adblock" configuration does not interfere. ++ // But for onPageAllowed event all configurations must contain ++ // page allowing rule so let's add rule there. ++ AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()) ++ .addAllowedDomain("test.org"); ++ mHelper.notifyCalled(); ++ }); ++ mHelper.waitForCallback(0, 1, 10, TimeUnit.SECONDS); ++ loadTestUrl(); ++ // Observer was notified about the allowed resource ++ Assert.assertTrue(mResourceFilteringObserver.blockedInfos.isEmpty()); ++ Assert.assertTrue(mResourceFilteringObserver.isAllowed("resource.png")); ++ Assert.assertTrue(mResourceFilteringObserver.isPageAllowed("test.org")); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCircumventionTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCircumventionTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCircumventionTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesCircumventionTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesCircumventionTest extends TestPagesCircumventionTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCspTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCspTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesCspTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesCspTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesCspTest extends TestPagesCspTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuInvTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuInvTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuInvTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesElemhideEmuInvTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesElemhideEmuInvTest extends TestPagesElemhideEmuInvTestBase { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideEmuTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesElemhideEmuTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesElemhideEmuTest extends TestPagesElemhideEmuTestBase { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesElemhideTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesElemhideTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesElemhideTest extends TestPagesElemhideTestBase { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesExceptionTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesExceptionTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesExceptionTest.java +@@ -0,0 +1,114 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.After; ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.task.PostTask; ++import org.chromium.base.task.TaskTraits; ++import org.chromium.base.test.util.CallbackHelper; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.Feature; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesExceptionTestBase; ++import org.chromium.content_public.browser.WebContents; ++import org.chromium.content_public.browser.test.util.JavaScriptUtils; ++import org.chromium.content_public.common.ContentSwitches; ++ ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesExceptionTest extends TestPagesExceptionTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ @CommandLineFlags.Add(ContentSwitches.DISABLE_POPUP_BLOCKING) ++ public void testVerifyPopupException() throws Exception { ++ final String POPUP_TESTACE_URL = ++ TestPagesHelper.EXCEPTION_TESTPAGES_TESTCASES_ROOT + "popup"; ++ mHelper.loadUrl(POPUP_TESTACE_URL); ++ Assert.assertEquals(1, mHelper.getTabCount()); ++ final CallbackHelper tabsLoadedWaiter = mHelper.getTabsOpenedAndLoadedWaiter(); ++ final WebContents webContents = mHelper.getWebContents(); ++ PostTask.postTask( ++ TaskTraits.BEST_EFFORT_MAY_BLOCK, ++ () -> { ++ try { ++ String numElements = ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ webContents, ++ "var elements =" ++ + " document.getElementsByClassName(\"testcase-trigger\");for" ++ + " (let i = 0; i < elements.length; ++i) { " ++ + " elements[i].click();}elements.length;"); ++ Assert.assertEquals("3", numElements); ++ } catch (TimeoutException e) { ++ Assert.assertEquals("Popups were triggered", "Popups were NOT triggered"); ++ } ++ }); ++ // Wait for three tab loaded events ++ tabsLoadedWaiter.waitForCallback(0, 3, TestPagesHelper.TEST_TIMEOUT_SEC, TimeUnit.SECONDS); ++ Assert.assertEquals(4, mHelper.getTabCount()); ++ Assert.assertEquals(3, mHelper.numAllowedPopups()); ++ Assert.assertTrue( ++ mHelper.isPopupAllowed( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT + "popup_exception/link.html")); ++ Assert.assertTrue( ++ mHelper.isPopupAllowed( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT ++ + "popup_exception/script-window.html")); ++ Assert.assertTrue( ++ mHelper.isPopupAllowed( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT ++ + "popup_exception/script-tab.html")); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesFilterTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesFilterTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesFilterTest.java +@@ -0,0 +1,113 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import androidx.test.filters.LargeTest; ++ ++import org.junit.After; ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.task.PostTask; ++import org.chromium.base.task.TaskTraits; ++import org.chromium.base.test.util.CallbackHelper; ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.base.test.util.Feature; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesFilterTestBase; ++import org.chromium.content_public.browser.WebContents; ++import org.chromium.content_public.browser.test.util.JavaScriptUtils; ++import org.chromium.content_public.common.ContentSwitches; ++ ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.TimeoutException; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesFilterTest extends TestPagesFilterTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @Test ++ @LargeTest ++ @Feature({"adblock"}) ++ @CommandLineFlags.Add(ContentSwitches.DISABLE_POPUP_BLOCKING) ++ public void testVerifyPopupFilters() throws Exception { ++ final String POPUP_TESTACE_URL = TestPagesHelper.FILTER_TESTPAGES_TESTCASES_ROOT + "popup"; ++ mHelper.loadUrl(POPUP_TESTACE_URL); ++ Assert.assertEquals(1, mHelper.getTabCount()); ++ final CallbackHelper tabsOpenedAndClosedWaiter = mHelper.getTabsOpenedAndClosedWaiter(); ++ final WebContents webContents = mHelper.getWebContents(); ++ // Trigger popups which open and close when blocked ++ PostTask.postTask( ++ TaskTraits.BEST_EFFORT_MAY_BLOCK, ++ () -> { ++ try { ++ String numElements = ++ JavaScriptUtils.executeJavaScriptAndWaitForResult( ++ webContents, ++ "var elements =" ++ + " document.getElementsByClassName(\"testcase-trigger\");for" ++ + " (let i = 0; i < elements.length; ++i) { " ++ + " elements[i].click();}elements.length;"); ++ Assert.assertEquals("3", numElements); ++ } catch (TimeoutException e) { ++ Assert.assertEquals("Popups were triggered", "Popups were NOT triggered"); ++ } ++ }); ++ // Wait for three tab open events and three close tabs events ++ tabsOpenedAndClosedWaiter.waitForCallback( ++ 0, 6, TestPagesHelper.TEST_TIMEOUT_SEC, TimeUnit.SECONDS); ++ Assert.assertEquals(3, mHelper.numBlockedPopups()); ++ Assert.assertTrue( ++ mHelper.isPopupBlocked( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT + "popup/link.html")); ++ Assert.assertTrue( ++ mHelper.isPopupBlocked( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT + "popup/script-window.html")); ++ Assert.assertTrue( ++ mHelper.isPopupBlocked( ++ TestPagesHelper.TESTPAGES_RESOURCES_ROOT + "popup/script-tab.html")); ++ Assert.assertEquals(1, mHelper.getTabCount()); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHeaderFilterTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHeaderFilterTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHeaderFilterTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesHeaderFilterTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesHeaderFilterTest extends TestPagesHeaderFilterTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHelper.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHelper.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesHelper.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.Assert; ++ ++import org.chromium.base.ThreadUtils; ++import org.chromium.base.test.util.CallbackHelper; ++import org.chromium.chrome.browser.profiles.ProfileManager; ++import org.chromium.chrome.browser.tab.EmptyTabObserver; ++import org.chromium.chrome.browser.tab.Tab; ++import org.chromium.chrome.browser.tab.Tab.LoadUrlResult; ++import org.chromium.chrome.browser.tabmodel.TabModelObserver; ++import org.chromium.chrome.browser.tabmodel.TabModelSelector; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.TestPagesHelperBase; ++import org.chromium.content_public.browser.BrowserContextHandle; ++import org.chromium.content_public.browser.WebContents; ++import org.chromium.url.GURL; ++ ++import java.util.concurrent.TimeoutException; ++ ++public class TestPagesHelper extends TestPagesHelperBase { ++ private ChromeTabbedActivityTestRule mActivityTestRule; ++ ++ public void setActivityTestRule(ChromeTabbedActivityTestRule activityTestRule) { ++ mActivityTestRule = activityTestRule; ++ } ++ ++ public void setUp(final ChromeTabbedActivityTestRule activityRule) { ++ mActivityTestRule = activityRule; ++ mActivityTestRule.startMainActivityOnBlankPage(); ++ mActivityTestRule.waitForActivityNativeInitializationComplete(); ++ super.setUp(); ++ } ++ ++ public CallbackHelper getTabsOpenedAndClosedWaiter() { ++ final CallbackHelper callbackHelper = new CallbackHelper(); ++ final TabModelSelector tabModelSelector = ++ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); ++ Assert.assertNotNull(tabModelSelector); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> ++ tabModelSelector ++ .getCurrentModel() ++ .addObserver( ++ new TabModelObserver() { ++ @Override ++ public void onFinishingTabClosure(Tab tab) { ++ // For some reason TabModelObserver#tabRemoved() is ++ // not called. ++ // Let's wait a bit to make sure tab is indeed ++ // closed. ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException ignored) { ++ } ++ callbackHelper.notifyCalled(); ++ } ++ ++ @Override ++ public void didAddTab( ++ Tab tab, ++ int type, ++ int creationState, ++ boolean markedForSelection) { ++ callbackHelper.notifyCalled(); ++ } ++ })); ++ return callbackHelper; ++ } ++ ++ public CallbackHelper getTabsOpenedAndLoadedWaiter() { ++ final CallbackHelper callbackHelper = new CallbackHelper(); ++ final TabModelSelector tabModelSelector = ++ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); ++ Assert.assertNotNull(tabModelSelector); ++ ThreadUtils.runOnUiThreadBlocking( ++ () -> ++ tabModelSelector ++ .getCurrentModel() ++ .addObserver( ++ new TabModelObserver() { ++ @Override ++ public void didAddTab( ++ Tab tab, ++ int type, ++ int creationState, ++ boolean markedForSelection) { ++ tab.addObserver( ++ new EmptyTabObserver() { ++ @Override ++ public void onPageLoadFinished( ++ Tab tab, GURL url) { ++ callbackHelper.notifyCalled(); ++ } ++ }); ++ } ++ })); ++ return callbackHelper; ++ } ++ ++ public int getTabCount() { ++ final TabModelSelector tabModelSelector = ++ mActivityTestRule.getActivity().getTabModelSelectorSupplier().get(); ++ Assert.assertNotNull(tabModelSelector); ++ return tabModelSelector.getTotalTabCount(); ++ } ++ ++ @Override ++ public BrowserContextHandle getBrowserContext() { ++ return ProfileManager.getLastUsedRegularProfile(); ++ } ++ ++ @Override ++ public WebContents getWebContents() { ++ return mActivityTestRule.getActivity().getCurrentWebContents(); ++ } ++ ++ @Override ++ public void loadUrl(final String url) throws InterruptedException, TimeoutException { ++ final LoadUrlResult result = mActivityTestRule.loadUrl(url, TEST_TIMEOUT_SEC); ++ Assert.assertEquals( ++ "Page did not load correctly. Load result enum: " ++ + String.valueOf(result.tabLoadStatus), ++ result.tabLoadStatus, ++ Tab.TabLoadStatus.DEFAULT_PAGE_LOAD); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesInlineCssTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesInlineCssTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesInlineCssTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesInlineCssTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesInlineCssTest extends TestPagesInlineCssTestBase { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRemoveTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRemoveTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRemoveTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesRemoveTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesRemoveTest extends TestPagesRemoveTestBase { ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRewriteTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRewriteTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesRewriteTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesRewriteTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesRewriteTest extends TestPagesRewriteTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesServiceWorkersTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesServiceWorkersTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesServiceWorkersTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesServiceWorkersTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesServiceWorkersTest extends TestPagesServiceWorkersTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSiteKeyTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSiteKeyTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSiteKeyTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesSiteKeyTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesSiteKeyTest extends TestPagesSiteKeyTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSnippetsTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSnippetsTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesSnippetsTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesSnippetsTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesSnippetsTest extends TestPagesSnippetsTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWebsocketTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWebsocketTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWebsocketTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesWebsocketTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesWebsocketTest extends TestPagesWebsocketTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWildcardDomainTest.java b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWildcardDomainTest.java +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/android/javatests/src/org/chromium/chrome/browser/adblock/TestPagesWildcardDomainTest.java +@@ -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 . ++ */ ++ ++package org.chromium.chrome.browser.adblock; ++ ++import org.junit.After; ++import org.junit.Before; ++import org.junit.Rule; ++import org.junit.runner.RunWith; ++ ++import org.chromium.base.test.util.CommandLineFlags; ++import org.chromium.chrome.browser.flags.ChromeSwitches; ++import org.chromium.chrome.test.ChromeBrowserTestRule; ++import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ++import org.chromium.chrome.test.ChromeTabbedActivityTestRule; ++import org.chromium.components.adblock.AdblockSwitches; ++import org.chromium.components.adblock.TestPagesWildcardDomainTestBase; ++ ++@RunWith(ChromeJUnit4ClassRunner.class) ++@CommandLineFlags.Add({ ++ ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, ++ AdblockSwitches.DISABLE_EYEO_REQUEST_THROTTLING ++}) ++public class TestPagesWildcardDomainTest extends TestPagesWildcardDomainTestBase { ++ @Rule public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); ++ ++ @Rule ++ public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); ++ ++ private final TestPagesHelper mHelper = new TestPagesHelper(); ++ ++ @Before ++ public void setUp() { ++ mHelper.setUp(mActivityTestRule); ++ super.setUp(mHelper); ++ } ++ ++ @After ++ public void tearDown() { ++ mHelper.tearDown(); ++ } ++} +diff --git a/chrome/browser/adblock/test/adblock_frame_hierarchy_builder_browsertest.cc b/chrome/browser/adblock/test/adblock_frame_hierarchy_builder_browsertest.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/test/adblock_frame_hierarchy_builder_browsertest.cc +@@ -0,0 +1,474 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++#include ++ ++#include "base/ranges/algorithm.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ssl/https_upgrades_util.h" ++#include "chrome/browser/ui/browser.h" ++#include "chrome/test/base/in_process_browser_test.h" ++#include "chrome/test/base/ui_test_utils.h" ++#include "components/adblock/content/browser/adblock_filter_match.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/frame_hierarchy_builder.h" ++#include "components/adblock/content/browser/resource_classification_runner.h" ++#include "components/adblock/core/common/adblock_constants.h" ++#include "components/adblock/core/subscription/subscription_service.h" ++#include "components/blocked_content/popup_blocker_tab_helper.h" ++#include "components/embedder_support/switches.h" ++#include "content/public/test/browser_test.h" ++#include "content/public/test/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 { ++ ++namespace { ++class TabAddedRemovedObserver : public TabStripModelObserver { ++ public: ++ explicit TabAddedRemovedObserver(TabStripModel* tab_strip_model) { ++ tab_strip_model->AddObserver(this); ++ } ++ ++ void OnTabStripModelChanged( ++ TabStripModel* tab_strip_model, ++ const TabStripModelChange& change, ++ const TabStripSelectionChange& selection) override { ++ if (change.type() == TabStripModelChange::kInserted) { ++ inserted_ = true; ++ return; ++ } ++ if (change.type() == TabStripModelChange::kRemoved) { ++ EXPECT_TRUE(inserted_); ++ removed_ = true; ++ loop_.Quit(); ++ return; ++ } ++ NOTREACHED(); ++ } ++ ++ void Wait() { ++ if (inserted_ && removed_) { ++ return; ++ } ++ loop_.Run(); ++ } ++ ++ private: ++ bool inserted_ = false; ++ bool removed_ = false; ++ base::RunLoop loop_; ++}; ++} // namespace ++ ++class ResourceClassificationRunnerObserver ++ : public ResourceClassificationRunner::Observer { ++ public: ++ ~ResourceClassificationRunnerObserver() override { ++ VerifyNoUnexpectedNotifications(); ++ } ++ // ResourceClassificationRunner::Observer: ++ void OnRequestMatched(const GURL& url, ++ FilterMatchResult match_result, ++ const std::vector& parent_frame_urls, ++ ContentType content_type, ++ content::RenderFrameHost* render_frame_host, ++ const GURL& subscription, ++ const std::string& configuration_name) override { ++ if (match_result == FilterMatchResult::kAllowRule) { ++ allowed_ads_notifications.push_back(url); ++ } 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 { ++ allowed_pages_notifications.push_back(url); ++ } ++ ++ 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 { ++ auto& list = (match_result == FilterMatchResult::kBlockRule ++ ? blocked_popups_notifications ++ : allowed_popups_notifications); ++ auto it = std::find(list.begin(), list.end(), url.ExtractFileName()); ++ ASSERT_FALSE(it == list.end()) ++ << "Path " << url.ExtractFileName() << " not on list"; ++ list.erase(it); ++ if (popup_notifications_run_loop_ && allowed_popups_notifications.empty() && ++ blocked_popups_notifications.empty()) { ++ popup_notifications_run_loop_->Quit(); ++ } ++ } ++ ++ void VerifyNotificationSent(std::string_view path, std::vector& list) { ++ auto it = base::ranges::find(list, path, &GURL::ExtractFileName); ++ ASSERT_FALSE(it == list.end()) << "Path " << path << " not on list"; ++ // Remove expected notifications so that we can verify there are no ++ // unexpected notifications left by the end of each test. ++ list.erase(it); ++ } ++ ++ void VerifyNoUnexpectedNotifications() { ++ EXPECT_TRUE(blocked_ads_notifications.empty()); ++ EXPECT_TRUE(allowed_ads_notifications.empty()); ++ EXPECT_TRUE(blocked_popups_notifications.empty()); ++ EXPECT_TRUE(allowed_popups_notifications.empty()); ++ EXPECT_TRUE(allowed_pages_notifications.empty()); ++ } ++ ++ std::vector blocked_ads_notifications; ++ std::vector allowed_ads_notifications; ++ std::vector allowed_pages_notifications; ++ std::vector blocked_popups_notifications; ++ std::vector allowed_popups_notifications; ++ std::unique_ptr popup_notifications_run_loop_; ++}; ++ ++// Simulated setup: ++// http://outer.com/outermost_frame.html ++// has an iframe: http://middle.com/middle_frame.html ++// has an iframe: http://inner.com/innermost_frame.html ++// has a subresource http://inner.com/resource.png ++// ++// All of these files are in components/test/data/adblock. Cross-domain ++// distribution is simulated via SetupCrossSiteRedirector. innermost_frame.html ++// reports whether resource.png is visible via window.top.postMessage to ++// outermost_frame.html, which stores a global subresource_visible JS variable. ++class AdblockFrameHierarchyBrowserTest : public InProcessBrowserTest { ++ public: ++ void SetUpOnMainThread() override { ++ InProcessBrowserTest::SetUpOnMainThread(); ++ host_resolver()->AddRule("*", "127.0.0.1"); ++ embedded_test_server()->ServeFilesFromSourceDirectory( ++ "components/test/data/adblock"); ++ content::SetupCrossSiteRedirector(embedded_test_server()); ++ AllowHttpForHostnamesForTesting({"outer.com", "inner.com", "middle.com"}, ++ browser()->profile()->GetPrefs()); ++ ASSERT_TRUE(embedded_test_server()->Start()); ++ auto* classification_runner = ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser()->profile()); ++ classification_runner->AddObserver(&observer); ++ } ++ ++ void TearDownOnMainThread() override { ++ auto* classification_runner = ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser()->profile()); ++ classification_runner->RemoveObserver(&observer); ++ InProcessBrowserTest::TearDownOnMainThread(); ++ } ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ command_line->AppendSwitch(embedder_support::kDisablePopupBlocking); ++ } ++ ++ void SetFilters(std::vector filters) { ++ auto* adblock_configuration = ++ SubscriptionServiceFactory::GetForBrowserContext(browser()->profile()) ++ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName); ++ adblock_configuration->RemoveCustomFilter(kAllowlistEverythingFilter); ++ for (auto& filter : filters) { ++ adblock_configuration->AddCustomFilter(filter); ++ } ++ } ++ ++ void NavigateToOutermostFrame() { ++ ASSERT_TRUE(ui_test_utils::NavigateToURL( ++ browser(), embedded_test_server()->GetURL( ++ "/cross-site/outer.com/outermost_frame.html"))); ++ } ++ ++ void NavigateToOutermostFrameWithAboutBlank() { ++ ASSERT_TRUE(ui_test_utils::NavigateToURL( ++ browser(), ++ embedded_test_server()->GetURL( ++ "/cross-site/outer.com/outermost_frame_with_about_blank.html"))); ++ } ++ ++ void NavigateToPopupParentFrameAndWaitForNotifications() { ++ observer.popup_notifications_run_loop_ = std::make_unique(); ++ ASSERT_TRUE(ui_test_utils::NavigateToURL( ++ browser(), embedded_test_server()->GetURL( ++ "/cross-site/outer.com/popup_parent.html"))); ++ if (!(observer.allowed_popups_notifications.empty() && ++ observer.blocked_popups_notifications.empty())) { ++ observer.popup_notifications_run_loop_->Run(); ++ } ++ } ++ ++ void VerifyTargetResourceShown(bool expected_visible, ++ const char* visibility_param) { ++ // Since in one test we dynamically load (write) `about:blank` iframe after ++ // parent page is loaded, we need to have some wait & poll mechanism to wait ++ // until such a frame (and its resources) is completely loaded by JS. ++ std::string script = base::StringPrintf( ++ R"( ++ (async () => { ++ let count = 10; ++ let visibility_param = '%s'; ++ function waitFor(condition) { ++ const poll = resolve => { ++ if(condition() || !count--) resolve(); ++ else setTimeout(_ => poll(resolve), 200); ++ } ++ return new Promise(poll); ++ } ++ // Waits up to 2 seconds ++ await waitFor(_ => window.subresource_visibility[visibility_param] === %s); ++ return window.subresource_visibility[visibility_param] === false; ++ })() ++ )", ++ visibility_param, expected_visible ? "false" : "true"); ++ EXPECT_EQ( ++ expected_visible, ++ content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), ++ script)); ++ } ++ ++ void VerifyTargetResourceBlockingStatus(bool expected_visible) { ++ VerifyTargetResourceShown(expected_visible, "is_blocked"); ++ } ++ ++ void VerifyTargetResourceHidingStatus(bool expected_visible) { ++ VerifyTargetResourceShown(expected_visible, "is_hidden"); ++ } ++ ++ int NumberOfOpenTabs() { return browser()->tab_strip_model()->GetTabCount(); } ++ ++ ResourceClassificationRunnerObserver observer; ++}; ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ SubresourceShownWithNoFilters) { ++ SetFilters({}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(true); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, SubresourceBlocked) { ++ SetFilters({"/resource.png"}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(false); ++ observer.VerifyNotificationSent("resource.png", ++ observer.blocked_ads_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ SubresourceAllowedViaInnerFrame) { ++ SetFilters({"/resource.png", "@@||inner.com^$document"}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(true); ++ observer.VerifyNotificationSent("resource.png", ++ observer.allowed_ads_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ SubresourceAllowedViaMiddleFrame) { ++ SetFilters({"/resource.png", "@@||middle.com^$document"}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(true); ++ observer.VerifyNotificationSent("resource.png", ++ observer.allowed_ads_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ SubresourceAllowedViaOutermostFrame) { ++ SetFilters({"/resource.png", "@@||outer.com^$document"}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(true); ++ observer.VerifyNotificationSent("resource.png", ++ observer.allowed_ads_notifications); ++ observer.VerifyNotificationSent("outermost_frame.html", ++ observer.allowed_pages_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ SubresourceBlockedWhenInvalidAllowRule) { ++ SetFilters({"/resource.png", "@@||bogus.com^$document"}); ++ NavigateToOutermostFrame(); ++ VerifyTargetResourceBlockingStatus(false); ++ observer.VerifyNotificationSent("resource.png", ++ observer.blocked_ads_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ DISABLED_PopupHandledByChromiumWithoutFilters) { ++ // Without any popup-specific filters, blocking popups is handed over to ++ // Chromium, which has it's own heuristics that are not based on filters. ++ SetFilters({}); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ // The popup was not opened: ++ EXPECT_EQ(1, NumberOfOpenTabs()); ++ // Because Chromium's built-in popup blocker stopped it: ++ EXPECT_EQ(1u, blocked_content::PopupBlockerTabHelper::FromWebContents( ++ browser()->tab_strip_model()->GetActiveWebContents()) ++ ->GetBlockedPopupsCount()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, PopupBlockedByFilter) { ++ SetFilters({"http://inner.com*/popup.html$popup"}); ++ observer.blocked_popups_notifications.emplace_back("popup.html"); ++ TabAddedRemovedObserver observer(browser()->tab_strip_model()); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ observer.Wait(); ++ EXPECT_EQ(1, NumberOfOpenTabs()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, PopupAllowedByFilter) { ++ SetFilters({"http://inner.com*/popup.html$popup", ++ "@@http://inner.com*/popup.html$popup"}); ++ observer.allowed_popups_notifications.emplace_back("popup.html"); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ // Popup was allowed to open in a new tab ++ EXPECT_EQ(2, NumberOfOpenTabs()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ PopupAllowedByDomainSpecificFilter) { ++ // The frame that wants to open the popup is hosted on middle.com. ++ // The $popup allow rule applies to that frame. ++ SetFilters({"http://inner.com*/popup.html$popup", ++ "@@http://inner.com*/popup.html$popup,domain=middle.com"}); ++ observer.allowed_popups_notifications.emplace_back("popup.html"); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ // Popup was allowed to open in a new tab ++ EXPECT_EQ(2, NumberOfOpenTabs()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ PopupNotAllowedByDomainSpecificFilter) { ++ // The frame that wants to open the popup is hosted on middle.com. ++ // The $popup allow rule does not apply because it is specific to outer.com. ++ // outer.com is not the frame that is opening the popup. ++ SetFilters({"http://inner.com*/popup.html$popup", ++ "@@http://inner.com*/popup.html$popup,domain=outer.com"}); ++ observer.blocked_popups_notifications.emplace_back("popup.html"); ++ TabAddedRemovedObserver observer(browser()->tab_strip_model()); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ observer.Wait(); ++ EXPECT_EQ(1, NumberOfOpenTabs()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ PopupAllowedByParentDocument) { ++ // The outermost frame has a blanket allowing rule of $document type. ++ SetFilters({"http://inner.com*/popup.html$popup", ++ "@@||outer.com^$document,domain=outer.com"}); ++ observer.allowed_popups_notifications.emplace_back("popup.html"); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ // Popup was allowed to open in a new tab ++ EXPECT_EQ(2, NumberOfOpenTabs()); ++ observer.VerifyNotificationSent("popup_parent.html", ++ observer.allowed_pages_notifications); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, ++ PopupAllowedByIntermediateParentDocument) { ++ // The middle frame has a blanket allowing rule of $document type. ++ SetFilters({"http://inner.com*/popup.html$popup", ++ "@@||middle.com^$document,domain=outer.com"}); ++ observer.allowed_popups_notifications.emplace_back("popup.html"); ++ NavigateToPopupParentFrameAndWaitForNotifications(); ++ // Popup was allowed to open in a new tab ++ EXPECT_EQ(2, NumberOfOpenTabs()); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, BlankFrameHiding) { ++ SetFilters({"##.about_blank_div"}); ++ NavigateToOutermostFrameWithAboutBlank(); ++ std::string script = R"( ++ function writeIframe() { ++ let frameDocument = document.getElementById("about_blank").contentWindow.document; ++ frameDocument.open("text/html"); ++ frameDocument.write(` ++ ++ ++
++ ++
++ ++ `); ++ frameDocument.close(); ++ } ++ if (document.readyState == "complete") { ++ writeIframe(); ++ } else { ++ document.getElementById("about_blank").addEventListener("load", writeIframe); ++ } ++ )"; ++ EXPECT_TRUE(content::ExecJs( ++ browser()->tab_strip_model()->GetActiveWebContents(), script)); ++ VerifyTargetResourceHidingStatus(false); ++ SetFilters({"@@^eyeo=true$document"}); ++ NavigateToOutermostFrameWithAboutBlank(); ++ EXPECT_TRUE(content::ExecJs( ++ browser()->tab_strip_model()->GetActiveWebContents(), script)); ++ VerifyTargetResourceHidingStatus(true); ++} ++ ++IN_PROC_BROWSER_TEST_F(AdblockFrameHierarchyBrowserTest, BlankFrameBlocking) { ++ SetFilters({"/resource.png"}); ++ NavigateToOutermostFrameWithAboutBlank(); ++ std::string script = R"( ++ function writeIframe() { ++ let frameDocument = document.getElementById("about_blank").contentWindow.document; ++ frameDocument.open("text/html"); ++ frameDocument.write(` ++ ++ ++ ++ ++ `); ++ frameDocument.close(); ++ } ++ if (document.readyState == "complete") { ++ writeIframe(); ++ } else { ++ document.getElementById("about_blank").addEventListener("load", writeIframe); ++ } ++ )"; ++ EXPECT_TRUE(content::ExecJs( ++ browser()->tab_strip_model()->GetActiveWebContents(), script)); ++ VerifyTargetResourceBlockingStatus(false); ++ observer.VerifyNotificationSent("resource.png", ++ observer.blocked_ads_notifications); ++ SetFilters({"@@^eyeo=true$document"}); ++ NavigateToOutermostFrameWithAboutBlank(); ++ EXPECT_TRUE(content::ExecJs( ++ browser()->tab_strip_model()->GetActiveWebContents(), script)); ++ VerifyTargetResourceBlockingStatus(true); ++ observer.VerifyNotificationSent("resource.png", ++ observer.allowed_ads_notifications); ++} ++ ++// More tests can be added / parametrized, e.g.: ++// - elemhide blocking filters (in conjunction with $elemhide allow rules) ++// - $subdocument-based allow rules ++ ++} // namespace adblock +diff --git a/chrome/browser/adblock/test/adblock_multiple_tabs_browsertest.cc b/chrome/browser/adblock/test/adblock_multiple_tabs_browsertest.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/test/adblock_multiple_tabs_browsertest.cc +@@ -0,0 +1,168 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.h" ++#include "chrome/browser/ui/browser_list.h" ++#include "chrome/browser/ui/tabs/recent_tabs_sub_menu_model.h" ++#include "chrome/test/base/in_process_browser_test.h" ++#include "chrome/test/base/ui_test_utils.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/core/common/adblock_constants.h" ++#include "components/adblock/core/subscription/subscription_service.h" ++#include "components/sessions/content/session_tab_helper.h" ++#include "content/public/test/browser_test.h" ++#include "content/public/test/browser_test_utils.h" ++#include "net/dns/mock_host_resolver.h" ++#include "net/test/embedded_test_server/embedded_test_server.h" ++ ++namespace adblock { ++ ++class AdblockMultipleTabsBrowserTest ++ : public InProcessBrowserTest, ++ public ResourceClassificationRunner::Observer { ++ public: ++ void SetUpOnMainThread() override { ++ host_resolver()->AddRule(kTestDomain, "127.0.0.1"); ++ embedded_test_server()->ServeFilesFromSourceDirectory( ++ "components/test/data/adblock"); ++ ASSERT_TRUE(embedded_test_server()->Start()); ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser()->profile()) ++ ->AddObserver(this); ++ SetFilters({"blocked.png", "allowed.png", "@@allowed.png"}); ++ } ++ ++ void TearDownInProcessBrowserTestFixture() override { ++ ASSERT_EQ(kTabsCount, static_cast(tabs_with_blocked_resource_.size())); ++ ASSERT_EQ(kTabsCount, static_cast(tabs_with_allowed_resource_.size())); ++ } ++ ++ void SetFilters(std::vector filters) { ++ auto* adblock_configuration = ++ SubscriptionServiceFactory::GetForBrowserContext(browser()->profile()) ++ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName); ++ adblock_configuration->RemoveCustomFilter(kAllowlistEverythingFilter); ++ for (auto& filter : filters) { ++ adblock_configuration->AddCustomFilter(filter); ++ } ++ } ++ ++ void RestoreTabs(Browser* browser) { ++ content::DOMMessageQueue queue; ++ RecentTabsSubMenuModel menu(nullptr, browser); ++ menu.ExecuteCommand(menu.GetFirstRecentTabsCommandId(), 0); ++ for (int i = 0; i < kTabsCount; ++i) { ++ std::string message; ++ EXPECT_TRUE(queue.WaitForMessage(&message)); ++ EXPECT_EQ("\"READY\"", message); ++ } ++ } ++ ++ // ResourceClassificationRunner::Observer: ++ void OnRequestMatched(const GURL& url, ++ FilterMatchResult match_result, ++ const std::vector& parent_frame_urls, ++ ContentType content_type, ++ content::RenderFrameHost* render_frame_host, ++ const GURL& subscription, ++ const std::string& configuration_name) override { ++ const content::WebContents* wc = ++ content::WebContents::FromRenderFrameHost(render_frame_host); ++ if (match_result == FilterMatchResult::kBlockRule && ++ url.path() == "/blocked.png") { ++ tabs_with_blocked_resource_.insert( ++ sessions::SessionTabHelper::IdForTab(wc).id()); ++ } else if (match_result == FilterMatchResult::kAllowRule && ++ url.path() == "/allowed.png") { ++ tabs_with_allowed_resource_.insert( ++ sessions::SessionTabHelper::IdForTab(wc).id()); ++ } ++ } ++ ++ 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 {} ++ ++ protected: ++ const int kTabsCount = 4; ++ const char* kTestDomain = "example.com"; ++ std::set tabs_with_blocked_resource_; ++ std::set tabs_with_allowed_resource_; ++}; ++ ++IN_PROC_BROWSER_TEST_F(AdblockMultipleTabsBrowserTest, PRE_OpenManyTabs) { ++ // Load page in already opened tab ++ ASSERT_TRUE(ui_test_utils::NavigateToURL( ++ browser(), ++ embedded_test_server()->GetURL(kTestDomain, "/tab-restore.html"))); ++ // Open more tabs ++ for (int i = 0; i < kTabsCount - 1; ++i) { ++ ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition( ++ browser(), ++ embedded_test_server()->GetURL(kTestDomain, "/tab-restore.html"), ++ WindowOpenDisposition::NEW_FOREGROUND_TAB, ++ ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); ++ } ++ EXPECT_EQ(kTabsCount, browser()->tab_strip_model()->count()); ++ EXPECT_EQ(kTabsCount, static_cast(tabs_with_blocked_resource_.size())); ++ EXPECT_EQ(kTabsCount, static_cast(tabs_with_allowed_resource_.size())); ++ ++ // Open a new browser instance ++ ui_test_utils::NavigateToURLWithDisposition( ++ browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW, ++ ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER); ++ BrowserList* active_browser_list = BrowserList::GetInstance(); ++ EXPECT_EQ(2u, active_browser_list->size()); ++ ++ // Close the 1st browser and clear tabs test data ++ CloseBrowserSynchronously(browser()); ++ EXPECT_EQ(1u, active_browser_list->size()); ++ tabs_with_blocked_resource_.clear(); ++ tabs_with_allowed_resource_.clear(); ++ ++ Browser* browser = active_browser_list->get(0); ++ // Restore tabs from1 st browser instance (already closed) in 2nd instance ++ RestoreTabs(browser); ++} ++ ++// TODO(atokodi): Enable this test once it works on OSX. It currently does not ++// work because of an upstream bug. See DPD-2528 ++#if BUILDFLAG(IS_MAC) ++#define MAYBE_OpenManyTabs DISABLED_OpenManyTabs ++#else ++#define MAYBE_OpenManyTabs OpenManyTabs ++#endif ++ ++IN_PROC_BROWSER_TEST_F(AdblockMultipleTabsBrowserTest, MAYBE_OpenManyTabs) { ++ ASSERT_EQ(0u, tabs_with_blocked_resource_.size()); ++ ASSERT_EQ(0u, tabs_with_allowed_resource_.size()); ++ // Restore tabs from previous session (previous test) ++ RestoreTabs(browser()); ++} ++ ++} // namespace adblock +diff --git a/chrome/browser/adblock/test/adblock_popup_browsertest.cc b/chrome/browser/adblock/test/adblock_popup_browsertest.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/adblock/test/adblock_popup_browsertest.cc +@@ -0,0 +1,462 @@ ++/* ++ * This file is part of eyeo Chromium SDK, ++ * Copyright (C) 2006-present eyeo GmbH ++ * ++ * eyeo Chromium SDK is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 3 as ++ * published by the Free Software Foundation. ++ * ++ * eyeo Chromium SDK is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with eyeo Chromium SDK. If not, see . ++ */ ++ ++#include "base/run_loop.h" ++#include "chrome/app/chrome_command_ids.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h" ++#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h" ++#include "chrome/browser/ui/browser.h" ++#include "chrome/browser/ui/browser_list.h" ++#include "chrome/test/base/in_process_browser_test.h" ++#include "chrome/test/base/ui_test_utils.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/core/common/adblock_constants.h" ++#include "components/adblock/core/subscription/subscription_service.h" ++#include "content/public/browser/render_view_host.h" ++#include "content/public/test/browser_test.h" ++#include "content/public/test/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 { ++ ++namespace { ++class TabAddedRemovedObserver : public TabStripModelObserver { ++ public: ++ explicit TabAddedRemovedObserver(TabStripModel* tab_strip_model) { ++ tab_strip_model->AddObserver(this); ++ } ++ ++ void OnTabStripModelChanged( ++ TabStripModel* tab_strip_model, ++ const TabStripModelChange& change, ++ const TabStripSelectionChange& selection) override { ++ if (change.type() == TabStripModelChange::kInserted) { ++ inserted_ = true; ++ return; ++ } ++ if (change.type() == TabStripModelChange::kRemoved) { ++ EXPECT_TRUE(inserted_); ++ removed_ = true; ++ loop_.Quit(); ++ return; ++ } ++ NOTREACHED(); ++ } ++ ++ void Wait() { ++ if (inserted_ && removed_) { ++ return; ++ } ++ loop_.Run(); ++ } ++ ++ private: ++ bool inserted_ = false; ++ bool removed_ = false; ++ base::RunLoop loop_; ++}; ++ ++enum class Redirection { ClientSide, ServerSide }; ++ ++} // namespace ++ ++class AdblockPopupBrowserTest ++ : public InProcessBrowserTest, ++ public ResourceClassificationRunner::Observer, ++ public testing::WithParamInterface { ++ public: ++ void SetUpOnMainThread() override { ++ InProcessBrowserTest::SetUpOnMainThread(); ++ host_resolver()->AddRule("*", "127.0.0.1"); ++ embedded_test_server()->RegisterRequestHandler(base::BindRepeating( ++ &AdblockPopupBrowserTest::RequestHandler, base::Unretained(this))); ++ ASSERT_TRUE(embedded_test_server()->Start()); ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser()->profile()) ++ ->AddObserver(this); ++ } ++ ++ void TearDownOnMainThread() override { ++ VerifyNoUnexpectedNotifications(); ++ ResourceClassificationRunnerFactory::GetForBrowserContext( ++ browser()->profile()) ++ ->RemoveObserver(this); ++ InProcessBrowserTest::TearDownOnMainThread(); ++ } ++ ++ void VerifyNoUnexpectedNotifications() { ++ EXPECT_TRUE(blocked_popups_notifications_expectations_.empty()); ++ EXPECT_TRUE(allowed_popups_notifications_expectations_.empty()); ++ } ++ ++ bool IsServerSideRedirection() { ++ return GetParam() == Redirection::ServerSide; ++ } ++ ++ virtual std::unique_ptr RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (base::StartsWith("/main_page.html", request.relative_url)) { ++ static constexpr char kPopupFrameParent[] = ++ R"( ++ ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ http_response->set_code(net::HTTP_OK); ++ http_response->set_content(kPopupFrameParent); ++ http_response->set_content_type("text/html"); ++ return std::move(http_response); ++ } ++ if (base::StartsWith("/popup_frame.html", request.relative_url)) { ++ static constexpr char kPopupFrame[] = ++ R"( ++ ++ ++ ++ ++ ++ ++ Trigger link based popup with redirect ++ Trigger link based popup without redirect ++ Trigger script based popup (tab) with redirect ++ Trigger script based popup (window) with redirect ++ ++ )"; ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ http_response->set_code(net::HTTP_OK); ++ http_response->set_content(kPopupFrame); ++ http_response->set_content_type("text/html"); ++ return std::move(http_response); ++ } ++ if (base::StartsWith("/popup_no_redirect.html", request.relative_url) || ++ base::StartsWith("/popup_redirected.html", request.relative_url)) { ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ http_response->set_code(net::HTTP_OK); ++ http_response->set_content(""); ++ http_response->set_content_type("text/html"); ++ return std::move(http_response); ++ } ++ if (base::StartsWith("/popup_will_redirect.html", request.relative_url)) { ++ static constexpr char kClientSideRedirectingPopup[] = ++ R"( ++ ++ ++ ++ ++ ++ ++ )"; ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ if (IsServerSideRedirection()) { ++ http_response->set_code(net::HTTP_MOVED_PERMANENTLY); ++ http_response->AddCustomHeader( ++ "Location", GetPageUrl("/popup_redirected.html").spec()); ++ } else { ++ http_response->set_code(net::HTTP_OK); ++ http_response->set_content(kClientSideRedirectingPopup); ++ http_response->set_content_type("text/html"); ++ } ++ return std::move(http_response); ++ } ++ return nullptr; ++ } ++ ++ void SetFilters(std::vector filters) { ++ auto* adblock_configuration = ++ SubscriptionServiceFactory::GetForBrowserContext(browser()->profile()) ++ ->GetFilteringConfiguration(kAdblockFilteringConfigurationName); ++ adblock_configuration->RemoveCustomFilter(kAllowlistEverythingFilter); ++ for (auto& filter : filters) { ++ adblock_configuration->AddCustomFilter(filter); ++ } ++ } ++ ++ void TriggerPopup(const std::string& popup_id) { ++ std::string script = base::StringPrintf( ++ R"( ++ let doc = document.querySelector('iframe[id="popup_frame"]').contentWindow.document; ++ let element = doc.getElementById('%s'); ++ element.click(); ++ )", ++ popup_id.c_str()); ++ EXPECT_TRUE(content::ExecJs( ++ browser()->tab_strip_model()->GetActiveWebContents(), script)); ++ } ++ ++ GURL GetPageUrl(const std::string& page) { ++ return embedded_test_server()->GetURL("popup_frame.org", page); ++ } ++ ++ void NavigateToPage() { ++ ASSERT_TRUE( ++ ui_test_utils::NavigateToURL(browser(), GetPageUrl("/main_page.html"))); ++ } ++ ++ void WaitForTabToLoad() { ++ content::WebContents* popup = ++ browser()->tab_strip_model()->GetActiveWebContents(); ++ WaitForLoadStop(popup); ++ } ++ ++ void SetupNotificationsWaiter(base::RunLoop* run_loop) { ++ run_loop_ = run_loop; ++ } ++ ++ // ResourceClassificationRunner::Observer: ++ void OnRequestMatched(const GURL& url, ++ FilterMatchResult match_result, ++ const std::vector& 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 { ++ auto& list = (match_result == FilterMatchResult::kBlockRule ++ ? blocked_popups_notifications_expectations_ ++ : allowed_popups_notifications_expectations_); ++ auto it = std::find(list.begin(), list.end(), url.ExtractFileName()); ++ ASSERT_FALSE(it == list.end()) ++ << "Path " << url.ExtractFileName() << " not on list"; ++ list.erase(it); ++ if (run_loop_ && allowed_popups_notifications_expectations_.empty() && ++ blocked_popups_notifications_expectations_.empty()) { ++ run_loop_->Quit(); ++ } ++ } ++ ++ std::vector allowed_popups_notifications_expectations_; ++ std::vector blocked_popups_notifications_expectations_; ++ raw_ptr run_loop_ = nullptr; ++}; ++ ++IN_PROC_BROWSER_TEST_F(AdblockPopupBrowserTest, PopupLinkBlocked) { ++ SetFilters({"popup_no_redirect.html^$popup"}); ++ blocked_popups_notifications_expectations_.emplace_back( ++ "popup_no_redirect.html"); ++ NavigateToPage(); ++ TabAddedRemovedObserver observer(browser()->tab_strip_model()); ++ TriggerPopup("popup_link_no_redirect"); ++ observer.Wait(); ++ EXPECT_EQ(1, browser()->tab_strip_model()->count()); ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ PopupScriptTabWithRedirectBlocked) { ++ SetFilters({"popup_redirected.html^$popup"}); ++ blocked_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ TabAddedRemovedObserver observer(browser()->tab_strip_model()); ++ TriggerPopup("popup_script_tab"); ++ observer.Wait(); ++ EXPECT_EQ(1, browser()->tab_strip_model()->count()); ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ PopupScriptWindowWithRedirectBlocked) { ++ SetFilters({"popup_redirected.html^$popup"}); ++ blocked_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ TriggerPopup("popup_script_window"); ++ // Wait for 2nd browser to get closed (new window popup blocked) ++ EXPECT_EQ(2u, BrowserList::GetInstance()->size()); ++ ui_test_utils::WaitForBrowserToClose(BrowserList::GetInstance()->get(1)); ++ EXPECT_EQ(1u, BrowserList::GetInstance()->size()); ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, PopupLinkWithRedirectBlocked) { ++ SetFilters({"popup_redirected.html^$popup"}); ++ blocked_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ TabAddedRemovedObserver observer(browser()->tab_strip_model()); ++ TriggerPopup("popup_link_will_redirect"); ++ observer.Wait(); ++ EXPECT_EQ(1, browser()->tab_strip_model()->count()); ++} ++ ++IN_PROC_BROWSER_TEST_P( ++ AdblockPopupBrowserTest, ++ PopupScriptTabWithRedirectAllowedByIntermediateParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/popup_frame.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ ui_test_utils::TabAddedWaiter waiter(browser()); ++ TriggerPopup("popup_script_tab"); ++ waiter.Wait(); ++ WaitForTabToLoad(); ++ EXPECT_EQ(2, browser()->tab_strip_model()->count()); ++} ++ ++IN_PROC_BROWSER_TEST_P( ++ AdblockPopupBrowserTest, ++ PopupScriptWindowWithRedirectAllowedByIntermediateParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/popup_frame.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ base::RunLoop run_loop; ++ SetupNotificationsWaiter(&run_loop); ++ TriggerPopup("popup_script_window"); ++ run_loop.Run(); ++ EXPECT_EQ(2u, BrowserList::GetInstance()->size()); ++} ++ ++IN_PROC_BROWSER_TEST_P( ++ AdblockPopupBrowserTest, ++ PopupLinkWithRedirectAllowedByIntermediateParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/popup_frame.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ ui_test_utils::TabAddedWaiter waiter(browser()); ++ TriggerPopup("popup_link_will_redirect"); ++ waiter.Wait(); ++ WaitForTabToLoad(); ++ EXPECT_EQ(2, browser()->tab_strip_model()->count()); ++ ; ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ PopupScriptTabWithRedirectAllowedByParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/main_page.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ ui_test_utils::TabAddedWaiter waiter(browser()); ++ TriggerPopup("popup_script_tab"); ++ waiter.Wait(); ++ WaitForTabToLoad(); ++ EXPECT_EQ(2, browser()->tab_strip_model()->count()); ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ PopupScriptWindowWithRedirectAllowedByParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/main_page.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ base::RunLoop run_loop; ++ SetupNotificationsWaiter(&run_loop); ++ TriggerPopup("popup_script_window"); ++ run_loop.Run(); ++ EXPECT_EQ(2u, BrowserList::GetInstance()->size()); ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ PopupLinkWithRedirectAllowedByParentDocument) { ++ SetFilters({"popup_redirected.html^$popup", "@@/main_page.html^$document"}); ++ allowed_popups_notifications_expectations_.emplace_back( ++ "popup_redirected.html"); ++ NavigateToPage(); ++ ui_test_utils::TabAddedWaiter waiter(browser()); ++ TriggerPopup("popup_link_will_redirect"); ++ waiter.Wait(); ++ WaitForTabToLoad(); ++ EXPECT_EQ(2, browser()->tab_strip_model()->count()); ++} ++ ++// Make sure that we correctly recognize and apply blocking of ++// redirected popups only for real popups. ++IN_PROC_BROWSER_TEST_P(AdblockPopupBrowserTest, ++ LinkOpenedByContextMenuInNewTabNotBlocked) { ++ SetFilters({"popup_redirected.html^$popup"}); ++ ContextMenuNotificationObserver menu_observer( ++ IDC_CONTENT_CONTEXT_OPENLINKNEWTAB); ++ ui_test_utils::AllBrowserTabAddedWaiter add_tab; ++ ++ std::string script = base::StringPrintf( ++ "data:text/html,link", ++ GetPageUrl("/popup_will_redirect.html").spec().c_str()); ++ // Go to a page with a link ++ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(script))); ++ ++ // Opens a link in a new tab via a "real" context menu. ++ blink::WebMouseEvent mouse_event( ++ blink::WebInputEvent::Type::kMouseDown, ++ blink::WebInputEvent::kNoModifiers, ++ blink::WebInputEvent::GetStaticTimeStampForTests()); ++ mouse_event.button = blink::WebMouseEvent::Button::kRight; ++ mouse_event.SetPositionInWidget(15, 15); ++ content::WebContents* tab = ++ browser()->tab_strip_model()->GetActiveWebContents(); ++ gfx::Rect offset = tab->GetContainerBounds(); ++ mouse_event.SetPositionInScreen(15 + offset.x(), 15 + offset.y()); ++ mouse_event.click_count = 1; ++ tab->GetPrimaryMainFrame() ++ ->GetRenderViewHost() ++ ->GetWidget() ++ ->ForwardMouseEvent(mouse_event); ++ mouse_event.SetType(blink::WebInputEvent::Type::kMouseUp); ++ tab->GetPrimaryMainFrame() ++ ->GetRenderViewHost() ++ ->GetWidget() ++ ->ForwardMouseEvent(mouse_event); ++ ++ // The menu_observer will select "Open in new tab", wait for the new tab to ++ // be added. ++ tab = add_tab.Wait(); ++ EXPECT_TRUE(content::WaitForLoadStop(tab)); ++ ++ // Verify that it's the correct tab. ++ EXPECT_EQ(GetPageUrl("/popup_redirected.html"), tab->GetLastCommittedURL()); ++} ++ ++INSTANTIATE_TEST_SUITE_P(All, ++ AdblockPopupBrowserTest, ++ testing::Values(Redirection::ClientSide, ++ Redirection::ServerSide)); ++ ++} // namespace adblock +diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc +--- a/chrome/browser/chrome_browser_main.cc ++++ b/chrome/browser/chrome_browser_main.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 "chrome/browser/chrome_browser_main.h" + +@@ -76,6 +80,7 @@ + #include "chrome/grit/branded_strings.h" + #include "chrome/grit/generated_resources.h" + #include "chrome/installer/util/google_update_settings.h" ++#include "components/adblock/content/browser/adblock_web_ui_controller_factory.h" + #include "components/color/color_mixers.h" + #include "components/device_event_log/device_event_log.h" + #include "components/embedder_support/origin_trials/component_updater_utils.h" +@@ -1545,6 +1550,8 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() { + // called inside PostProfileInit. + content::WebUIControllerFactory::RegisterFactory( + ChromeWebUIControllerFactory::GetInstance()); ++ content::WebUIControllerFactory::RegisterFactory( ++ adblock::AdblockWebUIControllerFactory::GetInstance()); + RegisterChromeWebUIConfigs(); + RegisterChromeUntrustedWebUIConfigs(); + +diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc +--- a/chrome/browser/client_hints/client_hints_browsertest.cc ++++ b/chrome/browser/client_hints/client_hints_browsertest.cc +@@ -1,6 +1,10 @@ + // Copyright 2017 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 "components/client_hints/common/client_hints.h" + +@@ -661,7 +665,12 @@ class ClientHintsBrowserTest : public policy::PolicyTest { + std::unique_ptr feature_list(new base::FeatureList); + // Force-enable the ClientHintsFormFactors feature, so that the header is + // represented in the various header counts. +- feature_list->InitFromCommandLine(kDefaultFeatures, ""); ++ feature_list->InitFromCommandLine( ++ kDefaultFeatures, ++ // Disabling AdblockPlus because the async implementation of ++ // AdblockURLLoaderThrottle::WillStartRequest confuses ++ // ThirdPartyURLLoaderInterceptor. ++ "AdblockPlus"); + scoped_feature_list.InitWithFeatureList(std::move(feature_list)); + } + +diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn +--- a/chrome/browser/extensions/BUILD.gn ++++ b/chrome/browser/extensions/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/chromebox_for_meetings/buildflags.gni") + import("//build/config/chromeos/ui_mode.gni") +@@ -271,6 +274,7 @@ source_set("extensions") { + "window_controller_list.h", + "window_controller_list_observer.h", + ] ++ + configs += [ + "//build/config:precompiled_headers", + "//build/config/compiler:wexit_time_destructors", +@@ -725,6 +729,7 @@ source_set("extensions") { + ] + } + ++ + # Chrome OS does not support Native Messaging policies. + if (!is_chromeos) { + sources += [ +diff --git a/chrome/browser/net/errorpage_browsertest.cc b/chrome/browser/net/errorpage_browsertest.cc +--- a/chrome/browser/net/errorpage_browsertest.cc ++++ b/chrome/browser/net/errorpage_browsertest.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 + #include + #include +@@ -38,6 +42,7 @@ + #include "chrome/common/pref_names.h" + #include "chrome/test/base/in_process_browser_test.h" + #include "chrome/test/base/ui_test_utils.h" ++#include "components/adblock/core/common/adblock_switches.h" + #include "components/browsing_data/content/browsing_data_helper.h" + #include "components/embedder_support/switches.h" + #include "components/error_page/content/browser/net_error_auto_reloader.h" +@@ -639,6 +644,9 @@ class ErrorPageAutoReloadTest : public InProcessBrowserTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(embedder_support::kEnableAutoReload); ++ // The URLLoaderInterceptor is not resilient to the browser making ++ // adblock-related requests, they confuse this test. ++ command_line->AppendSwitch(adblock::switches::kDisableAdblock); + } + + void TearDownOnMainThread() override { url_loader_interceptor_.reset(); } +diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc +--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc ++++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc +@@ -1,6 +1,10 @@ + // Copyright 2017 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 "components/page_load_metrics/browser/observers/ad_metrics/ads_page_load_metrics_observer.h" + +@@ -21,6 +25,7 @@ + #include "chrome/browser/ui/browser.h" + #include "chrome/test/base/in_process_browser_test.h" + #include "chrome/test/base/ui_test_utils.h" ++#include "components/adblock/core/features.h" + #include "components/heavy_ad_intervention/heavy_ad_features.h" + #include "components/page_load_metrics/browser/ads_page_load_metrics_test_waiter.h" + #include "components/page_load_metrics/browser/observers/ad_metrics/ad_intervention_browser_test_utils.h" +@@ -1499,7 +1504,7 @@ class AdsPageLoadMetricsObserverResourceBrowserTest + {heavy_ad_intervention::features::kHeavyAdIntervention, {}}, + {heavy_ad_intervention::features::kHeavyAdPrivacyMitigations, + {{"host-threshold", "3"}}}}, +- {}); ++ {adblock::kAdblockPlusFeature}); + if (IsReduceTransferSizeUpdatedIPCEnabled()) { + reduce_ipc_feature_list_.InitAndEnableFeature( + network::features::kReduceTransferSizeUpdatedIPC); +diff --git a/chrome/browser/preferences/BUILD.gn b/chrome/browser/preferences/BUILD.gn +--- a/chrome/browser/preferences/BUILD.gn ++++ b/chrome/browser/preferences/BUILD.gn +@@ -1,6 +1,9 @@ + # 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. + + import("//build/config/android/rules.gni") + import("//third_party/jni_zero/jni_zero.gni") +@@ -55,6 +58,7 @@ java_cpp_strings("java_pref_names_srcjar") { + "//chrome/browser/enterprise/reporting/prefs.cc", + "//chrome/browser/ui/safety_hub/safety_hub_prefs.h", + "//chrome/common/pref_names.h", ++ "//components/adblock/core/common/adblock_prefs.cc", + "//components/autofill/core/common/autofill_prefs.h", + "//components/commerce/core/pref_names.h", + "//components/dom_distiller/core/pref_names.h", +diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc +--- a/chrome/browser/prefs/chrome_pref_service_factory.cc ++++ b/chrome/browser/prefs/chrome_pref_service_factory.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 "chrome/browser/prefs/chrome_pref_service_factory.h" + +@@ -44,6 +48,7 @@ + #include "chrome/grit/branded_strings.h" + #include "chrome/grit/browser_resources.h" + #include "chrome/grit/generated_resources.h" ++#include "components/adblock/core/common/adblock_prefs.h" + #include "components/component_updater/pref_names.h" + #include "components/policy/core/browser/configuration_policy_pref_store.h" + #include "components/pref_registry/pref_registry_syncable.h" +@@ -192,6 +197,12 @@ const auto kTrackedPrefs = std::to_array({ + {35, prefs::kExtensionsUIDeveloperMode, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL}, + #endif ++ {100, adblock::common::prefs::kSubscriptionSignatures, ++ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::SPLIT, ++ ValueType::IMPERSONAL}, ++ {101, adblock::common::prefs::kLastUsedSchemaVersion, ++ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC, ++ ValueType::IMPERSONAL} + + // See note at top, new items added here also need to be added to + // histograms.xml's TrackedPreference enum. +diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc ++++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.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 "chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.h" + +@@ -230,6 +234,15 @@ + #include "chrome/browser/webid/federated_identity_permission_context_factory.h" + #include "chrome/common/buildflags.h" + #include "chrome/common/chrome_features.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/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/autofill/content/browser/autofill_log_router_factory.h" + #include "components/breadcrumbs/core/breadcrumbs_status.h" + #include "components/captive_portal/core/buildflags.h" +@@ -874,6 +887,15 @@ void ChromeBrowserMainExtraPartsProfiles:: + // Makes manual testing possible. + FakeSmartCardDeviceServiceFactory::GetInstance(); + #endif ++ adblock::AdblockTelemetryServiceFactory::GetInstance(); ++ adblock::AdblockRequestThrottleFactory::GetInstance(); ++ adblock::ContentSecurityPolicyInjectorFactory::GetInstance(); ++ adblock::ElementHiderFactory::GetInstance(); ++ adblock::ResourceClassificationRunnerFactory::GetInstance(); ++ adblock::SessionStatsFactory::GetInstance(); ++ adblock::SitekeyStorageFactory::GetInstance(); ++ adblock::SubscriptionPersistentMetadataFactory::GetInstance(); ++ adblock::SubscriptionServiceFactory::GetInstance(); + #if BUILDFLAG(IS_ANDROID) + FastCheckoutCapabilitiesFetcherFactory::GetInstance(); + #endif +diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc +--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc ++++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc +@@ -278,6 +278,17 @@ IN_PROC_BROWSER_TEST_F(ProfileKeyedServiceBrowserTest, + "PermissionsUpdaterShutdownFactory", + "PluginInfoHostImpl", + "TurnSyncOnHelperShutdownNotifier", ++ ++ // Eyeo services ++ "AdblockRequestThrottle", ++ "AdblockSubscriptionPersistentMetadata", ++ "AdblockSubscriptionService", ++ "AdblockTelemetryService", ++ "ContentSecurityPolicyInjector", ++ "ElementHider", ++ "ResourceClassificationRunner", ++ "SessionStats", ++ "SitekeyStorage", + }; + // clang-format on + +@@ -829,6 +840,13 @@ IN_PROC_BROWSER_TEST_F(ProfileKeyedServiceGuestBrowserTest, + "ZeroSuggestCacheServiceFactory", + #endif // !BUILDFLAG(IS_CHROMEOS) + ++ // eyeo Chromium SDK services: ++ "AdblockPrivateAPI", ++ "EyeoFilteringPrivateAPI", ++ "ResourceClassificationRunner", ++ "SessionStats", ++ "SitekeyStorage", ++ + #if BUILDFLAG(IS_CHROMEOS) + // TODO(crbug.com/374351946): + // Verify these are necessary: then reorder or remove. +diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn +--- a/chrome/browser/resources/BUILD.gn ++++ b/chrome/browser/resources/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/chromeos/ui_mode.gni") + import("//chrome/browser/buildflags.gni") +@@ -270,6 +273,7 @@ group("dev_ui_resources") { + "predictors:resources", + "privacy_sandbox/internals:resources", + "usb_internals:resources", ++ "//components/adblock/content/resources/adblock_internals:resources", + "//components/commerce/core/internals/resources", + "//components/download/resources/download_internals:resources", + "//components/history_clusters/history_clusters_internals/resources", +@@ -335,6 +339,7 @@ repack("dev_ui_paks") { + "$root_gen_dir/chrome/predictors_resources.pak", + "$root_gen_dir/chrome/privacy_sandbox_internals_resources.pak", + "$root_gen_dir/chrome/usb_internals_resources.pak", ++ "$root_gen_dir/components/adblock_internals_resources.pak", + "$root_gen_dir/components/commerce_internals_resources.pak", + "$root_gen_dir/components/dev_ui_components_resources.pak", + "$root_gen_dir/components/download_internals_resources.pak", +diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc +--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc ++++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc +@@ -5,6 +5,10 @@ + // This test creates a fake safebrowsing service, where we can inject known- + // threat urls. It then uses a real browser to go to these urls, and sends + // "goback" or "proceed" commands and verifies they work. ++// ++// 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 "components/safe_browsing/content/browser/safe_browsing_blocking_page.h" + +@@ -70,6 +74,7 @@ + #include "chrome/common/url_constants.h" + #include "chrome/test/base/in_process_browser_test.h" + #include "chrome/test/base/ui_test_utils.h" ++#include "components/adblock/core/features.h" + #include "components/enterprise/connectors/core/common.h" + #include "components/enterprise/connectors/core/connectors_prefs.h" + #include "components/google/core/common/google_util.h" +@@ -682,7 +687,7 @@ class SafeBrowsingBlockingPageBrowserTest + scoped_feature_list_.InitWithFeaturesAndParameters( + {tag_and_attribute, add_warning_shown_timestamp_csbrrs, + create_warning_shown_csbrrs, abusive_notification_revocation}, +- {}); ++ {adblock::kAdblockPlusFeature}); + } + + SafeBrowsingBlockingPageBrowserTest( +diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc +--- a/chrome/browser/sessions/session_restore_browsertest.cc ++++ b/chrome/browser/sessions/session_restore_browsertest.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. + + #ifdef UNSAFE_BUFFERS_BUILD + // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. +@@ -4445,8 +4449,10 @@ INSTANTIATE_TEST_SUITE_P( + SessionRestoreStaleSessionCookieDeletionTest, + testing::Bool()); + ++// Disabled as it's flaky, see: https://issues.chromium.org/issues/348923077 ++// See also: https://eyeo.atlassian.net/browse/DPD-2773 + IN_PROC_BROWSER_TEST_P(SessionRestoreStaleSessionCookieDeletionTest, +- CookieStorage) { ++ DISABLED_CookieStorage) { + GURL open_page = https_server()->GetURL("a.test", "/empty.html"); + GURL other_page = https_server()->GetURL("b.test", "/empty.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), open_page)); +diff --git a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc +--- a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc ++++ b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc +@@ -1,6 +1,10 @@ + // Copyright 2017 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 "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h" + +@@ -22,6 +26,7 @@ + #include "chrome/common/chrome_features.h" + #include "chrome/common/chrome_paths.h" + #include "chrome/test/base/chrome_test_utils.h" ++#include "components/adblock/core/features.h" + #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h" + #include "components/content_settings/browser/page_specific_content_settings.h" + #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h" +@@ -152,7 +157,8 @@ void SubresourceFilterSharedBrowserTest::NavigateFrame(const char* frame_name, + SubresourceFilterBrowserTest::SubresourceFilterBrowserTest() { + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{kAdTagging}, +- /*disabled_features=*/{features::kHttpsUpgrades}); ++ /*disabled_features=*/{features::kHttpsUpgrades, ++ adblock::kAdblockPlusFeature}); + } + + SubresourceFilterBrowserTest::~SubresourceFilterBrowserTest() = default; +diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn +--- a/chrome/browser/ui/BUILD.gn ++++ b/chrome/browser/ui/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/buildflags_paint_preview.gni") + import("//build/config/compiler/compiler.gni") +@@ -432,6 +435,7 @@ static_library("ui") { + "//chrome/services/media_gallery_util/public/mojom", + "//components/access_code_cast/common:metrics", + "//components/account_id", ++ "//components/adblock/content:browser", + "//components/affiliations/core/browser:affiliations", + "//components/autofill/content/browser", + "//components/autofill/content/browser:risk_proto", +diff --git a/chrome/browser/ui/prefs/pref_watcher.cc b/chrome/browser/ui/prefs/pref_watcher.cc +--- a/chrome/browser/ui/prefs/pref_watcher.cc ++++ b/chrome/browser/ui/prefs/pref_watcher.cc +@@ -1,6 +1,10 @@ + // Copyright 2018 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 "chrome/browser/ui/prefs/pref_watcher.h" + +@@ -21,6 +25,7 @@ + #include "chrome/browser/renderer_preferences_util.h" + #include "chrome/browser/ui/prefs/prefs_tab_helper.h" + #include "chrome/common/pref_names.h" ++#include "components/adblock/core/common/adblock_prefs.h" + #include "components/keyed_service/core/keyed_service.h" + #include "components/language/core/browser/pref_names.h" + #include "components/live_caption/pref_names.h" +@@ -82,6 +87,14 @@ const char* const kWebPrefsToObserve[] = { + prefs::kAccessibilityFocusHighlightEnabled, + #endif + prefs::kPageColorsBlockList, ++ ++ adblock::common::prefs::kAdblockAllowedDomainsLegacy, ++ adblock::common::prefs::kAdblockCustomFiltersLegacy, ++ adblock::common::prefs::kAdblockCustomSubscriptionsLegacy, ++ adblock::common::prefs::kAdblockSubscriptionsLegacy, ++ adblock::common::prefs::kEnableAcceptableAdsLegacy, ++ adblock::common::prefs::kEnableAdblockLegacy, ++ + }; + + } // namespace +diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc +--- a/chrome/browser/ui/tab_helpers.cc ++++ b/chrome/browser/ui/tab_helpers.cc +@@ -1,6 +1,10 @@ + // 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. + + #include "chrome/browser/ui/tab_helpers.h" + +@@ -105,6 +109,8 @@ + #include "chrome/common/chrome_features.h" + #include "chrome/common/chrome_isolated_world_ids.h" + #include "chrome/common/chrome_switches.h" ++#include "components/adblock/content/browser/adblock_webcontents_observer.h" ++#include "components/adblock/content/browser/factories/embedding_utils.h" + #include "components/autofill/content/browser/content_autofill_client.h" + #include "components/autofill/content/browser/content_autofill_driver_factory.h" + #include "components/autofill/core/browser/foundations/browser_autofill_manager.h" +@@ -336,6 +342,12 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { + optimization_guide_decider); + } + } ++ ++ auto* original_profile = profile->GetOriginalProfile(); ++ adblock::EnsureBackgroundServicesStarted(original_profile); ++ adblock::RegisterAdblockWebContentObserver< ++ adblock::AdblockWebContentObserver>(web_contents, original_profile); ++ + autofill::AutofillClientProvider& autofill_client_provider = + autofill::AutofillClientProviderFactory::GetForProfile(profile); + autofill_client_provider.CreateClientForWebContents(web_contents); +@@ -366,6 +378,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { + ip_protection::IpProtectionStatus::CreateForWebContents(web_contents); + } + #endif // BUILDFLAG(IS_ANDROID) ++ + if (breadcrumbs::IsEnabled(g_browser_process->local_state())) { + BreadcrumbManagerTabHelper::CreateForWebContents(web_contents); + } +diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni +--- a/chrome/chrome_paks.gni ++++ b/chrome/chrome_paks.gni +@@ -1,6 +1,9 @@ + # Copyright 2016 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("//ash/ambient/resources/resources.gni") + import("//build/config/chrome_build.gni") +@@ -115,6 +118,7 @@ template("chrome_extra_paks") { + "$root_gen_dir/chrome/data_sharing_internals_resources.pak", + "$root_gen_dir/chrome/saved_tab_groups_unsupported_resources.pak", + "$root_gen_dir/chrome/segmentation_internals_resources.pak", ++ "$root_gen_dir/components/adblock/core/resources/adblock_resources.pak", + "$root_gen_dir/components/autofill/core/browser/geo/autofill_address_rewriter_resources.pak", + "$root_gen_dir/components/autofill_and_password_manager_internals_resources.pak", + "$root_gen_dir/components/chrome_urls_resources.pak", +@@ -150,6 +154,7 @@ template("chrome_extra_paks") { + "//chrome/browser/resources:resources", + "//chrome/browser/resources/saved_tab_groups_unsupported:resources", + "//chrome/common:resources", ++ "//components/adblock/core/resources:adblock_resources", + "//components/autofill/core/browser:autofill_address_rewriter_resources", + "//components/metrics:server_urls_grd", + "//components/resources", +diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn +--- a/chrome/common/BUILD.gn ++++ b/chrome/common/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/buildflag_header.gni") + import("//build/config/chrome_build.gni") +diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn +--- a/chrome/test/BUILD.gn ++++ b/chrome/test/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/cipd/cipd.gni") + import("//build/config/buildflags_paint_preview.gni") +@@ -1641,6 +1644,7 @@ if (is_android) { + "//chrome/test/data/webui:browser_tests", + "//chrome/test/data/webui:interactive_ui_tests", + "//chrome/test/data/webui:resources", ++ "//components/adblock/content:browser", + "//components/autofill/content/browser:test_support", + "//components/autofill/content/renderer:test_support", + "//components/autofill/core/common:common", +@@ -2231,6 +2235,7 @@ if (!is_android) { + "//chrome/test/media_router/access_code_cast:access_code_cast_integration_base", + "//chrome/test/payments:test_support", + "//chrome/test/supervised_user:test_support", ++ "//components/adblock/content:browser", + "//components/affiliations/core/browser:affiliation_proto", + "//components/affiliations/core/browser:test_support", + "//components/autofill/content/browser:autofill_shared_storage_proto", +@@ -2734,6 +2739,7 @@ if (!is_android) { + "//chrome/renderer/resources/extensions/", + "//chrome/test/data/cart/", + "//components/test/data/ad_tagging/", ++ "//components/test/data/adblock/", + "//components/test/data/ads_observer/", + "//components/test/data/autofill/", + "//components/test/data/custom_handlers", +@@ -2822,6 +2828,9 @@ if (!is_android) { + "../browser/accessibility/interstitial_accessibility_browsertest.cc", + "../browser/accessibility/page_colors_browsertest.cc", + "../browser/accessibility/phrase_segmentation/dependency_parser_model_loader_browsertest.cc", ++ "../browser/adblock/test/adblock_frame_hierarchy_builder_browsertest.cc", ++ "../browser/adblock/test/adblock_multiple_tabs_browsertest.cc", ++ "../browser/adblock/test/adblock_popup_browsertest.cc", + "../browser/ai/ai_data_keyed_service_browsertest.cc", + "../browser/attribution_reporting/chrome_attribution_browsertest.cc", + "../browser/autocomplete/autocomplete_browsertest.cc", +@@ -4374,6 +4383,7 @@ if (!is_android) { + ] + } + ++ + if (is_chromeos) { + deps += + [ "//chromeos/ash/components/network/portal_detector:test_support" ] +@@ -5856,6 +5866,7 @@ test("unit_tests") { + sources = [ + # All unittests in browser, common, renderer and service. + "../browser/about_flags_unittest.cc", ++ "../browser/adblock/adblock_chrome_content_browser_client_unittest.cc", + "../browser/after_startup_task_utils_unittest.cc", + "../browser/apps/icon_standardizer_unittest.cc", + "../browser/apps/user_type_filter_unittest.cc", +@@ -6515,6 +6526,8 @@ test("unit_tests") { + "//chrome/common/themes:unit_tests", + "//chrome/services/file_util:unit_tests", + "//components/account_id", ++ "//components/adblock/content/browser:test_support", ++ "//components/adblock/core:test_support", + "//components/affiliations/core/browser:test_support", + "//components/assist_ranker/proto", + "//components/autofill/content/browser:test_support", +@@ -10319,6 +10332,7 @@ if (!is_android) { + "//chrome/browser:test_support_ui", + "//chrome/browser/ui/exclusive_access", + "//chrome/browser/ui/views/side_panel", ++ "//components/adblock/core:test_support", + "//components/supervised_user/core/browser", + "//components/webui/chrome_urls", + "//content/public/browser", +diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc +--- a/chrome/test/base/in_process_browser_test.cc ++++ b/chrome/test/base/in_process_browser_test.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 "chrome/test/base/in_process_browser_test.h" + + #include +@@ -156,6 +160,10 @@ + #include "ui/views/widget/widget.h" + #endif + ++#include "components/adblock/content/browser/factories/subscription_service_factory.h" ++#include "components/adblock/core/common/adblock_constants.h" ++#include "components/adblock/core/subscription/subscription_service.h" ++ + namespace { + + #if BUILDFLAG(IS_CHROMEOS) +@@ -615,8 +623,22 @@ void InProcessBrowserTest::CreatedBrowserMainParts( + + void InProcessBrowserTest::SelectFirstBrowser() { + const BrowserList* browser_list = BrowserList::GetInstance(); +- if (!browser_list->empty()) ++ if (!browser_list->empty()) { + browser_ = browser_list->get(0); ++ // Adding an allowing filter that overrides and disables all blocking ++ // filters in order to avoid unwanted interactions with simulated network ++ // loads. This custom filter is removed for tests that specifically verify ++ // ad-filtering. ++ auto* adblock_configuration = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ browser_->profile()->GetOriginalProfile()) ++ ->GetFilteringConfiguration( ++ adblock::kAdblockFilteringConfigurationName); ++ if (adblock_configuration) { ++ adblock_configuration->AddCustomFilter( ++ adblock::kAllowlistEverythingFilter); ++ } ++ } + } + + void InProcessBrowserTest::RecordPropertyFromMap( +-- diff --git a/build/cromite_patches/eyeo-beta-118.0.5993.48-extension_api.patch b/build/cromite_patches/eyeo-133.0.6943.49-extension_api.patch similarity index 57% rename from build/cromite_patches/eyeo-beta-118.0.5993.48-extension_api.patch rename to build/cromite_patches/eyeo-133.0.6943.49-extension_api.patch index 20e488415e8c7b40251b28364243a391ef3655be..86b3ae2e4dbf60b609dd21cf83f44ca7dd17ec94 100644 --- a/build/cromite_patches/eyeo-beta-118.0.5993.48-extension_api.patch +++ b/build/cromite_patches/eyeo-133.0.6943.49-extension_api.patch @@ -1,101 +1,109 @@ From: chromium-sdk -Date: Thu, 12 Oct 2023 14:46:09 +0200 +Date: Fri, 14 Feb 2025 08:26:41 +0100 Subject: eyeo Browser Ad filtering Solution: Extension API Module -Based on Chromium 118.0.5993.48 +Based on Chromium 133.0.6943.49 Pre-requisites: eyeo Browser Ad filtering Solution: Base Module --- - chrome/browser/extensions/BUILD.gn | 9 + - chrome/browser/extensions/api/BUILD.gn | 5 + - .../adblock_private/adblock_private_api.cc | 674 ++++++++++++ - .../api/adblock_private/adblock_private_api.h | 328 ++++++ - .../adblock_private_apitest.cc | 984 ++++++++++++++++++ + chrome/browser/extensions/BUILD.gn | 11 + + chrome/browser/extensions/api/BUILD.gn | 4 + + .../adblock_private/adblock_private_api.cc | 693 +++++++++++++++ + .../api/adblock_private/adblock_private_api.h | 360 ++++++++ + .../adblock_private_apitest.cc | 174 ++++ + .../adblock_private_apitest_backgroundpage.cc | 398 +++++++++ + .../adblock_private_apitest_base.cc | 103 +++ + .../adblock_private_apitest_base.h | 62 ++ + ...e_filter_lists_with_http_server_apitest.cc | 188 ++++ ...browser_context_keyed_service_factories.cc | 8 + - .../eyeo_filtering_private_api.cc | 772 ++++++++++++++ - .../eyeo_filtering_private_api.h | 360 +++++++ - .../eyeo_filtering_private_apitest.cc | 161 +++ - .../api/settings_private/prefs_util.cc | 19 + + .../eyeo_filtering_private_api.cc | 819 ++++++++++++++++++ + .../eyeo_filtering_private_api.h | 392 +++++++++ + .../eyeo_filtering_private_apitest.cc | 173 ++++ .../extension_function_registration_test.cc | 10 + .../common/extensions/api/_api_features.json | 22 + - .../extensions/api/_permission_features.json | 12 + - .../common/extensions/api/adblock_private.idl | 174 ++++ + .../extensions/api/_permission_features.json | 18 + + .../common/extensions/api/adblock_private.idl | 180 ++++ chrome/common/extensions/api/api_sources.gni | 6 + - .../extensions/api/eyeo_filtering_private.idl | 201 ++++ + .../extensions/api/eyeo_filtering_private.idl | 207 +++++ .../permissions/chrome_api_permissions.cc | 8 + - .../permissions/permission_set_unittest.cc | 4 + + .../permissions/permission_set_unittest.cc | 8 + + chrome/test/BUILD.gn | 14 + + .../api_test/adblock_private/empty.js | 14 + + .../api_test/adblock_private/main.html | 29 + + .../api_test/adblock_private/manifest.json | 32 + + .../api_test/adblock_private/some-popup.html | 24 + + .../api_test/adblock_private/test.html | 25 + + .../api_test/adblock_private/test.js | 609 +++++++++++++ + .../api_test/eyeo_filtering_private/empty.js | 14 + + .../api_test/eyeo_filtering_private/main.html | 29 + + .../eyeo_filtering_private/manifest.json | 31 + + .../api_test/eyeo_filtering_private/test.js | 429 +++++++++ .../browser/extension_event_histogram_value.h | 7 +- .../common/mojom/api_permission_id.mojom | 6 + - .../histograms/metadata/extensions/enums.xml | 3 + - .../definitions/adblock_private.d.ts | 172 +++ - .../definitions/eyeo_filtering_private.d.ts | 249 +++++ - 23 files changed, 4193 insertions(+), 1 deletion(-) + tools/metrics/histograms/enums.xml | 3 + + .../definitions/adblock_private.d.ts | 182 ++++ + .../definitions/eyeo_filtering_private.d.ts | 266 ++++++ + 37 files changed, 5557 insertions(+), 1 deletion(-) create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_api.cc create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_api.h create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_apitest.cc + create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_apitest_backgroundpage.cc + create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.cc + create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h + create mode 100644 chrome/browser/extensions/api/adblock_private/adblock_private_filter_lists_with_http_server_apitest.cc create mode 100644 chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc create mode 100644 chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h create mode 100644 chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_apitest.cc create mode 100644 chrome/common/extensions/api/adblock_private.idl create mode 100644 chrome/common/extensions/api/eyeo_filtering_private.idl + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/empty.js + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/main.html + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/manifest.json + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/some-popup.html + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/test.html + create mode 100644 chrome/test/data/extensions/api_test/adblock_private/test.js + create mode 100644 chrome/test/data/extensions/api_test/eyeo_filtering_private/empty.js + create mode 100644 chrome/test/data/extensions/api_test/eyeo_filtering_private/main.html + create mode 100644 chrome/test/data/extensions/api_test/eyeo_filtering_private/manifest.json + create mode 100644 chrome/test/data/extensions/api_test/eyeo_filtering_private/test.js create mode 100644 tools/typescript/definitions/adblock_private.d.ts create mode 100644 tools/typescript/definitions/eyeo_filtering_private.d.ts diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn --- a/chrome/browser/extensions/BUILD.gn +++ b/chrome/browser/extensions/BUILD.gn -@@ -1,6 +1,10 @@ - # 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. -+ +@@ -729,6 +729,17 @@ source_set("extensions") { + ] + } - import("//build/config/chromebox_for_meetings/buildflags.gni") - import("//build/config/chromeos/ui_mode.gni") -@@ -109,6 +113,8 @@ source_set("extensions") { - # here. - "api/automation_internal/chrome_automation_internal_api_delegate.cc", - "api/automation_internal/chrome_automation_internal_api_delegate.h", ++ ### Extensions API module start ++ sources += [ + "api/adblock_private/adblock_private_api.cc", + "api/adblock_private/adblock_private_api.h", - "api/bookmark_manager_private/bookmark_manager_private_api.cc", - "api/bookmark_manager_private/bookmark_manager_private_api.h", - "api/bookmarks/bookmark_api_helpers.cc", -@@ -158,6 +164,8 @@ source_set("extensions") { - "api/enterprise_reporting_private/conversion_utils.h", - "api/enterprise_reporting_private/enterprise_reporting_private_api.cc", - "api/enterprise_reporting_private/enterprise_reporting_private_api.h", + "api/eyeo_filtering_private/eyeo_filtering_private_api.cc", + "api/eyeo_filtering_private/eyeo_filtering_private_api.h", - "api/feedback_private/chrome_feedback_private_delegate.cc", - "api/feedback_private/chrome_feedback_private_delegate.h", - "api/file_system/chrome_file_system_delegate.cc", -@@ -792,6 +800,7 @@ source_set("extensions") { - "//chrome/browser/ui/safety_hub", - "//chrome/browser/ui/tabs:tab_enums", - "//chrome/browser/web_applications", -+ "//components/adblock/content:browser", - "//components/cbor:cbor", - "//components/commerce/core:pref_names", - "//components/device_reauth", ++ ] ++ ++ deps += [ "//components/adblock/content:browser" ] ++ ++ ### Extensions API module end + + # Chrome OS does not support Native Messaging policies. + if (!is_chromeos) { diff --git a/chrome/browser/extensions/api/BUILD.gn b/chrome/browser/extensions/api/BUILD.gn --- a/chrome/browser/extensions/api/BUILD.gn +++ b/chrome/browser/extensions/api/BUILD.gn -@@ -1,6 +1,10 @@ +@@ -1,6 +1,9 @@ # Copyright 2018 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("//chrome/common/extensions/api/api_sources.gni") import("//chrome/common/features.gni") -@@ -134,6 +138,7 @@ function_registration("api_registration") { +@@ -133,6 +136,7 @@ function_registration("api_registration") { # include generated headers from these targets. # TODO(brettw) this should be made unnecessary if possible. "//chrome/common/extensions/api", @@ -107,7 +115,7 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.c new file mode 100644 --- /dev/null +++ b/chrome/browser/extensions/api/adblock_private/adblock_private_api.cc -@@ -0,0 +1,674 @@ +@@ -0,0 +1,693 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -126,20 +134,24 @@ new file mode 100644 +#include "chrome/browser/extensions/api/adblock_private/adblock_private_api.h" + +#include "base/containers/flat_map.h" ++#include "base/i18n/time_formatting.h" +#include "base/logging.h" +#include "base/no_destructor.h" -+#include "base/time/time_to_iso8601.h" +#include "base/values.h" -+#include "chrome/browser/adblock/resource_classification_runner_factory.h" -+#include "chrome/browser/adblock/session_stats_factory.h" -+#include "chrome/browser/adblock/subscription_service_factory.h" +#include "chrome/browser/extensions/extension_tab_util.h" ++#include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/api/tabs.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/subscription_service_factory.h" +#include "components/adblock/content/browser/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/adblock_utils.h" +#include "components/adblock/core/common/content_type.h" +#include "components/adblock/core/session_stats.h" +#include "components/adblock/core/subscription/subscription_config.h" ++#include "components/prefs/pref_service.h" +#include "components/sessions/core/session_id.h" +#include "content/public/browser/web_contents.h" +#include "url/gurl.h" @@ -150,17 +162,30 @@ new file mode 100644 + +enum class SubscriptionAction { kInstall, kUninstall }; + ++content::BrowserContext* GetOriginalBrowserContext( ++ content::BrowserContext* browser_context) { ++ return Profile::FromBrowserContext(browser_context)->GetOriginalProfile(); ++} ++ ++adblock::FilteringConfiguration* GetAdblockConfiguration( ++ content::BrowserContext* browser_context) { ++ auto* adblock_configuration = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context)) ++ ->GetFilteringConfiguration( ++ adblock::kAdblockFilteringConfigurationName); ++ DCHECK(adblock_configuration) ++ << "adblock_private expects \"adblock\" configuration"; ++ return adblock_configuration; ++} ++ +std::string RunSubscriptionAction(SubscriptionAction action, + content::BrowserContext* browser_context, + const GURL& url) { + if (!url.is_valid()) { + return "Invalid URL"; + } -+ auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext(browser_context) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ auto* adblock_configuration = GetAdblockConfiguration(browser_context); + switch (action) { + case SubscriptionAction::kInstall: + adblock_configuration->AddFilterList(url); @@ -193,14 +218,15 @@ new file mode 100644 + switch (state) { + case State::Installed: + return "Installed"; -+ case State::Installing: -+ return "Installing"; ++ case State::AutoInstalled: ++ return "AutoInstalled"; + case State::Preloaded: + return "Preloaded"; ++ case State::Installing: ++ return "Installing"; + case State::Unknown: + return "Unknown"; + } -+ NOTREACHED(); + return ""; +} + @@ -215,8 +241,11 @@ new file mode 100644 + js_sub.current_version = sub->GetCurrentVersion(); + js_sub.installation_state = + SubscriptionInstallationStateToString(sub->GetInstallationState()); ++ const auto installation_time = sub->GetInstallationTime(); + js_sub.last_installation_time = -+ base::TimeToISO8601(sub->GetInstallationTime()); ++ installation_time.is_null() ++ ? "" ++ : base::TimeFormatAsIso8601(sub->GetInstallationTime()); + result.emplace_back(std::move(js_sub)); + } + return result; @@ -246,13 +275,13 @@ new file mode 100644 + public adblock::FilteringConfiguration::Observer { + public: + explicit AdblockAPIEventRouter(content::BrowserContext* context) -+ : context_(context) { ++ : context_(GetOriginalBrowserContext(context)) { + adblock::ResourceClassificationRunnerFactory::GetForBrowserContext(context_) + ->AddObserver(this); + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext(context_); + subscription_service->AddObserver(this); -+ subscription_service->GetAdblockFilteringConfiguration()->AddObserver(this); ++ GetAdblockConfiguration(context_)->AddObserver(this); + } + + ~AdblockAPIEventRouter() override { @@ -261,18 +290,17 @@ new file mode 100644 + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext(context_); + subscription_service->RemoveObserver(this); -+ subscription_service->GetAdblockFilteringConfiguration()->RemoveObserver( -+ this); ++ GetAdblockConfiguration(context_)->RemoveObserver(this); + } + + // adblock::ResourceClassificationRunner::Observer: -+ void OnAdMatched(const GURL& url, -+ adblock::FilterMatchResult match_result, -+ const std::vector& parent_frame_urls, -+ adblock::ContentType content_type, -+ content::RenderFrameHost* render_frame_host, -+ const GURL& subscription, -+ const std::string& configuration_name) override { ++ void OnRequestMatched(const GURL& url, ++ adblock::FilterMatchResult match_result, ++ const std::vector& parent_frame_urls, ++ adblock::ContentType content_type, ++ content::RenderFrameHost* render_frame_host, ++ const GURL& subscription, ++ const std::string& configuration_name) override { + std::unique_ptr event; + api::adblock_private::AdInfo info = CreateAdInfoObject( + url, subscription, configuration_name, render_frame_host); @@ -462,16 +490,11 @@ new file mode 100644 +AdblockPrivateSetEnabledFunction::~AdblockPrivateSetEnabledFunction() {} + +ExtensionFunction::ResponseAction AdblockPrivateSetEnabledFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::SetEnabled::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); -+ + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + adblock_configuration->SetEnabled(params->enabled); + return RespondNow(NoArguments()); +} @@ -482,11 +505,7 @@ new file mode 100644 + +ExtensionFunction::ResponseAction AdblockPrivateIsEnabledFunction::Run() { + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + return RespondNow( + ArgumentList(api::adblock_private::IsEnabled::Results::Create( + adblock_configuration->IsEnabled()))); @@ -500,16 +519,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateSetAcceptableAdsEnabledFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::SetAcceptableAdsEnabled::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + if (params->enabled) { + adblock_configuration->AddFilterList(adblock::AcceptableAdsUrl()); + } else { @@ -528,17 +543,51 @@ new file mode 100644 +ExtensionFunction::ResponseAction +AdblockPrivateIsAcceptableAdsEnabledFunction::Run() { + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + return RespondNow(ArgumentList( + api::adblock_private::IsAcceptableAdsEnabled::Results::Create( + std::ranges::any_of(adblock_configuration->GetFilterLists(), -+ [&](const auto& url) { -+ return url == adblock::AcceptableAdsUrl(); -+ })))); ++ [&](const auto& url) { ++ return url == adblock::AcceptableAdsUrl(); ++ })))); ++} ++ ++AdblockPrivateSetAutoInstallEnabledFunction:: ++ AdblockPrivateSetAutoInstallEnabledFunction() {} ++ ++AdblockPrivateSetAutoInstallEnabledFunction:: ++ ~AdblockPrivateSetAutoInstallEnabledFunction() {} ++ ++ExtensionFunction::ResponseAction ++AdblockPrivateSetAutoInstallEnabledFunction::Run() { ++ absl::optional params = ++ api::adblock_private::SetAutoInstallEnabled::Params::Create(args()); ++ EXTENSION_FUNCTION_VALIDATE(params); ++ ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ ++ subscription_service->SetAutoInstallEnabled(params->enabled); ++ ++ return RespondNow(NoArguments()); ++} ++ ++AdblockPrivateIsAutoInstallEnabledFunction:: ++ AdblockPrivateIsAutoInstallEnabledFunction() {} ++ ++AdblockPrivateIsAutoInstallEnabledFunction:: ++ ~AdblockPrivateIsAutoInstallEnabledFunction() {} ++ ++ExtensionFunction::ResponseAction ++AdblockPrivateIsAutoInstallEnabledFunction::Run() { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ ++ return RespondNow( ++ ArgumentList(api::adblock_private::IsAutoInstallEnabled::Results::Create( ++ subscription_service->IsAutoInstallEnabled()))); +} + +AdblockPrivateGetBuiltInSubscriptionsFunction:: @@ -573,12 +622,13 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateInstallSubscriptionFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::InstallSubscription::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto url = GURL{params->url}; -+ auto status = RunSubscriptionAction(SubscriptionAction::kInstall, -+ browser_context(), url); ++ auto status = ++ RunSubscriptionAction(SubscriptionAction::kInstall, ++ GetOriginalBrowserContext(browser_context()), url); + if (!status.empty()) { + return RespondNow(Error(status)); + } @@ -594,12 +644,13 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateUninstallSubscriptionFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::UninstallSubscription::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto url = GURL{params->url}; -+ auto status = RunSubscriptionAction(SubscriptionAction::kUninstall, -+ browser_context(), url); ++ auto status = ++ RunSubscriptionAction(SubscriptionAction::kUninstall, ++ GetOriginalBrowserContext(browser_context()), url); + if (!status.empty()) { + return RespondNow(Error(status)); + } @@ -617,15 +668,13 @@ new file mode 100644 +AdblockPrivateGetInstalledSubscriptionsFunction::Run() { + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()); ++ GetOriginalBrowserContext(browser_context())); + auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + return RespondNow(ArgumentList( + api::adblock_private::GetInstalledSubscriptions::Results::Create( + CopySubscriptions(subscription_service->GetCurrentSubscriptions( -+ subscription_service->GetAdblockFilteringConfiguration()))))); ++ adblock_configuration))))); +} + +AdblockPrivateAddAllowedDomainFunction:: @@ -636,15 +685,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateAddAllowedDomainFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::AddAllowedDomain::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + adblock_configuration->AddAllowedDomain(params->domain); + return RespondNow(NoArguments()); +} @@ -657,15 +702,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateRemoveAllowedDomainFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::RemoveAllowedDomain::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + adblock_configuration->RemoveAllowedDomain(params->domain); + + return RespondNow(NoArguments()); @@ -680,11 +721,7 @@ new file mode 100644 +ExtensionFunction::ResponseAction +AdblockPrivateGetAllowedDomainsFunction::Run() { + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + return RespondNow( + ArgumentList(api::adblock_private::GetAllowedDomains::Results::Create( + adblock_configuration->GetAllowedDomains()))); @@ -697,15 +734,11 @@ new file mode 100644 + ~AdblockPrivateAddCustomFilterFunction() {} + +ExtensionFunction::ResponseAction AdblockPrivateAddCustomFilterFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::AddCustomFilter::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + adblock_configuration->AddCustomFilter(params->filter); + return RespondNow(NoArguments()); +} @@ -719,11 +752,7 @@ new file mode 100644 +ExtensionFunction::ResponseAction +AdblockPrivateGetCustomFiltersFunction::Run() { + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + return RespondNow( + ArgumentList(api::adblock_private::GetCustomFilters::Results::Create( + adblock_configuration->GetCustomFilters()))); @@ -737,15 +766,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateRemoveCustomFilterFunction::Run() { -+ std::optional params = ++ absl::optional params = + api::adblock_private::RemoveCustomFilter::Params::Create(args()); + EXTENSION_FUNCTION_VALIDATE(params); + auto* adblock_configuration = -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()) -+ ->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "adblock_private expects \"adblock\" configuration"; ++ GetAdblockConfiguration(GetOriginalBrowserContext(browser_context())); + adblock_configuration->RemoveCustomFilter(params->filter); + return RespondNow(NoArguments()); +} @@ -758,11 +783,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateGetSessionAllowedAdsCountFunction::Run() { -+ auto* session_stats_ = -+ adblock::SessionStatsFactory::GetForBrowserContext(browser_context()); ++ auto* session_stats_ = adblock::SessionStatsFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); + return RespondNow(ArgumentList( + api::adblock_private::GetSessionAllowedAdsCount::Results::Create( -+ CopySessionsStats(session_stats_->GetSessionAllowedAdsCount())))); ++ CopySessionsStats( ++ session_stats_->GetSessionAllowedResourcesCount())))); +} + +AdblockPrivateGetSessionBlockedAdsCountFunction:: @@ -773,11 +799,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +AdblockPrivateGetSessionBlockedAdsCountFunction::Run() { -+ auto* session_stats_ = -+ adblock::SessionStatsFactory::GetForBrowserContext(browser_context()); ++ auto* session_stats_ = adblock::SessionStatsFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); + return RespondNow(ArgumentList( + api::adblock_private::GetSessionAllowedAdsCount::Results::Create( -+ CopySessionsStats(session_stats_->GetSessionBlockedAdsCount())))); ++ CopySessionsStats( ++ session_stats_->GetSessionBlockedResourcesCount())))); +} + +} // namespace api @@ -786,7 +813,7 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_api.h new file mode 100644 --- /dev/null +++ b/chrome/browser/extensions/api/adblock_private/adblock_private_api.h -@@ -0,0 +1,328 @@ +@@ -0,0 +1,360 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -911,6 +938,38 @@ new file mode 100644 + const AdblockPrivateIsAcceptableAdsEnabledFunction&) = delete; +}; + ++class AdblockPrivateSetAutoInstallEnabledFunction : public ExtensionFunction { ++ public: ++ DECLARE_EXTENSION_FUNCTION("adblockPrivate.setAutoInstallEnabled", UNKNOWN) ++ AdblockPrivateSetAutoInstallEnabledFunction(); ++ ++ private: ++ ~AdblockPrivateSetAutoInstallEnabledFunction() override; ++ ++ ResponseAction Run() override; ++ ++ AdblockPrivateSetAutoInstallEnabledFunction( ++ const AdblockPrivateSetAutoInstallEnabledFunction&) = delete; ++ AdblockPrivateSetAutoInstallEnabledFunction& operator=( ++ const AdblockPrivateSetAutoInstallEnabledFunction&) = delete; ++}; ++ ++class AdblockPrivateIsAutoInstallEnabledFunction : public ExtensionFunction { ++ public: ++ DECLARE_EXTENSION_FUNCTION("adblockPrivate.isAutoInstallEnabled", UNKNOWN) ++ AdblockPrivateIsAutoInstallEnabledFunction(); ++ ++ private: ++ ~AdblockPrivateIsAutoInstallEnabledFunction() override; ++ ++ ResponseAction Run() override; ++ ++ AdblockPrivateIsAutoInstallEnabledFunction( ++ const AdblockPrivateIsAutoInstallEnabledFunction&) = delete; ++ AdblockPrivateIsAutoInstallEnabledFunction& operator=( ++ const AdblockPrivateIsAutoInstallEnabledFunction&) = delete; ++}; ++ +class AdblockPrivateGetBuiltInSubscriptionsFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("adblockPrivate.getBuiltInSubscriptions", UNKNOWN) @@ -1119,7 +1178,7 @@ diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_apite new file mode 100644 --- /dev/null +++ b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest.cc -@@ -0,0 +1,984 @@ +@@ -0,0 +1,174 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -1136,139 +1195,47 @@ new file mode 100644 +// along with eyeo Chromium SDK. If not, see . + +#include -+#include +#include + -+#include "chrome/browser/adblock/adblock_content_browser_client.h" -+#include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/extensions/extension_apitest.h" -+#include "chrome/browser/ssl/https_upgrades_interceptor.h" -+#include "chrome/browser/ssl/https_upgrades_util.h" -+#include "chrome/common/chrome_switches.h" -+#include "chrome/common/extensions/api/adblock_private.h" -+#include "chrome/common/extensions/api/tabs.h" -+#include "chrome/test/base/ui_test_utils.h" ++#include "chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.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 "content/public/test/browser_test.h" -+#include "extensions/browser/background_script_executor.h" -+#include "net/dns/mock_host_resolver.h" -+#include "net/test/embedded_test_server/embedded_test_server.h" -+#include "net/test/embedded_test_server/http_request.h" -+#include "net/test/embedded_test_server/http_response.h" -+#include "testing/gmock/include/gmock/gmock.h" -+#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + -+namespace { -+enum class Mode { Normal, Incognito }; -+enum class EyeoExtensionApi { Old, New }; -+} // namespace -+ -+class AdblockPrivateApiTest : public ExtensionApiTest, -+ public testing::WithParamInterface { ++class AdblockPrivateApiTest ++ : public AdblockPrivateApiTestBase, ++ public testing::WithParamInterface< ++ std::tuple> { + public: + AdblockPrivateApiTest() {} + ~AdblockPrivateApiTest() override = default; + AdblockPrivateApiTest(const AdblockPrivateApiTest&) = delete; + AdblockPrivateApiTest& operator=(const AdblockPrivateApiTest&) = delete; + -+ void SetUpCommandLine(base::CommandLine* command_line) override { -+ extensions::ExtensionApiTest::SetUpCommandLine(command_line); -+ if (IsIncognito()) { -+ command_line->AppendSwitch(switches::kIncognito); -+ } -+ } -+ -+ protected: -+ void SetUpOnMainThread() override { -+ ExtensionApiTest::SetUpOnMainThread(); -+ -+ // When any of that fails we need to update comment in adblock_private.idl -+ ASSERT_EQ(api::tabs::TAB_ID_NONE, -1); -+ ASSERT_EQ(SessionID::InvalidValue().id(), -1); -+ -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser()->profile()) -+ ->GetAdblockFilteringConfiguration() -+ ->RemoveCustomFilter(adblock::kAllowlistEverythingFilter); -+ -+ AdblockContentBrowserClient::ForceAdblockProxyForTesting(); -+ } -+ -+ bool IsIncognito() { return GetParam() == Mode::Incognito; } -+ -+ bool RunTest(const std::string& subtest) { -+ const std::string page_url = "main.html?subtest=" + subtest; -+ return RunExtensionTest("adblock_private", -+ {.extension_url = page_url.c_str()}, -+ {.allow_in_incognito = IsIncognito(), -+ .load_as_component = !IsIncognito()}); -+ } -+ -+ bool RunTestWithParams(const std::string& subtest, -+ const std::map& params) { -+ if (params.empty()) { -+ return RunTest(subtest); -+ } -+ std::string subtest_with_params = subtest; -+ for (const auto& [key, value] : params) { -+ subtest_with_params += "&" + key + "=" + value; -+ } -+ return RunTest(subtest_with_params); -+ } -+ -+ bool RunTestWithServer(const std::string& subtest, -+ const std::string& subscription_path, -+ const std::string& subscription_filters, -+ std::map params = {{}}) { -+ net::EmbeddedTestServer https_server{ -+ net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS)}; -+ https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK); -+ https_server.RegisterRequestHandler(base::BindRepeating( -+ &AdblockPrivateApiTest::HandleSubscriptionUpdateRequest, -+ base::Unretained(this), subscription_path, subscription_filters)); -+ if (!https_server.Start()) { -+ return false; -+ } -+ params.insert( -+ {"subscription_url", https_server.GetURL(subscription_path).spec()}); -+ return RunTestWithParams(subtest, params); ++ bool IsIncognito() override { ++ return std::get<1>(GetParam()) == ++ AdblockPrivateApiTestBase::Mode::Incognito; + } + -+ std::unique_ptr -+ HandleSubscriptionUpdateRequest( -+ const std::string subscription_path, -+ const std::string subscription_filters, -+ 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, subscription_path, -+ base::CompareCase::SENSITIVE)) { -+ auto http_response = -+ std::make_unique(); -+ http_response->set_code(net::HTTP_OK); -+ http_response->set_content(kSubscriptionHeader + subscription_filters); -+ 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; ++ std::string GetApiEndpoint() override { ++ return std::get<0>(GetParam()) == ++ AdblockPrivateApiTestBase::EyeoExtensionApi::Old ++ ? "adblockPrivate" ++ : "eyeoFilteringPrivate"; + } + -+ std::map SubscriptionsManagementSetup() { -+ DCHECK(browser()->profile()); ++ std::map FindExpectedDefaultFilterLists() { ++ DCHECK(browser()->profile()->GetOriginalProfile()); + auto selected = adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser()->profile()) -+ ->GetAdblockFilteringConfiguration() ++ browser()->profile()->GetOriginalProfile()) ++ ->GetFilteringConfiguration( ++ adblock::kAdblockFilteringConfigurationName) + ->GetFilterLists(); + const auto easylist = std::find_if( + selected.begin(), selected.end(), [&](const GURL& subscription) { @@ -1299,96 +1266,39 @@ new file mode 100644 + EXPECT_TRUE(RunTest("setEnabled_isEnabled")) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SetAndCheckEnabledNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("setEnabled_isEnabled", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SetAndCheckAAEnabled) { -+ EXPECT_TRUE(RunTest("setAAEnabled_isAAEnabled")) << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SetAndCheckAAEnabledNewAPI) { -+ EXPECT_TRUE(RunTest("setAAEnabled_isAAEnabled_newAPI")) << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, GetBuiltInSubscriptions) { -+ EXPECT_TRUE(RunTest("getBuiltInSubscriptions")) << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ InstalledSubscriptionsDataSchema) { -+ EXPECT_TRUE(RunTest("installedSubscriptionsDataSchema")) << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ InstalledSubscriptionsDataSchemaNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("installedSubscriptionsDataSchema", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ InstalledSubscriptionsDataSchemaConfigDisabled) { -+ EXPECT_TRUE(RunTestWithParams("installedSubscriptionsDataSchema", -+ {{"disabled", "true"}})) ++ EXPECT_TRUE(RunTest(GetApiEndpoint() == "adblockPrivate" ++ ? "setAAEnabled_isAAEnabled" ++ : "setAAEnabled_isAAEnabled_newAPI")) + << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ InstalledSubscriptionsDataSchemaConfigDisabledNewAPI) { -+ EXPECT_TRUE(RunTestWithParams( -+ "installedSubscriptionsDataSchema", -+ {{"api", "eyeoFilteringPrivate"}, {"disabled", "true"}})) -+ << message_; ++IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, GetBuiltInSubscriptions) { ++ if (GetApiEndpoint() == "adblockPrivate") { ++ EXPECT_TRUE(RunTest("getBuiltInSubscriptions")) << message_; ++ } +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, InstallSubscriptionInvalidURL) { + EXPECT_TRUE(RunTest("installSubscriptionInvalidURL")) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ InstallSubscriptionInvalidURLNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("installSubscriptionInvalidURL", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, UninstallSubscriptionInvalidURL) { + EXPECT_TRUE(RunTest("uninstallSubscriptionInvalidURL")) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ UninstallSubscriptionInvalidURLNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("uninstallSubscriptionInvalidURL", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SubscriptionsManagement) { -+ auto params = SubscriptionsManagementSetup(); -+ if (params.empty()) { -+ // Since default configuration has been changed let's skip this test -+ return; -+ } -+ EXPECT_TRUE(RunTestWithParams("subscriptionsManagement", params)) << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SubscriptionsManagementNewAPI) { -+ auto params = SubscriptionsManagementSetup(); ++ auto params = FindExpectedDefaultFilterLists(); + if (params.empty()) { + // Since default configuration has been changed let's skip this test + return; + } -+ params.insert({"api", "eyeoFilteringPrivate"}); -+ SubscriptionsManagementSetup(); + EXPECT_TRUE(RunTestWithParams("subscriptionsManagement", params)) << message_; +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, + SubscriptionsManagementConfigDisabled) { -+ auto params = SubscriptionsManagementSetup(); ++ auto params = FindExpectedDefaultFilterLists(); + if (params.empty()) { + // Since default configuration has been changed let's skip this test + return; @@ -1397,88 +1307,24 @@ new file mode 100644 + EXPECT_TRUE(RunTestWithParams("subscriptionsManagement", params)) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, -+ SubscriptionsManagementConfigDisabledNewAPI) { -+ auto params = SubscriptionsManagementSetup(); -+ if (params.empty()) { -+ // Since default configuration has been changed let's skip this test -+ return; -+ } -+ params.insert({"api", "eyeoFilteringPrivate"}); -+ params.insert({"disabled", "true"}); -+ SubscriptionsManagementSetup(); -+ EXPECT_TRUE(RunTestWithParams("subscriptionsManagement", params)) << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AllowedDomainsManagement) { + EXPECT_TRUE(RunTest("allowedDomainsManagement")) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AllowedDomainsManagementNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("allowedDomainsManagement", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, CustomFiltersManagement) { + EXPECT_TRUE(RunTest("customFiltersManagement")) << message_; +} + -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, CustomFiltersManagementNewAPI) { -+ EXPECT_TRUE(RunTestWithParams("customFiltersManagement", -+ {{"api", "eyeoFilteringPrivate"}})) -+ << message_; -+} -+ +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AdBlockedEvents) { -+ std::string subscription_path = "/testeventssub.txt"; -+ std::string subscription_filters = "test1.png"; -+ EXPECT_TRUE(RunTestWithServer("adBlockedEvents", subscription_path, -+ subscription_filters)) -+ << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AdBlockedEventsNewAPI) { -+ std::string subscription_path = "/testeventssub.txt"; -+ std::string subscription_filters = "test1.png"; -+ std::map params = {{"api", "eyeoFilteringPrivate"}}; -+ EXPECT_TRUE(RunTestWithServer("adBlockedEvents", subscription_path, -+ subscription_filters, std::move(params))) -+ << message_; ++ EXPECT_TRUE(RunTest("adBlockedEvents")) << message_; +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AdAllowedEvents) { -+ std::string subscription_path = "/testeventssub.txt"; -+ std::string subscription_filters = "test2.png\n@@test2.png"; -+ EXPECT_TRUE(RunTestWithServer("adAllowedEvents", subscription_path, -+ subscription_filters)) -+ << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AdAllowedEventsNewAPI) { -+ std::string subscription_path = "/testeventssub.txt"; -+ std::string subscription_filters = "test2.png\n@@test2.png"; -+ std::map params = {{"api", "eyeoFilteringPrivate"}}; -+ EXPECT_TRUE(RunTestWithServer("adAllowedEvents", subscription_path, -+ subscription_filters, std::move(params))) -+ << message_; ++ EXPECT_TRUE(RunTest("adAllowedEvents")) << message_; +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SessionStats) { -+ std::string subscription_path = "/teststatssub.txt"; -+ std::string subscription_filters = "test3.png\ntest4.png\n@@test4.png"; -+ EXPECT_TRUE(RunTestWithServer("sessionStats", subscription_path, -+ subscription_filters)) -+ << message_; -+} -+ -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, SessionStatsNewAPI) { -+ std::string subscription_path = "/teststatssub.txt"; -+ std::string subscription_filters = "test3.png\ntest4.png\n@@test4.png"; -+ std::map params = {{"api", "eyeoFilteringPrivate"}}; -+ EXPECT_TRUE(RunTestWithServer("sessionStats", subscription_path, -+ subscription_filters, std::move(params))) -+ << message_; ++ EXPECT_TRUE(RunTest("sessionStats")) << message_; +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiTest, AllowedDomainsEvent) { @@ -1497,59 +1343,109 @@ new file mode 100644 + EXPECT_TRUE(RunTest("customFiltersEvent")) << message_; +} + -+INSTANTIATE_TEST_SUITE_P(All, -+ AdblockPrivateApiTest, -+ testing::Values(Mode::Normal, Mode::Incognito)); -+ -+class AdblockPrivateApiBackgroundPageTest -+ : public ExtensionApiTest, -+ public testing::WithParamInterface> { -+ public: -+ AdblockPrivateApiBackgroundPageTest() {} -+ ~AdblockPrivateApiBackgroundPageTest() override = default; -+ AdblockPrivateApiBackgroundPageTest( -+ const AdblockPrivateApiBackgroundPageTest&) = delete; -+ AdblockPrivateApiBackgroundPageTest& operator=( -+ const AdblockPrivateApiBackgroundPageTest&) = delete; -+ -+ void SetUpCommandLine(base::CommandLine* command_line) override { -+ extensions::ExtensionApiTest::SetUpCommandLine(command_line); -+ if (IsIncognito()) { -+ command_line->AppendSwitch(switches::kIncognito); -+ } -+ } -+ -+ protected: -+ void SetUpOnMainThread() override { -+ ExtensionApiTest::SetUpOnMainThread(); ++INSTANTIATE_TEST_SUITE_P( ++ , ++ AdblockPrivateApiTest, ++ testing::Combine( ++ testing::Values(AdblockPrivateApiTestBase::EyeoExtensionApi::Old, ++ AdblockPrivateApiTestBase::EyeoExtensionApi::New), ++ testing::Values(AdblockPrivateApiTestBase::Mode::Normal, ++ AdblockPrivateApiTestBase::Mode::Incognito))); ++ ++} // namespace extensions +diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_backgroundpage.cc b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_backgroundpage.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_backgroundpage.cc +@@ -0,0 +1,398 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . ++ ++#include "chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.h" ++#include "chrome/test/base/ui_test_utils.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/content_mock_cert_verifier.h" ++#include "extensions/browser/background_script_executor.h" ++#include "net/dns/mock_host_resolver.h" ++#include "net/test/embedded_test_server/embedded_test_server.h" ++ ++namespace extensions { ++ ++// Here are extension API tests for PageAllowed event and popup ++// events and stats, which are difficult to implement like other ++// tests in AdblockPrivateAPI class (purely in JS in test.js file). ++// Tests here require a background page to run script code. ++class AdblockPrivateApiBackgroundPageTest ++ : public AdblockPrivateApiTestBase, ++ public testing::WithParamInterface< ++ std::tuple> { ++ public: ++ AdblockPrivateApiBackgroundPageTest() {} ++ ~AdblockPrivateApiBackgroundPageTest() override = default; ++ AdblockPrivateApiBackgroundPageTest( ++ const AdblockPrivateApiBackgroundPageTest&) = delete; ++ AdblockPrivateApiBackgroundPageTest& operator=( ++ const AdblockPrivateApiBackgroundPageTest&) = delete; ++ ++ void SetUpCommandLine(base::CommandLine* command_line) override { ++ AdblockPrivateApiTestBase::SetUpCommandLine(command_line); ++ mock_cert_verifier_.SetUpCommandLine(command_line); ++ } ++ ++ protected: ++ void SetUpOnMainThread() override { ++ ExtensionApiTest::SetUpOnMainThread(); ++ ++ // Map example.com to localhost. ++ host_resolver()->AddRule("example.com", "127.0.0.1"); ++ ++ mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); + + https_test_server_ = std::make_unique( + net::EmbeddedTestServer::TYPE_HTTPS); + https_test_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_OK); -+ https_test_server_->ServeFilesFromSourceDirectory( -+ "chrome/test/data/extensions/api_test/adblock_private"); -+ embedded_test_server()->ServeFilesFromSourceDirectory( -+ "chrome/test/data/extensions/api_test/adblock_private"); -+ HttpsUpgradesInterceptor::SetHttpsPortForTesting( -+ https_test_server_->port()); -+ HttpsUpgradesInterceptor::SetHttpPortForTesting( -+ embedded_test_server()->port()); ++ https_test_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir()); + ASSERT_TRUE(https_test_server_->Start()); -+ AllowHttpForHostnamesForTesting({"example.com"}, -+ browser()->profile()->GetPrefs()); + -+ // Map example.com to localhost. -+ host_resolver()->AddRule("example.com", "127.0.0.1"); + ASSERT_TRUE(StartEmbeddedTestServer()); + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser()->profile()) -+ ->GetAdblockFilteringConfiguration() ++ browser()->profile()->GetOriginalProfile()) ++ ->GetFilteringConfiguration(adblock::kAdblockFilteringConfigurationName) + ->RemoveCustomFilter(adblock::kAllowlistEverythingFilter); ++ ++ extension_ = LoadExtension(test_data_dir_.AppendASCII("adblock_private"), ++ {.allow_in_incognito = IsIncognito()}); ++ ASSERT_TRUE(extension_); + } + + bool IsOldApi() { return std::get<0>(GetParam()) == EyeoExtensionApi::Old; } + -+ bool IsIncognito() { return std::get<1>(GetParam()) == Mode::Incognito; } ++ std::string GetApiEndpoint() override { ++ return IsOldApi() ? "adblockPrivate" : "eyeoFilteringPrivate"; ++ } ++ ++ bool IsIncognito() override { ++ return std::get<1>(GetParam()) == ++ AdblockPrivateApiBackgroundPageTest::Mode::Incognito; ++ } + + void ExecuteScript(const std::string& js_code) const { + content::WebContents* web_contents = @@ -1557,45 +1453,44 @@ new file mode 100644 + ASSERT_TRUE(content::ExecJs(web_contents->GetPrimaryMainFrame(), js_code)); + } + -+ std::string ExecuteScriptInBackgroundPage(const std::string& extension_id, -+ const std::string& script) { ++ int ExecuteScriptIAndGetInt(const std::string& extension_id, ++ const std::string& script) { + return ExtensionApiTest::ExecuteScriptInBackgroundPage(extension_id, script) -+ .GetString(); ++ .GetInt(); + } + + void SetupApiObjectAndMethods(const std::string& extension_id) { + std::string setup_script; + if (IsOldApi()) { + setup_script = -+ "var apiObject = chrome.adblockPrivate;" -+ "var sessionAllowedCount = 'getSessionAllowedAdsCount';" -+ "var sessionBlockedCount = 'getSessionBlockedAdsCount';" -+ "var onAllowedEvent = 'onAdAllowed';" -+ "var onBlockedEvent = 'onAdBlocked';" -+ "chrome.test.sendScriptResult('');"; ++ "let apiObject = chrome.adblockPrivate;" ++ "let sessionAllowedCount = 'getSessionAllowedAdsCount';" ++ "let sessionBlockedCount = 'getSessionBlockedAdsCount';" ++ "let onAllowedEvent = 'onAdAllowed';" ++ "let onBlockedEvent = 'onAdBlocked';" ++ "chrome.test.sendScriptResult(0);"; + } else { + setup_script = -+ "var apiObject = chrome.eyeoFilteringPrivate;" -+ "var sessionAllowedCount = 'getSessionAllowedRequestsCount';" -+ "var sessionBlockedCount = 'getSessionBlockedRequestsCount';" -+ "var onAllowedEvent = 'onRequestAllowed';" -+ "var onBlockedEvent = 'onRequestBlocked';" -+ "chrome.test.sendScriptResult('');"; ++ "let apiObject = chrome.eyeoFilteringPrivate;" ++ "let sessionAllowedCount = 'getSessionAllowedRequestsCount';" ++ "let sessionBlockedCount = 'getSessionBlockedRequestsCount';" ++ "let onAllowedEvent = 'onRequestAllowed';" ++ "let onBlockedEvent = 'onRequestBlocked';" ++ "chrome.test.sendScriptResult(0);"; + } -+ ExecuteScriptInBackgroundPage(extension_id, setup_script); ++ ExecuteScriptIAndGetInt(extension_id, setup_script); + } + ++ const ExtensionId& GetExtensionId() const { return extension_->id(); } ++ + std::unique_ptr https_test_server_; ++ content::ContentMockCertVerifier mock_cert_verifier_; ++ raw_ptr extension_; +}; + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTest, PageAllowedEvents) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); -+ + constexpr char kSetListenersScript[] = R"( -+ var testData = {}; ++ let testData = {}; + testData.pageAllowedCount = 0; + apiObject.onPageAllowed.addListener(function(e) { + if (!e.url.endsWith('test.html')) { @@ -1603,85 +1498,95 @@ new file mode 100644 + } + testData.pageAllowedCount = testData.pageAllowedCount + 1; + }); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + + constexpr char kReadCountersScript[] = R"( -+ chrome.test.sendScriptResult(testData.pageAllowedCount.toString()); ++ var intervalId = setInterval(function() { ++ if (testData.pageAllowedCount == %d) { ++ if (intervalId) { ++ clearInterval(intervalId); ++ intervalId = null; ++ } ++ chrome.test.sendScriptResult(testData.pageAllowedCount); ++ } ++ }, 100); + )"; + + constexpr char kAllowDomainScript[] = R"( + apiObject.addAllowedDomain(%s'example.com'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + + const GURL test_url = https_test_server_->GetURL( + "example.com", "/extensions/api_test/adblock_private/test.html"); + -+ SetupApiObjectAndMethods(extension->id()); ++ SetupApiObjectAndMethods(GetExtensionId()); + -+ ExecuteScriptInBackgroundPage(extension->id(), kSetListenersScript); ++ ExecuteScriptIAndGetInt(GetExtensionId(), kSetListenersScript); + + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); + ASSERT_EQ( -+ "0", ExecuteScriptInBackgroundPage(extension->id(), kReadCountersScript)); ++ 0, ExecuteScriptIAndGetInt(GetExtensionId(), ++ base::StringPrintf(kReadCountersScript, 0))); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kAllowDomainScript, IsOldApi() ? "" : "'adblock', ")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); + ASSERT_EQ( -+ "1", ExecuteScriptInBackgroundPage(extension->id(), kReadCountersScript)); ++ 1, ExecuteScriptIAndGetInt(GetExtensionId(), ++ base::StringPrintf(kReadCountersScript, 1))); +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTest, PageAllowedStats) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); -+ + constexpr char kReadAllowedStatsScript[] = R"( -+ apiObject[sessionAllowedCount](function(sessionStats) { -+ let count = 0; -+ for (const entry of sessionStats) { -+ if (entry.url === 'adblock:custom') { -+ count = entry.count; ++ var intervalId = setInterval(function() { ++ apiObject[sessionAllowedCount](function(sessionStats) { ++ let count = 0; ++ for (const entry of sessionStats) { ++ if (entry.url === 'adblock:custom') { ++ count = entry.count; ++ } + } -+ } -+ chrome.test.sendScriptResult(count.toString()); -+ }); ++ if (%d == 0 || count == %d) { ++ if (intervalId) { ++ clearInterval(intervalId); ++ intervalId = null; ++ } ++ chrome.test.sendScriptResult(count); ++ } ++ }); ++ }, 100); + )"; + + constexpr char kAllowDomainScript[] = R"( + apiObject.addAllowedDomain(%s'example.com'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + + const GURL test_url = https_test_server_->GetURL( + "example.com", "/extensions/api_test/adblock_private/test.html"); + -+ SetupApiObjectAndMethods(extension->id()); ++ SetupApiObjectAndMethods(GetExtensionId()); + -+ int initial_value = std::stoi( -+ ExecuteScriptInBackgroundPage(extension->id(), kReadAllowedStatsScript)); ++ int initial_value = ExecuteScriptIAndGetInt( ++ GetExtensionId(), base::StringPrintf(kReadAllowedStatsScript, 0, 0)); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kAllowDomainScript, IsOldApi() ? "" : "'adblock', ")); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); + -+ ASSERT_EQ(initial_value + 1, std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), kReadAllowedStatsScript))); ++ EXPECT_EQ( ++ initial_value + 1, ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), base::StringPrintf(kReadAllowedStatsScript, 1, 1))); +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTest, PopupEvents) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); -+ + constexpr char kSetListenersScript[] = R"( -+ var testData = {}; ++ let testData = {}; + testData.popupBlockedCount = 0; + testData.popupAllowedCount = 0; + apiObject.onPopupAllowed.addListener(function(e, blocked) { @@ -1696,464 +1601,341 @@ new file mode 100644 + } + testData.popupBlockedCount = testData.popupBlockedCount + 1; + }); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + -+ auto read_allowed_stats_script = [](int expected) { -+ std::string script = base::StringPrintf( -+ R"( -+ var intervalAllowedId = setInterval(function() { -+ if (testData.popupAllowedCount == %d) { -+ if (intervalAllowedId) { -+ clearInterval(intervalAllowedId); -+ intervalAllowedId = null; ++ constexpr char verify_stats_script_tmpl[] = R"( ++ var intervalId = setInterval(function() { ++ let result = testData.%s; ++ if (result == %d) { ++ if (intervalId) { ++ clearInterval(intervalId); ++ intervalId = null; + } -+ chrome.test.sendScriptResult( -+ testData.popupAllowedCount.toString()); ++ chrome.test.sendScriptResult(result); + } -+ }, 100))", -+ expected); ++ }, 100); ++ )"; ++ ++ auto read_allowed_stats_script = [verify_stats_script_tmpl](int expected) { ++ std::string script = base::StringPrintf(verify_stats_script_tmpl, ++ "popupAllowedCount", expected); + return script; + }; + -+ auto read_blocked_stats_script = [](int expected) { -+ std::string script = base::StringPrintf( -+ R"( -+ var intervalBlockedId = setInterval(function() { -+ if (testData.popupBlockedCount == %d) { -+ if (intervalBlockedId) { -+ clearInterval(intervalBlockedId); -+ intervalBlockedId = null; -+ } -+ chrome.test.sendScriptResult( -+ testData.popupBlockedCount.toString()); -+ } -+ }, 100))", -+ expected); ++ auto read_blocked_stats_script = [verify_stats_script_tmpl](int expected) { ++ std::string script = base::StringPrintf(verify_stats_script_tmpl, ++ "popupBlockedCount", expected); + return script; + }; + + constexpr char kBlockPopupScript[] = R"( + apiObject.addCustomFilter(%s'some-popup.html^$popup'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + + constexpr char kAllowPopupScript[] = R"( + apiObject.addCustomFilter(%s'@@some-popup.html^$popup'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + -+ const GURL test_url = -+ embedded_test_server()->GetURL("example.com", "/test.html"); ++ const GURL test_url = https_test_server_->GetURL( ++ "example.com", "/extensions/api_test/adblock_private/test.html"); + constexpr char kOpenPopupScript[] = + "document.getElementById('popup_id').click()"; + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ SetupApiObjectAndMethods(extension->id()); ++ SetupApiObjectAndMethods(GetExtensionId()); + -+ ExecuteScriptInBackgroundPage(extension->id(), kSetListenersScript); ++ ExecuteScriptIAndGetInt(GetExtensionId(), kSetListenersScript); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kBlockPopupScript, IsOldApi() ? "" : "'adblock', ")); + ExecuteScript(kOpenPopupScript); -+ ASSERT_EQ(1, std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), read_blocked_stats_script(1)))); ++ EXPECT_EQ(0, ExecuteScriptIAndGetInt(GetExtensionId(), ++ read_allowed_stats_script(0))); ++ EXPECT_EQ(1, ExecuteScriptIAndGetInt(GetExtensionId(), ++ read_blocked_stats_script(1))); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kAllowPopupScript, IsOldApi() ? "" : "'adblock', ")); + ExecuteScript(kOpenPopupScript); -+ ASSERT_EQ(1, std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), read_allowed_stats_script(1)))); -+ ASSERT_EQ(1, std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), read_blocked_stats_script(1)))); ++ EXPECT_EQ(1, ExecuteScriptIAndGetInt(GetExtensionId(), ++ read_allowed_stats_script(1))); ++ EXPECT_EQ(1, ExecuteScriptIAndGetInt(GetExtensionId(), ++ read_blocked_stats_script(1))); +} + +IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTest, PopupStats) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); ++ constexpr char verify_stats_script_tmpl[] = R"( ++ var intervalId = setInterval(function() { ++ apiObject[%s](function(sessionStats) { ++ let count = 0; ++ for (const entry of sessionStats) { ++ if (entry.url === 'adblock:custom') { ++ count = entry.count; ++ } ++ } ++ if (%d == 0 || count == %d) { ++ if (intervalId) { ++ clearInterval(intervalId); ++ intervalId = null; ++ } ++ chrome.test.sendScriptResult(count); ++ } ++ } ++ )}, 100); ++ )"; + -+ auto read_allowed_stats_script = [](int expected) { ++ auto read_allowed_stats_script = [verify_stats_script_tmpl](int expected) { + std::string script = base::StringPrintf( -+ R"( -+ var intervalAllowedId = setInterval(function() { -+ apiObject[sessionAllowedCount](function(sessionStats) { -+ let count = 0; -+ for (const entry of sessionStats) { -+ if (entry.url === 'adblock:custom') { -+ count = entry.count; -+ } -+ } -+ if (%d == 0 || count == %d) { -+ if (intervalAllowedId) { -+ clearInterval(intervalAllowedId); -+ intervalAllowedId = null; -+ } -+ chrome.test.sendScriptResult(count.toString()); -+ } -+ } -+ )}, 100))", -+ expected, expected); ++ verify_stats_script_tmpl, "sessionAllowedCount", expected, expected); + return script; + }; + -+ auto read_blocked_stats_script = [](int expected) { ++ auto read_blocked_stats_script = [verify_stats_script_tmpl](int expected) { + std::string script = base::StringPrintf( -+ R"( -+ var intervalBlockedId = setInterval(function() { -+ apiObject[sessionBlockedCount](function(sessionStats) { -+ let count = 0; -+ for (const entry of sessionStats) { -+ if (entry.url === 'adblock:custom') { -+ count = entry.count; -+ } -+ } -+ if (%d == 0 || count == %d) { -+ if (intervalBlockedId) { -+ clearInterval(intervalBlockedId); -+ intervalBlockedId = null; -+ } -+ chrome.test.sendScriptResult(count.toString()); -+ } -+ } -+ )}, 100))", -+ expected, expected); ++ verify_stats_script_tmpl, "sessionBlockedCount", expected, expected); + return script; + }; + + constexpr char kBlockPopupScript[] = R"( + apiObject.addCustomFilter(%s'some-popup.html^$popup'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + + constexpr char kAllowPopupScript[] = R"( + apiObject.addCustomFilter(%s'@@some-popup.html^$popup'); -+ chrome.test.sendScriptResult(''); ++ chrome.test.sendScriptResult(0); + )"; + -+ const GURL test_url = -+ embedded_test_server()->GetURL("example.com", "/test.html"); ++ const GURL test_url = https_test_server_->GetURL( ++ "example.com", "/extensions/api_test/adblock_private/test.html"); + constexpr char kOpenPopupScript[] = + "document.getElementById('popup_id').click()"; + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ SetupApiObjectAndMethods(extension->id()); ++ SetupApiObjectAndMethods(GetExtensionId()); + -+ int initial_allowed_value = std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), read_allowed_stats_script(0))); -+ int initial_blocked_value = std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), read_blocked_stats_script(0))); ++ int initial_allowed_value = ++ ExecuteScriptIAndGetInt(GetExtensionId(), read_allowed_stats_script(0)); ++ int initial_blocked_value = ++ ExecuteScriptIAndGetInt(GetExtensionId(), read_blocked_stats_script(0)); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kBlockPopupScript, IsOldApi() ? "" : "'adblock', ")); + ExecuteScript(kOpenPopupScript); + ASSERT_EQ(initial_blocked_value + 1, -+ std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), -+ read_blocked_stats_script(initial_blocked_value + 1)))); ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), ++ read_blocked_stats_script(initial_blocked_value + 1))); + -+ ExecuteScriptInBackgroundPage( -+ extension->id(), ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), + base::StringPrintf(kAllowPopupScript, IsOldApi() ? "" : "'adblock', ")); + ExecuteScript(kOpenPopupScript); + ASSERT_EQ(initial_allowed_value + 1, -+ std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), -+ read_allowed_stats_script(initial_allowed_value + 1)))); ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), ++ read_allowed_stats_script(initial_allowed_value + 1))); + ASSERT_EQ(initial_blocked_value + 1, -+ std::stoi(ExecuteScriptInBackgroundPage( -+ extension->id(), -+ read_blocked_stats_script(initial_blocked_value + 1)))); ++ ExecuteScriptIAndGetInt( ++ GetExtensionId(), ++ read_blocked_stats_script(initial_blocked_value + 1))); +} + +INSTANTIATE_TEST_SUITE_P( + All, + AdblockPrivateApiBackgroundPageTest, -+ testing::Combine(testing::Values(EyeoExtensionApi::Old, -+ EyeoExtensionApi::New), -+ testing::Values(Mode::Normal, Mode::Incognito))); ++ testing::Combine( ++ testing::Values( ++ AdblockPrivateApiBackgroundPageTest::EyeoExtensionApi::Old, ++ AdblockPrivateApiBackgroundPageTest::EyeoExtensionApi::New), ++ testing::Values(AdblockPrivateApiBackgroundPageTest::Mode::Normal, ++ AdblockPrivateApiBackgroundPageTest::Mode::Incognito))); + -+class AdblockPrivateApiBackgroundPageTestWithRedirect -+ : public AdblockPrivateApiBackgroundPageTest { -+ public: -+ AdblockPrivateApiBackgroundPageTestWithRedirect() {} -+ ~AdblockPrivateApiBackgroundPageTestWithRedirect() override = default; -+ AdblockPrivateApiBackgroundPageTestWithRedirect( -+ const AdblockPrivateApiBackgroundPageTestWithRedirect&) = delete; -+ AdblockPrivateApiBackgroundPageTestWithRedirect& operator=( -+ const AdblockPrivateApiBackgroundPageTestWithRedirect&) = delete; ++} // namespace extensions +diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.cc b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.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 . + -+ protected: -+ void SetUpOnMainThread() override { -+ ExtensionApiTest::SetUpOnMainThread(); ++#include "chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h" + -+ // Map domains to localhost. Add redirect handler. -+ host_resolver()->AddRule(before_redirect_domain, "127.0.0.1"); -+ host_resolver()->AddRule(after_redirect_domain, "127.0.0.1"); -+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating( -+ &AdblockPrivateApiBackgroundPageTestWithRedirect::RequestHandler, -+ base::Unretained(this))); -+ ASSERT_TRUE(StartEmbeddedTestServer()); -+ adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser()->profile()) -+ ->GetAdblockFilteringConfiguration() -+ ->RemoveCustomFilter(adblock::kAllowlistEverythingFilter); -+ } ++#include "chrome/browser/adblock/adblock_chrome_content_browser_client.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.h" ++#include "chrome/common/chrome_switches.h" ++#include "chrome/common/extensions/api/adblock_private.h" ++#include "chrome/common/extensions/api/tabs.h" ++#include "components/adblock/content/browser/factories/adblock_request_throttle_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/net/adblock_request_throttle.h" ++#include "extensions/common/switches.h" + -+ std::unique_ptr RequestHandler( -+ const net::test_server::HttpRequest& request) { -+ if (base::EndsWith(request.relative_url, before_redirect_domain, -+ base::CompareCase::SENSITIVE)) { -+ std::unique_ptr http_response( -+ new net::test_server::BasicHttpResponse()); -+ http_response->set_code(net::HTTP_FOUND); -+ http_response->set_content("Redirecting..."); -+ http_response->set_content_type("text/plain"); -+ http_response->AddCustomHeader( -+ "Location", "http://" + after_redirect_domain + ":" + -+ base::NumberToString(embedded_test_server()->port()) + -+ request.relative_url.substr( -+ 0, request.relative_url.size() - -+ before_redirect_domain.size() - 1)); -+ return std::move(http_response); -+ } -+ if (base::EndsWith(request.relative_url, subresource_with_redirect, -+ base::CompareCase::SENSITIVE)) { -+ auto http_response = -+ std::make_unique(); -+ http_response->set_code(net::HTTP_OK); -+ // Create a sub resource url which causes redirection -+ const GURL url = embedded_test_server()->GetURL( -+ before_redirect_domain, "/image.png?" + before_redirect_domain); -+ std::string body = ""; -+ http_response->set_content(body); -+ http_response->set_content_type("text/html"); -+ return std::move(http_response); -+ } ++namespace extensions { + -+ // Unhandled requests result in the Embedded test server sending a 404. -+ return nullptr; ++void AdblockPrivateApiTestBase::SetUpCommandLine( ++ base::CommandLine* command_line) { ++ ExtensionApiTest::SetUpCommandLine(command_line); ++ AllowTestExtension(command_line); ++ if (IsIncognito()) { ++ EnableIncognitoMode(command_line); + } ++} + -+ const std::string before_redirect_domain = "before-redirect.com"; -+ const std::string after_redirect_domain = "after-redirect.com"; -+ const std::string subresource_with_redirect = "subresource_with_redirect"; -+}; -+ -+// Test for DPD-1519 -+// This test verifies redirection of a main page -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTestWithRedirect, -+ PageAllowedEvents) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); ++void AdblockPrivateApiTestBase::SetUpOnMainThread() { ++ ExtensionApiTest::SetUpOnMainThread(); + -+ constexpr char kSetListenersScript[] = R"( -+ var testData = {}; -+ testData.pageAllowedBeforeRedirectCount = 0; -+ testData.pageAllowedAfterRedirectCount = 0; -+ apiObject.onPageAllowed.addListener(function(e) { -+ if (e.url.includes('before-redirect.com')) { -+ ++testData.pageAllowedBeforeRedirectCount; -+ } else if (e.url.includes('after-redirect.com')) { -+ ++testData.pageAllowedAfterRedirectCount; -+ } -+ }); -+ chrome.test.sendScriptResult(''); -+ )"; ++ // When any of that fails we need to update comment in adblock_private.idl ++ ASSERT_EQ(api::tabs::TAB_ID_NONE, -1); ++ ASSERT_EQ(SessionID::InvalidValue().id(), -1); + -+ constexpr char kReadCountersScript[] = R"( -+ chrome.test.sendScriptResult( -+ testData.pageAllowedBeforeRedirectCount + '-' + -+ testData.pageAllowedAfterRedirectCount); -+ )"; ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ browser()->profile()->GetOriginalProfile()) ++ ->GetFilteringConfiguration(adblock::kAdblockFilteringConfigurationName) ++ ->RemoveCustomFilter(adblock::kAllowlistEverythingFilter); ++ // Allow requests for filter lists to be "downloaded" immediately, otherwise ++ // the tests will hang for 30 seconds. ++ adblock::AdblockRequestThrottleFactory::GetForBrowserContext( ++ browser()->profile()->GetOriginalProfile()) ++ ->AllowRequestsAfter(base::Seconds(0)); + -+ constexpr char kAddAllowDomainScript[] = R"( -+ apiObject.addAllowedDomain(%s'after-redirect.com'); -+ chrome.test.sendScriptResult(''); -+ )"; ++ AdblockChromeContentBrowserClient::ForceAdblockProxyForTesting(); ++} + -+ constexpr char kRemoveAllowDomainScript[] = R"( -+ apiObject.removeAllowedDomain(%s'after-redirect.com'); -+ chrome.test.sendScriptResult(''); -+ )"; ++bool AdblockPrivateApiTestBase::IsIncognito() { ++ return false; ++} + -+ constexpr char kAddBlockDomainFilterScript[] = R"( -+ apiObject.addCustomFilter(%s'after-redirect.com'); -+ chrome.test.sendScriptResult(''); -+ )"; ++std::string AdblockPrivateApiTestBase::GetApiEndpoint() { ++ return "adblockPrivate"; ++} + -+ // Because RequestHandler handler sees just a 127.0.0.1 instead of -+ // a domain we are passing here source domain as a path in url. -+ const GURL test_url = embedded_test_server()->GetURL( -+ before_redirect_domain, "/" + before_redirect_domain); ++bool AdblockPrivateApiTestBase::RunTest(const std::string& subtest) { ++ std::string page_url = "main.html?subtest=" + subtest; ++ page_url += "&api=" + GetApiEndpoint(); ++ return RunExtensionTest("adblock_private", ++ {.extension_url = page_url.c_str()}, ++ {.allow_in_incognito = IsIncognito(), ++ .load_as_component = !IsIncognito()}); ++} + -+ SetupApiObjectAndMethods(extension->id()); ++bool AdblockPrivateApiTestBase::RunTestWithParams( ++ const std::string& subtest, ++ const std::map& params) { ++ if (params.empty()) { ++ return RunTest(subtest); ++ } ++ std::string subtest_with_params = subtest; ++ for (const auto& [key, value] : params) { ++ subtest_with_params += "&" + key + "=" + value; ++ } ++ return RunTest(subtest_with_params); ++} + -+ ExecuteScriptInBackgroundPage(extension->id(), kSetListenersScript); ++void AdblockPrivateApiTestBase::AllowTestExtension( ++ base::CommandLine* command_line) { ++ command_line->AppendSwitchASCII(switches::kAllowlistedExtensionID, ++ "hfkjbmnbjpodjjpikbpnphphoimfacom"); ++} + -+ // No filter -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("0-0", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); ++void AdblockPrivateApiTestBase::EnableIncognitoMode( ++ base::CommandLine* command_line) { ++ command_line->AppendSwitch(::switches::kIncognito); ++} + -+ // Just allow filter -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kAddAllowDomainScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("0-1", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); -+ // Just block filter -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kAddBlockDomainFilterScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kRemoveAllowDomainScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("0-1", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); ++} // namespace extensions +diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.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 . + -+ // Allow and block filter -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kAddAllowDomainScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("0-2", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); -+} ++#ifndef CHROME_BROWSER_EXTENSIONS_API_ADBLOCK_PRIVATE_ADBLOCK_PRIVATE_APITEST_BASE_H_ ++#define CHROME_BROWSER_EXTENSIONS_API_ADBLOCK_PRIVATE_ADBLOCK_PRIVATE_APITEST_BASE_H_ + -+// This test verifies redirection of a sub resource -+IN_PROC_BROWSER_TEST_P(AdblockPrivateApiBackgroundPageTestWithRedirect, -+ AdMatchedEvents) { -+ const Extension* extension = -+ LoadExtension(test_data_dir_.AppendASCII("adblock_private"), -+ {.allow_in_incognito = IsIncognito()}); -+ ASSERT_TRUE(extension); ++#include ++#include + -+ constexpr char kSetListenersScript[] = R"( -+ var testData = {}; -+ testData.adBlockedCount = 0; -+ testData.adAllowedCount = 0; -+ apiObject[onBlockedEvent].addListener(function(e) { -+ if (e.url.includes('http://after-redirect.com')) { -+ ++testData.adBlockedCount; -+ } -+ }); -+ apiObject[onAllowedEvent].addListener(function(e) { -+ if (e.url.includes('http://after-redirect.com')) { -+ ++testData.adAllowedCount; -+ } -+ }); -+ chrome.test.sendScriptResult(''); -+ )"; ++#include "base/command_line.h" ++#include "chrome/browser/extensions/extension_apitest.h" ++#include "content/public/test/browser_test.h" ++#include "testing/gmock/include/gmock/gmock.h" ++#include "testing/gtest/include/gtest/gtest.h" + -+ constexpr char kReadCountersScript[] = R"( -+ chrome.test.sendScriptResult( -+ testData.adBlockedCount + '-' + testData.adAllowedCount); -+ )"; ++namespace extensions { + -+ constexpr char kAddBlockingFilterScript[] = R"( -+ apiObject.addCustomFilter(%s'||after-redirect.com*/image.png'); -+ chrome.test.sendScriptResult(''); -+ )"; ++class AdblockPrivateApiTestBase : public ExtensionApiTest { ++ public: ++ enum class Mode { Normal, Incognito }; ++ enum class EyeoExtensionApi { Old, New }; + -+ constexpr char kAddAllowingFilterScript[] = R"( -+ apiObject.addCustomFilter(%s'@@||after-redirect.com*/image.png'); -+ chrome.test.sendScriptResult(''); -+ )"; ++ AdblockPrivateApiTestBase() {} ++ ~AdblockPrivateApiTestBase() override = default; ++ AdblockPrivateApiTestBase(const AdblockPrivateApiTestBase&) = delete; ++ AdblockPrivateApiTestBase& operator=(const AdblockPrivateApiTestBase&) = ++ delete; + -+ SetupApiObjectAndMethods(extension->id()); ++ void SetUpCommandLine(base::CommandLine* command_line) override; ++ void SetUpOnMainThread() override; + -+ ExecuteScriptInBackgroundPage(extension->id(), kSetListenersScript); ++ virtual bool IsIncognito(); + -+ const GURL test_url = embedded_test_server()->GetURL( -+ after_redirect_domain, "/" + subresource_with_redirect); ++ virtual std::string GetApiEndpoint(); + -+ // No filter -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("0-0", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); ++ virtual bool RunTest(const std::string& subtest); + -+ // Just block filter -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kAddBlockingFilterScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("1-0", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); ++ virtual bool RunTestWithParams( ++ const std::string& subtest, ++ const std::map& params); + -+ // Allow and block filter -+ ExecuteScriptInBackgroundPage( -+ extension->id(), base::StringPrintf(kAddAllowingFilterScript, -+ IsOldApi() ? "" : "'adblock', ")); -+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url)); -+ ASSERT_EQ("1-1", ExecuteScriptInBackgroundPage(extension->id(), -+ kReadCountersScript)); -+} ++ private: ++ void AllowTestExtension(base::CommandLine* command_line); + -+INSTANTIATE_TEST_SUITE_P( -+ All, -+ AdblockPrivateApiBackgroundPageTestWithRedirect, -+ testing::Combine(testing::Values(EyeoExtensionApi::Old, -+ EyeoExtensionApi::New), -+ testing::Values(Mode::Normal, Mode::Incognito))); ++ void EnableIncognitoMode(base::CommandLine* command_line); ++}; + +} // namespace extensions -diff --git a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc ---- a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc -+++ b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc -@@ -1,6 +1,10 @@ - // Copyright 2022 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 "chrome/browser/extensions/api/cookies/cookies_api.h" - #include "chrome/browser/extensions/api/notifications/extension_notification_display_helper_factory.h" -@@ -14,12 +18,14 @@ - #include "build/build_config.h" - #include "build/chromeos_buildflags.h" - #include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" -+#include "chrome/browser/extensions/api/adblock_private/adblock_private_api.h" - #include "chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h" - #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h" - #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h" - #include "chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.h" - #include "chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h" - #include "chrome/browser/extensions/api/developer_private/developer_private_api.h" -+#include "chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h" - #include "chrome/browser/extensions/api/font_settings/font_settings_api.h" - #include "chrome/browser/extensions/api/history/history_api.h" - #include "chrome/browser/extensions/api/identity/identity_api.h" -@@ -76,6 +82,7 @@ void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() { - - #if BUILDFLAG(ENABLE_EXTENSIONS) - extensions::ActivityLogAPI::GetFactoryInstance(); -+ extensions::AdblockPrivateAPI::GetFactoryInstance(); - extensions::AutofillPrivateEventRouterFactory::GetInstance(); - extensions::BluetoothLowEnergyAPI::GetFactoryInstance(); - extensions::BookmarksAPI::GetFactoryInstance(); -@@ -87,6 +94,7 @@ void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() { - #if BUILDFLAG(IS_CHROMEOS) - extensions::DocumentScanAPIHandler::GetFactoryInstance(); - #endif -+ extensions::EyeoFilteringPrivateAPI::GetFactoryInstance(); - extensions::FontSettingsAPI::GetFactoryInstance(); - extensions::HistoryAPI::GetFactoryInstance(); - extensions::IdentityAPI::GetFactoryInstance(); -diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc ++ ++#endif // CHROME_BROWSER_EXTENSIONS_API_ADBLOCK_PRIVATE_ADBLOCK_PRIVATE_APITEST_BASE_H_ +diff --git a/chrome/browser/extensions/api/adblock_private/adblock_private_filter_lists_with_http_server_apitest.cc b/chrome/browser/extensions/api/adblock_private/adblock_private_filter_lists_with_http_server_apitest.cc new file mode 100644 --- /dev/null -+++ b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc -@@ -0,0 +1,772 @@ ++++ b/chrome/browser/extensions/api/adblock_private/adblock_private_filter_lists_with_http_server_apitest.cc +@@ -0,0 +1,188 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -2169,20 +1951,258 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+#include "chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h" ++#include + -+#include "base/containers/flat_map.h" -+#include "base/logging.h" -+#include "base/no_destructor.h" -+#include "base/time/time_to_iso8601.h" -+#include "base/values.h" -+#include "chrome/browser/adblock/resource_classification_runner_factory.h" -+#include "chrome/browser/adblock/session_stats_factory.h" -+#include "chrome/browser/adblock/subscription_service_factory.h" -+#include "chrome/browser/extensions/extension_tab_util.h" ++#include "chrome/browser/extensions/api/adblock_private/adblock_private_apitest_base.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.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/subscription/subscription_config.h" ++#include "components/adblock/core/subscription/subscription_service.h" ++#include "net/dns/mock_host_resolver.h" ++#include "net/test/embedded_test_server/embedded_test_server.h" ++#include "net/test/embedded_test_server/http_request.h" ++#include "net/test/embedded_test_server/http_response.h" ++ ++namespace extensions { ++ ++/** ++ * Extension tests which require intercepting and returning content ++ * for filter lists download requests. ++ */ ++class AdblockPrivateApiFilterListWithHttpServer ++ : public AdblockPrivateApiTestBase, ++ public testing::WithParamInterface< ++ std::tuple> { ++ public: ++ AdblockPrivateApiFilterListWithHttpServer() ++ : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { ++ const auto testing_interval = base::Seconds(1); ++ adblock::SubscriptionServiceFactory::SetUpdateCheckIntervalForTesting( ++ testing_interval); ++ https_server_.RegisterRequestHandler(base::BindRepeating( ++ &AdblockPrivateApiFilterListWithHttpServer::RequestHandler, ++ base::Unretained(this))); ++ net::EmbeddedTestServer::ServerCertificateConfig cert_config; ++ cert_config.dns_names = {kEyeoFilterListHost}; ++ https_server_.SetSSLConfig(cert_config); ++ EXPECT_TRUE(https_server_.Start()); ++ adblock::SetFilterListServerPortForTesting(https_server_.port()); ++ geolocated_list_1_ = ++ base::StringPrintf("https://%s:%d/easylistpolish.txt", ++ kEyeoFilterListHost, https_server_.port()); ++ geolocated_list_2_ = ++ base::StringPrintf("https://%s:%d/easylistgermany.txt", ++ kEyeoFilterListHost, https_server_.port()); ++ } ++ ++ ~AdblockPrivateApiFilterListWithHttpServer() override = default; ++ AdblockPrivateApiFilterListWithHttpServer( ++ const AdblockPrivateApiFilterListWithHttpServer&) = delete; ++ AdblockPrivateApiFilterListWithHttpServer& operator=( ++ const AdblockPrivateApiFilterListWithHttpServer&) = delete; ++ ++ bool IsIncognito() override { ++ return std::get<1>(GetParam()) == ++ AdblockPrivateApiTestBase::Mode::Incognito; ++ } ++ ++ std::string GetApiEndpoint() override { ++ return std::get<0>(GetParam()) == ++ AdblockPrivateApiTestBase::EyeoExtensionApi::Old ++ ? "adblockPrivate" ++ : "eyeoFilteringPrivate"; ++ } ++ ++ std::unique_ptr RequestHandler( ++ const net::test_server::HttpRequest& request) { ++ if (base::StartsWith(request.relative_url, "/recommendations.json")) { ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ http_response->set_code(net::HTTP_OK); ++ auto payload = base::StringPrintf( ++ R"( ++ [{"url": "%s"}, {"url": "%s"}] ++ )", ++ geolocated_list_1_.c_str(), geolocated_list_2_.c_str()); ++ http_response->set_content(payload); ++ http_response->set_content_type("text/plain"); ++ return std::move(http_response); ++ } else if (base::StartsWith(request.relative_url, "/easylistpolish.txt") || ++ base::StartsWith(request.relative_url, "/easylistgermany.txt") || ++ base::StartsWith(request.relative_url, "/easylist.txt") || ++ base::StartsWith(request.relative_url, "/exceptionrules.txt") || ++ base::StartsWith(request.relative_url, ++ "/abp-filters-anti-cv.txt")) { ++ std::unique_ptr http_response( ++ new net::test_server::BasicHttpResponse); ++ http_response->set_code(net::HTTP_OK); ++ auto filename = request.GetURL().path(); ++ auto filter_list_header = ++ base::StringPrintf("[Adblock Plus 2.0]\n! Version: %zu\n! Title: %s", ++ filename.length(), filename.c_str()); ++ http_response->set_content(filter_list_header); ++ http_response->set_content_type("text/plain"); ++ 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; ++ } ++ ++ void SetUpOnMainThread() override { ++ AdblockPrivateApiTestBase::SetUpOnMainThread(); ++ host_resolver()->AddRule(kEyeoFilterListHost, "127.0.0.1"); ++ } ++ ++ void WaitForGeolocatedLists() { ++ std::vector subscriptions = {GURL(geolocated_list_1_), ++ GURL(geolocated_list_2_)}; ++ WaitForLists(subscriptions); ++ } ++ ++ void WaitForDefaultLists() { ++ std::vector subscriptions = { ++ GURL(base::StringPrintf("https://%s:%d/easylist.txt", ++ kEyeoFilterListHost, https_server_.port())), ++ GURL(base::StringPrintf("https://%s:%d/exceptionrules.txt", ++ kEyeoFilterListHost, https_server_.port())), ++ GURL(base::StringPrintf("https://%s:%d/abp-filters-anti-cv.txt", ++ kEyeoFilterListHost, https_server_.port()))}; ++ WaitForLists(subscriptions); ++ } ++ ++ protected: ++ net::EmbeddedTestServer https_server_; ++ std::string geolocated_list_1_; ++ std::string geolocated_list_2_; ++ ++ private: ++ void WaitForLists(const std::vector& subscriptions) { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ browser()->profile()->GetOriginalProfile()); ++ auto waiter = adblock::SubscriptionInstalledWaiter(subscription_service); ++ waiter.WaitUntilSubscriptionsInstalled(std::move(subscriptions)); ++ } ++ static constexpr char kEyeoFilterListHost[] = ++ "easylist-downloads.adblockplus.org"; ++}; ++ ++IN_PROC_BROWSER_TEST_P(AdblockPrivateApiFilterListWithHttpServer, ++ InstalledSubscriptionsDataSchema) { ++ WaitForDefaultLists(); ++ EXPECT_TRUE(RunTest("installedSubscriptionsDataSchema")) << message_; ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPrivateApiFilterListWithHttpServer, ++ InstalledSubscriptionsDataSchemaConfigDisabled) { ++ EXPECT_TRUE(RunTestWithParams("installedSubscriptionsDataSchema", ++ {{"disabled", "true"}})) ++ << message_; ++} ++ ++IN_PROC_BROWSER_TEST_P(AdblockPrivateApiFilterListWithHttpServer, ++ GeolocationDiabledHidesFilterLists) { ++ WaitForGeolocatedLists(); ++ EXPECT_TRUE(RunTestWithParams("disableGeolocation", ++ {{"geolocated_list_1", geolocated_list_1_}, ++ {"geolocated_list_2", geolocated_list_2_}})) ++ << message_; ++} ++ ++INSTANTIATE_TEST_SUITE_P( ++ , ++ AdblockPrivateApiFilterListWithHttpServer, ++ testing::Combine( ++ testing::Values(AdblockPrivateApiTestBase::EyeoExtensionApi::Old, ++ AdblockPrivateApiTestBase::EyeoExtensionApi::New), ++ testing::Values(AdblockPrivateApiTestBase::Mode::Normal, ++ AdblockPrivateApiTestBase::Mode::Incognito))); ++ ++} // namespace extensions +diff --git a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc +--- a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc ++++ b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc +@@ -1,6 +1,10 @@ + // Copyright 2022 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 "chrome/browser/extensions/api/cookies/cookies_api.h" + #include "chrome/browser/extensions/api/developer_private/developer_private_api.h" +@@ -15,11 +19,13 @@ + #include "build/build_config.h" + #include "build/chromeos_buildflags.h" + #include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h" ++#include "chrome/browser/extensions/api/adblock_private/adblock_private_api.h" + #include "chrome/browser/extensions/api/autofill_private/autofill_private_event_router_factory.h" + #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h" + #include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h" + #include "chrome/browser/extensions/api/bookmarks/bookmarks_api_watcher.h" // nogncheck + #include "chrome/browser/extensions/api/braille_display_private/braille_display_private_api.h" ++#include "chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h" + #include "chrome/browser/extensions/api/font_settings/font_settings_api.h" + #include "chrome/browser/extensions/api/history/history_api.h" + #include "chrome/browser/extensions/api/identity/identity_api.h" +@@ -83,6 +89,7 @@ void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() { + + #if BUILDFLAG(ENABLE_EXTENSIONS) + extensions::ActivityLogAPI::GetFactoryInstance(); ++ extensions::AdblockPrivateAPI::GetFactoryInstance(); + extensions::AutofillPrivateEventRouterFactory::GetInstance(); + extensions::BluetoothLowEnergyAPI::GetFactoryInstance(); + extensions::BookmarksAPI::GetFactoryInstance(); +@@ -93,6 +100,7 @@ void EnsureApiBrowserContextKeyedServiceFactoriesBuilt() { + #if BUILDFLAG(IS_CHROMEOS) + extensions::DocumentScanAPIHandler::GetFactoryInstance(); + #endif ++ extensions::EyeoFilteringPrivateAPI::GetFactoryInstance(); + extensions::FontSettingsAPI::GetFactoryInstance(); + extensions::HistoryAPI::GetFactoryInstance(); + extensions::IdentityAPI::GetFactoryInstance(); +diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc +new file mode 100644 +--- /dev/null ++++ b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.cc +@@ -0,0 +1,819 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . ++ ++#include "chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h" ++ ++#include "base/containers/flat_map.h" ++#include "base/i18n/time_formatting.h" ++#include "base/logging.h" ++#include "base/no_destructor.h" ++#include "base/values.h" ++#include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/api/tabs.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/subscription_service_factory.h" +#include "components/adblock/content/browser/resource_classification_runner.h" ++#include "components/adblock/core/common/adblock_prefs.h" +#include "components/adblock/core/common/adblock_utils.h" +#include "components/adblock/core/common/content_type.h" +#include "components/adblock/core/configuration/filtering_configuration.h" @@ -2210,7 +2230,7 @@ new file mode 100644 + subscription_service->GetInstalledFilteringConfigurations(); + auto configuration_it = + std::ranges::find(installed_configurations, config_name, -+ &adblock::FilteringConfiguration::GetName); ++ &adblock::FilteringConfiguration::GetName); + return configuration_it != installed_configurations.end() ? *configuration_it + : nullptr; +} @@ -2256,14 +2276,15 @@ new file mode 100644 + switch (state) { + case State::Installed: + return "Installed"; -+ case State::Installing: -+ return "Installing"; ++ case State::AutoInstalled: ++ return "AutoInstalled"; + case State::Preloaded: + return "Preloaded"; ++ case State::Installing: ++ return "Installing"; + case State::Unknown: + return "Unknown"; + } -+ NOTREACHED(); + return ""; +} + @@ -2278,13 +2299,21 @@ new file mode 100644 + js_sub.current_version = sub->GetCurrentVersion(); + js_sub.installation_state = + SubscriptionInstallationStateToString(sub->GetInstallationState()); ++ const auto installation_time = sub->GetInstallationTime(); + js_sub.last_installation_time = -+ base::TimeToISO8601(sub->GetInstallationTime()); ++ installation_time.is_null() ++ ? "" ++ : base::TimeFormatAsIso8601(sub->GetInstallationTime()); + result.emplace_back(std::move(js_sub)); + } + return result; +} + ++content::BrowserContext* GetOriginalBrowserContext( ++ content::BrowserContext* browser_context) { ++ return Profile::FromBrowserContext(browser_context)->GetOriginalProfile(); ++} ++ +} // namespace + +class EyeoFilteringPrivateAPI::EyeoFilteringAPIEventRouter @@ -2293,7 +2322,7 @@ new file mode 100644 + public adblock::FilteringConfiguration::Observer { + public: + explicit EyeoFilteringAPIEventRouter(content::BrowserContext* context) -+ : context_(context) { ++ : context_(GetOriginalBrowserContext(context)) { + adblock::ResourceClassificationRunnerFactory::GetForBrowserContext(context_) + ->AddObserver(this); + auto* subscription_service = @@ -2318,13 +2347,13 @@ new file mode 100644 + } + + // adblock::ResourceClassificationRunner::Observer: -+ void OnAdMatched(const GURL& url, -+ adblock::FilterMatchResult match_result, -+ const std::vector& parent_frame_urls, -+ adblock::ContentType content_type, -+ content::RenderFrameHost* render_frame_host, -+ const GURL& subscription, -+ const std::string& configuration_name) override { ++ void OnRequestMatched(const GURL& url, ++ adblock::FilterMatchResult match_result, ++ const std::vector& parent_frame_urls, ++ adblock::ContentType content_type, ++ content::RenderFrameHost* render_frame_host, ++ const GURL& subscription, ++ const std::string& configuration_name) override { + std::unique_ptr event; + api::eyeo_filtering_private::RequestInfo info = CreateRequestInfoObject( + url, subscription, configuration_name, render_frame_host); @@ -2550,13 +2579,13 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateCreateConfigurationFunction::Run() { -+ std::optional ++ absl::optional + params(api::eyeo_filtering_private::CreateConfiguration::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params); + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()); ++ GetOriginalBrowserContext(browser_context())); + const auto installed_configurations = + subscription_service->GetInstalledFilteringConfigurations(); + auto configuration_it = @@ -2565,8 +2594,8 @@ new file mode 100644 + if (configuration_it == installed_configurations.end()) { + auto new_filtering_configuration = + std::make_unique( -+ Profile::FromBrowserContext(browser_context()) -+ ->GetOriginalProfile() ++ Profile::FromBrowserContext( ++ GetOriginalBrowserContext(browser_context())) + ->GetPrefs(), + params->config_name); + subscription_service->InstallFilteringConfiguration( @@ -2583,21 +2612,21 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateRemoveConfigurationFunction::Run() { -+ std::optional ++ absl::optional + params(api::eyeo_filtering_private::RemoveConfiguration::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params); + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()); ++ GetOriginalBrowserContext(browser_context())); + const auto installed_configurations = + subscription_service->GetInstalledFilteringConfigurations(); + auto configuration_it = -+ std::ranges::find(installed_configurations, params->config_name, ++ std::ranges::find(installed_configurations, params->config_name, + &adblock::FilteringConfiguration::GetName); + if (configuration_it != installed_configurations.end()) { + subscription_service->UninstallFilteringConfiguration( -+ (*configuration_it)->GetName()); ++ std::string((*configuration_it)->GetName())); + } + return RespondNow(NoArguments()); +} @@ -2612,7 +2641,7 @@ new file mode 100644 +EyeoFilteringPrivateGetConfigurationsFunction::Run() { + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()); ++ GetOriginalBrowserContext(browser_context())); + const auto installed_configurations = + subscription_service->GetInstalledFilteringConfigurations(); + std::vector configurations; @@ -2626,6 +2655,45 @@ new file mode 100644 + std::move(configurations)))); +} + ++EyeoFilteringPrivateSetAutoInstallEnabledFunction:: ++ EyeoFilteringPrivateSetAutoInstallEnabledFunction() {} ++ ++EyeoFilteringPrivateSetAutoInstallEnabledFunction:: ++ ~EyeoFilteringPrivateSetAutoInstallEnabledFunction() {} ++ ++ExtensionFunction::ResponseAction ++EyeoFilteringPrivateSetAutoInstallEnabledFunction::Run() { ++ absl::optional ++ params = ++ api::eyeo_filtering_private::SetAutoInstallEnabled::Params::Create( ++ args()); ++ EXTENSION_FUNCTION_VALIDATE(params); ++ ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ subscription_service->SetAutoInstallEnabled(params->enabled); ++ ++ return RespondNow(NoArguments()); ++} ++ ++EyeoFilteringPrivateIsAutoInstallEnabledFunction:: ++ EyeoFilteringPrivateIsAutoInstallEnabledFunction() {} ++ ++EyeoFilteringPrivateIsAutoInstallEnabledFunction:: ++ ~EyeoFilteringPrivateIsAutoInstallEnabledFunction() {} ++ ++ExtensionFunction::ResponseAction ++EyeoFilteringPrivateIsAutoInstallEnabledFunction::Run() { ++ auto* subscription_service = ++ adblock::SubscriptionServiceFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ ++ return RespondNow(ArgumentList( ++ api::eyeo_filtering_private::IsAutoInstallEnabled::Results::Create( ++ subscription_service->IsAutoInstallEnabled()))); ++} ++ +EyeoFilteringPrivateSetEnabledFunction:: + EyeoFilteringPrivateSetEnabledFunction() = default; + @@ -2634,11 +2702,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateSetEnabledFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::SetEnabled::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2654,11 +2722,11 @@ new file mode 100644 + ~EyeoFilteringPrivateIsEnabledFunction() = default; + +ExtensionFunction::ResponseAction EyeoFilteringPrivateIsEnabledFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::IsEnabled::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2681,20 +2749,19 @@ new file mode 100644 + adblock::AcceptableAdsUrl().spec()))); +} + -+EyeoFilteringPrivateSubscribeToFilterListFunction:: -+ EyeoFilteringPrivateSubscribeToFilterListFunction() = default; ++EyeoFilteringPrivateAddFilterListFunction:: ++ EyeoFilteringPrivateAddFilterListFunction() = default; + -+EyeoFilteringPrivateSubscribeToFilterListFunction:: -+ ~EyeoFilteringPrivateSubscribeToFilterListFunction() = default; ++EyeoFilteringPrivateAddFilterListFunction:: ++ ~EyeoFilteringPrivateAddFilterListFunction() = default; + +ExtensionFunction::ResponseAction -+EyeoFilteringPrivateSubscribeToFilterListFunction::Run() { -+ std::optional -+ params(api::eyeo_filtering_private::SubscribeToFilterList::Params::Create( -+ args())); ++EyeoFilteringPrivateAddFilterListFunction::Run() { ++ absl::optional params( ++ api::eyeo_filtering_private::AddFilterList::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2709,20 +2776,19 @@ new file mode 100644 + return RespondNow(NoArguments()); +} + -+EyeoFilteringPrivateUnsubscribeFromFilterListFunction:: -+ EyeoFilteringPrivateUnsubscribeFromFilterListFunction() = default; ++EyeoFilteringPrivateRemoveFilterListFunction:: ++ EyeoFilteringPrivateRemoveFilterListFunction() = default; + -+EyeoFilteringPrivateUnsubscribeFromFilterListFunction:: -+ ~EyeoFilteringPrivateUnsubscribeFromFilterListFunction() = default; ++EyeoFilteringPrivateRemoveFilterListFunction:: ++ ~EyeoFilteringPrivateRemoveFilterListFunction() = default; + +ExtensionFunction::ResponseAction -+EyeoFilteringPrivateUnsubscribeFromFilterListFunction::Run() { -+ std::optional -+ params(api::eyeo_filtering_private::UnsubscribeFromFilterList::Params:: -+ Create(args())); ++EyeoFilteringPrivateRemoveFilterListFunction::Run() { ++ absl::optional params( ++ api::eyeo_filtering_private::RemoveFilterList::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2745,18 +2811,18 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateGetFilterListsFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::GetFilterLists::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); + } + auto* subscription_service = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser_context()); ++ GetOriginalBrowserContext(browser_context())); + return RespondNow( + ArgumentList(api::eyeo_filtering_private::GetFilterLists::Results::Create( + CopySubscriptions( @@ -2771,11 +2837,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateAddAllowedDomainFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::AddAllowedDomain::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2792,12 +2858,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateRemoveAllowedDomainFunction::Run() { -+ std::optional ++ absl::optional + params(api::eyeo_filtering_private::RemoveAllowedDomain::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2814,11 +2880,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateGetAllowedDomainsFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::GetAllowedDomains::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2836,11 +2902,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateAddCustomFilterFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::AddCustomFilter::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2857,12 +2923,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateRemoveCustomFilterFunction::Run() { -+ std::optional ++ absl::optional + params(api::eyeo_filtering_private::RemoveCustomFilter::Params::Create( + args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2873,11 +2939,11 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateGetCustomFiltersFunction::Run() { -+ std::optional params( ++ absl::optional params( + api::eyeo_filtering_private::GetCustomFilters::Params::Create(args())); + EXTENSION_FUNCTION_VALIDATE(params); -+ auto* configuration = -+ FindFilteringConfiguration(browser_context(), params->configuration); ++ auto* configuration = FindFilteringConfiguration( ++ GetOriginalBrowserContext(browser_context()), params->configuration); + if (!configuration) { + return RespondNow(Error(base::StringPrintf(kConfigurationMissing, + params->configuration.c_str()))); @@ -2901,12 +2967,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateGetSessionAllowedRequestsCountFunction::Run() { -+ auto* session_stats = -+ adblock::SessionStatsFactory::GetForBrowserContext(browser_context()); -+ return RespondNow(ArgumentList( -+ api::eyeo_filtering_private::GetSessionAllowedRequestsCount::Results:: -+ Create( -+ CopySessionsStats(session_stats->GetSessionAllowedAdsCount())))); ++ auto* session_stats = adblock::SessionStatsFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ return RespondNow( ++ ArgumentList(api::eyeo_filtering_private::GetSessionAllowedRequestsCount:: ++ Results::Create(CopySessionsStats( ++ session_stats->GetSessionAllowedResourcesCount())))); +} + +EyeoFilteringPrivateGetSessionBlockedRequestsCountFunction:: @@ -2917,12 +2983,12 @@ new file mode 100644 + +ExtensionFunction::ResponseAction +EyeoFilteringPrivateGetSessionBlockedRequestsCountFunction::Run() { -+ auto* session_stats = -+ adblock::SessionStatsFactory::GetForBrowserContext(browser_context()); -+ return RespondNow(ArgumentList( -+ api::eyeo_filtering_private::GetSessionBlockedRequestsCount::Results:: -+ Create( -+ CopySessionsStats(session_stats->GetSessionBlockedAdsCount())))); ++ auto* session_stats = adblock::SessionStatsFactory::GetForBrowserContext( ++ GetOriginalBrowserContext(browser_context())); ++ return RespondNow( ++ ArgumentList(api::eyeo_filtering_private::GetSessionBlockedRequestsCount:: ++ Results::Create(CopySessionsStats( ++ session_stats->GetSessionBlockedResourcesCount())))); +} +} // namespace api +} // namespace extensions @@ -2930,7 +2996,7 @@ diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering new file mode 100644 --- /dev/null +++ b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_api.h -@@ -0,0 +1,360 @@ +@@ -0,0 +1,392 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -3042,6 +3108,42 @@ new file mode 100644 + const EyeoFilteringPrivateGetConfigurationsFunction&) = delete; +}; + ++class EyeoFilteringPrivateSetAutoInstallEnabledFunction ++ : public ExtensionFunction { ++ public: ++ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.setAutoInstallEnabled", ++ UNKNOWN) ++ EyeoFilteringPrivateSetAutoInstallEnabledFunction(); ++ ++ private: ++ ~EyeoFilteringPrivateSetAutoInstallEnabledFunction() override; ++ ++ ResponseAction Run() override; ++ ++ EyeoFilteringPrivateSetAutoInstallEnabledFunction( ++ const EyeoFilteringPrivateSetAutoInstallEnabledFunction&) = delete; ++ EyeoFilteringPrivateSetAutoInstallEnabledFunction& operator=( ++ const EyeoFilteringPrivateSetAutoInstallEnabledFunction&) = delete; ++}; ++ ++class EyeoFilteringPrivateIsAutoInstallEnabledFunction ++ : public ExtensionFunction { ++ public: ++ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.isAutoInstallEnabled", ++ UNKNOWN) ++ EyeoFilteringPrivateIsAutoInstallEnabledFunction(); ++ ++ private: ++ ~EyeoFilteringPrivateIsAutoInstallEnabledFunction() override; ++ ++ ResponseAction Run() override; ++ ++ EyeoFilteringPrivateIsAutoInstallEnabledFunction( ++ const EyeoFilteringPrivateIsAutoInstallEnabledFunction&) = delete; ++ EyeoFilteringPrivateIsAutoInstallEnabledFunction& operator=( ++ const EyeoFilteringPrivateIsAutoInstallEnabledFunction&) = delete; ++}; ++ +class EyeoFilteringPrivateSetEnabledFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.setEnabled", UNKNOWN) @@ -3093,40 +3195,36 @@ new file mode 100644 + const EyeoFilteringPrivateGetAcceptableAdsUrlFunction&) = delete; +}; + -+class EyeoFilteringPrivateSubscribeToFilterListFunction -+ : public ExtensionFunction { ++class EyeoFilteringPrivateAddFilterListFunction : public ExtensionFunction { + public: -+ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.subscribeToFilterList", -+ UNKNOWN) -+ EyeoFilteringPrivateSubscribeToFilterListFunction(); ++ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.addFilterList", UNKNOWN) ++ EyeoFilteringPrivateAddFilterListFunction(); + + private: -+ ~EyeoFilteringPrivateSubscribeToFilterListFunction() override; ++ ~EyeoFilteringPrivateAddFilterListFunction() override; + + ResponseAction Run() override; + -+ EyeoFilteringPrivateSubscribeToFilterListFunction( -+ const EyeoFilteringPrivateSubscribeToFilterListFunction&) = delete; -+ EyeoFilteringPrivateSubscribeToFilterListFunction& operator=( -+ const EyeoFilteringPrivateSubscribeToFilterListFunction&) = delete; ++ EyeoFilteringPrivateAddFilterListFunction( ++ const EyeoFilteringPrivateAddFilterListFunction&) = delete; ++ EyeoFilteringPrivateAddFilterListFunction& operator=( ++ const EyeoFilteringPrivateAddFilterListFunction&) = delete; +}; + -+class EyeoFilteringPrivateUnsubscribeFromFilterListFunction -+ : public ExtensionFunction { ++class EyeoFilteringPrivateRemoveFilterListFunction : public ExtensionFunction { + public: -+ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.unsubscribeFromFilterList", -+ UNKNOWN) -+ EyeoFilteringPrivateUnsubscribeFromFilterListFunction(); ++ DECLARE_EXTENSION_FUNCTION("eyeoFilteringPrivate.removeFilterList", UNKNOWN) ++ EyeoFilteringPrivateRemoveFilterListFunction(); + + private: -+ ~EyeoFilteringPrivateUnsubscribeFromFilterListFunction() override; ++ ~EyeoFilteringPrivateRemoveFilterListFunction() override; + + ResponseAction Run() override; + -+ EyeoFilteringPrivateUnsubscribeFromFilterListFunction( -+ const EyeoFilteringPrivateUnsubscribeFromFilterListFunction&) = delete; -+ EyeoFilteringPrivateUnsubscribeFromFilterListFunction& operator=( -+ const EyeoFilteringPrivateUnsubscribeFromFilterListFunction&) = delete; ++ EyeoFilteringPrivateRemoveFilterListFunction( ++ const EyeoFilteringPrivateRemoveFilterListFunction&) = delete; ++ EyeoFilteringPrivateRemoveFilterListFunction& operator=( ++ const EyeoFilteringPrivateRemoveFilterListFunction&) = delete; +}; + +class EyeoFilteringPrivateGetFilterListsFunction : public ExtensionFunction { @@ -3295,7 +3393,7 @@ diff --git a/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering new file mode 100644 --- /dev/null +++ b/chrome/browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_apitest.cc -@@ -0,0 +1,161 @@ +@@ -0,0 +1,173 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -3311,15 +3409,17 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+#include "chrome/browser/adblock/adblock_content_browser_client.h" -+#include "chrome/browser/adblock/subscription_service_factory.h" ++#include "chrome/browser/adblock/adblock_chrome_content_browser_client.h" +#include "chrome/browser/extensions/extension_apitest.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/api/adblock_private.h" -+#include "components/adblock/core/adblock_controller.h" ++#include "components/adblock/content/browser/factories/subscription_service_factory.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 "extensions/common/switches.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + @@ -3327,6 +3427,15 @@ new file mode 100644 + +namespace { +enum class Mode { Normal, Incognito }; ++ ++void AllowTestExtension(base::CommandLine* command_line) { ++ command_line->AppendSwitchASCII(extensions::switches::kAllowlistedExtensionID, ++ "hjlolhdkflcejcekagdgahplimkppjml"); ++} ++ ++void EnableIncognitoMode(base::CommandLine* command_line) { ++ command_line->AppendSwitch(::switches::kIncognito); ++} +} // namespace + +class EyeoFilteringPrivateApiTest : public ExtensionApiTest, @@ -3340,8 +3449,9 @@ new file mode 100644 + + void SetUpCommandLine(base::CommandLine* command_line) override { + extensions::ExtensionApiTest::SetUpCommandLine(command_line); ++ AllowTestExtension(command_line); + if (IsIncognito()) { -+ command_line->AppendSwitch(switches::kIncognito); ++ EnableIncognitoMode(command_line); + } + } + @@ -3351,14 +3461,15 @@ new file mode 100644 + + auto* adblock_configuration = + adblock::SubscriptionServiceFactory::GetForBrowserContext( -+ browser()->profile()) -+ ->GetAdblockFilteringConfiguration(); ++ browser()->profile()->GetOriginalProfile()) ++ ->GetFilteringConfiguration( ++ adblock::kAdblockFilteringConfigurationName); + if (adblock_configuration) { + adblock_configuration->RemoveCustomFilter( + adblock::kAllowlistEverythingFilter); + } + -+ AdblockContentBrowserClient::ForceAdblockProxyForTesting(); ++ AdblockChromeContentBrowserClient::ForceAdblockProxyForTesting(); + } + + bool IsIncognito() { return GetParam() == Mode::Incognito; } @@ -3416,14 +3527,13 @@ new file mode 100644 +} + +IN_PROC_BROWSER_TEST_P(EyeoFilteringPrivateApiTest, -+ SubscribeToFilterListInCustomConfiguration) { -+ EXPECT_TRUE(RunTest("subscribeToFilterListInCustomConfiguration")) -+ << message_; ++ AddFilterListInCustomConfiguration) { ++ EXPECT_TRUE(RunTest("addFilterListInCustomConfiguration")) << message_; +} + +IN_PROC_BROWSER_TEST_P(EyeoFilteringPrivateApiTest, -+ SubscribeToFilterListInCustomConfigurationWithPromises) { -+ EXPECT_TRUE(RunTest("subscribeToFilterListInCustomConfigurationWithPromises")) ++ AddFilterListInCustomConfigurationWithPromises) { ++ EXPECT_TRUE(RunTest("addFilterListInCustomConfigurationWithPromises")) + << message_; +} + @@ -3452,54 +3562,11 @@ new file mode 100644 + EXPECT_TRUE(RunTest("customFiltersEvent")) << message_; +} + -+INSTANTIATE_TEST_SUITE_P(All, ++INSTANTIATE_TEST_SUITE_P(, + EyeoFilteringPrivateApiTest, + testing::Values(Mode::Normal, Mode::Incognito)); + +} // namespace extensions -diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc ---- a/chrome/browser/extensions/api/settings_private/prefs_util.cc -+++ b/chrome/browser/extensions/api/settings_private/prefs_util.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 "chrome/browser/extensions/api/settings_private/prefs_util.h" - - #include -@@ -32,6 +36,7 @@ - #include "chrome/browser/ui/toolbar/toolbar_pref_names.h" - #include "chrome/common/chrome_features.h" - #include "chrome/common/pref_names.h" -+#include "components/adblock/core/common/adblock_prefs.h" - #include "components/autofill/core/common/autofill_prefs.h" - #include "components/bookmarks/common/bookmark_pref_names.h" - #include "components/browsing_data/core/pref_names.h" -@@ -175,6 +180,20 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() { - } - s_allowlist = new PrefsUtil::TypedPrefMap(); - -+ // Adblock settings -+ (*s_allowlist)[adblock::common::prefs::kEnableAdblockLegacy] = -+ settings_api::PrefType::kBoolean; -+ (*s_allowlist)[adblock::common::prefs::kEnableAcceptableAdsLegacy] = -+ settings_api::PrefType::kBoolean; -+ (*s_allowlist)[adblock::common::prefs::kAdblockSubscriptionsLegacy] = -+ settings_api::PrefType::kList; -+ (*s_allowlist)[adblock::common::prefs::kAdblockCustomSubscriptionsLegacy] = -+ settings_api::PrefType::kList; -+ (*s_allowlist)[adblock::common::prefs::kAdblockAllowedDomainsLegacy] = -+ settings_api::PrefType::kList; -+ (*s_allowlist)[adblock::common::prefs::kAdblockCustomFiltersLegacy] = -+ settings_api::PrefType::kList; -+ - // Miscellaneous - (*s_allowlist)[::embedder_support::kAlternateErrorPagesEnabled] = - settings_api::PrefType::kBoolean; diff --git a/chrome/browser/extensions/extension_function_registration_test.cc b/chrome/browser/extensions/extension_function_registration_test.cc --- a/chrome/browser/extensions/extension_function_registration_test.cc +++ b/chrome/browser/extensions/extension_function_registration_test.cc @@ -3547,10 +3614,10 @@ diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/ext }, + "adblockPrivate": [{ + "dependencies": ["permission:adblockPrivate"], -+ "contexts": ["blessed_extension"] ++ "contexts": ["privileged_extension"] + }, { -+ "channel": "stable", + "contexts": ["webui"], ++ "dependencies": [], + "matches": [ + "chrome://settings/*" + ] @@ -3558,16 +3625,16 @@ diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/ext "app": { "blocklist": [ "2FC374607C2DF285634B67C64A2E356C607091C3", // Quickoffice -@@ -525,6 +539,14 @@ +@@ -526,6 +540,14 @@ "channel": "stable", "contexts": [] }, + "eyeoFilteringPrivate": [{ + "dependencies": ["permission:eyeoFilteringPrivate"], -+ "contexts": ["blessed_extension"] ++ "contexts": ["privileged_extension"] + }, { -+ "channel": "stable", + "contexts": ["webui"], ++ "dependencies": [], + "matches": ["chrome://settings/*"] + }], "fileBrowserHandler": { @@ -3587,33 +3654,39 @@ diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/com // This features file defines permissions for extension APIs implemented // under src/chrome. -@@ -62,6 +66,10 @@ +@@ -62,6 +66,13 @@ "extension" ] }, + "adblockPrivate": { + "channel": "stable", -+ "extension_types": ["extension", "platform_app"] ++ "extension_types": ["extension", "platform_app"], ++ "allowlist" : [ ++ "664F11343A17783FC7F6DC994BBC8AAF6823739C" // eyeo test extension ++ ] + }, "autofillPrivate": { "channel": "trunk", "extension_types": ["extension", "platform_app"], -@@ -172,6 +180,10 @@ - "extension", "legacy_packaged_app", "hosted_app", "platform_app" - ] +@@ -376,6 +387,13 @@ + "channel": "beta", + "command_line_switch": "extension-ai-data-collection" }, + "eyeoFilteringPrivate": { + "channel": "stable", -+ "extension_types": ["extension", "platform_app"] ++ "extension_types": ["extension", "platform_app"], ++ "allowlist" : [ ++ "664F11343A17783FC7F6DC994BBC8AAF6823739C" // eyeo test extension ++ ] + }, - "commandLinePrivate": { + "favicon": { "channel": "stable", - "extension_types": ["extension", "legacy_packaged_app", "platform_app"], + "extension_types": ["extension"] diff --git a/chrome/common/extensions/api/adblock_private.idl b/chrome/common/extensions/api/adblock_private.idl new file mode 100644 --- /dev/null +++ b/chrome/common/extensions/api/adblock_private.idl -@@ -0,0 +1,174 @@ +@@ -0,0 +1,180 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -3629,9 +3702,9 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+// DEPRECATED: Prefer eyeoFilteringPrivate. -+// adblockPrivate API. -+// This is a private API exposing ability to control AdblockPlus. ++// DEPRECATED: Prefer eyeoFilteringPrivate API. ++// ++// This is a private API exposing ability to control ad-filtering settings via adblockPrivate API. +// +// Devs: when modifying this file, ensure you update +// tools/typescript/definitions/adblock_private.d.ts as well. @@ -3662,6 +3735,7 @@ new file mode 100644 + DOMString current_version; + // Time of last successful installation or update, in ISO 8601 format. + // May be passed directly to the Date constructor. ++ // Empty for subscriptions that are not installed yet. + DOMString last_installation_time; +}; + @@ -3712,6 +3786,7 @@ new file mode 100644 +callback ListCallback = void(DOMString[] result); +callback SessionStatsCallback = void(SessionStatsEntry[] result); + ++[deprecated="Use eyeoFilteringPrivate methods instead."] +interface Functions { + // Allows to turn Adblock on or off. + static void setEnabled(boolean enabled); @@ -3721,6 +3796,10 @@ new file mode 100644 + static void setAcceptableAdsEnabled(boolean enabled); + // Returns whether Acceptable Ads is on. + static void isAcceptableAdsEnabled(StateCallback callback); ++ // Allows to turn autoinstalling geolocation recommendation on or off. ++ static void setAutoInstallEnabled(boolean enabled); ++ // Returns whether autoinstalling geolocation recommendation is on. ++ static void isAutoInstallEnabled(StateCallback callback); + // Gets the list of built-in subscriptions. + static void getBuiltInSubscriptions(BuiltInSubscriptionsCallback callback); + // Adds a domain ads should not be blocked on. @@ -3791,38 +3870,31 @@ new file mode 100644 diff --git a/chrome/common/extensions/api/api_sources.gni b/chrome/common/extensions/api/api_sources.gni --- a/chrome/common/extensions/api/api_sources.gni +++ b/chrome/common/extensions/api/api_sources.gni -@@ -1,6 +1,10 @@ +@@ -1,6 +1,9 @@ # Copyright 2018 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/chromeos/ui_mode.gni") import("//build/config/features.gni") -@@ -39,6 +43,7 @@ if (enable_extensions) { - schema_sources_ += [ - "accessibility_features.json", - "activity_log_private.json", + import("//chrome/common/features.gni") +@@ -90,6 +93,9 @@ if (enable_extensions) { + + "webstore_private.json", + "windows.json", ++ + "adblock_private.idl", - "experimental_ai_data.idl", - "autofill_private.idl", - "autotest_private.idl", -@@ -57,6 +62,7 @@ if (enable_extensions) { - "downloads_internal.idl", - "enterprise_hardware_platform.idl", - "enterprise_reporting_private.idl", + "eyeo_filtering_private.idl", - "font_settings.json", - "gcm.json", - "history.json", + ] + + if (!is_android) { diff --git a/chrome/common/extensions/api/eyeo_filtering_private.idl b/chrome/common/extensions/api/eyeo_filtering_private.idl new file mode 100644 --- /dev/null +++ b/chrome/common/extensions/api/eyeo_filtering_private.idl -@@ -0,0 +1,201 @@ +@@ -0,0 +1,207 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -3838,8 +3910,7 @@ new file mode 100644 +// You should have received a copy of the GNU General Public License +// along with eyeo Chromium SDK. If not, see . + -+// eyeoFilteringPrivate API. -+// This is a private API exposing control over resource filtering. ++// This is a private API exposing control over resource filtering via eyeoFilteringPrivate API. +// +// Devs: when modifying this file, ensure you update +// tools/typescript/definitions/eyeo_filtering_private.d.ts as well. @@ -3925,6 +3996,13 @@ new file mode 100644 + static void removeConfiguration(DOMString config_name); + // Returns a list of existing configurations. + [supportsPromises] static void getConfigurations(ListCallback callback); ++ // Returns whether autoinstalling geolocation recommendation is on. ++ [supportsPromises] static void isAutoInstallEnabled( ++ StateCallback callback); ++ // Allows to turn autoinstalling geolocation recommendation on or off. ++ [supportsPromises] static void setAutoInstallEnabled( ++ boolean enabled, ++ optional CompletionCallback status); + // Allows to turn filtering configuration on or off. + [supportsPromises] static void setEnabled( + DOMString configuration, @@ -3962,33 +4040,33 @@ new file mode 100644 + [supportsPromises] static void getCustomFilters( + DOMString configuration, + ListCallback callback); -+ // Compiles a list of currently installed subscriptions. Filter lists that -+ // are still being installed and don't yet participate in filtering also -+ // appear on this list and can be discerned by their installation_state. -+ [supportsPromises] static void getFilterLists( -+ DOMString configuration, -+ SubscriptionsCallback callback); + // Subscribes to a filter list. If subscription was already done and not -+ // removed later by "unsubscribeFromFilterList()" call then this is a no-op ++ // removed later by "removeFilterList()" call then this is a no-op + // call, otherwise it initiates a download. |url| must point to a https URL. -+ [supportsPromises] static void subscribeToFilterList( ++ [supportsPromises] static void addFilterList( + DOMString configuration, + DOMString url, + optional CompletionCallback status); + // Removes a subscription to a filter list downloaded from |url| and deletes + // corresponding files from disk. Does nothing if the filter list was never + // subscribed to. -+ [supportsPromises] static void unsubscribeFromFilterList( ++ [supportsPromises] static void removeFilterList( + DOMString configuration, + DOMString url, + optional CompletionCallback status); ++ // Compiles a list of currently installed subscriptions. Filter lists that ++ // are still being installed and don't yet participate in filtering also ++ // appear on this list and can be discerned by their installation_state. ++ [supportsPromises] static void getFilterLists( ++ DOMString configuration, ++ SubscriptionsCallback callback); + [supportsPromises] static void getSessionAllowedRequestsCount( + SessionStatsCallback callback); + // Returns number of blocked requests in a current session (runtime). + [supportsPromises] static void getSessionBlockedRequestsCount( + SessionStatsCallback callback); + // Returns Acceptable Ads url which can be passed to -+ // (ref:subscribeToFilterList) or (ref:unsubscribeFromFilterList). ++ // (ref:addFilterList) or (ref:removeFilterList). + [supportsPromises] static void getAcceptableAdsUrl(UrlCallback callback); + }; + @@ -3996,80 +4074,1401 @@ new file mode 100644 + // Fired when an request is explicitly allowed by an exception rule. + static void onRequestAllowed(RequestInfo info); + -+ // Fired when an request is blocked. -+ static void onRequestBlocked(RequestInfo info); ++ // Fired when an request is blocked. ++ static void onRequestBlocked(RequestInfo info); ++ ++ // Fired when a whole page is allowlisted in every filtering configuration. ++ static void onPageAllowed(RequestInfo info); ++ ++ // Fired when a popup is blocked. ++ static void onPopupBlocked(RequestInfo info); ++ ++ // Fired when a popup is allowlisted. ++ static void onPopupAllowed(RequestInfo info); ++ ++ // Fired when a subscription has been updated. ++ static void onSubscriptionUpdated(DOMString subscription_url); ++ ++ // Fired when configuration enable state changed ++ static void onEnabledStateChanged(DOMString config_name); ++ ++ // Fired when subscription added or removed. ++ static void onFilterListsChanged(DOMString config_name); ++ ++ // Fired when allowed domain list has been updated. ++ static void onAllowedDomainsChanged(DOMString config_name); ++ ++ // Fired when custom filter added or removed. ++ static void onCustomFiltersChanged(DOMString config_name); ++ }; ++}; +diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc +--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc ++++ b/chrome/common/extensions/permissions/chrome_api_permissions.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 "chrome/common/extensions/permissions/chrome_api_permissions.h" + +@@ -32,6 +36,8 @@ std::unique_ptr CreateAPIPermission( + // ChromePermissionMessageProvider::GetPermissionMessages as well. + constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { + // Register permissions for all extension types. ++ {APIPermissionID::kAdblockPrivate, "adblockPrivate", ++ APIPermissionInfo::kFlagCannotBeOptional}, + {APIPermissionID::kBackground, "background", + APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, + {APIPermissionID::kDeclarativeContent, "declarativeContent"}, +@@ -87,6 +93,8 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { + {APIPermissionID::kEnterpriseNetworkingAttributes, + "enterprise.networkingAttributes", + APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, ++ {APIPermissionID::kEyeoFilteringPrivate, "eyeoFilteringPrivate", ++ APIPermissionInfo::kFlagCannotBeOptional}, + {APIPermissionID::kEnterprisePlatformKeys, "enterprise.platformKeys", + APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, + {APIPermissionID::kFavicon, "favicon"}, +diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc +--- a/chrome/common/extensions/permissions/permission_set_unittest.cc ++++ b/chrome/common/extensions/permissions/permission_set_unittest.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 "extensions/common/permissions/permission_set.h" + +@@ -874,6 +878,10 @@ TEST(PermissionsTest, PermissionMessages) { + skip.insert(APIPermissionID::kSystemLog); + skip.insert(APIPermissionID::kOdfsConfigPrivate); + ++ // Adblock API is also private (in separate block to minimize conflicts). ++ skip.insert(APIPermissionID::kAdblockPrivate); ++ skip.insert(APIPermissionID::kEyeoFilteringPrivate); ++ + // Warned as part of host permissions. + skip.insert(APIPermissionID::kDevtools); + +diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn +--- a/chrome/test/BUILD.gn ++++ b/chrome/test/BUILD.gn +@@ -3762,6 +3762,20 @@ if (!is_android) { + ] + } + ++ ### Extensions API module start ++ sources += [ ++ "../browser/extensions/api/adblock_private/adblock_private_apitest.cc", ++ "../browser/extensions/api/adblock_private/adblock_private_apitest_backgroundpage.cc", ++ "../browser/extensions/api/adblock_private/adblock_private_apitest_base.cc", ++ "../browser/extensions/api/adblock_private/adblock_private_apitest_base.h", ++ "../browser/extensions/api/adblock_private/adblock_private_filter_lists_with_http_server_apitest.cc", ++ "../browser/extensions/api/eyeo_filtering_private/eyeo_filtering_private_apitest.cc", ++ ] ++ ++ deps += [ "//components/adblock/content/browser:browser_tests_support" ] ++ ++ ### Extensions API module end ++ + if (is_chromeos) { + deps += [ + "//chrome/browser/chromeos/cros_apps:all_browser_tests", +diff --git a/chrome/test/data/extensions/api_test/adblock_private/empty.js b/chrome/test/data/extensions/api_test/adblock_private/empty.js +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/empty.js +@@ -0,0 +1,14 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . +diff --git a/chrome/test/data/extensions/api_test/adblock_private/main.html b/chrome/test/data/extensions/api_test/adblock_private/main.html +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/main.html +@@ -0,0 +1,29 @@ ++ ++ ++ ++ ++ ++ ++ Adblock private API test ++ ++ ++ ++

chrome.adblockPrivate.* tests

++ ++ ++ +diff --git a/chrome/test/data/extensions/api_test/adblock_private/manifest.json b/chrome/test/data/extensions/api_test/adblock_private/manifest.json +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/manifest.json +@@ -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 . ++{ ++ "name": "adblockPrivate API test", ++ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyURY+BO7WO3B/dixoRSitosEKb1OOCsS1EF8dRoywUP+iQlHDJw2JL2A7d/E6JpoBQ/CUxX8lcHcsAs7zC31zb2iosBbfd5mCDd24bjLaIF/WNBRno6QYwbM/J7gCxn/aGFvAXdLnPhs2XFiP7iSQEY67NtTlah9EFGalB45UFUssrxFOXTFWT/gJmRIHqfCSUzHdPmFRJ1Sk6UpyZBPxp2MJAISbfTUhWIXa7WG+JxW95OEtNggfhYzX9wbCVSEU18RiMiMLdqNwHf7hYI30KiwrQhWcaB5kCnvJYEa43JggcE9xAaHV+1t2hSMyo5Xbz2YslI5UfDe8112hGVIUQIDAQAB", ++ "version": "0.1", ++ "incognito": "split", ++ "manifest_version": 2, ++ "background": { ++ "scripts": [ ++ "empty.js" ++ ] ++ }, ++ "description": "Test of chrome.adblockPrivate interface", ++ "permissions": [ ++ "adblockPrivate", ++ "eyeoFilteringPrivate", ++ "" ++ ] ++} +diff --git a/chrome/test/data/extensions/api_test/adblock_private/some-popup.html b/chrome/test/data/extensions/api_test/adblock_private/some-popup.html +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/some-popup.html +@@ -0,0 +1,24 @@ ++ ++ ++ ++ Popup content ++ ++ ++

I am a popup

++ ++ +diff --git a/chrome/test/data/extensions/api_test/adblock_private/test.html b/chrome/test/data/extensions/api_test/adblock_private/test.html +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/test.html +@@ -0,0 +1,25 @@ ++ ++ ++ ++ Test file ++ ++ ++ ++ popup link ++ ++ +diff --git a/chrome/test/data/extensions/api_test/adblock_private/test.js b/chrome/test/data/extensions/api_test/adblock_private/test.js +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/adblock_private/test.js +@@ -0,0 +1,609 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . ++ ++'use strict'; ++ ++const urlParams = new URLSearchParams(window.location.search); ++ ++// This class binds 1st argument for chrome.eyeoFilteringPrivate to 'adblock' ++// configuration and redirects methods renamed in chrome.eyeoFilteringPrivate ++// (eg. `onAdAllowed` => `onRequestAllowed`) which allows test code to ++// seamlessly call the same methods on chrome.adblockPrivate and on ++// chrome.eyeoFilteringPrivate. ++class FilteringPrivateBoundWithAdblock { ++ constructor() { ++ this.delegate = chrome.eyeoFilteringPrivate; ++ this.configuration = 'adblock'; ++ this.getSessionAllowedAdsCount = ++ this.delegate.getSessionAllowedRequestsCount; ++ this.getSessionBlockedAdsCount = ++ this.delegate.getSessionBlockedRequestsCount; ++ this.onAdAllowed = this.delegate.onRequestAllowed; ++ this.onAdBlocked = this.delegate.onRequestBlocked; ++ this.onAllowedDomainsChanged = this.delegate.onAllowedDomainsChanged; ++ this.onCustomFiltersChanged = this.delegate.onCustomFiltersChanged; ++ this.onEnabledStateChanged = this.delegate.onEnabledStateChanged; ++ this.onFilterListsChanged = this.delegate.onFilterListsChanged; ++ this.onSubscriptionUpdated = this.delegate.onSubscriptionUpdated; ++ const methodsToBind = [ ++ 'isEnabled', 'setEnabled', 'getAllowedDomains', 'addAllowedDomain', ++ 'removeAllowedDomain', 'getCustomFilters', 'addCustomFilter', ++ 'removeCustomFilter' ++ ]; ++ for (const method of methodsToBind) { ++ this[method] = function() { ++ const args = Array.from(arguments); ++ args.unshift(this.configuration); ++ this.delegate[method].apply(this.delegate, args); ++ } ++ } ++ const methodsToBindWithoutConfigurationParam = [ ++ 'isAutoInstallEnabled', 'setAutoInstallEnabled' ++ ]; ++ for (const method of methodsToBindWithoutConfigurationParam) { ++ this[method] = function() { ++ const args = Array.from(arguments); ++ this.delegate[method].apply(this.delegate, args); ++ } ++ } ++ const methodsToBindRenamed = new Map([ ++ ['installSubscription', 'addFilterList'], ++ ['uninstallSubscription', 'removeFilterList'], ++ ['getInstalledSubscriptions', 'getFilterLists'], ++ ]); ++ methodsToBindRenamed.forEach((value, key) => { ++ this[key] = ++ function() { ++ const args = Array.from(arguments); ++ args.unshift(this.configuration); ++ this.delegate[value].apply(this.delegate, args); ++ } ++ }); ++ } ++}; ++ ++// Set API object for tests, defaults to chrome.adblockPrivate ++let apiObject = chrome.adblockPrivate; ++if (urlParams.get('api') === 'eyeoFilteringPrivate') { ++ apiObject = new FilteringPrivateBoundWithAdblock; ++} ++ ++function findEnglishEasyList(item) { ++ console.log(item.title + ' ' + item.url + ' ' + item.languages); ++ return item.title.toLowerCase().includes('easylist') && ++ item.url.toLowerCase().includes('easylist') && ++ item.languages.includes('en'); ++} ++ ++function containsSubscription(subscriptions, url) { ++ for (const subscription of subscriptions) { ++ if (subscription.url === url) { ++ return true; ++ } ++ } ++ return false; ++} ++ ++// Checks that session stats data contains expected filter list url entry with a ++// single hit ++function verifySessionStats(sessionStats) { ++ let index = -1; ++ for (let i = 0; i < sessionStats.length; i++) { ++ if (sessionStats[i].url === 'adblock:custom') { ++ index = i; ++ break; ++ } ++ } ++ if (index == -1) { ++ chrome.test.fail('Failed: Missing entry in sessionStats'); ++ return; ++ } ++ if (sessionStats[index].count != 1) { ++ chrome.test.fail( ++ 'Failed: Expected a single subscription hit number, got: ' + ++ sessionStats[index].count); ++ } ++} ++ ++// Checks that even contains no error and expected filter list url ++function verifyEventData(event) { ++ if (event.tab_id < 1) { ++ chrome.test.fail('Failed: Wrong tab_id value'); ++ } ++ if (event.window_id < 1) { ++ chrome.test.fail('Failed: Wrong window_id value'); ++ } ++ if (event.content_type !== 'XMLHTTPREQUEST') { ++ chrome.test.fail('Failed: Wrong content_type value: ' + event.content_type); ++ } ++ if (event.subscription !== 'adblock:custom') { ++ chrome.test.fail('Failed: Wrong subscription value: ' + event.subscription); ++ } ++} ++ ++function arrayEquals(a, b) { ++ if (a === b) ++ return true; ++ if (a === null || b === null) ++ return false; ++ if (a.length !== b.length) ++ return false; ++ for (var i = 0; i < a.length; i++) { ++ if (a[i] !== b[i]) ++ return false; ++ } ++ return true; ++}; ++ ++const availableTests = [ ++ function setEnabled_isEnabled() { ++ apiObject.setEnabled(false); ++ apiObject.isEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.fail('Failed: ad blocking should NOT be enabled'); ++ }); ++ apiObject.setEnabled(true); ++ apiObject.isEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.succeed(); ++ else ++ chrome.test.fail('Failed: ad blocking should be enabled'); ++ }); ++ }, ++ function setAAEnabled_isAAEnabled() { ++ chrome.adblockPrivate.setAcceptableAdsEnabled(false); ++ chrome.adblockPrivate.isAcceptableAdsEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.fail('Failed: AA should NOT be enabled'); ++ }); ++ chrome.adblockPrivate.setAcceptableAdsEnabled(true); ++ chrome.adblockPrivate.getInstalledSubscriptions(function(selected) { ++ if (!containsSubscription( ++ selected, ++ 'https://easylist-downloads.adblockplus.org/exceptionrules.txt')) { ++ chrome.test.fail('Failed: AA subscription should be on the list'); ++ } ++ }); ++ chrome.adblockPrivate.isAcceptableAdsEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.succeed(); ++ else ++ chrome.test.fail('Failed: AA should be enabled'); ++ }); ++ }, ++ function setAAEnabled_isAAEnabled_newAPI() { ++ const default_config = 'adblock'; ++ chrome.eyeoFilteringPrivate.getAcceptableAdsUrl(function(aa_url) { ++ chrome.eyeoFilteringPrivate.removeFilterList( ++ default_config, aa_url); ++ chrome.adblockPrivate.isAcceptableAdsEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.fail('Failed: AA should NOT be enabled'); ++ }); ++ chrome.eyeoFilteringPrivate.getAcceptableAdsUrl(function(aa_url) { ++ chrome.eyeoFilteringPrivate.addFilterList( ++ default_config, aa_url); ++ chrome.adblockPrivate.isAcceptableAdsEnabled(function(enabled) { ++ if (enabled) ++ chrome.test.succeed(); ++ else ++ chrome.test.fail('Failed: AA should be enabled'); ++ }); ++ }); ++ }); ++ }, ++ function getBuiltInSubscriptions() { ++ chrome.adblockPrivate.getBuiltInSubscriptions(function(recommended) { ++ const found = recommended.find(findEnglishEasyList); ++ if (found) { ++ chrome.test.succeed(); ++ } else { ++ chrome.test.fail('Failed: invalid built-in subscriptions'); ++ } ++ }); ++ }, ++ // This test works because at the beginning getInstalledSubscriptions returns ++ // just default entries from recommended subscriptions. ++ function installedSubscriptionsDataSchema() { ++ const disabled = !!urlParams.get('disabled'); ++ if (disabled) { ++ apiObject.setEnabled(false); ++ } ++ apiObject.getInstalledSubscriptions(function(installed) { ++ for (const subscription of installed) { ++ if (!subscription.url) ++ chrome.test.fail('Failed: Must contain "url" property'); ++ if (!(subscription.installation_state == 'Unknown' && disabled || ++ subscription.installation_state != 'Unknown' && !disabled)) ++ chrome.test.fail( ++ 'Failed: Must contain valid "installation_state" property'); ++ if (disabled) ++ continue; ++ const pathname = new URL(subscription.url).pathname; ++ if (subscription.title != pathname) ++ chrome.test.fail('Failed: Must contain expected "title" property'); ++ if (subscription.current_version != pathname.length) ++ chrome.test.fail('Failed: Must contain expected "current_version" property'); ++ if (!subscription.last_installation_time) ++ chrome.test.fail( ++ 'Failed: Installed subscription must contain "last_installation_time" property'); ++ } ++ chrome.test.succeed(); ++ }); ++ }, ++ function installSubscriptionInvalidURL() { ++ apiObject.installSubscription('http://', function() { ++ if (!chrome.runtime.lastError) ++ chrome.test.fail('Failed: invalid input accepted'); ++ else ++ chrome.test.succeed(); ++ }); ++ }, ++ function uninstallSubscriptionInvalidURL() { ++ apiObject.uninstallSubscription('http://', function() { ++ if (!chrome.runtime.lastError) ++ chrome.test.fail('Failed: invalid input accepted'); ++ else ++ chrome.test.succeed(); ++ }); ++ }, ++ function subscriptionsManagement() { ++ const kEasylist = urlParams.get('easylist'); ++ const kExceptionrules = urlParams.get('exceptions'); ++ const kABPFilters = urlParams.get('snippets'); ++ const kCustom = 'https://example.com/subscription.txt'; ++ ++ function containsDefaultSubscriptions(subscriptions) { ++ return containsSubscription(subscriptions, kEasylist) && ++ containsSubscription(subscriptions, kExceptionrules) && ++ containsSubscription(subscriptions, kABPFilters); ++ } ++ ++ if (urlParams.get('disabled')) { ++ apiObject.setEnabled(false); ++ } ++ ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (installed.length) { ++ if (!containsDefaultSubscriptions(installed)) { ++ chrome.test.fail('Failed: Should contain all default subscriptions'); ++ } ++ for (const subscription of installed) { ++ apiObject.uninstallSubscription(subscription.url); ++ } ++ } else { ++ chrome.test.fail('Failed: Should contain default subscriptions'); ++ } ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (installed.length) { ++ chrome.test.fail( ++ 'Failed: There shouldn\'t be any installed subscriptions'); ++ } ++ apiObject.installSubscription(kEasylist); ++ apiObject.installSubscription(kExceptionrules); ++ apiObject.installSubscription(kABPFilters); ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (installed.length) { ++ if (!containsDefaultSubscriptions(installed)) { ++ chrome.test.fail( ++ 'Failed: Should contain all default subscriptions'); ++ } ++ } else { ++ chrome.test.fail('Failed: Should contain default subscriptions'); ++ } ++ apiObject.installSubscription(kCustom); ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (!containsSubscription(installed, kCustom)) { ++ chrome.test.fail('Failed: Should contain custom subscription'); ++ } ++ apiObject.uninstallSubscription(kCustom); ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (containsSubscription(installed, kCustom)) { ++ chrome.test.fail( ++ 'Failed: Should not contain custom subscription'); ++ } else { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ }); ++ }); ++ }); ++ }, ++ function allowedDomainsManagement() { ++ apiObject.getAllowedDomains(function(domains) { ++ if (domains.length) { ++ for (const domain of domains) ++ apiObject.removeAllowedDomain(domain); ++ } ++ ++ apiObject.getAllowedDomains(function(domains) { ++ if (domains.length) { ++ chrome.test.fail('Failed: There shouldn\'t be any allowed domains'); ++ return; ++ } ++ ++ apiObject.addAllowedDomain('foo.bar'); ++ apiObject.addAllowedDomain('bar.baz'); ++ apiObject.getAllowedDomains(function(domains) { ++ if (domains.length != 2) { ++ chrome.test.fail('Failed: There should be 2 allowed domains'); ++ return; ++ } ++ ++ if (domains.indexOf('foo.bar') == -1 || ++ domains.indexOf('bar.baz') == -1) { ++ chrome.test.fail('Failed: Didn\'t find expected allowed domains'); ++ return; ++ } ++ ++ apiObject.removeAllowedDomain('foo.bar'); ++ apiObject.removeAllowedDomain('bar.baz'); ++ ++ apiObject.getAllowedDomains(function(domains) { ++ if (domains.length) ++ chrome.test.fail('Failed: Still have allowed domains'); ++ else ++ chrome.test.succeed(); ++ }); ++ }); ++ }); ++ }); ++ }, ++ function customFiltersManagement() { ++ apiObject.getCustomFilters(function(filters) { ++ if (filters.length) { ++ for (const filter of filters) ++ apiObject.removeCustomFilter(filter); ++ } ++ ++ apiObject.getCustomFilters(function(filters) { ++ if (filters.length) { ++ chrome.test.fail('Failed: There shouldn\'t be any custom filters'); ++ return; ++ } ++ ++ apiObject.addCustomFilter('foo.bar'); ++ apiObject.addCustomFilter('bar.baz'); ++ apiObject.getCustomFilters(function(filters) { ++ if (filters.length != 2) { ++ chrome.test.fail('Failed: There should be 2 custom filters'); ++ return; ++ } ++ ++ if (filters.indexOf('foo.bar') == -1 || ++ filters.indexOf('bar.baz') == -1) { ++ chrome.test.fail('Failed: Didn\'t find expected custom filters'); ++ return; ++ } ++ ++ apiObject.removeCustomFilter('foo.bar'); ++ apiObject.removeCustomFilter('bar.baz'); ++ ++ apiObject.getCustomFilters(function(filters) { ++ if (filters.length) ++ chrome.test.fail('Failed: Still have custom filters'); ++ else ++ chrome.test.succeed(); ++ }); ++ }); ++ }); ++ }); ++ }, ++ function adBlockedEvents() { ++ let expected_filters_count = 1; ++ apiObject.onCustomFiltersChanged.addListener(function(filter) { ++ if (--expected_filters_count) ++ return; ++ ++ apiObject.onAdBlocked.addListener(function(e) { ++ verifyEventData(e); ++ if (e.url.includes('test1.png')) { ++ chrome.test.succeed(); ++ } ++ }); ++ // External request to delay triggering test request ++ const delaying_wrapper_request = new XMLHttpRequest(); ++ const delaying_wrapper_request_handler = function() { ++ // Internal request expected by test logic ++ const blocked_url_request = new XMLHttpRequest(); ++ const blocked_url_request_handler = function() {}; ++ blocked_url_request.onload = blocked_url_request_handler; ++ blocked_url_request.onerror = blocked_url_request_handler; ++ blocked_url_request.open('GET', 'https://example.com/test1.png', true); ++ blocked_url_request.send(); ++ }; ++ delaying_wrapper_request.onload = delaying_wrapper_request_handler; ++ delaying_wrapper_request.onerror = delaying_wrapper_request_handler; ++ delaying_wrapper_request.open('GET', 'https://example.com/', true); ++ delaying_wrapper_request.send(); ++ }); ++ apiObject.addCustomFilter('test1.png'); ++ }, ++ function adAllowedEvents() { ++ let expected_filters_count = 2; ++ apiObject.onCustomFiltersChanged.addListener(function(filter) { ++ if (--expected_filters_count) ++ return; + -+ // Fired when a whole page is allowlisted in every filtering configuration. -+ static void onPageAllowed(RequestInfo info); ++ apiObject.onAdAllowed.addListener(function(e) { ++ verifyEventData(e); ++ if (e.url.includes('test2.png')) { ++ chrome.test.succeed(); ++ } ++ }); ++ // External request to delay triggering test request ++ const delaying_wrapper_request = new XMLHttpRequest(); ++ const delaying_wrapper_request_handler = function() { ++ // Internal request expected by test logic ++ const allowed_url_request = new XMLHttpRequest(); ++ const allowed_url_request_handler = function() {}; ++ allowed_url_request.onload = allowed_url_request_handler; ++ allowed_url_request.onerror = allowed_url_request_handler; ++ allowed_url_request.open('GET', 'https://example.com/test2.png', true); ++ allowed_url_request.send(); ++ }; ++ delaying_wrapper_request.onload = delaying_wrapper_request_handler; ++ delaying_wrapper_request.onerror = delaying_wrapper_request_handler; ++ delaying_wrapper_request.open('GET', 'https://example.com/', true); ++ delaying_wrapper_request.send(); ++ }); ++ apiObject.addCustomFilter('test2.png'); ++ apiObject.addCustomFilter('@@test2.png'); ++ }, ++ function sessionStats() { ++ let expected_filters_count = 3; ++ apiObject.onCustomFiltersChanged.addListener(function(filter) { ++ if (--expected_filters_count) ++ return; + -+ // Fired when a popup is blocked. -+ static void onPopupBlocked(RequestInfo info); ++ const blocked_request_handler = function() { ++ apiObject.getSessionBlockedAdsCount(function(sessionStats) { ++ verifySessionStats(sessionStats); ++ const allowed_request_handler = function() { ++ apiObject.getSessionAllowedAdsCount(function(sessionStats) { ++ verifySessionStats(sessionStats); ++ chrome.test.succeed(); ++ }); ++ }; ++ const request = new XMLHttpRequest(); ++ request.onload = allowed_request_handler; ++ request.onerror = allowed_request_handler; ++ request.open('GET', 'https://example.com/test4.png', true); ++ request.send(); ++ }); ++ }; ++ const request = new XMLHttpRequest(); ++ request.onload = blocked_request_handler; ++ request.onerror = blocked_request_handler; ++ request.open('GET', 'https://example.com/test3.png', true); ++ request.send(); ++ }); ++ apiObject.addCustomFilter('test3.png'); ++ apiObject.addCustomFilter('test4.png'); ++ apiObject.addCustomFilter('@@test4.png'); ++ }, ++ function allowedDomainsEvent() { ++ const domain = 'domain.com'; ++ let data = [domain]; ++ let attempts = 2; ++ apiObject.onAllowedDomainsChanged.addListener(function() { ++ apiObject.getAllowedDomains(function(domains) { ++ if (!arrayEquals(data, domains)) { ++ chrome.test.fail('Unexpected domain list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ apiObject.addAllowedDomain(domain); ++ data = []; ++ apiObject.removeAllowedDomain(domain); ++ }, ++ function enabledStateEvent() { ++ let state = false; ++ let attempts = 2; ++ apiObject.onEnabledStateChanged.addListener(function() { ++ apiObject.isEnabled(function(enabled) { ++ if (enabled !== state) { ++ chrome.test.fail('Unexpected enabled state'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ apiObject.setEnabled(false); ++ state = true; ++ apiObject.setEnabled(true); ++ }, ++ function filterListsEvent() { ++ const kCustom = 'https://example.com/subscription.txt'; ++ let data = [kCustom]; ++ let attempts = 2; ++ apiObject.onFilterListsChanged.addListener(function() { ++ apiObject.getInstalledSubscriptions(function(custom) { ++ if (!(data.length + 3, custom.length)) { ++ chrome.test.fail('Unexpected subscription list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ data = []; ++ apiObject.uninstallSubscription(kCustom); ++ }); ++ }); ++ apiObject.installSubscription(kCustom); ++ }, ++ function customFiltersEvent() { ++ const filter = 'foo.bar'; ++ let data = [filter]; ++ let attempts = 2; ++ apiObject.onCustomFiltersChanged.addListener(function() { ++ apiObject.getCustomFilters(function(filters) { ++ if (!arrayEquals(data, filters)) { ++ chrome.test.fail('Unexpected custom filter list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ apiObject.addCustomFilter(filter); ++ data = []; ++ apiObject.removeCustomFilter(filter); ++ }, ++ function disableGeolocation() { ++ const geoLocatedList1 = urlParams.get('geolocated_list_1'); ++ const geoLocatedList2 = urlParams.get('geolocated_list_2'); ++ if (!geoLocatedList1 || !geoLocatedList2) { ++ chrome.test.fail('Failed: missing expected filter list param(s)'); ++ } ++ apiObject.isAutoInstallEnabled(function(enabled) { ++ if (!enabled) { ++ chrome.test.fail('Failed: auto installed FL feature should be enabled by default'); ++ } ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (!containsSubscription(installed, geoLocatedList1) ++ || !containsSubscription(installed, geoLocatedList2)) { ++ chrome.test.fail('Failed: Should contain expected auto installed filter lists'); ++ } ++ // Make geoLocatedList1 not auto-installed ++ apiObject.uninstallSubscription(geoLocatedList1); ++ apiObject.installSubscription(geoLocatedList1); ++ apiObject.setAutoInstallEnabled(false); ++ apiObject.isAutoInstallEnabled(function(enabled) { ++ if (enabled) { ++ chrome.test.fail('Failed: auto installed FL feature should be disabled'); ++ } ++ apiObject.getInstalledSubscriptions(function(installed) { ++ if (!containsSubscription(installed, geoLocatedList1)) { ++ chrome.test.fail('Failed: Should contain manually installed filter list'); ++ } ++ if (containsSubscription(installed, geoLocatedList2)) { ++ chrome.test.fail('Failed: Should NOT contain auto installed filter list'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }); ++ }); ++ }, ++]; + -+ // Fired when a popup is allowlisted. -+ static void onPopupAllowed(RequestInfo info); ++chrome.test.runTests(availableTests.filter(function(op) { ++ return op.name == urlParams.get('subtest'); ++})); +diff --git a/chrome/test/data/extensions/api_test/eyeo_filtering_private/empty.js b/chrome/test/data/extensions/api_test/eyeo_filtering_private/empty.js +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/eyeo_filtering_private/empty.js +@@ -0,0 +1,14 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . +diff --git a/chrome/test/data/extensions/api_test/eyeo_filtering_private/main.html b/chrome/test/data/extensions/api_test/eyeo_filtering_private/main.html +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/eyeo_filtering_private/main.html +@@ -0,0 +1,29 @@ ++ ++ ++ ++ ++ ++ ++ Eyeo filtering private API test ++ ++ ++ ++

chrome.eyeoFilteringPrivate.* tests

++ ++ ++ +diff --git a/chrome/test/data/extensions/api_test/eyeo_filtering_private/manifest.json b/chrome/test/data/extensions/api_test/eyeo_filtering_private/manifest.json +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/eyeo_filtering_private/manifest.json +@@ -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 . ++{ ++ "name": "eyeoFilteringPrivate API test", ++ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRxyv6tzqVnC+2gR3QxaN32fynw6GBZd9CXH8huHopx5xZ0fE1gScXnnjna7YW2sc6dhZZv326lRwmsJRwJN+RZpxBQbTD4CuCiqfUo+Xdyigh91QqyScLLRmg3SPsBBF9X/M50LO/6MD2eETiWbQQRy1TXNz52lt6NtjXKmS2lVZzR/jnGyAA96vMOmxeNIJmSYHFOHlSIphAJr/Erd0v1ZcBjJnZxqSrKZwUTHHc/FxcN1YqJkU/6O6gjMLNo3Uv33bqRAYmGUq+TTftwLg2hzEE1OllThcF9VVmQ3HZ5eTqqw/XP/tiQ/vUBflKer2mSVk708VBNpktao44kAtQIDAQAB", ++ "version": "0.1", ++ "incognito": "split", ++ "manifest_version": 3, ++ "background": { ++ "service_worker": "empty.js" ++ }, ++ "description": "Test of chrome.eyeoFilteringPrivate interface", ++ "host_permissions": [ ++ "" ++ ], ++ "permissions": [ ++ "eyeoFilteringPrivate" ++ ] ++} +diff --git a/chrome/test/data/extensions/api_test/eyeo_filtering_private/test.js b/chrome/test/data/extensions/api_test/eyeo_filtering_private/test.js +new file mode 100644 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/eyeo_filtering_private/test.js +@@ -0,0 +1,429 @@ ++// This file is part of eyeo Chromium SDK, ++// Copyright (C) 2006-present eyeo GmbH ++// ++// eyeo Chromium SDK is free software: you can redistribute it and/or modify ++// it under the terms of the GNU General Public License version 3 as ++// published by the Free Software Foundation. ++// ++// eyeo Chromium SDK is distributed in the hope that it will be useful, ++// but WITHOUT ANY WARRANTY; without even the implied warranty of ++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++// GNU General Public License for more details. ++// ++// You should have received a copy of the GNU General Public License ++// along with eyeo Chromium SDK. If not, see . + -+ // Fired when a subscription has been updated. -+ static void onSubscriptionUpdated(DOMString subscription_url); ++'use strict'; + -+ // Fired when configuration enable state changed -+ static void onEnabledStateChanged(DOMString config_name); ++const custom_config = 'custom'; + -+ // Fired when subscription added or removed. -+ static void onFilterListsChanged(DOMString config_name); ++async function pollUntil(predicate, pollEveryMs) { ++ return new Promise(r => { ++ const id = setInterval(() => { ++ let ret; ++ if (ret = predicate()) { ++ clearInterval(id); ++ r(ret); ++ } ++ }, pollEveryMs); ++ }); ++} + -+ // Fired when allowed domain list has been updated. -+ static void onAllowedDomainsChanged(DOMString config_name); ++function containsSubscription(subscriptions, url) { ++ for (const subscription of subscriptions) { ++ if (subscription.url === url) { ++ return true; ++ } ++ } ++ return false; ++} + -+ // Fired when custom filter added or removed. -+ static void onCustomFiltersChanged(DOMString config_name); -+ }; ++function arrayEquals(a, b) { ++ if (a === b) ++ return true; ++ if (a === null || b === null) ++ return false; ++ if (a.length !== b.length) ++ return false; ++ for (var i = 0; i < a.length; i++) { ++ if (a[i] !== b[i]) ++ return false; ++ } ++ return true; +}; -diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc ---- a/chrome/common/extensions/permissions/chrome_api_permissions.cc -+++ b/chrome/common/extensions/permissions/chrome_api_permissions.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 "chrome/common/extensions/permissions/chrome_api_permissions.h" - -@@ -32,6 +36,8 @@ std::unique_ptr CreateAPIPermission( - // ChromePermissionMessageProvider::GetPermissionMessages as well. - constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { - // Register permissions for all extension types. -+ {APIPermissionID::kAdblockPrivate, "adblockPrivate", -+ APIPermissionInfo::kFlagCannotBeOptional}, - {APIPermissionID::kBackground, "background", - APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, - {APIPermissionID::kDeclarativeContent, "declarativeContent"}, -@@ -87,6 +93,8 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { - {APIPermissionID::kEnterpriseNetworkingAttributes, - "enterprise.networkingAttributes", - APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, -+ {APIPermissionID::kEyeoFilteringPrivate, "eyeoFilteringPrivate", -+ APIPermissionInfo::kFlagCannotBeOptional}, - {APIPermissionID::kEnterprisePlatformKeys, "enterprise.platformKeys", - APIPermissionInfo::kFlagDoesNotRequireManagedSessionFullLoginWarning}, - {APIPermissionID::kFavicon, "favicon"}, -diff --git a/chrome/common/extensions/permissions/permission_set_unittest.cc b/chrome/common/extensions/permissions/permission_set_unittest.cc ---- a/chrome/common/extensions/permissions/permission_set_unittest.cc -+++ b/chrome/common/extensions/permissions/permission_set_unittest.cc -@@ -876,6 +876,10 @@ TEST(PermissionsTest, PermissionMessages) { - skip.insert(APIPermissionID::kSystemLog); - skip.insert(APIPermissionID::kOdfsConfigPrivate); - -+ // Adblock API is also private (in separate block to minimize conflicts). -+ skip.insert(APIPermissionID::kAdblockPrivate); -+ skip.insert(APIPermissionID::kEyeoFilteringPrivate); + - // Warned as part of host permissions. - skip.insert(APIPermissionID::kDevtools); - ++const availableTests = [ ++ function createRemoveAndGetConfigurations() { ++ chrome.eyeoFilteringPrivate.getConfigurations(function(configs) { ++ if (configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should NOT be a custom configuration'); ++ } ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.getConfigurations(function(configs) { ++ if (!configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should be a custom configuration'); ++ } ++ chrome.eyeoFilteringPrivate.removeConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.getConfigurations(function(configs) { ++ if (configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should NOT be a custom configuration'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }); ++ }, ++ async function createRemoveAndGetConfigurationsWithPromises() { ++ let configs = await chrome.eyeoFilteringPrivate.getConfigurations(); ++ if (configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should NOT be a custom configuration'); ++ } ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ configs = await chrome.eyeoFilteringPrivate.getConfigurations(); ++ if (!configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should be a custom configuration'); ++ } ++ chrome.eyeoFilteringPrivate.removeConfiguration(custom_config); ++ configs = await chrome.eyeoFilteringPrivate.getConfigurations(); ++ if (configs.includes(custom_config)) { ++ chrome.test.fail('Failed: There should NOT be a custom configuration'); ++ } else { ++ chrome.test.succeed(); ++ } ++ }, ++ function enableAndDisableConfiguration() { ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.isEnabled(custom_config, function(enabled) { ++ if (!enabled) { ++ chrome.test.fail('Failed: Configuration should be enabled'); ++ } ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, false); ++ chrome.eyeoFilteringPrivate.isEnabled(custom_config, function(enabled) { ++ if (enabled) { ++ chrome.test.fail('Failed: Configuration should NOT be enabled'); ++ } ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, true); ++ chrome.eyeoFilteringPrivate.isEnabled(custom_config, function(enabled) { ++ if (!enabled) { ++ chrome.test.fail('Failed: Configuration should be enabled'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }); ++ }, ++ function enableAndDisableConfigurationWithPromises() { ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.isEnabled(custom_config) ++ .then(enabled => { ++ if (!enabled) { ++ throw 'Failed: Configuration should be enabled'; ++ } ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, false); ++ }) ++ .then(() => chrome.eyeoFilteringPrivate.isEnabled(custom_config)) ++ .then(enabled => { ++ if (enabled) { ++ throw 'Failed: Configuration should NOT be enabled'; ++ } ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, true); ++ }) ++ .then(() => chrome.eyeoFilteringPrivate.isEnabled(custom_config)) ++ .then(enabled => { ++ if (!enabled) { ++ throw 'Failed: Configuration should be enabled'; ++ } ++ chrome.test.succeed(); ++ }) ++ .catch(message => chrome.test.fail(message)) ++ }, ++ function addAllowedDomainToCustomConfiguration() { ++ const domain = 'domain.com'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addAllowedDomain(custom_config, domain); ++ chrome.eyeoFilteringPrivate.getAllowedDomains( ++ custom_config, function(domains) { ++ if (domains.length != 1 || domains.indexOf(domain) == -1) { ++ chrome.test.fail('Failed: There should be a custom domain'); ++ } ++ chrome.eyeoFilteringPrivate.removeAllowedDomain(custom_config, domain); ++ chrome.eyeoFilteringPrivate.getAllowedDomains( ++ custom_config, function(domains) { ++ if (domains.length) { ++ chrome.test.fail('Failed: Still have custom domain(s)'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }, ++ function addAllowedDomainToCustomConfigurationWithPromises() { ++ const domain = 'domain.com'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addAllowedDomain(custom_config, domain) ++ .then(() => chrome.eyeoFilteringPrivate.getAllowedDomains(custom_config)) ++ .then(domains => { ++ if (domains.length != 1 || domains.indexOf(domain) == -1) { ++ throw 'Failed: There should be a custom domain'; ++ } ++ chrome.eyeoFilteringPrivate.removeAllowedDomain(custom_config, domain); ++ }) ++ .then(() => chrome.eyeoFilteringPrivate.getAllowedDomains(custom_config)) ++ .then(domains => { ++ if (domains.length) { ++ throw 'Failed: Still have custom domain(s)'; ++ } ++ chrome.test.succeed(); ++ }) ++ .catch(message => chrome.test.fail(message)) ++ }, ++ function addCustomFilterToCustomConfiguration() { ++ const filter = '||foo.bar'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addCustomFilter(custom_config, filter); ++ chrome.eyeoFilteringPrivate.getCustomFilters( ++ custom_config, function(filters) { ++ if (filters.length != 1 || filters.indexOf(filter) == -1) { ++ chrome.test.fail('Failed: There should be a custom filter'); ++ } ++ chrome.eyeoFilteringPrivate.removeCustomFilter(custom_config, filter); ++ chrome.eyeoFilteringPrivate.getCustomFilters( ++ custom_config, function(filters) { ++ if (filters.length) { ++ chrome.test.fail('Failed: Still have custom filter(s)'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }, ++ function addCustomFilterToCustomConfigurationWithPromises() { ++ const filter = '||foo.bar'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addCustomFilter(custom_config, filter) ++ .then(() => chrome.eyeoFilteringPrivate.getCustomFilters(custom_config)) ++ .then(filters => { ++ if (filters.length != 1 || filters.indexOf(filter) == -1) { ++ throw 'Failed: There should be a custom filter'; ++ } ++ chrome.eyeoFilteringPrivate.removeCustomFilter(custom_config, filter); ++ }) ++ .then(() => chrome.eyeoFilteringPrivate.getCustomFilters(custom_config)) ++ .then(filters => { ++ if (filters.length) { ++ throw 'Failed: Still have custom filter(s)'; ++ } ++ chrome.test.succeed(); ++ }) ++ .catch(message => chrome.test.fail(message)); ++ }, ++ function addFilterListInCustomConfiguration() { ++ const subscription = 'https://example.com/subscription.txt'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addFilterList(custom_config, subscription); ++ chrome.eyeoFilteringPrivate.getFilterLists( ++ custom_config, function(subscriptions) { ++ if (subscriptions.length != 1 || !containsSubscription(subscriptions, subscription)) { ++ chrome.test.fail('Failed: There should be a single custom subscription'); ++ } ++ chrome.eyeoFilteringPrivate.removeFilterList(custom_config, subscription); ++ chrome.eyeoFilteringPrivate.getFilterLists( ++ custom_config, function(subscriptions) { ++ if (subscriptions.length) { ++ chrome.test.fail('Failed: Still have custom subscription(s)'); ++ } ++ chrome.test.succeed(); ++ }); ++ }); ++ }, ++ function addFilterListInCustomConfigurationWithPromises() { ++ const subscription = 'https://example.com/subscription.txt'; ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addFilterList(custom_config, subscription) ++ .then(() => chrome.eyeoFilteringPrivate.getFilterLists(custom_config)) ++ .then(subscriptions => { ++ if (subscriptions.length != 1 || !containsSubscription(subscriptions, subscription)) { ++ throw 'Failed: There should be a single custom subscription'; ++ } ++ chrome.eyeoFilteringPrivate.removeFilterList(custom_config, subscription); ++ }) ++ .then(() => chrome.eyeoFilteringPrivate.getFilterLists(custom_config)) ++ .then(subscriptions => { ++ if (subscriptions.length) { ++ throw 'Failed: Still have custom subscription(s)'; ++ } ++ chrome.test.succeed(); ++ }) ++ .catch(message => chrome.test.fail(message)); ++ }, ++ async function missingConfiguration() { ++ const input = 'https://dummy.com'; ++ const expectedError = 'Configuration with name \'custom\' does not exist!'; ++ const setters = [ ++ 'addFilterList', 'removeFilterList', 'addAllowedDomain', ++ 'removeAllowedDomain', 'addCustomFilter', 'removeCustomFilter' ++ ]; ++ const getters = [ ++ 'isEnabled', 'getFilterLists', 'getAllowedDomains', 'getCustomFilters' ++ ]; ++ const allMethodsCount = 1 + setters.length + getters.length; ++ let counter = 0; ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, false, function() { ++ if (!chrome.runtime.lastError) { ++ chrome.test.fail('Failed: missing configuration accepted'); ++ } ++ chrome.test.assertEq(expectedError, chrome.runtime.lastError.message); ++ ++counter; ++ }); ++ for (const method of setters) { ++ chrome.eyeoFilteringPrivate[method](custom_config, input, function() { ++ if (!chrome.runtime.lastError) { ++ chrome.test.fail('Failed: missing configuration accepted'); ++ } ++ chrome.test.assertEq(expectedError, chrome.runtime.lastError.message); ++ ++counter; ++ }); ++ } ++ for (const method of getters) { ++ chrome.eyeoFilteringPrivate[method](custom_config, function(result) { ++ if (!chrome.runtime.lastError) { ++ chrome.test.fail('Failed: missing configuration accepted'); ++ } ++ chrome.test.assertEq(expectedError, chrome.runtime.lastError.message); ++ ++counter; ++ }); ++ } ++ await pollUntil(() => counter === allMethodsCount, 100); ++ chrome.test.succeed(); ++ }, ++ async function missingConfigurationWithPromises() { ++ const input = 'https://dummy.com'; ++ const expectedError = ++ 'Error: Configuration with name \'custom\' does not exist!' ++ const setters = [ ++ 'addFilterList', 'removeFilterList', 'addAllowedDomain', ++ 'removeAllowedDomain', 'addCustomFilter', 'removeCustomFilter' ++ ]; ++ const getters = [ ++ 'isEnabled', 'getFilterLists', 'getAllowedDomains', 'getCustomFilters' ++ ]; ++ const allMethodsCount = 1 + setters.length + getters.length; ++ let counter = 0; ++ const errorHandler = function(error) { ++ chrome.test.assertEq(expectedError, error.toString()); ++ ++counter; ++ }; ++ await chrome.eyeoFilteringPrivate.setEnabled(custom_config, false) ++ .catch(error => errorHandler(error)); ++ for (const method of setters) { ++ await chrome.eyeoFilteringPrivate[method](custom_config, input) ++ .catch(error => errorHandler(error)); ++ } ++ for (const method of getters) { ++ await chrome.eyeoFilteringPrivate[method](custom_config) ++ .catch(error => errorHandler(error)); ++ } ++ if (counter == allMethodsCount) { ++ chrome.test.succeed(); ++ } else { ++ chrome.test.fail('Failed: expected missing configuration for every call'); ++ } ++ }, ++ function allowedDomainsEvent() { ++ const domain = 'domain.com'; ++ let data = [domain]; ++ let attempts = 2; ++ chrome.eyeoFilteringPrivate.onAllowedDomainsChanged.addListener(function( ++ config_name) { ++ if (config_name != custom_config) { ++ chrome.test.fail('Failed: Wrong config name'); ++ } ++ chrome.eyeoFilteringPrivate.getAllowedDomains( ++ custom_config, function(domains) { ++ if (!arrayEquals(data, domains)) { ++ chrome.test.fail('Unexpected domain list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addAllowedDomain(custom_config, domain); ++ data = []; ++ chrome.eyeoFilteringPrivate.removeAllowedDomain(custom_config, domain); ++ }, ++ function enabledStateEvent() { ++ let state = false; ++ let attempts = 2; ++ chrome.eyeoFilteringPrivate.onEnabledStateChanged.addListener(function( ++ config_name) { ++ if (config_name != custom_config) { ++ chrome.test.fail('Failed: Wrong config name'); ++ } ++ chrome.eyeoFilteringPrivate.isEnabled(custom_config, function(enabled) { ++ if (enabled !== state) { ++ chrome.test.fail('Unexpected enabled state'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, false); ++ state = true; ++ chrome.eyeoFilteringPrivate.setEnabled(custom_config, true); ++ }, ++ function filterListsEvent() { ++ const list = 'http://example.com/list.txt'; ++ let data = [list]; ++ let attempts = 2; ++ chrome.eyeoFilteringPrivate.onFilterListsChanged.addListener(function( ++ config_name) { ++ if (config_name != custom_config) { ++ chrome.test.fail('Failed: Wrong config name'); ++ } ++ chrome.eyeoFilteringPrivate.getFilterLists( ++ custom_config, function(custom) { ++ if (!arrayEquals(data, custom)) { ++ chrome.test.fail('Unexpected subscription list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addFilterList(custom_config, list); ++ data = []; ++ chrome.eyeoFilteringPrivate.removeFilterList(custom_config, list); ++ }, ++ function customFiltersEvent() { ++ const filter = 'foo.bar'; ++ let data = [filter]; ++ let attempts = 2; ++ chrome.eyeoFilteringPrivate.onCustomFiltersChanged.addListener(function( ++ config_name) { ++ if (config_name != custom_config) { ++ chrome.test.fail('Failed: Wrong config name'); ++ } ++ chrome.eyeoFilteringPrivate.getCustomFilters( ++ custom_config, function(filters) { ++ if (!arrayEquals(data, filters)) { ++ chrome.test.fail('Unexpected custom filter list'); ++ } ++ if (--attempts == 0) { ++ chrome.test.succeed(); ++ } ++ }); ++ }); ++ chrome.eyeoFilteringPrivate.createConfiguration(custom_config); ++ chrome.eyeoFilteringPrivate.addCustomFilter(custom_config, filter); ++ data = []; ++ chrome.eyeoFilteringPrivate.removeCustomFilter(custom_config, filter); ++ }, ++]; ++ ++const urlParams = new URLSearchParams(window.location.search); ++chrome.test.runTests(availableTests.filter(function(op) { ++ return op.name == urlParams.get('subtest'); ++})); diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h --- a/extensions/browser/extension_event_histogram_value.h +++ b/extensions/browser/extension_event_histogram_value.h @@ -4084,7 +5483,7 @@ diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/b #ifndef EXTENSIONS_BROWSER_EXTENSION_EVENT_HISTOGRAM_VALUE_H_ #define EXTENSIONS_BROWSER_EXTENSION_EVENT_HISTOGRAM_VALUE_H_ -@@ -40,7 +44,8 @@ enum HistogramValue { +@@ -42,7 +46,8 @@ enum HistogramValue { APP_WINDOW_ON_MAXIMIZED = 19, APP_WINDOW_ON_MINIMIZED = 20, APP_WINDOW_ON_RESTORED = 21, @@ -4117,31 +5516,24 @@ diff --git a/extensions/common/mojom/api_permission_id.mojom b/extensions/common // Add new entries at the end of the enum and be sure to update the // "ExtensionPermission3" enum in -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 -@@ -766,6 +766,7 @@ Called by update_extension_histograms.py.--> - - - -+ -
+diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml +--- a/tools/metrics/histograms/enums.xml ++++ b/tools/metrics/histograms/enums.xml +@@ -17,6 +17,9 @@ https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histogra + Please pretty-print and validate your edits by running the pretty_print.py + and validate_format.py scripts in the same directory as this file before + uploading your change for review. These are checked by presubmit scripts. ++ ++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. + --> - -@@ -3176,6 +3177,8 @@ Called by update_extension_permission.py.--> - - - -+ -+ - - - + diff --git a/tools/typescript/definitions/adblock_private.d.ts b/tools/typescript/definitions/adblock_private.d.ts new file mode 100644 --- /dev/null +++ b/tools/typescript/definitions/adblock_private.d.ts -@@ -0,0 +1,172 @@ +@@ -0,0 +1,182 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -4250,6 +5642,16 @@ new file mode 100644 + ) => void, + ): void; + ++ export function setAutoInstallEnabled( ++ enabled: boolean, ++ ): void; ++ ++ export function isAutoInstallEnabled( ++ callback: ( ++ result: boolean, ++ ) => void, ++ ): void; ++ + export function getBuiltInSubscriptions( + callback: ( + result: BuiltInSubscription[], @@ -4318,7 +5720,7 @@ diff --git a/tools/typescript/definitions/eyeo_filtering_private.d.ts b/tools/ty new file mode 100644 --- /dev/null +++ b/tools/typescript/definitions/eyeo_filtering_private.d.ts -@@ -0,0 +1,249 @@ +@@ -0,0 +1,266 @@ +// This file is part of eyeo Chromium SDK, +// Copyright (C) 2006-present eyeo GmbH +// @@ -4443,6 +5845,23 @@ new file mode 100644 + ) => void, + ): void; + ++ export function setAutoInstallEnabled( ++ enabled: boolean, ++ ): Promise; ++ ++ export function setAutoInstallEnabled( ++ enabled: boolean, ++ status?: () => void, ++ ): void; ++ ++ export function isAutoInstallEnabled(): Promise; ++ ++ export function isAutoInstallEnabled( ++ callback?: ( ++ result: boolean, ++ ) => void, ++ ): void; ++ + export function addAllowedDomain( + configuration: string, + domain: string, @@ -4520,23 +5939,23 @@ new file mode 100644 + ) => void, + ): void; + -+ export function subscribeToFilterList( ++ export function addFilterList( + configuration: string, + url: string, + ): Promise; + -+ export function subscribeToFilterList( ++ export function addFilterList( + configuration: string, + url: string, + status?: () => void, + ): void; + -+ export function unsubscribeFromFilterList( ++ export function removeFilterList( + configuration: string, + url: string, + ): Promise; + -+ export function unsubscribeFromFilterList( ++ export function removeFilterList( + configuration: string, + url: string, + status?: () => void, diff --git a/build/cromite_patches/eyeo-beta-118.0.5993.48-base.patch b/build/cromite_patches/eyeo-beta-118.0.5993.48-base.patch deleted file mode 100644 index fa2e478348ca29e396088492d67333e63f9da9b1..0000000000000000000000000000000000000000 --- a/build/cromite_patches/eyeo-beta-118.0.5993.48-base.patch +++ /dev/null @@ -1,53091 +0,0 @@ -From: chromium-sdk -Date: Thu, 12 Oct 2023 14:46:00 +0200 -Subject: eyeo Browser Ad filtering Solution: Base Module - -Based on Chromium 118.0.5993.48 ---- - DEPS | 22 + - LICENSE | 15 + - README.md | 12 + - base/trace_event/builtin_categories.h | 1 + - build/install-build-deps.py | 10 +- - components/BUILD.gn | 11 + - components/adblock/CHANGELOG.md | 635 ++++++++ - components/adblock/LICENSE | 674 ++++++++ - components/adblock/README.md | 80 + - components/adblock/content/BUILD.gn | 22 + - components/adblock/content/browser/BUILD.gn | 136 ++ - .../adblock_controller_factory_base.cc | 88 + - .../browser/adblock_controller_factory_base.h | 50 + - .../content/browser/adblock_filter_match.h | 30 + - .../adblock_telemetry_service_factory_base.cc | 100 ++ - .../adblock_telemetry_service_factory_base.h | 54 + - .../browser/adblock_url_loader_factory.cc | 734 +++++++++ - .../browser/adblock_url_loader_factory.h | 100 ++ - .../adblock_url_loader_factory_for_test.cc | 436 +++++ - .../adblock_url_loader_factory_for_test.h | 70 + - .../browser/adblock_webcontents_observer.cc | 188 +++ - .../browser/adblock_webcontents_observer.h | 85 + - .../content_security_policy_injector.h | 61 + - .../content_security_policy_injector_impl.cc | 126 ++ - .../content_security_policy_injector_impl.h | 66 + - .../adblock/content/browser/element_hider.h | 70 + - .../content/browser/element_hider_impl.cc | 326 ++++ - .../content/browser/element_hider_impl.h | 55 + - .../content/browser/element_hider_info.cc | 37 + - .../content/browser/element_hider_info.h | 44 + - .../browser/frame_hierarchy_builder.cc | 117 ++ - .../content/browser/frame_hierarchy_builder.h | 63 + - .../content/browser/frame_opener_info.cc | 37 + - .../content/browser/frame_opener_info.h | 46 + - .../browser/resource_classification_runner.h | 113 ++ - .../resource_classification_runner_impl.cc | 451 ++++++ - .../resource_classification_runner_impl.h | 156 ++ - .../content/browser/session_stats_impl.cc | 86 + - .../content/browser/session_stats_impl.h | 71 + - .../subscription_service_factory_base.cc | 225 +++ - .../subscription_service_factory_base.h | 60 + - components/adblock/core/BUILD.gn | 175 ++ - .../activeping_telemetry_topic_provider.cc | 285 ++++ - .../activeping_telemetry_topic_provider.h | 87 + - components/adblock/core/adblock_controller.h | 64 + - .../adblock/core/adblock_controller_impl.cc | 270 ++++ - .../adblock/core/adblock_controller_impl.h | 95 ++ - components/adblock/core/adblock_switches.cc | 26 + - components/adblock/core/adblock_switches.h | 29 + - .../adblock/core/adblock_telemetry_service.cc | 258 +++ - .../adblock/core/adblock_telemetry_service.h | 120 ++ - components/adblock/core/classifier/BUILD.gn | 76 + - .../core/classifier/resource_classifier.cc | 24 + - .../core/classifier/resource_classifier.h | 90 ++ - .../classifier/resource_classifier_impl.cc | 404 +++++ - .../classifier/resource_classifier_impl.h | 64 + - components/adblock/core/common/BUILD.gn | 110 ++ - .../adblock/core/common/adblock_constants.cc | 164 ++ - .../adblock/core/common/adblock_constants.h | 47 + - .../adblock/core/common/adblock_prefs.cc | 122 ++ - .../adblock/core/common/adblock_prefs.h | 46 + - .../adblock/core/common/adblock_utils.cc | 171 ++ - .../adblock/core/common/adblock_utils.h | 80 + - .../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 | 33 + - .../core/common/regex_filter_pattern.h | 32 + - components/adblock/core/common/sitekey.h | 29 + - .../adblock/core/configuration/BUILD.gn | 59 + - .../configuration/filtering_configuration.h | 89 + - .../filtering_configuration_prefs.cc | 33 + - .../filtering_configuration_prefs.h | 30 + - .../persistent_filtering_configuration.cc | 273 ++++ - .../persistent_filtering_configuration.h | 86 + - components/adblock/core/converter/BUILD.gn | 83 + - .../adblock/core/converter/converter_main.cc | 115 ++ - .../core/converter/flatbuffer_converter.cc | 133 ++ - .../core/converter/flatbuffer_converter.h | 55 + - .../adblock/core/converter/parser/BUILD.gn | 66 + - .../core/converter/parser/content_filter.cc | 68 + - .../core/converter/parser/content_filter.h | 49 + - .../core/converter/parser/domain_option.cc | 111 ++ - .../core/converter/parser/domain_option.h | 57 + - .../converter/parser/filter_classifier.cc | 39 + - .../core/converter/parser/filter_classifier.h | 45 + - .../adblock/core/converter/parser/metadata.cc | 144 ++ - .../adblock/core/converter/parser/metadata.h | 64 + - .../core/converter/parser/snippet_filter.cc | 62 + - .../core/converter/parser/snippet_filter.h | 46 + - .../converter/parser/snippet_tokenizer.cc | 103 ++ - .../core/converter/parser/snippet_tokenizer.h | 45 + - .../core/converter/parser/url_filter.cc | 218 +++ - .../core/converter/parser/url_filter.h | 48 + - .../converter/parser/url_filter_options.cc | 263 +++ - .../converter/parser/url_filter_options.h | 122 ++ - .../core/converter/serializer/BUILD.gn | 50 + - .../serializer/filter_keyword_extractor.cc | 63 + - .../serializer/filter_keyword_extractor.h | 58 + - .../serializer/flatbuffer_serializer.cc | 397 +++++ - .../serializer/flatbuffer_serializer.h | 129 ++ - .../core/converter/serializer/serializer.h | 41 + - components/adblock/core/features.cc | 25 + - components/adblock/core/features.h | 31 + - .../core/schema/filter_list_schema.fbs | 165 ++ - components/adblock/core/session_stats.h | 42 + - components/adblock/core/sitekey_storage.h | 53 + - .../adblock/core/sitekey_storage_impl.cc | 146 ++ - .../adblock/core/sitekey_storage_impl.h | 59 + - components/adblock/core/subscription/BUILD.gn | 178 ++ - .../core/subscription/conversion_executors.h | 50 + - .../core/subscription/domain_splitter.cc | 46 + - .../core/subscription/domain_splitter.h | 43 + - .../filtering_configuration_maintainer.h | 52 + - ...filtering_configuration_maintainer_impl.cc | 489 ++++++ - .../filtering_configuration_maintainer_impl.h | 119 ++ - .../subscription/installed_subscription.cc | 42 + - .../subscription/installed_subscription.h | 137 ++ - .../installed_subscription_impl.cc | 551 +++++++ - .../installed_subscription_impl.h | 149 ++ - .../ongoing_subscription_request.h | 50 + - .../ongoing_subscription_request_impl.cc | 190 +++ - .../ongoing_subscription_request_impl.h | 74 + - .../core/subscription/pattern_matcher.cc | 263 +++ - .../core/subscription/pattern_matcher.h | 33 + - .../preloaded_subscription_provider.h | 56 + - .../preloaded_subscription_provider_impl.cc | 119 ++ - .../preloaded_subscription_provider_impl.h | 50 + - .../core/subscription/regex_matcher.cc | 162 ++ - .../adblock/core/subscription/regex_matcher.h | 73 + - .../adblock/core/subscription/subscription.cc | 24 + - .../adblock/core/subscription/subscription.h | 78 + - .../subscription/subscription_collection.h | 97 ++ - .../subscription_collection_impl.cc | 352 ++++ - .../subscription_collection_impl.h | 97 ++ - .../core/subscription/subscription_config.cc | 338 ++++ - .../core/subscription/subscription_config.h | 120 ++ - .../subscription/subscription_downloader.h | 66 + - .../subscription_downloader_impl.cc | 287 ++++ - .../subscription_downloader_impl.h | 102 ++ - .../subscription_persistent_metadata.h | 86 + - .../subscription_persistent_metadata_impl.cc | 170 ++ - .../subscription_persistent_metadata_impl.h | 62 + - .../subscription_persistent_storage.h | 60 + - .../subscription_persistent_storage_impl.cc | 229 +++ - .../subscription_persistent_storage_impl.h | 80 + - .../core/subscription/subscription_service.h | 87 + - .../subscription/subscription_service_impl.cc | 210 +++ - .../subscription/subscription_service_impl.h | 97 ++ - .../core/subscription/subscription_updater.h | 35 + - .../subscription/subscription_updater_impl.cc | 65 + - .../subscription/subscription_updater_impl.h | 52 + - .../subscription/subscription_validator.h | 59 + - .../subscription_validator_impl.cc | 144 ++ - .../subscription_validator_impl.h | 53 + - .../subscription/url_keyword_extractor.cc | 66 + - .../core/subscription/url_keyword_extractor.h | 59 + - .../adblock/docs/ad-filtering/README.md | 18 + - .../ad-filtering/element-hiding-sequence.png | Bin 0 -> 177168 bytes - .../ad-filtering/element-hiding-sequence.txt | 32 + - .../docs/ad-filtering/element-hiding.md | 11 + - .../adblock/docs/ad-filtering/filter-lists.md | 42 + - .../request-filter-matching-sequence.png | Bin 0 -> 140660 bytes - .../request_filter_matching_sequence.txt | 28 + - .../docs/ad-filtering/resource-filtering.md | 28 + - .../response-filter-matching-sequence.png | Bin 0 -> 160751 bytes - .../response_filter_matching_sequence.txt | 30 + - .../adblock/docs/ad-filtering/snippets.md | 7 + - components/adblock/docs/adr/README.md | 3 + - .../docs/adr/consuming-full-filter-lists.md | 8 + - ...ving-user-counting-to-dedicated-service.md | 5 + - .../adr/not-extending-subresource-filter.md | 13 + - ...ring-filter-lists-in-flatbuffers-format.md | 17 + - .../adblock/docs/data-collection/README.md | 11 + - .../docs/data-collection/user-counting.md | 38 + - components/adblock/docs/design-overview.md | 32 + - components/adblock/docs/developer-notes.md | 117 ++ - .../adblock/docs/initialization-sequence.png | Bin 0 -> 129976 bytes - .../adblock/docs/initialization-sequence.txt | 27 + - components/adblock/docs/integration-how-to.md | 216 +++ - components/adblock/docs/settings/README.md | 46 + - .../docs/settings/user-setting-sequence.png | Bin 0 -> 61104 bytes - .../docs/settings/user-setting-sequence.txt | 14 + - components/adblock/features.gni | 47 + - components/resources/BUILD.gn | 5 + - components/resources/adblock_resources.grdp | 27 + - components/resources/adblocking/.gitignore | 1 + - components/resources/adblocking/BUILD.gn | 82 + - components/resources/adblocking/LICENSE | 14 + - components/resources/adblocking/anticv.txt.gz | Bin 0 -> 66744 bytes - .../resources/adblocking/easylist.txt.gz | Bin 0 -> 124964 bytes - components/resources/adblocking/elemhide.js | 43 + - .../adblocking/elemhide_for_selector.jst | 53 + - .../resources/adblocking/elemhideemu.jst | 1436 +++++++++++++++++ - .../adblocking/exceptionrules.txt.gz | Bin 0 -> 846414 bytes - components/resources/adblocking/update.sh | 33 + - components/resources/components_resources.grd | 6 + - .../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 + - ...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/exported/web_document.cc | 48 + - .../core/frame/local_frame_mojo_handler.cc | 8 + - .../core/frame/local_frame_mojo_handler.h | 5 + - .../url_loader/mojo_url_loader_client.cc | 6 +- - tools/eyeo/generate_interdiffs.sh | 150 ++ - tools/eyeo/generate_modules.sh | 209 +++ - tools/eyeo/snippets_deps.py | 61 + - tools/gritsettings/resource_ids.spec | 11 +- - 219 files changed, 22023 insertions(+), 4 deletions(-) - 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_controller_factory_base.cc - create mode 100644 components/adblock/content/browser/adblock_controller_factory_base.h - create mode 100644 components/adblock/content/browser/adblock_filter_match.h - create mode 100644 components/adblock/content/browser/adblock_telemetry_service_factory_base.cc - create mode 100644 components/adblock/content/browser/adblock_telemetry_service_factory_base.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_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/element_hider_info.cc - create mode 100644 components/adblock/content/browser/element_hider_info.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/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/subscription_service_factory_base.cc - create mode 100644 components/adblock/content/browser/subscription_service_factory_base.h - 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_controller.h - create mode 100644 components/adblock/core/adblock_controller_impl.cc - create mode 100644 components/adblock/core/adblock_controller_impl.h - create mode 100644 components/adblock/core/adblock_switches.cc - create mode 100644 components/adblock/core/adblock_switches.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/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_utils.cc - create mode 100644 components/adblock/core/common/adblock_utils.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/configuration/BUILD.gn - create mode 100644 components/adblock/core/configuration/filtering_configuration.h - create mode 100644 components/adblock/core/configuration/filtering_configuration_prefs.cc - create mode 100644 components/adblock/core/configuration/filtering_configuration_prefs.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/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/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/features.cc - create mode 100644 components/adblock/core/features.h - create mode 100644 components/adblock/core/schema/filter_list_schema.fbs - 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/ongoing_subscription_request.h - create mode 100644 components/adblock/core/subscription/ongoing_subscription_request_impl.cc - create mode 100644 components/adblock/core/subscription/ongoing_subscription_request_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/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_updater.h - create mode 100644 components/adblock/core/subscription/subscription_updater_impl.cc - create mode 100644 components/adblock/core/subscription/subscription_updater_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/url_keyword_extractor.cc - create mode 100644 components/adblock/core/subscription/url_keyword_extractor.h - create mode 100644 components/adblock/docs/ad-filtering/README.md - create mode 100644 components/adblock/docs/ad-filtering/element-hiding-sequence.png - create mode 100644 components/adblock/docs/ad-filtering/element-hiding-sequence.txt - create mode 100644 components/adblock/docs/ad-filtering/element-hiding.md - create mode 100644 components/adblock/docs/ad-filtering/filter-lists.md - create mode 100644 components/adblock/docs/ad-filtering/request-filter-matching-sequence.png - create mode 100644 components/adblock/docs/ad-filtering/request_filter_matching_sequence.txt - create mode 100644 components/adblock/docs/ad-filtering/resource-filtering.md - create mode 100644 components/adblock/docs/ad-filtering/response-filter-matching-sequence.png - create mode 100644 components/adblock/docs/ad-filtering/response_filter_matching_sequence.txt - create mode 100644 components/adblock/docs/ad-filtering/snippets.md - create mode 100644 components/adblock/docs/adr/README.md - create mode 100644 components/adblock/docs/adr/consuming-full-filter-lists.md - create mode 100644 components/adblock/docs/adr/moving-user-counting-to-dedicated-service.md - create mode 100644 components/adblock/docs/adr/not-extending-subresource-filter.md - create mode 100644 components/adblock/docs/adr/storing-filter-lists-in-flatbuffers-format.md - create mode 100644 components/adblock/docs/data-collection/README.md - create mode 100644 components/adblock/docs/data-collection/user-counting.md - create mode 100644 components/adblock/docs/design-overview.md - create mode 100644 components/adblock/docs/developer-notes.md - create mode 100644 components/adblock/docs/initialization-sequence.png - create mode 100644 components/adblock/docs/initialization-sequence.txt - create mode 100644 components/adblock/docs/integration-how-to.md - create mode 100644 components/adblock/docs/settings/README.md - create mode 100644 components/adblock/docs/settings/user-setting-sequence.png - create mode 100644 components/adblock/docs/settings/user-setting-sequence.txt - create mode 100644 components/adblock/features.gni - create mode 100644 components/resources/adblock_resources.grdp - create mode 100644 components/resources/adblocking/.gitignore - create mode 100644 components/resources/adblocking/BUILD.gn - create mode 100644 components/resources/adblocking/LICENSE - create mode 100644 components/resources/adblocking/anticv.txt.gz - create mode 100644 components/resources/adblocking/easylist.txt.gz - create mode 100644 components/resources/adblocking/elemhide.js - create mode 100644 components/resources/adblocking/elemhide_for_selector.jst - create mode 100644 components/resources/adblocking/elemhideemu.jst - create mode 100644 components/resources/adblocking/exceptionrules.txt.gz - create mode 100755 components/resources/adblocking/update.sh - create mode 100755 tools/eyeo/generate_interdiffs.sh - create mode 100755 tools/eyeo/generate_modules.sh - create mode 100644 tools/eyeo/snippets_deps.py - -diff --git a/DEPS b/DEPS ---- a/DEPS -+++ b/DEPS -@@ -168,6 +168,10 @@ vars = { - # flag is set True. - 'checkout_wpr_archives': False, - -+ # Eyeo WPR archives are kept separately from Chromium WPRs due to -+ # access restrictions (authenticated Google Cloud Storage). -+ 'checkout_eyeo_wpr_archives': False, -+ - # By default, do not check out WebKit for iOS, as it is not needed unless - # running against ToT WebKit rather than system WebKit. This can be overridden - # e.g. with custom_vars. -@@ -262,6 +266,8 @@ vars = { - 'download_libvpx_testdata': False, - - 'android_git': 'https://android.googlesource.com', -+ 'eyeo_distpartners_gitlab': 'https://gitlab.com/eyeo/distpartners', -+ 'eyeo_snippets_gitlab': 'https://gitlab.com/eyeo/anti-cv/snippets.git', - 'aomedia_git': 'https://aomedia.googlesource.com', - 'boringssl_git': 'https://boringssl.googlesource.com', - 'chrome_git': 'https://chrome-internal.googlesource.com', -@@ -498,6 +504,11 @@ vars = { - # and whatever else without interference from each other. - 'llvm_libc_revision': '6d0c8ee02e2fd44e69ac30e721e13be463035ee5', - -+ # 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': 'v0.8.1', -+ - # If you change this, also update the libc++ revision in - # //buildtools/deps_revisions.gni. - 'libcxx_revision': '2e25154d49c29fa9aa42c30ad4a027bd30123434', -@@ -1456,6 +1467,10 @@ deps = { - 'condition': 'checkout_android and checkout_src_internal', - }, - -+ 'src/components/resources/adblocking/snippets': { -+ 'url': Var('eyeo_snippets_gitlab') + '@' + Var('eyeo_snippets_revision'), -+ }, -+ - 'src/docs/website': { - 'url': Var('chromium_git') + '/website.git' + '@' + '600fc3a0b121d5007b4bb97b001e756625e6d418', - }, -@@ -1596,6 +1611,13 @@ deps = { - 'dep_type': 'cipd', - }, - -+ 'src/tools/perf/eyeo_data': { -+ 'condition': -+ 'checkout_eyeo_wpr_archives', -+ 'url': -+ Var('eyeo_distpartners_gitlab') + '/web-page-recordings.git@69fd10f2076334d10b2dd938ae84f8cc7c0c9018', -+ }, -+ - 'src/third_party/android_toolchain/ndk': { - 'packages': [ - { -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 . -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/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 -@@ -97,6 +97,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/build/install-build-deps.py b/build/install-build-deps.py ---- a/build/install-build-deps.py -+++ b/build/install-build-deps.py -@@ -8,6 +8,14 @@ - # including items requiring sudo privileges. - # See https://chromium.googlesource.com/chromium/src/+/main/docs/linux/build_instructions.md - -+# eyeo Chromium SDK is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with eyeo Chromium SDK. If not, see . -+ - import argparse - import functools - import os -@@ -562,7 +570,7 @@ def backwards_compatible_list(options): - "msttcorefonts", - "python-dev", - "python-setuptools", -- "snapcraft", -+ #"snapcraft", - "ttf-dejavu-core", - "ttf-indic-fonts", - "ttf-kochi-gothic", -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") -@@ -177,6 +180,14 @@ test("components_unittests") { - - deps = [ - "//base", -+ "//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/converter:unit_tests", -+ "//components/adblock/core/subscription:unit_tests", - "//components/affiliations/core/browser:unit_tests", - "//components/aggregation_service:unit_tests", - "//components/apdu: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,635 @@ -+# Release Notes -+ -+## 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 (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. -+ 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. -+ -+ -+ Copyright (C) -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 . -+ -+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: -+ -+ Copyright (C) -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 -+. -+ -+ 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 -+. -\ 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 . -+ -+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,136 @@ -+# -+# This file is part of eyeo Chromium SDK, -+# Copyright (C) 2006-present eyeo GmbH -+# -+# eyeo Chromium SDK is free software: you can redistribute it and/or modify -+# it under the terms of the GNU General Public License version 3 as -+# published by the Free Software Foundation. -+# -+# eyeo Chromium SDK is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with eyeo Chromium SDK. If not, see . -+ -+import("//components/adblock/features.gni") -+ -+config("adblock_content_common_config") { -+ defines = [] -+ -+ if (eyeo_intercept_debug_url) { -+ print("WARNING! Enabled intercepting eyeo debug url \"adblock.test.data\"") -+ defines += [ "EYEO_INTERCEPT_DEBUG_URL=1" ] -+ } -+} -+ -+source_set("browser_impl") { -+ visibility = [ -+ ":*", -+ "//components/adblock/content:*", -+ ] -+ sources = [ -+ "adblock_controller_factory_base.cc", -+ "adblock_controller_factory_base.h", -+ "adblock_filter_match.h", -+ "adblock_telemetry_service_factory_base.cc", -+ "adblock_telemetry_service_factory_base.h", -+ "adblock_url_loader_factory.cc", -+ "adblock_url_loader_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", -+ "element_hider_info.cc", -+ "element_hider_info.h", -+ "frame_hierarchy_builder.cc", -+ "frame_hierarchy_builder.h", -+ "frame_opener_info.cc", -+ "frame_opener_info.h", -+ "resource_classification_runner.h", -+ "resource_classification_runner_impl.cc", -+ "resource_classification_runner_impl.h", -+ "session_stats_impl.cc", -+ "session_stats_impl.h", -+ "subscription_service_factory_base.cc", -+ "subscription_service_factory_base.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/core/converter:converter", -+ "//components/keyed_service/content:content", -+ "//components/resources:components_resources_grit", -+ "//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/resource_classification_runner_impl_test.cc", -+ "test/session_stats_impl_test.cc", -+ ] -+ -+ deps = [ -+ "//base/test:test_support", -+ "//components/adblock/core/classifier:test_support", -+ "//components/adblock/core/subscription:test_support", -+ "//components/adblock/core:test_support", -+ "//components/adblock/core:test_support", -+ "//components/prefs:test_support", -+ "//components/resources:components_resources_grit", -+ "//components/sync_preferences:test_support", -+ "//content/test:test_support", -+ "//net:test_support", -+ "//services/network:test_support", -+ "//ui/base:test_support", -+ ":test_support", -+ ] -+} -diff --git a/components/adblock/content/browser/adblock_controller_factory_base.cc b/components/adblock/content/browser/adblock_controller_factory_base.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/adblock_controller_factory_base.cc -@@ -0,0 +1,88 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/content/browser/adblock_controller_factory_base.h" -+ -+#include -+ -+#include "base/command_line.h" -+#include "components/adblock/core/adblock_controller_impl.h" -+#include "components/adblock/core/adblock_switches.h" -+#include "components/adblock/core/common/adblock_constants.h" -+#include "components/adblock/core/common/adblock_prefs.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/pref_registry/pref_registry_syncable.h" -+#include "components/version_info/version_info.h" -+#include "content/public/browser/browser_context.h" -+ -+namespace adblock { -+ -+AdblockControllerFactoryBase::~AdblockControllerFactoryBase() = default; -+ -+AdblockControllerFactoryBase::AdblockControllerFactoryBase() -+ : BrowserContextKeyedServiceFactory( -+ "AdblockController", -+ BrowserContextDependencyManager::GetInstance()) {} -+ -+std::unique_ptr -+AdblockControllerFactoryBase::BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const { -+ auto* prefs = GetPrefs(context); -+ auto adblock_filtering_configuration = -+ std::make_unique( -+ prefs, kAdblockFilteringConfigurationName); -+ -+ if (base::CommandLine::ForCurrentProcess()->HasSwitch( -+ adblock::switches::kDisableAcceptableAds)) { -+ adblock_filtering_configuration->RemoveFilterList(AcceptableAdsUrl()); -+ } -+ if (base::CommandLine::ForCurrentProcess()->HasSwitch( -+ switches::kDisableAdblock) || -+ base::CommandLine::ForCurrentProcess()->HasSwitch( -+ switches::kDisableEyeoFiltering)) { -+ adblock_filtering_configuration->SetEnabled(false); -+ } -+ -+ auto* subscription_service = GetSubscriptionService(context); -+ auto controller = std::make_unique( -+ adblock_filtering_configuration.get(), subscription_service, GetLocale(), -+ config::GetKnownSubscriptions()); -+ controller->RunFirstRunLogic(prefs); -+ controller->MigrateLegacyPrefs(prefs); -+ -+ subscription_service->InstallFilteringConfiguration( -+ std::move(adblock_filtering_configuration)); -+ -+ return controller; -+} -+ -+void AdblockControllerFactoryBase::RegisterProfilePrefs( -+ user_prefs::PrefRegistrySyncable* registry) { -+ adblock::common::prefs::RegisterProfilePrefs(registry); -+} -+ -+bool AdblockControllerFactoryBase::ServiceIsNULLWhileTesting() const { -+ return true; -+} -+ -+bool AdblockControllerFactoryBase::ServiceIsCreatedWithBrowserContext() const { -+ return true; -+} -+ -+} // namespace adblock -diff --git a/components/adblock/content/browser/adblock_controller_factory_base.h b/components/adblock/content/browser/adblock_controller_factory_base.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/adblock_controller_factory_base.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTROLLER_FACTORY_BASE_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTROLLER_FACTORY_BASE_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" -+#include "content/public/browser/browser_context.h" -+ -+namespace adblock { -+ -+class SubscriptionService; -+class AdblockControllerFactoryBase : public BrowserContextKeyedServiceFactory { -+ protected: -+ virtual PrefService* GetPrefs(content::BrowserContext* context) const = 0; -+ virtual const std::string& GetLocale() const = 0; -+ virtual SubscriptionService* GetSubscriptionService( -+ content::BrowserContext* context) const = 0; -+ AdblockControllerFactoryBase(); -+ ~AdblockControllerFactoryBase() override; -+ -+ private: -+ // BrowserContextKeyedServiceFactory: -+ std::unique_ptr BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const override; -+ void RegisterProfilePrefs( -+ user_prefs::PrefRegistrySyncable* registry) override; -+ bool ServiceIsNULLWhileTesting() const override; -+ bool ServiceIsCreatedWithBrowserContext() const override; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_CONTROLLER_FACTORY_BASE_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 . -+ */ -+#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; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_FILTER_MATCH_H_ -diff --git a/components/adblock/content/browser/adblock_telemetry_service_factory_base.cc b/components/adblock/content/browser/adblock_telemetry_service_factory_base.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/adblock_telemetry_service_factory_base.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 . -+ */ -+ -+#include "components/adblock/content/browser/adblock_telemetry_service_factory_base.h" -+ -+#include -+ -+#include "components/adblock/core/activeping_telemetry_topic_provider.h" -+#include "components/adblock/core/adblock_telemetry_service.h" -+#include "components/adblock/core/common/adblock_utils.h" -+#include "components/adblock/core/subscription/subscription_service.h" -+#include "content/public/browser/browser_context.h" -+#include "content/public/browser/storage_partition.h" -+ -+namespace adblock { -+namespace { -+ -+std::optional g_check_interval_for_testing; -+std::optional g_initial_delay_for_testing; -+ -+base::TimeDelta GetInitialDelay() { -+ static base::TimeDelta kInitialDelay = -+ g_initial_delay_for_testing ? g_initial_delay_for_testing.value() -+ : base::Seconds(30); -+ return kInitialDelay; -+} -+ -+base::TimeDelta GetCheckInterval() { -+ static base::TimeDelta kCheckInterval = -+ g_check_interval_for_testing ? g_check_interval_for_testing.value() -+ : base::Minutes(5); -+ return kCheckInterval; -+} -+ -+} // namespace -+ -+AdblockTelemetryServiceFactoryBase::AdblockTelemetryServiceFactoryBase() -+ : BrowserContextKeyedServiceFactory( -+ "AdblockTelemetryService", -+ BrowserContextDependencyManager::GetInstance()) {} -+ -+AdblockTelemetryServiceFactoryBase::~AdblockTelemetryServiceFactoryBase() = -+ default; -+ -+KeyedService* AdblockTelemetryServiceFactoryBase::BuildServiceInstanceFor( -+ 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 url_loader_factory = -+ context->GetDefaultStoragePartition() -+ ->GetURLLoaderFactoryForBrowserProcess(); -+ auto* prefs = GetPrefs(context); -+ auto service = std::make_unique( -+ GetSubscriptionService(context)->GetAdblockFilteringConfiguration(), -+ url_loader_factory, GetInitialDelay(), GetCheckInterval()); -+ service->AddTopicProvider(std::make_unique( -+ utils::GetAppInfo(), prefs, GetSubscriptionService(context), -+ ActivepingTelemetryTopicProvider::DefaultBaseUrl(), -+ ActivepingTelemetryTopicProvider::DefaultAuthToken())); -+ -+ if (url_loader_factory) { -+ service->Start(); -+ } -+ -+ return service.release(); -+} -+ -+bool AdblockTelemetryServiceFactoryBase::ServiceIsNULLWhileTesting() const { -+ return true; -+} -+ -+bool AdblockTelemetryServiceFactoryBase::ServiceIsCreatedWithBrowserContext() -+ const { -+ return true; -+} -+ -+void AdblockTelemetryServiceFactoryBase::SetCheckAndDelayIntervalsForTesting( -+ base::TimeDelta check_interval, -+ base::TimeDelta initial_delay) { -+ g_check_interval_for_testing = check_interval; -+ g_initial_delay_for_testing = initial_delay; -+} -+ -+} // namespace adblock -diff --git a/components/adblock/content/browser/adblock_telemetry_service_factory_base.h b/components/adblock/content/browser/adblock_telemetry_service_factory_base.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/adblock_telemetry_service_factory_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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_TELEMETRY_SERVICE_FACTORY_BASE_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_TELEMETRY_SERVICE_FACTORY_BASE_H_ -+ -+#include "base/time/time.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 AdblockTelemetryServiceFactoryBase -+ : public BrowserContextKeyedServiceFactory { -+ public: -+ // Sets the initial delay and interval checks required for browser tests. -+ // Must be called before BuildServiceInstanceFor(). -+ void SetCheckAndDelayIntervalsForTesting(base::TimeDelta check_interval, -+ base::TimeDelta initial_delay); -+ -+ protected: -+ virtual PrefService* GetPrefs(content::BrowserContext* context) const = 0; -+ virtual SubscriptionService* GetSubscriptionService( -+ content::BrowserContext* context) const = 0; -+ AdblockTelemetryServiceFactoryBase(); -+ ~AdblockTelemetryServiceFactoryBase() override; -+ -+ private: -+ // BrowserContextKeyedServiceFactory: -+ KeyedService* BuildServiceInstanceFor( -+ content::BrowserContext* context) const override; -+ bool ServiceIsNULLWhileTesting() const override; -+ bool ServiceIsCreatedWithBrowserContext() const override; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_TELEMETRY_SERVICE_FACTORY_BASE_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,734 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/frame_opener_info.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; -+} -+ -+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: -+ case network::mojom::RequestDestination::kSpeculationRules: -+ 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::kJson: -+ 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::kReport: -+ case network::mojom::RequestDestination::kAudioWorklet: -+ case network::mojom::RequestDestination::kDictionary: -+ case network::mojom::RequestDestination::kManifest: -+ case network::mojom::RequestDestination::kPaintWorklet: -+ case network::mojom::RequestDestination::kWebIdentity: -+ return ContentType::Other; -+ } -+ NOTREACHED(); -+ return ContentType::Other; -+} -+ -+} // namespace -+ -+class AdblockURLLoaderFactory::InProgressRequest -+ : public network::mojom::URLLoader, -+ public network::mojom::URLLoaderClient { -+ public: -+ InProgressRequest( -+ AdblockURLLoaderFactory* factory, -+ mojo::PendingReceiver loader_receiver, -+ int32_t request_id, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ mojo::PendingRemote client, -+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation); -+ -+ void FollowRedirect( -+ const std::vector& removed_headers, -+ const net::HttpRequestHeaders& modified_headers, -+ const net::HttpRequestHeaders& modified_cors_exempt_headers, -+ const std::optional& 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, -+ std::optional 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; -+ using CheckRewriteFilterMatchCallback = -+ base::OnceCallback&)>; -+ -+ void OnBindingsClosed(); -+ void OnClientDisconnected(); -+ void Start(uint32_t options, -+ network::ResourceRequest request, -+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, -+ mojo::PendingReceiver target_loader, -+ mojo::PendingRemote proxy_client, -+ const std::optional& rewrite); -+ void OnRequestFilterMatchResult( -+ ::mojo::PendingReceiver loader, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ ::mojo::PendingRemote 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, -+ std::optional cached_metadata, -+ FilterMatchResult result, -+ network::mojom::ParsedHeadersPtr parsed_headers); -+ void OnRequestError(int error_code); -+ void CheckFilterMatch(CheckFilterMatchCallback callback); -+ void ProcessResponseHeaders( -+ const scoped_refptr& headers, -+ ProcessResponseHeadersCallback callback); -+ void CheckRewriteFilterMatch(CheckRewriteFilterMatchCallback callback); -+ void OnRequestUrlClassified(CheckFilterMatchCallback callback, -+ FilterMatchResult result); -+ void OnResponseHeadersClassified( -+ const scoped_refptr& 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&)> callback, -+ const std::optional& url); -+ -+ GURL request_url_; -+ int request_id_; -+ bool is_document_request_; -+ ContentType adblock_resource_type_; -+ const raw_ptr 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 target_client_; -+ mojo::Receiver loader_receiver_; -+ // 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 target_loader_; -+ mojo::Receiver client_receiver_{this}; -+ base::WeakPtrFactory -+ weak_factory_{this}; -+}; -+ -+AdblockURLLoaderFactory::InProgressRequest::InProgressRequest( -+ AdblockURLLoaderFactory* factory, -+ mojo::PendingReceiver loader_receiver, -+ int32_t request_id, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ mojo::PendingRemote 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)) { -+ CheckRewriteFilterMatch(base::BindOnce( -+ &InProgressRequest::Start, weak_factory_.GetWeakPtr(), options, request, -+ traffic_annotation, target_loader_.BindNewPipeAndPassReceiver(), -+ client_receiver_.BindNewPipeAndPassRemote())); -+ -+ // 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& removed_headers, -+ const net::HttpRequestHeaders& modified_headers, -+ const net::HttpRequestHeaders& modified_cors_exempt_headers, -+ const std::optional& 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, -+ std::optional cached_metadata) { -+ if (net::IsLocalhost(request_url_) || (!request_url_.SchemeIsHTTPOrHTTPS() && -+ !request_url_.SchemeIsWSOrWSS())) { -+ VLOG(1) -+ << "[eyeo] Ignoring URL (local url or unsupported scheme), allowing " -+ "load."; -+ target_client_->OnReceiveResponse(std::move(head), std::move(body), -+ std::move(cached_metadata)); -+ return; -+ } -+ -+ VLOG(1) << "[eyeo] Sending headers for processing: " << request_url_; -+ client_receiver_.Pause(); -+ const scoped_refptr& 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, -+ std::optional 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) { -+ if (!factory_->CheckHostValid()) { -+ PostFilterMatchCallbackToUI(std::move(callback), -+ FilterMatchResult::kNoRule); -+ return; -+ } -+ -+ auto subscription_service = factory_->config_.subscription_service; -+ if (is_document_request_) { -+ auto* host = content::RenderFrameHost::FromID(factory_->host_id_); -+ 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. -+ bool is_popup = info && content::RenderFrameHost::FromID(info->GetOpener()); -+ if (is_popup) { -+ factory_->config_.resource_classifier->CheckPopupFilterMatch( -+ subscription_service->GetCurrentSnapshot(), request_url_, -+ factory_->host_id_, -+ base::BindOnce( -+ &AdblockURLLoaderFactory::InProgressRequest:: -+ OnRequestUrlClassified, -+ weak_factory_.GetWeakPtr(), -+ base::BindOnce(&AdblockURLLoaderFactory::InProgressRequest:: -+ PostFilterMatchCallbackToUI, -+ weak_factory_.GetWeakPtr(), std::move(callback)))); -+ } else { -+ factory_->config_.resource_classifier->CheckDocumentAllowlisted( -+ subscription_service->GetCurrentSnapshot(), request_url_, -+ factory_->host_id_); -+ PostFilterMatchCallbackToUI(std::move(callback), -+ FilterMatchResult::kNoRule); -+ } -+ } else { -+ factory_->config_.resource_classifier->CheckRequestFilterMatch( -+ subscription_service->GetCurrentSnapshot(), request_url_, -+ adblock_resource_type_, factory_->host_id_, -+ 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& headers, -+ ProcessResponseHeadersCallback callback) { -+ if (!factory_->CheckHostValid()) { -+ 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_->host_id_, 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 (!factory_->CheckHostValid()) { -+ PostRewriteCallbackToUI(std::move(callback), std::optional{}); -+ return; -+ } -+ -+ auto subscription_service = factory_->config_.subscription_service; -+ factory_->config_.resource_classifier->CheckRewriteFilterMatch( -+ subscription_service->GetCurrentSnapshot(), request_url_, -+ factory_->host_id_, -+ base::BindOnce( -+ &AdblockURLLoaderFactory::InProgressRequest::PostRewriteCallbackToUI, -+ weak_factory_.GetWeakPtr(), std::move(callback))); -+} -+ -+void AdblockURLLoaderFactory::InProgressRequest::OnRequestUrlClassified( -+ CheckFilterMatchCallback callback, -+ FilterMatchResult result) { -+ if (result == FilterMatchResult::kBlockRule) { -+ if (factory_->CheckHostValid()) { -+ if (is_document_request_) { -+ // This path means we classified popup -+ auto* wc = content::WebContents::FromRenderFrameHost( -+ content::RenderFrameHost::FromID(factory_->host_id_)); -+ DCHECK(wc); -+ wc->ClosePage(); -+ } else { -+ ElementHider* element_hider = factory_->config_.element_hider; -+ if (element_hider->IsElementTypeHideable(adblock_resource_type_)) { -+ element_hider->HideBlockedElement( -+ request_url_, -+ content::RenderFrameHost::FromID(factory_->host_id_)); -+ } -+ } -+ } -+ } -+ PostFilterMatchCallbackToUI(std::move(callback), result); -+} -+ -+void AdblockURLLoaderFactory::InProgressRequest::OnResponseHeadersClassified( -+ const scoped_refptr& headers, -+ ProcessResponseHeadersCallback callback, -+ FilterMatchResult result) { -+ if (!factory_->CheckHostValid() || result == FilterMatchResult::kDisabled) { -+ PostResponseHeadersCallbackToUI(std::move(callback), result, nullptr); -+ return; -+ } -+ -+ if (result == FilterMatchResult::kBlockRule) { -+ ElementHider* element_hider = factory_->config_.element_hider; -+ if (element_hider->IsElementTypeHideable(adblock_resource_type_)) { -+ element_hider->HideBlockedElement( -+ request_url_, content::RenderFrameHost::FromID(factory_->host_id_)); -+ } -+ 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_); -+ -+ factory_->config_.csp_injector -+ ->InsertContentSecurityPolicyHeadersIfApplicable( -+ request_url_, factory_->host_id_, 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&)> callback, -+ const std::optional& 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 (net::IsLocalhost(request_url_) || (!request_url_.SchemeIsHTTPOrHTTPS() && -+ !request_url_.SchemeIsWSOrWSS())) { -+ VLOG(1) -+ << "[eyeo] Ignoring URL (local url or unsupported scheme), allowing " -+ "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 (is_document_request_ && factory_->CheckHostValid()) { -+ // This path means we classified popup -+ auto* wc = content::WebContents::FromRenderFrameHost( -+ content::RenderFrameHost::FromID(factory_->host_id_)); -+ DCHECK(wc); -+ wc->ClosePage(); -+ } -+ 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 target_loader, -+ mojo::PendingRemote proxy_client, -+ const std::optional& 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::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 (net::IsLocalhost(request_url_) || (!request_url_.SchemeIsHTTPOrHTTPS() && -+ !request_url_.SchemeIsWSOrWSS())) { -+ VLOG(1) -+ << "[eyeo] Ignoring URL (local url or unsupported scheme), allowing " -+ "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 target_loader, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ ::mojo::PendingRemote 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); -+} -+ -+AdblockURLLoaderFactory::AdblockURLLoaderFactory( -+ AdblockURLLoaderFactoryConfig config, -+ content::GlobalRenderFrameHostId host_id, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote target_factory, -+ std::string user_agent_string, -+ DisconnectCallback on_disconnect) -+ : config_(std::move(config)), -+ host_id_(host_id), -+ 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 loader, -+ int32_t request_id, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ ::mojo::PendingRemote client, -+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { -+ requests_.insert(std::make_unique( -+ this, std::move(loader), request_id, options, request, std::move(client), -+ traffic_annotation)); -+} -+ -+void AdblockURLLoaderFactory::Clone( -+ ::mojo::PendingReceiver factory) { -+ proxy_receivers_.Add(this, std::move(factory)); -+} -+ -+bool AdblockURLLoaderFactory::CheckHostValid() const { -+ return content::RenderFrameHost::FromID(host_id_) != nullptr; -+} -+ -+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 . -+ */ -+ -+#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 "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" -+ -+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 -+ subscription_service = nullptr; -+ raw_ptr -+ resource_classifier = nullptr; -+ raw_ptr element_hider = nullptr; -+ raw_ptr sitekey_storage = -+ nullptr; -+ raw_ptr -+ csp_injector = nullptr; -+}; -+ -+// Processing network requests and responses. -+class AdblockURLLoaderFactory : public network::mojom::URLLoaderFactory { -+ public: -+ using DisconnectCallback = base::OnceCallback; -+ -+ AdblockURLLoaderFactory( -+ AdblockURLLoaderFactoryConfig config, -+ content::GlobalRenderFrameHostId host_id, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote 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 factory) override; -+ -+ virtual bool CheckHostValid() const; -+ -+ private: -+ class InProgressRequest; -+ friend class InProgressRequest; -+ -+ void OnTargetFactoryError(); -+ void OnProxyBindingError(); -+ void RemoveRequest(InProgressRequest* request); -+ void MaybeDestroySelf(); -+ -+ AdblockURLLoaderFactoryConfig config_; -+ content::GlobalRenderFrameHostId host_id_; -+ mojo::ReceiverSet proxy_receivers_; -+ std::set, base::UniquePtrComparator> -+ requests_; -+ mojo::Remote 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,436 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/utf_string_conversions.h" -+#include "components/adblock/core/configuration/filtering_configuration.h" -+#include "components/adblock/core/subscription/subscription_config.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 kTopicFilters[] = "filters"; -+constexpr char kTopicDomains[] = "domains"; -+constexpr char kTopicSubscriptions[] = "subscriptions"; -+constexpr char kTopicAdblock[] = "adblock"; -+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 kHelp[] = R"( -+ Command syntax `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. -+ -+ An example: -+ Call `adblock.test.data/filters/add/%2FadsPlugin%2F%2A%0A%2Fadsponsor.` -+ Then calling `adblock.test.data/filters/list` returns: -+ -+ OK -+ -+ /adsPlugin/* -+ /adsponsor. -+)"; -+ -+constexpr char payload_delimiter[] = "\n"; -+ -+std::string DecodePayload(const std::string& encoded) { -+ // Example how to encode: -+ // url::RawCanonOutputT buffer; -+ // url::EncodeURIComponent(base.c_str(), base.size(), &buffer); -+ // std::string encoded(buffer.data(), buffer.length()); -+ VLOG(2) << "[eyeo] Encoded payload: " << encoded; -+ url::RawCanonOutputT output; -+ url::DecodeURLEscapeSequences(encoded.c_str(), encoded.size(), -+ url::DecodeURLMode::kUTF8OrIsomorphic, &output); -+ std::string decoded = -+ base::UTF16ToUTF8(std::u16string(output.data(), output.length())); -+ VLOG(2) << "[eyeo] Decoded payload: " << decoded; -+ return decoded; -+} -+ -+std::string GetPayload(const std::string& input) { -+ static const char kPayloadParam[] = "payload="; -+ if (base::StartsWith(input, kPayloadParam)) { -+ return input.substr(sizeof(kPayloadParam) - 1); -+ } -+ return ""; -+} -+ -+std::vector GetCommandElements(const GURL& url) { -+ std::vector 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); -+ if (command_elements.size() == 1) { -+ // Check old format -+ if (path == kActionAdd || path == kActionRemove) { -+ auto payload = GetPayload(url.query()); -+ if (payload.length() > 0) { -+ command_elements = {kTopicFilters, -+ path == kActionAdd ? kActionAdd : kActionRemove, -+ payload}; -+ } -+ } -+ } -+ } -+ return command_elements; -+} -+ -+void Clear(SubscriptionService* subscription_service, -+ std::vector (FilteringConfiguration::*getter)() const, -+ void (FilteringConfiguration::*action)(const std::string&)) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ for (const auto& item : (adblock_configuration->*getter)()) { -+ (adblock_configuration->*action)(item); -+ } -+} -+ -+std::vector List( -+ SubscriptionService* subscription_service, -+ std::vector (FilteringConfiguration::*getter)() const) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ return (adblock_configuration->*getter)(); -+} -+ -+void AddOrRemove(SubscriptionService* subscription_service, -+ 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); -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ for (const auto& item : items_list) { -+ (adblock_configuration->*action)(item); -+ } -+} -+ -+void ClearSubscriptions(SubscriptionService* subscription_service) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ for (const auto& subscription : adblock_configuration->GetFilterLists()) { -+ adblock_configuration->RemoveFilterList(subscription); -+ } -+} -+ -+std::vector ListSubscriptions( -+ SubscriptionService* subscription_service) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ std::vector subscriptions; -+ std::ranges::transform( -+ adblock_configuration->GetFilterLists(), -+ std::back_inserter(subscriptions), -+ [](const auto& subscription) { return subscription.spec(); }); -+ return subscriptions; -+} -+ -+void AddOrRemoveSubscription( -+ SubscriptionService* subscription_service, -+ void (FilteringConfiguration::*action)(const GURL&), -+ std::string& items) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ auto items_list = base::SplitString(items, payload_delimiter, -+ base::WhitespaceHandling::TRIM_WHITESPACE, -+ base::SplitResult::SPLIT_WANT_NONEMPTY); -+ for (const auto& item : items_list) { -+ (adblock_configuration->*action)(GURL{item}); -+ } -+} -+ -+bool IsAdblockEnabled(SubscriptionService* subscription_service) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ return adblock_configuration->IsEnabled(); -+} -+ -+void SetAdblockEnabled(SubscriptionService* subscription_service, -+ bool enabled) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ adblock_configuration->SetEnabled(enabled); -+} -+ -+bool IsAAEnabled(SubscriptionService* subscription_service) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ return std::ranges::any_of( -+ adblock_configuration->GetFilterLists(), -+ [&](const auto& url) { return url == AcceptableAdsUrl(); }); -+} -+ -+void SetAAEnabled(SubscriptionService* subscription_service, bool enabled) { -+ auto* adblock_configuration = -+ subscription_service->GetAdblockFilteringConfiguration(); -+ DCHECK(adblock_configuration) -+ << "Action called for non existing \"adblock\" configuration"; -+ enabled ? adblock_configuration->AddFilterList(AcceptableAdsUrl()) -+ : adblock_configuration->RemoveFilterList(AcceptableAdsUrl()); -+} -+ -+} // namespace -+ -+// static -+const std::string AdblockURLLoaderFactoryForTest::kAdblockDebugDataHostName = -+ "adblock.test.data"; -+ -+AdblockURLLoaderFactoryForTest::AdblockURLLoaderFactoryForTest( -+ AdblockURLLoaderFactoryConfig config, -+ content::GlobalRenderFrameHostId host_id, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote target_factory, -+ std::string user_agent_string, -+ DisconnectCallback on_disconnect, -+ SubscriptionService* subscription_service) -+ : AdblockURLLoaderFactory(std::move(config), -+ host_id, -+ std::move(receiver), -+ std::move(target_factory), -+ user_agent_string, -+ std::move(on_disconnect)), -+ subscription_service_(subscription_service) {} -+ -+AdblockURLLoaderFactoryForTest::~AdblockURLLoaderFactoryForTest() = default; -+ -+void AdblockURLLoaderFactoryForTest::CreateLoaderAndStart( -+ ::mojo::PendingReceiver loader, -+ int32_t request_id, -+ uint32_t options, -+ const network::ResourceRequest& request, -+ ::mojo::PendingRemote client, -+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { -+ DCHECK(subscription_service_); -+ if (request.url.host() != kAdblockDebugDataHostName) { -+ 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 = HandleCommand(request.url); -+ SendResponse(std::move(response_body), std::move(client)); -+} -+ -+void AdblockURLLoaderFactoryForTest::SendResponse( -+ std::string response_body, -+ ::mojo::PendingRemote client) const { -+ auto response_head = network::mojom::URLResponseHead::New(); -+ response_head->mime_type = "text/plain"; -+ mojo::Remote 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; -+ } -+ uint32_t write_size = static_cast(response_body.size()); -+ producer->WriteData(response_body.c_str(), &write_size, -+ MOJO_WRITE_DATA_FLAG_NONE); -+ 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::HandleCommand( -+ const GURL& url) const { -+ auto command_elements = GetCommandElements(url); -+ if (command_elements.size() == 1 && command_elements[0] == kTopicHelp) { -+ return kHelp; -+ } -+ // 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(subscription_service_, action_ptr, payload); -+ return kResponseOk; -+ } -+ } else if (action == kActionClear) { -+ ClearSubscriptions(subscription_service_); -+ return kResponseOk; -+ } else if (action == kActionList) { -+ std::string response = kResponseOk; -+ auto subscriptions = ListSubscriptions(subscription_service_); -+ 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(subscription_service_, action_ptr, payload); -+ return kResponseOk; -+ } -+ } else { -+ std::vector (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(subscription_service_, getter, deleter); -+ return kResponseOk; -+ } else if (getter) { -+ std::string response = kResponseOk; -+ auto items = List(subscription_service_, getter); -+ if (!items.empty()) { -+ response += "\n\n"; -+ for (const auto& item : items) { -+ response += item + "\n"; -+ } -+ } -+ return response; -+ } -+ } -+ } else if (topic == kTopicAdblock || topic == kTopicAcceptableAds) { -+ if (action == kActionState) { -+ std::string response = kResponseOk; -+ response += "\n\n"; -+ bool enabled = topic == kTopicAdblock -+ ? IsAdblockEnabled(subscription_service_) -+ : IsAAEnabled(subscription_service_); -+ response += enabled ? "enabled" : "disabled"; -+ return response; -+ } -+ std::optional value = absl::nullopt; -+ if (action == kActionEnable) { -+ value = true; -+ } else if (action == kActionDisable) { -+ value = false; -+ } -+ if (value.has_value()) { -+ if (topic == kTopicAdblock) { -+ SetAdblockEnabled(subscription_service_, value.value()); -+ } else { -+ SetAAEnabled(subscription_service_, value.value()); -+ } -+ return kResponseOk; -+ } -+ } -+ } -+ return kResponseInvalidCommand; -+} -+ -+} // 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,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 . -+ */ -+ -+#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/core/subscription/subscription_service.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, -+ content::GlobalRenderFrameHostId host_id, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote target_factory, -+ std::string user_agent_string, -+ DisconnectCallback on_disconnect, -+ SubscriptionService* subscription_service); -+ ~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 std::string kAdblockDebugDataHostName; -+ -+ private: -+ std::string HandleCommand(const GURL& url) const; -+ void SendResponse( -+ std::string response_body, -+ ::mojo::PendingRemote client) const; -+ raw_ptr subscription_service_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ADBLOCK_URL_LOADER_FACTORY_FOR_TEST_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,188 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/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 ""; -+ } -+} -+} // namespace -+ -+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)); -+} -+ -+AdblockWebContentObserver::AdblockWebContentObserver( -+ content::WebContents* web_contents, -+ adblock::SubscriptionService* subscription_service, -+ adblock::ElementHider* element_hider, -+ adblock::SitekeyStorage* sitekey_storage, -+ std::unique_ptr frame_hierarchy_builder) -+ : content::WebContentsObserver(web_contents), -+ content::WebContentsUserData(*web_contents), -+ subscription_service_(subscription_service), -+ element_hider_(element_hider), -+ sitekey_storage_(sitekey_storage), -+ frame_hierarchy_builder_(std::move(frame_hierarchy_builder)) {} -+ -+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. -+ adblock::FrameOpenerInfo::CreateForWebContents(new_contents); -+ auto* info = adblock::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(); -+ if (navigation_handle->HasCommitted()) { -+ if (!navigation_handle->GetRenderFrameHost()) { -+ return; -+ } -+ if (!navigation_handle->IsErrorPage()) { -+ DVLOG(3) << "[eyeo] Ready to inject JS to " << url.spec(); -+ HandleOnLoad(navigation_handle->GetRenderFrameHost()); -+ } -+ if (navigation_handle->IsErrorPage() || url.IsAboutBlank() || -+ !url.SchemeIsHTTPOrHTTPS()) { -+ DVLOG(3) << "[eyeo] Not suitable URL " << url.spec() -+ << ", error = " << navigation_handle->GetNetErrorCode() -+ << ", IsAboutSrcdoc = " << url.IsAboutSrcdoc(); -+ -+ if (navigation_handle->GetNetErrorCode() == -+ net::ERR_BLOCKED_BY_ADMINISTRATOR && -+ navigation_handle->GetRenderFrameHost()->GetFrameOwnerElementType() == -+ blink::FrameOwnerElementType::kIframe) { -+ // element hiding for blocked element -+ DVLOG(3) << "[eyeo] Subframe url=" << url.spec() -+ << ", isAboutSrcDoc = " << url.IsAboutSrcdoc(); -+ element_hider_->HideBlockedElement( -+ url, navigation_handle->GetRenderFrameHost()->GetParent()); -+ } -+ } -+ } -+} -+ -+void AdblockWebContentObserver::HandleOnLoad( -+ content::RenderFrameHost* frame_host) { -+ DCHECK(frame_host); -+ const GURL url = -+ frame_hierarchy_builder_->FindUrlForFrame(frame_host, web_contents()); -+ if (net::IsLocalhost(url)) { -+ DVLOG(1) << "[eyeo] skipping element hiding on localhost URL"; -+ return; -+ } -+ const auto rfh_trace_id = reinterpret_cast(frame_host); -+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( -+ "eyeo", "AdblockWebContentObserver::HandleOnLoad", -+ TRACE_ID_LOCAL(rfh_trace_id), "url", url.spec()); -+ -+ auto referrers_chain = -+ frame_hierarchy_builder_->BuildFrameHierarchy(frame_host); -+ DVLOG(1) << "[eyeo] Got " << referrers_chain.size() << " referrers for " -+ << url.spec(); -+ -+ adblock::SiteKey site_key; -+ auto url_key_pair = sitekey_storage_->FindSiteKeyForAnyUrl(referrers_chain); -+ 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(referrers_chain), frame_host, -+ std::move(site_key), -+ base::BindOnce(&TraceHandleLoadComplete, rfh_trace_id)); -+} -+ -+bool AdblockWebContentObserver::IsAdblockEnabled() const { -+ return std::ranges::any_of( -+ subscription_service_->GetInstalledFilteringConfigurations(), -+ &adblock::FilteringConfiguration::IsEnabled); -+} -+ -+WEB_CONTENTS_USER_DATA_KEY_IMPL(AdblockWebContentObserver); -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,85 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/adblock_controller.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; -+/** -+ * @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 { -+ public: -+ AdblockWebContentObserver( -+ content::WebContents* web_contents, -+ adblock::SubscriptionService* subscription_service, -+ adblock::ElementHider* element_hider, -+ adblock::SitekeyStorage* sitekey_storage, -+ std::unique_ptr frame_hierarchy_builder); -+ ~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; -+ WEB_CONTENTS_USER_DATA_KEY_DECL(); -+ -+ raw_ptr subscription_service_; -+ raw_ptr element_hider_; -+ raw_ptr sitekey_storage_; -+ -+ std::unique_ptr frame_hierarchy_builder_; -+}; -+#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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_H_ -+ -+#include -+ -+#include "components/adblock/content/browser/adblock_filter_match.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; -+ -+// 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. -+ // |callback| will be posted, never called in-stack. 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, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& 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,126 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/content/browser/content_security_policy_injector_impl.h" -+ -+#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 "services/network/public/mojom/url_response_head.mojom.h" -+ -+namespace adblock { -+namespace { -+ -+std::set GetCspInjections( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL request_url, -+ const std::vector frame_hierarchy_chain) { -+ TRACE_EVENT1("eyeo", "GetCspInjection", "url", request_url.spec()); -+ std::set injections; -+ if ((true)) return injections; -+ for (const auto& collection : subscription_collections) { -+ const auto injection = -+ collection->GetCspInjections(request_url, frame_hierarchy_chain); -+ injections.insert(injection.begin(), injection.end()); -+ } -+ 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 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, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& headers, -+ InsertContentSecurityPolicyHeadersCallback callback) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ content::RenderFrameHost* host = -+ frame_hierarchy_builder_->FindRenderFrameHost(render_frame_host_id); -+ if (host) { -+ // 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(host)), -+ base::BindOnce( -+ &ContentSecurityPolicyInjectorImpl::OnCspInjectionsSearchFinished, -+ weak_ptr_factory.GetWeakPtr(), request_url, std::move(headers), -+ std::move(callback))); -+ } else { -+ // This request is likely dead, since there's no associated RenderFrameHost. -+ // Post the callback to the current sequence to unblock any callers that -+ // might be waiting for it. -+ std::move(callback).Run(nullptr); -+ } -+} -+ -+void ContentSecurityPolicyInjectorImpl::OnCspInjectionsSearchFinished( -+ const GURL request_url, -+ const scoped_refptr headers, -+ InsertContentSecurityPolicyHeadersCallback callback, -+ std::set 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, -+ base::BindOnce( -+ &ContentSecurityPolicyInjectorImpl::OnUpdatedHeadersParsed, -+ weak_ptr_factory.GetWeakPtr(), std::move(callback))); -+ } else { -+ // No headers are injected, no need to update parsed headers. -+ std::move(callback).Run(nullptr); -+ } -+} -+ -+void ContentSecurityPolicyInjectorImpl::OnUpdatedHeadersParsed( -+ InsertContentSecurityPolicyHeadersCallback callback, -+ network::mojom::ParsedHeadersPtr parsed_headers) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ std::move(callback).Run(std::move(parsed_headers)); -+} -+ -+} // 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_CONTENT_SECURITY_POLICY_INJECTOR_IMPL_H_ -+ -+#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 frame_hierarchy_builder); -+ -+ ~ContentSecurityPolicyInjectorImpl() final; -+ -+ void InsertContentSecurityPolicyHeadersIfApplicable( -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& headers, -+ InsertContentSecurityPolicyHeadersCallback callback) final; -+ -+ private: -+ void OnCspInjectionsSearchFinished( -+ const GURL request_url, -+ const scoped_refptr headers, -+ InsertContentSecurityPolicyHeadersCallback callback, -+ std::set csp_injections); -+ -+ void OnUpdatedHeadersParsed( -+ InsertContentSecurityPolicyHeadersCallback callback, -+ network::mojom::ParsedHeadersPtr parsed_headers); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ raw_ptr subscription_service_; -+ std::unique_ptr frame_hierarchy_builder_; -+ base::WeakPtrFactory 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_H_ -+ -+#include -+#include -+ -+#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 frame_hierarchy, -+ content::RenderFrameHost* render_frame_host, -+ SiteKey sitekey, -+ base::OnceCallback 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,326 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/content/browser/element_hider_impl.h" -+ -+#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/element_hider_info.h" -+#include "components/adblock/core/subscription/subscription_service.h" -+#include "components/grit/components_resources.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 { -+ -+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, -+ std::vector& input, -+ std::string& output) { -+ TRACE_EVENT1("eyeo", "GenerateStylesheet", "url", url.spec()); -+ -+ const base::span selectors(input); -+ -+ // 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 < input.size(); -+ i += max_selector_count) { -+ const size_t batch_size = std::min( -+ max_selector_count, input.size() - i); -+ output += base::JoinString(selectors.subspan(i, batch_size), ", ") + -+ " {display: none !important;}\n"; -+ } -+} -+ -+void GenerateElemHidingEmuJavaScript( -+ const GURL& url, -+ const std::vector& input, -+ std::string& output) { -+ TRACE_EVENT1("eyeo", "GenerateElemHidingEmuJavaScript", "url", url.spec()); -+ // build the string with selectors -+ std::string build_string; -+ for (const auto& selector : input) { -+ build_string.append("{selector:"); -+ base::EscapeJSONString(selector, true, &build_string); -+ build_string.append(", text:"); -+ base::EscapeJSONString(url.host(), 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) << "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); -+} -+ -+ElementHider::ElemhideInjectionData PrepareElemhideEmulationData( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL url, -+ const std::vector frame_hierarchy, -+ const SiteKey sitekey) { -+ TRACE_EVENT1("eyeo", "PrepareElemhideEmulationData", "url", url.spec()); -+ -+ std::vector stylesheet; -+ std::vector elemhide_js; -+ 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) { -+ std::ranges::copy( -+ collection->GetElementHideSelectors(url, frame_hierarchy, sitekey), -+ std::back_inserter(stylesheet)); -+ std::ranges::copy(collection->GetElementHideEmulationSelectors(url), -+ std::back_inserter(elemhide_js)); -+ } -+ 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 (!stylesheet.empty()) { -+ DVLOG(2) << "[eyeo] Got " << stylesheet.size() << " EH selectors for url " -+ << url; -+ GenerateStylesheet(url, stylesheet, result.stylesheet); -+ } -+ if (!elemhide_js.empty()) { -+ DVLOG(2) << "[eyeo] Got " << elemhide_js.size() -+ << " EH emu selectors for url " << url; -+ GenerateElemHidingEmuJavaScript(url, elemhide_js, 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 -+ 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 = ElementHiderInfo::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 frame_hierarchy, -+ content::RenderFrameHost* render_frame_host, -+ SiteKey sitekey, -+ base::OnceCallback on_finished) { -+ auto* info = ElementHiderInfo::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()); -+ // 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_ -+ -+#include -+ -+#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 frame_hierarchy, -+ content::RenderFrameHost* render_frame_host, -+ SiteKey sitekey, -+ base::OnceCallback 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 subscription_service_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_IMPL_H_ -diff --git a/components/adblock/content/browser/element_hider_info.cc b/components/adblock/content/browser/element_hider_info.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/element_hider_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 . -+ */ -+ -+#include "components/adblock/content/browser/element_hider_info.h" -+ -+namespace adblock { -+ -+DOCUMENT_USER_DATA_KEY_IMPL(ElementHiderInfo); -+ -+ElementHiderInfo::ElementHiderInfo(content::RenderFrameHost* rfh) -+ : content::DocumentUserData(rfh) {} -+ -+ElementHiderInfo::~ElementHiderInfo() = default; -+ -+bool ElementHiderInfo::IsElementHidingDone() const { -+ return element_hining_done_; -+} -+ -+void ElementHiderInfo::SetElementHidingDone() { -+ element_hining_done_ = true; -+} -+ -+} // namespace adblock -diff --git a/components/adblock/content/browser/element_hider_info.h b/components/adblock/content/browser/element_hider_info.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/element_hider_info.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_INFO_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_INFO_H_ -+ -+#include "content/public/browser/document_user_data.h" -+ -+namespace adblock { -+ -+class ElementHiderInfo final -+ : public content::DocumentUserData { -+ public: -+ ~ElementHiderInfo() final; -+ -+ bool IsElementHidingDone() const; -+ void SetElementHidingDone(); -+ -+ private: -+ explicit ElementHiderInfo(content::RenderFrameHost* rfh); -+ -+ bool element_hining_done_ = false; -+ -+ friend DocumentUserData; -+ DOCUMENT_USER_DATA_KEY_DECL(); -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_ELEMENT_HIDER_INFO_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,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 . -+ */ -+ -+#include "components/adblock/content/browser/frame_hierarchy_builder.h" -+ -+#include "base/logging.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 `GetLastCommittedURL()`. -+GURL GetUrlAsReferrer(const content::RenderFrameHost* frame_host) { -+ auto last_commited_referrer = -+ frame_host->GetLastCommittedURL().GetAsReferrer(); -+ if (last_commited_referrer.is_empty()) { -+ auto last_commited_url = frame_host->GetLastCommittedURL(); -+ if (last_commited_url.IsAboutBlank() && -+ IsValidForFrameHierarchy(last_commited_url)) { -+ last_commited_referrer = last_commited_url; -+ } -+ } -+ return last_commited_referrer; -+} -+ -+} // namespace -+ -+FrameHierarchyBuilder::FrameHierarchyBuilder() = default; -+ -+FrameHierarchyBuilder::~FrameHierarchyBuilder() = default; -+ -+content::RenderFrameHost* FrameHierarchyBuilder::FindRenderFrameHost( -+ content::GlobalRenderFrameHostId render_frame_host_id) const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ return content::RenderFrameHost::FromID(render_frame_host_id); -+} -+ -+std::vector FrameHierarchyBuilder::BuildFrameHierarchy( -+ content::RenderFrameHost* host) const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ -+ DCHECK(host) << "RenderFrameHost is needed to build frame hierarchy"; -+ -+ std::vector referrers_chain; -+ for (auto* iter = 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(); -+ referrers_chain.emplace_back(last_commited_referrer); -+ } -+ } -+ -+ return referrers_chain; -+} -+ -+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::IsExactlyAboutBlank (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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_FRAME_HIERARCHY_BUILDER_H_ -+ -+#include -+ -+#include "base/sequence_checker.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(); -+ -+ virtual content::RenderFrameHost* FindRenderFrameHost( -+ content::GlobalRenderFrameHostId render_frame_host_id) const; -+ -+ virtual std::vector BuildFrameHierarchy( -+ content::RenderFrameHost* host) 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 . -+ */ -+ -+#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(*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 . -+ */ -+ -+#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 { -+ 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/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,113 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_H_ -+ -+#include -+#include -+ -+#include "base/observer_list.h" -+#include "components/adblock/content/browser/adblock_filter_match.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 { -+struct GlobalRenderFrameHostId; -+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 OnAdMatched(const GURL& url, -+ FilterMatchResult match_result, -+ const std::vector& 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::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) = 0; -+ virtual void CheckRequestFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) = 0; -+ virtual void CheckRequestFilterMatchForWebSocket( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) = 0; -+ // No callback, just notify observers -+ virtual void CheckDocumentAllowlisted( -+ SubscriptionService::Snapshot subscription_collection, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id) = 0; -+ virtual void CheckResponseFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& response_url, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& headers, -+ CheckFilterMatchCallback callback) = 0; -+ virtual void CheckRewriteFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ base::OnceCallback&)> 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,451 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/core/common/adblock_utils.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 resource_classifier, -+ std::unique_ptr 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& frame_hierarchy, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ std::optional 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(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::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DVLOG(1) << "[eyeo] CheckPopupFilterMatch for " << popup_url.spec(); -+ auto* frame_host = content::RenderFrameHost::FromID(render_frame_host_id); -+ if (!frame_host) { -+ // Host has died, likely because this is a deferred execution. It does not -+ // matter anymore whether the resource is blocked, the page is gone. -+ VLOG(1) << "[eyeo] CheckPopupFilterMatch for " << popup_url.spec() -+ << " no frame host!"; -+ std::move(callback).Run(FilterMatchResult::kNoRule); -+ return; -+ } -+ -+ auto* wc = content::WebContents::FromRenderFrameHost(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(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_id, std::move(callback))); -+} -+ -+void ResourceClassificationRunnerImpl::CheckRequestFilterMatchForWebSocket( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(request_url.SchemeIsWSOrWSS()); -+ CheckRequestFilterMatch(std::move(subscription_collections), request_url, -+ ContentType::Websocket, render_frame_host_id, -+ std::move(callback)); -+} -+ -+void ResourceClassificationRunnerImpl::CheckDocumentAllowlisted( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id) { -+ // We pass main frame no matter what -+ DVLOG(1) << "[eyeo] Main document. Passing it through: " << request_url; -+ base::ThreadPool::PostTaskAndReplyWithResult( -+ FROM_HERE, {}, -+ base::BindOnce(&CheckDocumentAllowlistedInternal, -+ std::move(subscription_collections), request_url), -+ base::BindOnce( -+ &ResourceClassificationRunnerImpl::ProcessDocumentAllowlistedResponse, -+ weak_ptr_factory_.GetWeakPtr(), request_url, render_frame_host_id)); -+} -+ -+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, -+ content::GlobalRenderFrameHostId frame_host_id, -+ CheckFilterMatchCallback callback) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DVLOG(1) << "[eyeo] CheckRequestFilterMatchImpl for " << request_url.spec(); -+ -+ auto* host = content::RenderFrameHost::FromID(frame_host_id); -+ if (!host) { -+ // Host has died, likely because this is a deferred execution. It does not -+ // matter anymore whether the resource is blocked, the page is gone. -+ std::move(callback).Run(FilterMatchResult::kNoRule); -+ return; -+ } -+ const std::vector frame_hierarchy_chain = -+ frame_hierarchy_builder_->BuildFrameHierarchy(host); -+ -+ DVLOG(1) << "[eyeo] Got " << frame_hierarchy_chain.size() -+ << " frame_hierarchy for " << request_url.spec(); -+ -+ auto site_key_pair = -+ sitekey_storage_->FindSiteKeyForAnyUrl(frame_hierarchy_chain); -+ 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_chain, adblock_resource_type, -+ std::move(site_key)), -+ base::BindOnce( -+ &ResourceClassificationRunnerImpl::OnCheckResourceFilterMatchComplete, -+ weak_ptr_factory_.GetWeakPtr(), request_url, frame_hierarchy_chain, -+ adblock_resource_type, frame_host_id, std::move(callback))); -+} -+ -+// static -+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult -+ResourceClassificationRunnerImpl::CheckRequestFilterMatchInternal( -+ const scoped_refptr& resource_classifier, -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL request_url, -+ const std::vector 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] Document 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] Document 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 frame_hierarchy, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ 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); -+ auto* render_frame_host = -+ content::RenderFrameHost::FromID(render_frame_host_id); -+ if (render_frame_host) { -+ // 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) { -+ NotifyAdMatched(request_url, result.status, frame_hierarchy, -+ adblock_resource_type, render_frame_host, -+ result.subscription, result.configuration_name); -+ } -+ } -+} -+ -+void ResourceClassificationRunnerImpl::NotifyAdMatched( -+ const GURL& url, -+ FilterMatchResult result, -+ const std::vector& parent_frame_urls, -+ ContentType content_type, -+ content::RenderFrameHost* render_frame_host, -+ const GURL& subscription, -+ const std::string& configuration_name) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ VLOG(1) << "[eyeo] NotifyAdMatched() called for " << url; -+ -+ for (auto& observer : observers_) { -+ observer.OnAdMatched(url, result, parent_frame_urls, -+ static_cast(content_type), -+ render_frame_host, subscription, configuration_name); -+ } -+} -+ -+// static -+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult -+ResourceClassificationRunnerImpl::CheckDocumentAllowlistedInternal( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url) { -+ 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(), -+ 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, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& headers, -+ CheckFilterMatchCallback callback) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DVLOG(1) << "[eyeo] CheckResponseFilterMatch for " << response_url.spec(); -+ content::RenderFrameHost* host = -+ frame_hierarchy_builder_->FindRenderFrameHost(render_frame_host_id); -+ if (!host) { -+ // This request is likely dead, since there's no associated RenderFrameHost. -+ std::move(callback).Run(FilterMatchResult::kNoRule); -+ return; -+ } -+ -+ auto frame_hierarchy = frame_hierarchy_builder_->BuildFrameHierarchy(host); -+ // 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, host->GetGlobalId(), std::move(callback))); -+} -+ -+// static -+ResourceClassificationRunnerImpl::CheckResourceFilterMatchResult -+ResourceClassificationRunnerImpl::CheckResponseFilterMatchInternal( -+ const scoped_refptr resource_classifier, -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL response_url, -+ const std::vector frame_hierarchy, -+ ContentType adblock_resource_type, -+ const scoped_refptr 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] Document 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] Document 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, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ base::OnceCallback&)> callback) { -+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -+ DVLOG(1) << "[eyeo] CheckRewriteFilterMatch for " << request_url.spec(); -+ -+ content::RenderFrameHost* host = -+ frame_hierarchy_builder_->FindRenderFrameHost(render_frame_host_id); -+ if (!host) { -+ std::move(callback).Run(std::optional{}); -+ return; -+ } -+ -+ const std::vector frame_hierarchy = -+ frame_hierarchy_builder_->BuildFrameHierarchy(host); -+ 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_RESOURCE_CLASSIFICATION_RUNNER_IMPL_H_ -+ -+#include -+ -+#include "base/memory/raw_ptr.h" -+#include "components/adblock/content/browser/frame_hierarchy_builder.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 resource_classifier, -+ std::unique_ptr 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::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) final; -+ void CheckRequestFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) final; -+ void CheckRequestFilterMatchForWebSocket( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback) final; -+ // No callback, just notify observers -+ void CheckDocumentAllowlisted( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id) final; -+ void CheckResponseFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& response_url, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ const scoped_refptr& headers, -+ CheckFilterMatchCallback callback) final; -+ void CheckRewriteFilterMatch( -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ base::OnceCallback&)> result) final; -+ -+ private: -+ struct CheckResourceFilterMatchResult { -+ FilterMatchResult status; -+ GURL subscription; -+ std::string configuration_name; -+ }; -+ -+ static CheckResourceFilterMatchResult CheckRequestFilterMatchInternal( -+ const scoped_refptr& resource_classifier, -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL request_url, -+ const std::vector frame_hierarchy, -+ ContentType adblock_resource_type, -+ const SiteKey sitekey); -+ -+ void OnCheckResourceFilterMatchComplete( -+ const GURL request_url, -+ const std::vector frame_hierarchy, -+ ContentType adblock_resource_type, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckFilterMatchCallback callback, -+ const CheckResourceFilterMatchResult result); -+ -+ void OnCheckPopupFilterMatchComplete( -+ const GURL& popup_url, -+ const std::vector& frame_hierarchy, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ std::optional callback, -+ const ResourceClassifier::ClassificationResult& result); -+ -+ static CheckResourceFilterMatchResult CheckResponseFilterMatchInternal( -+ const scoped_refptr resource_classifier, -+ SubscriptionService::Snapshot subscription_collections, -+ const GURL response_url, -+ const std::vector frame_hierarchy, -+ ContentType adblock_resource_type, -+ const scoped_refptr response_headers); -+ -+ void NotifyAdMatched(const GURL& url, -+ FilterMatchResult result, -+ const std::vector& parent_frame_urls, -+ ContentType content_type, -+ content::RenderFrameHost* render_frame_host, -+ const GURL& subscription, -+ const std::string& configuration_name); -+ -+ void PostFilterMatchCallbackToUI(CheckFilterMatchCallback callback, -+ FilterMatchResult result); -+ -+ void PostRewriteCallbackToUI( -+ base::OnceCallback&)> callback, -+ std::optional url); -+ -+ static CheckResourceFilterMatchResult CheckDocumentAllowlistedInternal( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url); -+ -+ void ProcessDocumentAllowlistedResponse( -+ const GURL request_url, -+ content::GlobalRenderFrameHostId render_frame_host_id, -+ CheckResourceFilterMatchResult result); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ scoped_refptr resource_classifier_; -+ std::unique_ptr frame_hierarchy_builder_; -+ raw_ptr sitekey_storage_; -+ base::ObserverList observers_; -+ base::WeakPtrFactory 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,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 . -+ */ -+ -+#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 SessionStatsImpl::GetSessionAllowedAdsCount() const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ return allowed_map_; -+} -+ -+std::map SessionStatsImpl::GetSessionBlockedAdsCount() const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ return blocked_map_; -+} -+ -+void SessionStatsImpl::OnAdMatched(const GURL& url, -+ FilterMatchResult match_result, -+ const std::vector& 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 . -+ */ -+ -+#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 GetSessionAllowedAdsCount() const final; -+ -+ std::map GetSessionBlockedAdsCount() const final; -+ -+ // ResourceClassificationRunner::Observer: -+ void OnAdMatched(const GURL& url, -+ FilterMatchResult match_result, -+ const std::vector& 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 classification_runner_; -+ std::map allowed_map_; -+ std::map blocked_map_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_SESSION_STATS_IMPL_H_ -diff --git a/components/adblock/content/browser/subscription_service_factory_base.cc b/components/adblock/content/browser/subscription_service_factory_base.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/subscription_service_factory_base.cc -@@ -0,0 +1,225 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/content/browser/subscription_service_factory_base.h" -+ -+#include -+#include -+#include -+ -+#include "base/command_line.h" -+#include "base/files/file_util.h" -+#include "base/functional/bind.h" -+#include "base/task/thread_pool.h" -+#include "base/trace_event/trace_event.h" -+#include "components/adblock/core/adblock_switches.h" -+#include "components/adblock/core/common/adblock_constants.h" -+#include "components/adblock/core/configuration/persistent_filtering_configuration.h" -+#include "components/adblock/core/converter/flatbuffer_converter.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/ongoing_subscription_request_impl.h" -+#include "components/adblock/core/subscription/preloaded_subscription_provider_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_updater.h" -+#include "components/adblock/core/subscription/subscription_updater_impl.h" -+#include "components/adblock/core/subscription/subscription_validator_impl.h" -+#include "components/keyed_service/content/browser_context_dependency_manager.h" -+#include "components/pref_registry/pref_registry_syncable.h" -+#include "components/prefs/pref_service.h" -+#include "content/public/browser/browser_context.h" -+#include "content/public/browser/storage_partition.h" -+ -+namespace adblock { -+namespace { -+ -+std::optional g_update_check_interval_for_testing; -+std::optional g_update_initial_delay_for_testing; -+ -+base::TimeDelta GetUpdateInitialDelay() { -+ static base::TimeDelta kInitialDelay = -+ g_update_initial_delay_for_testing -+ ? g_update_initial_delay_for_testing.value() -+ : base::Seconds(30); -+ return kInitialDelay; -+} -+ -+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 MakeOngoingSubscriptionRequest( -+ scoped_refptr url_loader_factory) { -+ return std::make_unique(&kRetryBackoffPolicy, -+ url_loader_factory); -+} -+ -+ConversionResult ConvertFilterFile(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 { -+ result = FlatbufferConverter::Convert( -+ input_stream, subscription_url, -+ config::AllowPrivilegedFilters(subscription_url)); -+ } -+ base::DeleteFile(path); -+ return result; -+} -+ -+std::unique_ptr MakeSubscriptionUpdater() { -+ return std::make_unique(GetUpdateInitialDelay(), -+ GetUpdateCheckInterval()); -+} -+ -+std::unique_ptr -+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(); -+ scoped_refptr url_loader_factory = -+ context->GetDefaultStoragePartition() -+ ->GetURLLoaderFactoryForBrowserProcess(); -+ -+ const std::string storage_dir = configuration->GetName() + "_subscriptions"; -+ -+ auto storage = std::make_unique( -+ context->GetPath().AppendASCII(storage_dir), -+ std::make_unique(prefs, -+ CurrentSchemaVersion()), -+ persistent_metadata); -+ -+ auto downloader = std::make_unique( -+ utils::GetAppInfo(), -+ base::BindRepeating(&MakeOngoingSubscriptionRequest, url_loader_factory), -+ const_cast(conversion_executors), -+ persistent_metadata); -+ -+ auto maintainer = std::make_unique( -+ configuration, std::move(storage), std::move(downloader), -+ std::make_unique(), -+ MakeSubscriptionUpdater(), -+ const_cast(conversion_executors), -+ persistent_metadata, observer); -+ maintainer->InitializeStorage(); -+ return maintainer; -+} -+ -+void CleanupPersistedConfiguration(PrefService* prefs, -+ FilteringConfiguration* configuration) { -+ PersistentFilteringConfiguration::RemovePersistedData( -+ prefs, configuration->GetName()); -+} -+ -+} // namespace -+ -+SubscriptionServiceFactoryBase::SubscriptionServiceFactoryBase() -+ : BrowserContextKeyedServiceFactory( -+ "AdblockSubscriptionService", -+ BrowserContextDependencyManager::GetInstance()) {} -+ -+SubscriptionServiceFactoryBase::~SubscriptionServiceFactoryBase() = default; -+ -+std::unique_ptr -+SubscriptionServiceFactoryBase::BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const { -+ auto* prefs = GetPrefs(context); -+ auto subscription_service = std::make_unique( -+ base::BindRepeating(&MakeFilterConfigurationMaintainer, context, prefs, -+ GetSubscriptionPersistentMetadata(context), -+ static_cast(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 (persisted_configuration->GetName() == -+ kAdblockFilteringConfigurationName) { -+ // "adblock" configuration is taken care by -+ // AdblockController(Factory) -+ continue; -+ } -+ if (start_disabled) { -+ persisted_configuration->SetEnabled(false); -+ } -+ subscription_service->InstallFilteringConfiguration( -+ std::move(persisted_configuration)); -+ } -+ return subscription_service; -+} -+ -+scoped_refptr -+SubscriptionServiceFactoryBase::ConvertCustomFilters( -+ const std::vector& filters) const { -+ auto raw_data = -+ FlatbufferConverter::Convert(filters, CustomFiltersUrl(), true); -+ return base::MakeRefCounted( -+ std::move(raw_data), Subscription::InstallationState::Installed, -+ base::Time()); -+} -+ -+void SubscriptionServiceFactoryBase::ConvertFilterListFile( -+ const GURL& subscription_url, -+ const base::FilePath& path, -+ base::OnceCallback result_callback) const { -+ base::ThreadPool::PostTaskAndReplyWithResult( -+ FROM_HERE, {base::MayBlock()}, -+ base::BindOnce(&ConvertFilterFile, subscription_url, path), -+ std::move(result_callback)); -+} -+ -+void SubscriptionServiceFactoryBase::RegisterProfilePrefs( -+ user_prefs::PrefRegistrySyncable* registry) { -+ adblock::PersistentFilteringConfiguration::RegisterProfilePrefs(registry); -+} -+ -+// static -+void SubscriptionServiceFactoryBase::SetUpdateCheckAndDelayIntervalsForTesting( -+ base::TimeDelta check_interval, -+ base::TimeDelta initial_delay) { -+ g_update_check_interval_for_testing = check_interval; -+ g_update_initial_delay_for_testing = initial_delay; -+} -+ -+} // namespace adblock -diff --git a/components/adblock/content/browser/subscription_service_factory_base.h b/components/adblock/content/browser/subscription_service_factory_base.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/content/browser/subscription_service_factory_base.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CONTENT_BROWSER_SUBSCRIPTION_SERVICE_FACTORY_BASE_H_ -+#define COMPONENTS_ADBLOCK_CONTENT_BROWSER_SUBSCRIPTION_SERVICE_FACTORY_BASE_H_ -+ -+#include "components/adblock/core/subscription/conversion_executors.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 SubscriptionServiceFactoryBase : public BrowserContextKeyedServiceFactory, -+ public ConversionExecutors { -+ public: -+ static void SetUpdateCheckAndDelayIntervalsForTesting( -+ base::TimeDelta check_interval, -+ base::TimeDelta initial_delay); -+ -+ // ConversionExecutors: -+ scoped_refptr ConvertCustomFilters( -+ const std::vector& filters) const override; -+ void ConvertFilterListFile( -+ const GURL& subscription_url, -+ const base::FilePath& path, -+ base::OnceCallback) const override; -+ -+ protected: -+ virtual PrefService* GetPrefs(content::BrowserContext* context) const = 0; -+ virtual SubscriptionPersistentMetadata* GetSubscriptionPersistentMetadata( -+ content::BrowserContext* context) const = 0; -+ SubscriptionServiceFactoryBase(); -+ ~SubscriptionServiceFactoryBase() override; -+ -+ // BrowserContextKeyedServiceFactory: -+ std::unique_ptr BuildServiceInstanceForBrowserContext( -+ content::BrowserContext* context) const override; -+ void RegisterProfilePrefs( -+ user_prefs::PrefRegistrySyncable* registry) override; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CONTENT_BROWSER_SUBSCRIPTION_SERVICE_FACTORY_BASE_H_ -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,175 @@ -+# -+# This file is part of eyeo Chromium SDK, -+# Copyright (C) 2006-present eyeo GmbH -+# -+# eyeo Chromium SDK is free software: you can redistribute it and/or modify -+# it under the terms of the GNU General Public License version 3 as -+# published by the Free Software Foundation. -+# -+# eyeo Chromium SDK is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with eyeo Chromium SDK. If not, see . -+ -+import("//components/adblock/features.gni") -+import("//third_party/flatbuffers/flatbuffer.gni") -+ -+flatbuffer("schema") { -+ sources = [ "schema/filter_list_schema.fbs" ] -+} -+ -+template("generate_sha256_header") { -+ output_prefix_ = "${target_gen_dir}/hash/${target_name}" -+ generated_files = [ -+ "${output_prefix_}.h", -+ "${output_prefix_}.cc", -+ ] -+ -+ # Generates .h and .cc files which contain the hashes of invoker.files_to_hash. -+ action("${target_name}_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 = generated_files -+ inputs = invoker.files_to_hash -+ -+ args = [ rebase_path(output_prefix_, root_build_dir) ] + -+ rebase_path(inputs, root_build_dir) -+ -+ deps = invoker.deps -+ } -+ -+ # Allows linking the generated .h and .cc files, is the main target of -+ # this template. -+ source_set("${target_name}") { -+ sources = generated_files -+ -+ deps = [ ":${target_name}_hash" ] -+ } -+} -+ -+generate_sha256_header("schema_hash") { -+ deps = [ ":schema" ] -+ -+ # Using the generated flatbuffer header instead of source .fbs file to: -+ # - avoid generating new schema hash if just a comment in the .fbs changes -+ # - generate a new schema hash if .fbs -> .h conversion changes unexpectedly -+ files_to_hash = [ "${target_gen_dir}/schema/filter_list_schema_generated.h" ] -+} -+ -+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_controller.h", -+ "adblock_controller_impl.cc", -+ "adblock_controller_impl.h", -+ "adblock_switches.cc", -+ "adblock_switches.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_adblock_controller.cc", -+ "test/mock_adblock_controller.h", -+ "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_controller_impl_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/subscription:test_support", -+ "//components/prefs:test_support", -+ "//components/resources:components_resources_grit", -+ "//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,285 @@ -+/* This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/activeping_telemetry_topic_provider.h" -+ -+#include "base/json/json_reader.h" -+#include "base/json/json_writer.h" -+#include "base/system/sys_info.h" -+#include "base/time/time.h" -+#include "base/time/time_to_iso8601.h" -+#include "base/uuid.h" -+#include "components/adblock/core/common/adblock_prefs.h" -+#include "components/adblock/core/subscription/subscription_config.h" -+ -+namespace adblock { -+namespace { -+int g_http_port_for_testing = 0; -+std::optional g_time_delta_for_testing; -+ -+GURL GetUrl() { -+ GURL url(EYEO_TELEMETRY_SERVER_URL); -+ if (!g_http_port_for_testing) { -+ return url; -+ } -+ DCHECK_EQ(url::kHttpsScheme, url.scheme()); -+ GURL::Replacements replacements; -+ replacements.SetSchemeStr(url::kHttpScheme); -+ const std::string port_str = base::NumberToString(g_http_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, -+ base::StringPiece payload_key, -+ base::Value::Dict& payload) { -+ auto str = pref_service->GetString(pref_name); -+ if (!str.empty()) { -+ payload.Set(payload_key, std::move(str)); -+ } -+} -+} // namespace -+ -+ActivepingTelemetryTopicProvider::ActivepingTelemetryTopicProvider( -+ utils::AppInfo app_info, -+ PrefService* pref_service, -+ SubscriptionService* subscription_service, -+ const GURL& base_url, -+ const std::string& auth_token) -+ : app_info_(std::move(app_info)), -+ pref_service_(pref_service), -+ subscription_service_(subscription_service), -+ base_url_(base_url), -+ auth_token_(auth_token) {} -+ -+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/1"); -+} -+ -+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 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", -+ base::TimeToISO8601(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)); -+ debug_info.Set("next_ping", -+ base::TimeToISO8601(pref_service_->GetTime( -+ adblock::common::prefs::kTelemetryNextPingTime))); -+ -+ 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); -+} -+ -+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_->GetAdblockFilteringConfiguration(); -+ 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", app_info_.name); -+ payload.Set("application_version", app_info_.version); -+ payload.Set("aa_active", aa_enabled); -+ payload.Set("platform", base::SysInfo::OperatingSystemName()); -+ payload.Set("platform_version", base::SysInfo::OperatingSystemVersion()); -+ // 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::SetHttpPortForTesting( -+ int http_port_for_testing) { -+ g_http_port_for_testing = http_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,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 . -+ */ -+ -+#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/common/adblock_utils.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: -+ ActivepingTelemetryTopicProvider(utils::AppInfo app_info, -+ PrefService* pref_service, -+ SubscriptionService* subscription_service, -+ const GURL& base_url, -+ const std::string& auth_token); -+ ~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 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 SetHttpPortForTesting(int http_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; -+ -+ const utils::AppInfo app_info_; -+ raw_ptr pref_service_; -+ raw_ptr subscription_service_; -+ const GURL base_url_; -+ const std::string auth_token_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_ACTIVEPING_TELEMETRY_TOPIC_PROVIDER_H_ -diff --git a/components/adblock/core/adblock_controller.h b/components/adblock/core/adblock_controller.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/adblock_controller.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_H_ -+#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_H_ -+ -+#include -+#include -+ -+#include "base/observer_list.h" -+#include "components/adblock/core/subscription/installed_subscription.h" -+#include "components/adblock/core/subscription/subscription_config.h" -+#include "components/keyed_service/core/keyed_service.h" -+ -+class GURL; -+ -+namespace adblock { -+/** -+ * @brief Provides the way for the UI to interact with the filter engine. -+ * It allows to set the states of the ad-block and acceptable ads and -+ * adding, removing and listing subscriptions and allowed domains. -+ */ -+class AdblockController : public KeyedService { -+ public: -+ ~AdblockController() override = default; -+ -+ virtual void SetAdblockEnabled(bool enabled) = 0; -+ virtual bool IsAdblockEnabled() const = 0; -+ virtual void SetAcceptableAdsEnabled(bool enabled) = 0; -+ virtual bool IsAcceptableAdsEnabled() const = 0; -+ -+ virtual void InstallSubscription(const GURL& url) = 0; -+ virtual void UninstallSubscription(const GURL& url) = 0; -+ virtual std::vector> GetInstalledSubscriptions() -+ const = 0; -+ -+ virtual void AddAllowedDomain(const std::string& domain) = 0; -+ virtual void RemoveAllowedDomain(const std::string& domain) = 0; -+ virtual std::vector GetAllowedDomains() const = 0; -+ -+ virtual void AddCustomFilter(const std::string& filter) = 0; -+ virtual void RemoveCustomFilter(const std::string& filter) = 0; -+ virtual std::vector GetCustomFilters() const = 0; -+ -+ virtual std::vector GetKnownSubscriptions() const = 0; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_H_ -diff --git a/components/adblock/core/adblock_controller_impl.cc b/components/adblock/core/adblock_controller_impl.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/adblock_controller_impl.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 . -+ */ -+ -+#include "components/adblock/core/adblock_controller_impl.h" -+ -+#include -+#include -+#include -+#include -+ -+#include "absl/types/optional.h" -+#include "base/command_line.h" -+#include "base/functional/bind.h" -+#include "base/logging.h" -+#include "base/strings/string_util.h" -+#include "base/version.h" -+#include "components/adblock/core/adblock_switches.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/subscription/installed_subscription.h" -+#include "components/adblock/core/subscription/subscription.h" -+#include "components/adblock/core/subscription/subscription_config.h" -+#include "components/adblock/core/subscription/subscription_service.h" -+#include "components/language/core/common/locale_util.h" -+#include "components/prefs/pref_service.h" -+#include "components/prefs/scoped_user_pref_update.h" -+#include "components/version_info/version_info.h" -+#include "url/gurl.h" -+ -+namespace adblock { -+ -+namespace { -+ -+template -+std::vector MigrateItemsFromList(PrefService* pref_service, -+ const std::string& pref_name) { -+ std::vector 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; -+} -+ -+std::optional 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; -+} -+ -+} // namespace -+ -+AdblockControllerImpl::AdblockControllerImpl( -+ FilteringConfiguration* adblock_filtering_configuration, -+ SubscriptionService* subscription_service, -+ const std::string& locale, -+ std::vector known_subscriptions) -+ : adblock_filtering_configuration_(adblock_filtering_configuration), -+ subscription_service_(subscription_service), -+ language_(language::ExtractBaseLanguage(locale)), -+ known_subscriptions_(std::move(known_subscriptions)) { -+ // language::ExtractBaseLanguage is pretty basic, if it doesn't return -+ // something that looks like a valid language, fallback to English and use the -+ // default EasyList. -+ if (language_.size() != 2u) { -+ language_ = "en"; -+ } -+} -+ -+AdblockControllerImpl::~AdblockControllerImpl() = default; -+ -+void AdblockControllerImpl::SetAdblockEnabled(bool enabled) { -+ adblock_filtering_configuration_->SetEnabled(enabled); -+} -+ -+bool AdblockControllerImpl::IsAdblockEnabled() const { -+ return adblock_filtering_configuration_->IsEnabled(); -+} -+ -+void AdblockControllerImpl::SetAcceptableAdsEnabled(bool enabled) { -+ if (enabled) { -+ InstallSubscription(AcceptableAdsUrl()); -+ } else { -+ UninstallSubscription(AcceptableAdsUrl()); -+ } -+} -+ -+bool AdblockControllerImpl::IsAcceptableAdsEnabled() const { -+ return std::ranges::any_of( -+ adblock_filtering_configuration_->GetFilterLists(), -+ [&](const auto& url) { return url == AcceptableAdsUrl(); }); -+} -+ -+void AdblockControllerImpl::InstallSubscription(const GURL& url) { -+ adblock_filtering_configuration_->AddFilterList(url); -+} -+ -+void AdblockControllerImpl::UninstallSubscription(const GURL& url) { -+ adblock_filtering_configuration_->RemoveFilterList(url); -+} -+ -+std::vector> -+AdblockControllerImpl::GetInstalledSubscriptions() const { -+ return GetSubscriptionsThatMatchConfiguration(); -+} -+ -+void AdblockControllerImpl::AddAllowedDomain(const std::string& domain) { -+ adblock_filtering_configuration_->AddAllowedDomain(domain); -+} -+ -+void AdblockControllerImpl::RemoveAllowedDomain(const std::string& domain) { -+ adblock_filtering_configuration_->RemoveAllowedDomain(domain); -+} -+ -+std::vector AdblockControllerImpl::GetAllowedDomains() const { -+ return adblock_filtering_configuration_->GetAllowedDomains(); -+} -+ -+void AdblockControllerImpl::AddCustomFilter(const std::string& filter) { -+ adblock_filtering_configuration_->AddCustomFilter(filter); -+} -+ -+void AdblockControllerImpl::RemoveCustomFilter(const std::string& filter) { -+ adblock_filtering_configuration_->RemoveCustomFilter(filter); -+} -+ -+std::vector AdblockControllerImpl::GetCustomFilters() const { -+ return adblock_filtering_configuration_->GetCustomFilters(); -+} -+ -+std::vector -+AdblockControllerImpl::GetKnownSubscriptions() const { -+ return known_subscriptions_; -+} -+ -+void AdblockControllerImpl::RunFirstRunLogic(PrefService* pref_service) { -+ // If the state of installed subscriptions in SubscriptionService is different -+ // than the state in prefs, prefs take precedence. -+ if (pref_service->GetBoolean( -+ common::prefs::kInstallFirstStartSubscriptions)) { -+ // On first run, install additional subscriptions. -+ for (const auto& cur : known_subscriptions_) { -+ if (cur.first_run == SubscriptionFirstRunBehavior::Subscribe) { -+ if (cur.url == AcceptableAdsUrl() && -+ base::CommandLine::ForCurrentProcess()->HasSwitch( -+ switches::kDisableAcceptableAds)) { -+ // Do not install Acceptable Ads on first run because a command line -+ // switch forbids it. Mostly used for testing. -+ continue; -+ } -+ InstallSubscription(cur.url); -+ } -+ } -+ -+ if (IsEyeoFilteringDisabledByDefault()) { -+ SetAdblockEnabled(false); -+ } -+ -+ InstallLanguageBasedRecommendedSubscriptions(); -+ pref_service->SetBoolean(common::prefs::kInstallFirstStartSubscriptions, -+ false); -+ } -+} -+ -+void AdblockControllerImpl::MigrateLegacyPrefs(PrefService* pref_service) { -+ if (auto aa_value = MigrateBoolFromPrefs( -+ pref_service, common::prefs::kEnableAcceptableAdsLegacy)) { -+ SetAcceptableAdsEnabled(*aa_value); -+ VLOG(1) << "[eyeo] Migrated kEnableAcceptableAdsLegacy pref"; -+ } -+ -+ if (auto enable_value = MigrateBoolFromPrefs( -+ pref_service, common::prefs::kEnableAdblockLegacy)) { -+ SetAdblockEnabled(*enable_value); -+ VLOG(1) << "[eyeo] Migrated kEnableAdblockLegacy pref"; -+ } -+ -+ for (const auto& url : MigrateItemsFromList( -+ pref_service, common::prefs::kAdblockCustomSubscriptionsLegacy)) { -+ adblock_filtering_configuration_->AddFilterList(url); -+ VLOG(1) << "[eyeo] Migrated " << url -+ << " from kAdblockCustomSubscriptionsLegacy pref"; -+ } -+ -+ for (const auto& url : MigrateItemsFromList( -+ pref_service, common::prefs::kAdblockSubscriptionsLegacy)) { -+ adblock_filtering_configuration_->AddFilterList(url); -+ VLOG(1) << "[eyeo] Migrated " << url -+ << " from kAdblockSubscriptionsLegacy pref"; -+ } -+ -+ for (const auto& domain : MigrateItemsFromList( -+ pref_service, common::prefs::kAdblockAllowedDomainsLegacy)) { -+ adblock_filtering_configuration_->AddAllowedDomain(domain); -+ VLOG(1) << "[eyeo] Migrated " << domain -+ << " from kAdblockAllowedDomainsLegacy pref"; -+ } -+ -+ for (const auto& filter : MigrateItemsFromList( -+ pref_service, common::prefs::kAdblockCustomFiltersLegacy)) { -+ adblock_filtering_configuration_->AddCustomFilter(filter); -+ VLOG(1) << "[eyeo] Migrated " << filter -+ << " from kAdblockCustomFiltersLegacy pref"; -+ } -+} -+ -+void AdblockControllerImpl::InstallLanguageBasedRecommendedSubscriptions() { -+ bool language_specific_subscription_installed = false; -+ for (const auto& subscription : known_subscriptions_) { -+ 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; -+ InstallSubscription(subscription.url); -+ } -+ } -+ if (language_specific_subscription_installed) { -+ return; -+ } -+ -+ // If there's no language-specific recommended subscription, see if we may -+ // install the default subscription.. -+ if (std::ranges::any_of( -+ known_subscriptions_, [&](const KnownSubscriptionInfo& subscription) { -+ return subscription.url == DefaultSubscriptionUrl() && -+ subscription.first_run != -+ SubscriptionFirstRunBehavior::Ignore; -+ })) { -+ VLOG(1) << "[eyeo] Using the default subscription for language \"" -+ << language_ << "\""; -+ InstallSubscription(DefaultSubscriptionUrl()); -+ } -+ VLOG(1) << "[eyeo] No default subscription found, neither " -+ "language-specific, nor generic."; -+} -+ -+std::vector> -+AdblockControllerImpl::GetSubscriptionsThatMatchConfiguration() const { -+ return subscription_service_->GetCurrentSubscriptions( -+ adblock_filtering_configuration_); -+} -+ -+} // namespace adblock -diff --git a/components/adblock/core/adblock_controller_impl.h b/components/adblock/core/adblock_controller_impl.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/adblock_controller_impl.h -@@ -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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_IMPL_H_ -+ -+#include -+#include -+ -+#include "base/memory/raw_ptr.h" -+#include "base/memory/weak_ptr.h" -+#include "base/sequence_checker.h" -+#include "components/adblock/core/adblock_controller.h" -+#include "components/adblock/core/configuration/filtering_configuration.h" -+#include "components/adblock/core/subscription/subscription_config.h" -+#include "components/adblock/core/subscription/subscription_service.h" -+#include "components/prefs/pref_member.h" -+ -+class GURL; -+class PrefService; -+ -+namespace adblock { -+ -+/** -+ * @brief Implementation of the AdblockController interface. Uses a -+ * FilteringConfiguration as the backend for all the state set via the -+ * interface. -+ */ -+class AdblockControllerImpl : public AdblockController { -+ public: -+ AdblockControllerImpl(FilteringConfiguration* adblock_filtering_configuration, -+ SubscriptionService* subscription_service, -+ const std::string& locale, -+ std::vector known_subscriptions); -+ ~AdblockControllerImpl() override; -+ -+ AdblockControllerImpl(const AdblockControllerImpl&) = delete; -+ AdblockControllerImpl& operator=(const AdblockControllerImpl&) = delete; -+ AdblockControllerImpl(AdblockControllerImpl&&) = delete; -+ AdblockControllerImpl& operator=(AdblockControllerImpl&&) = delete; -+ -+ void SetAdblockEnabled(bool enabled) override; -+ bool IsAdblockEnabled() const override; -+ void SetAcceptableAdsEnabled(bool enabled) override; -+ bool IsAcceptableAdsEnabled() const override; -+ -+ void InstallSubscription(const GURL& url) override; -+ void UninstallSubscription(const GURL& url) override; -+ std::vector> GetInstalledSubscriptions() -+ const override; -+ -+ void AddAllowedDomain(const std::string& domain) override; -+ void RemoveAllowedDomain(const std::string& domain) override; -+ std::vector GetAllowedDomains() const override; -+ -+ void AddCustomFilter(const std::string& filter) override; -+ void RemoveCustomFilter(const std::string& filter) override; -+ std::vector GetCustomFilters() const override; -+ -+ std::vector GetKnownSubscriptions() const override; -+ -+ void RunFirstRunLogic(PrefService* pref_service); -+ void MigrateLegacyPrefs(PrefService* pref_service); -+ -+ protected: -+ SEQUENCE_CHECKER(sequence_checker_); -+ void NotifySubscriptionChanged(const GURL& subscription_url); -+ void InstallLanguageBasedRecommendedSubscriptions(); -+ std::vector> -+ GetSubscriptionsThatMatchConfiguration() const; -+ -+ raw_ptr adblock_filtering_configuration_; -+ raw_ptr subscription_service_; -+ std::string language_; -+ std::vector known_subscriptions_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_ADBLOCK_CONTROLLER_IMPL_H_ -diff --git a/components/adblock/core/adblock_switches.cc b/components/adblock/core/adblock_switches.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/adblock_switches.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 . -+ */ -+ -+#include "components/adblock/core/adblock_switches.h" -+ -+namespace adblock::switches { -+ -+const char kDisableAcceptableAds[] = "disable-aa"; -+const char kDisableAdblock[] = "disable-adblock"; -+const char kDisableEyeoFiltering[] = "disable-eyeo-filtering"; -+ -+} // namespace adblock::switches -diff --git a/components/adblock/core/adblock_switches.h b/components/adblock/core/adblock_switches.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/adblock_switches.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_SWITCHES_H_ -+#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_SWITCHES_H_ -+ -+namespace adblock::switches { -+ -+extern const char kDisableAcceptableAds[]; -+extern const char kDisableAdblock[]; -+extern const char kDisableEyeoFiltering[]; -+ -+} // namespace adblock::switches -+ -+#endif // COMPONENTS_ADBLOCK_CORE_ADBLOCK_SWITCHES_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,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 . -+ */ -+ -+#include "components/adblock/core/adblock_telemetry_service.h" -+ -+#include -+ -+#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_prefs.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 topic_provider, -+ scoped_refptr url_loader_factory) -+ : topic_provider_(std::move(topic_provider)), -+ url_loader_factory_(url_loader_factory) {} -+ -+ 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"; -+ topic_provider_->GetPayload(base::BindOnce(&Conversation::MakeRequest, -+ weak_ptr_factory_.GetWeakPtr())); -+ } -+ -+ void Stop() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ url_loader_.reset(); -+ } -+ -+ const std::unique_ptr& GetTopicProvider() const { -+ return topic_provider_; -+ } -+ -+ private: -+ bool IsRequestInFlight() { -+ return url_loader_ != nullptr || weak_ptr_factory_.HasWeakPtrs(); -+ } -+ -+ void MakeRequest(std::string payload) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ auto request = std::make_unique(); -+ 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 server_response) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ topic_provider_->ParseResponse(std::move(server_response)); -+ url_loader_.reset(); -+ } -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ std::unique_ptr topic_provider_; -+ scoped_refptr url_loader_factory_; -+ std::unique_ptr url_loader_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+AdblockTelemetryService::AdblockTelemetryService( -+ FilteringConfiguration* filtering_configuration, -+ scoped_refptr url_loader_factory, -+ base::TimeDelta initial_delay, -+ base::TimeDelta check_interval) -+ : adblock_filtering_configuration_(filtering_configuration), -+ url_loader_factory_(url_loader_factory), -+ initial_delay_(initial_delay), -+ check_interval_(check_interval) { -+ DCHECK(adblock_filtering_configuration_); -+ adblock_filtering_configuration_->AddObserver(this); -+} -+ -+AdblockTelemetryService::~AdblockTelemetryService() { -+ DCHECK(adblock_filtering_configuration_); -+ adblock_filtering_configuration_->RemoveObserver(this); -+} -+ -+void AdblockTelemetryService::AddTopicProvider( -+ std::unique_ptr topic_provider) { -+ ongoing_conversations_.push_back(std::make_unique( -+ std::move(topic_provider), url_loader_factory_)); -+} -+ -+void AdblockTelemetryService::Start() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ OnEnabledStateChangedInternal(); -+} -+ -+void AdblockTelemetryService::OnEnabledStateChanged(FilteringConfiguration*) { -+ OnEnabledStateChangedInternal(); -+} -+ -+void AdblockTelemetryService::GetTopicProvidersDebugInfo( -+ TopicProvidersDebugInfoCallback service_callback) const { -+ const auto barrier_callback = base::BarrierCallback( -+ ongoing_conversations_.size(), std::move(service_callback)); -+ for (const auto& conversation : ongoing_conversations_) { -+ conversation->GetTopicProvider()->FetchDebugInfo(barrier_callback); -+ } -+} -+ -+void AdblockTelemetryService::OnEnabledStateChangedInternal() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ if (adblock_filtering_configuration_->IsEnabled() && !timer_.IsRunning()) { -+ VLOG(1) << "[eyeo] Starting periodic Telemetry requests"; -+ timer_.Start(FROM_HERE, initial_delay_, -+ base::BindRepeating(&AdblockTelemetryService::RunPeriodicCheck, -+ base::Unretained(this))); -+ } else if (!adblock_filtering_configuration_->IsEnabled() && -+ timer_.IsRunning()) { -+ VLOG(1) << "[eyeo] Stopping periodic Telemetry requests"; -+ 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(); -+ } -+} -+ -+} // 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,120 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_ -+#define COMPONENTS_ADBLOCK_CORE_ADBLOCK_TELEMETRY_SERVICE_H_ -+ -+#include -+#include -+#include -+ -+#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/subscription/subscription_service.h" -+#include "components/keyed_service/core/keyed_service.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: -+ // 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; -+ using DebugInfoCallback = base::OnceCallback; -+ 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 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( -+ FilteringConfiguration* filtering_configuration, -+ scoped_refptr url_loader_factory, -+ base::TimeDelta initial_delay, -+ base::TimeDelta check_interval); -+ ~AdblockTelemetryService() override; -+ using TopicProvidersDebugInfoCallback = -+ base::OnceCallback)>; -+ -+ // Add all required topic providers before calling Start(). -+ void AddTopicProvider(std::unique_ptr 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; -+ -+ private: -+ void OnEnabledStateChangedInternal(); -+ void RunPeriodicCheck(); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ raw_ptr adblock_filtering_configuration_; -+ scoped_refptr url_loader_factory_; -+ base::TimeDelta initial_delay_; -+ base::TimeDelta check_interval_; -+ -+ class Conversation; -+ std::vector> 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,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 . -+ -+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", -+ "//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 . -+ */ -+ -+#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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_H_ -+ -+#include -+ -+#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 { -+ 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& 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& opener_frame_hierarchy, -+ const SiteKey& sitekey) const = 0; -+ -+ virtual ClassificationResult ClassifyResponse( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const scoped_refptr& response_headers) -+ const = 0; -+ -+ virtual std::optional CheckRewrite( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy) const = 0; -+ -+ protected: -+ friend class base::RefCountedThreadSafe; -+ 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,404 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/classifier/resource_classifier_impl.h" -+ -+#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; -+ -+std::optional IsHeaderFilterOverruled( -+ std::string_view blocking_header_filter, -+ std::set& 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& 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& 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& 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 frame_hierarchy, -+ const scoped_refptr& headers, -+ std::set blocking_filters, -+ std::set 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& frame_hierarchy, -+ ContentType content_type, -+ const scoped_refptr& 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)); -+} -+ -+std::optional CheckRewriteWithSingleCollection( -+ const SubscriptionCollection& subscription_collection, -+ const GURL& request_url, -+ const std::vector& 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 std::optional(GURL{*blocking_rewrites.begin()}); -+ } -+ -+ // Change set to vector to call algorithm -+ std::vector 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 std::optional(GURL{*blocking_rewrites_v.begin()}); -+} -+ -+} // namespace -+ -+ResourceClassifierImpl::~ResourceClassifierImpl() = default; -+ -+ClassificationResult ResourceClassifierImpl::ClassifyRequest( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& 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& 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& frame_hierarchy, -+ ContentType content_type, -+ const scoped_refptr& 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; -+} -+ -+std::optional ResourceClassifierImpl::CheckRewrite( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_ -+ -+#include -+ -+#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& frame_hierarchy, -+ ContentType content_type, -+ const SiteKey& sitekey) const final; -+ -+ ClassificationResult ClassifyPopup( -+ const SubscriptionService::Snapshot& subscription_collections, -+ const GURL& popup_url, -+ const std::vector& opener_frame_hierarchy, -+ const SiteKey& sitekey) const final; -+ -+ ClassificationResult ClassifyResponse( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const scoped_refptr& response_headers) -+ const final; -+ -+ std::optional CheckRewrite( -+ const SubscriptionService::Snapshot subscription_collections, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy) const final; -+ -+ protected: -+ friend class base::RefCountedThreadSafe; -+ ~ResourceClassifierImpl() override; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_CLASSIFIER_RESOURCE_CLASSIFIER_IMPL_H_ -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,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 . -+ -+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" ] -+ } -+} -+ -+source_set("common") { -+ configs += [ ":eyeo_filtering_config" ] -+ -+ sources = [ -+ "adblock_constants.cc", -+ "adblock_constants.h", -+ "adblock_prefs.cc", -+ "adblock_prefs.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", -+ ] -+ -+ deps = [ "//components/prefs" ] -+ -+ public_deps = [ -+ "//base", -+ "//components/adblock/core:schema", -+ "//components/adblock/core:schema_hash", -+ "//third_party/abseil-cpp:absl", -+ "//url", -+ ] -+} -+ -+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("utils") { -+ configs += [ ":eyeo_application_config" ] -+ -+ sources = [ -+ "adblock_utils.cc", -+ "adblock_utils.h", -+ ] -+ -+ deps = [ -+ "//components/resources:components_resources_grit", -+ "//components/version_info", -+ "//net", -+ "//third_party/icu/", -+ "//third_party/re2", -+ "//ui/base", -+ "//url", -+ ] -+ -+ public_deps = [ -+ ":common", -+ "//base", -+ ] -+} -+ -+source_set("unit_tests") { -+ testonly = true -+ sources = [ -+ "test/adblock_utils_test.cc", -+ "test/flatbuffer_data_test.cc", -+ ] -+ -+ deps = [ -+ ":common", -+ ":utils", -+ "//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,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 . -+ */ -+ -+#include "components/adblock/core/common/adblock_constants.h" -+ -+#include "base/base64.h" -+#include "components/adblock/core/hash/schema_hash.h" -+#include "components/adblock/core/schema/filter_list_schema_generated.h" -+ -+namespace adblock { -+ -+const char kSiteKeyHeaderKey[] = "x-adblock-key"; -+ -+const char kAllowlistEverythingFilter[] = "@@*$document"; -+ -+const char kAdblockFilteringConfigurationName[] = "adblock"; -+ -+const char kBlankHtml[] = -+ "data:text/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 OverrideEyeoFilteringDisabledByDefault(bool val) { -+ return base::AutoReset(&g_eyeo_disable_filtering_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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_CONSTANTS_H_ -+ -+#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(); -+ -+// Override result of IsEyeoFilteringDisabledByDefault() for the -+// duration of the returned AutoReset's lifetime. Used for testing. -+base::AutoReset OverrideEyeoFilteringDisabledByDefault(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,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 . -+ */ -+ -+#include "components/adblock/core/common/adblock_prefs.h" -+ -+#include "base/logging.h" -+#include "components/prefs/pref_registry_simple.h" -+ -+namespace adblock::common::prefs { -+ -+// 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"; -+ -+// List of domains ad blocking will be disabled for -+const char kAdblockAllowedDomainsLegacy[] = "adblock.allowed_domains"; -+ -+// List of custom filters added explicitly by the user -+const char kAdblockCustomFiltersLegacy[] = "adblock.custom_filters"; -+ -+// 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"; -+ -+void RegisterTelemetryPrefs(PrefRegistrySimple* registry) { -+ registry->RegisterStringPref(kTelemetryLastPingTag, ""); -+ registry->RegisterStringPref(kTelemetryLastPingTime, ""); -+ registry->RegisterStringPref(kTelemetryPreviousLastPingTime, ""); -+ registry->RegisterStringPref(kTelemetryFirstPingTime, ""); -+ registry->RegisterTimePref(kTelemetryNextPingTime, base::Time()); -+} -+ -+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); -+ RegisterTelemetryPrefs(registry); -+ -+ VLOG(3) << "[eyeo] Registered 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_H_ -+ -+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[]; -+ -+void RegisterProfilePrefs(PrefRegistrySimple* registry); -+ -+} // namespace adblock::common::prefs -+ -+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_PREFS_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,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 . -+ */ -+ -+#include "components/adblock/core/common/adblock_utils.h" -+ -+#include -+ -+#include "base/containers/flat_map.h" -+#include "base/json/string_escape.h" -+#include "base/logging.h" -+#include "base/memory/scoped_refptr.h" -+#include "base/strings/safe_sprintf.h" -+#include "base/strings/string_split.h" -+#include "base/strings/string_util.h" -+#include "base/strings/utf_string_conversions.h" -+#include "components/adblock/core/common/adblock_constants.h" -+#include "components/grit/components_resources.h" -+#include "components/version_info/version_info.h" -+#include "net/http/http_response_headers.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" -+#include "url/gurl.h" -+ -+namespace adblock { -+namespace utils { -+namespace { -+constexpr char kLanguagesSeparator[] = ","; -+} -+ -+std::string CreateDomainAllowlistingFilter(const std::string& domain) { -+ return "@@||" + base::ToLowerASCII(domain) + -+ "^$document,domain=" + base::ToLowerASCII(domain); -+} -+ -+SiteKey GetSitekeyHeader( -+ const scoped_refptr& 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 {}; -+} -+ -+AppInfo::AppInfo() = default; -+ -+AppInfo::~AppInfo() = default; -+ -+AppInfo::AppInfo(const AppInfo&) = default; -+ -+AppInfo GetAppInfo() { -+ AppInfo info; -+ -+#if defined(EYEO_APPLICATION_NAME) -+ info.name = EYEO_APPLICATION_NAME; -+#else -+ info.name = version_info::GetProductName(); -+#endif -+#if defined(EYEO_APPLICATION_VERSION) -+ info.version = EYEO_APPLICATION_VERSION; -+#else -+ info.version = version_info::GetVersionNumber(); -+#endif -+ base::ReplaceChars(version_info::GetOSType(), base::kWhitespaceASCII, "", -+ &info.client_os); -+ return info; -+} -+ -+std::string SerializeLanguages(const std::vector languages) { -+ if (languages.empty()) { -+ return {}; -+ } -+ -+ return std::accumulate(std::next(languages.begin()), languages.end(), -+ languages[0], -+ [](const std::string& a, const std::string& b) { -+ return a + kLanguagesSeparator + b; -+ }); -+} -+ -+std::vector DeserializeLanguages(const std::string languages) { -+ return base::SplitString(languages, kLanguagesSeparator, -+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); -+} -+ -+std::vector ConvertURLs(const std::vector& input) { -+ std::vector 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::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); -+} -+ -+std::unique_ptr MakeFlatbufferDataFromResourceBundle( -+ int resource_id) { -+ return std::make_unique( -+ 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 utils -+} // namespace adblock -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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_H_ -+ -+#include -+ -+#include "base/functional/callback_forward.h" -+#include "base/memory/scoped_refptr.h" -+#include "base/time/time.h" -+#include "components/adblock/core/common/flatbuffer_data.h" -+#include "components/adblock/core/common/sitekey.h" -+ -+class GURL; -+ -+namespace net { -+class HttpResponseHeaders; -+} -+ -+namespace adblock { -+ -+class Subscription; -+ -+namespace utils { -+ -+struct AppInfo { -+ AppInfo(); -+ ~AppInfo(); -+ AppInfo(const AppInfo&); -+ std::string name; -+ std::string version; -+ std::string client_os; -+}; -+ -+std::string CreateDomainAllowlistingFilter(const std::string& domain); -+ -+SiteKey GetSitekeyHeader( -+ const scoped_refptr& headers); -+ -+AppInfo GetAppInfo(); -+ -+std::string SerializeLanguages(const std::vector languages); -+ -+std::vector DeserializeLanguages(const std::string languages); -+ -+std::vector ConvertURLs(const std::vector& input); -+ -+// converts |date| into abp version format ex: 202107210821 -+// in UTC format as necessary for server -+ -+std::string ConvertBaseTimeToABPFilterVersionFormat(const base::Time& date); -+ -+// Creates a FlatbufferData object that holds data from the ResourceBundle -+ -+std::unique_ptr MakeFlatbufferDataFromResourceBundle( -+ int resource_id); -+ -+bool RegexMatches(std::string_view pattern, -+ std::string_view input, -+ bool case_sensitive); -+ -+} // namespace utils -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_ADBLOCK_UTILS_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 . -+ */ -+ -+#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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_CONTENT_TYPE_H_ -+ -+#include -+ -+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 . -+ */ -+ -+#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 memory, -+ absl::optional 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(data_.data()); -+} -+ -+size_t InMemoryFlatbufferData::size() const { -+ return data_.size(); -+} -+ -+const base::span 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(); -+ if (!file_->Initialize(path_)) { -+ file_.reset(); -+ } -+} -+ -+MemoryMappedFlatbufferData::~MemoryMappedFlatbufferData() { -+ const auto path_to_remove = -+ permanently_remove_path_.load() -+ ? absl::optional(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 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_FLATBUFFER_DATA_H_ -+ -+#include -+#include -+#include -+ -+#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 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 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 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 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_HEADER_FILTER_DATA_H_ -+ -+#include -+ -+#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 . -+ */ -+ -+#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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_KEYWORD_EXTRACTOR_UTILS_H_ -+ -+#include -+ -+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,33 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/common/regex_filter_pattern.h" -+ -+#include "absl/types/optional.h" -+ -+namespace adblock { -+ -+std::optional 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_ -+#define COMPONENTS_ADBLOCK_CORE_COMMON_REGEX_FILTER_PATTERN_H_ -+ -+#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. -+std::optional 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 . -+ */ -+ -+#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; -+ -+} -+ -+#endif // COMPONENTS_ADBLOCK_CORE_COMMON_SITEKEY_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,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 . -+ -+source_set("configuration") { -+ sources = [ -+ "filtering_configuration_prefs.cc", -+ "filtering_configuration_prefs.h", -+ "filtering_configuration.h", -+ "persistent_filtering_configuration.cc", -+ "persistent_filtering_configuration.h", -+ ] -+ -+ 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/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,89 @@ -+ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_ -+ -+#include -+#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 GetFilterLists() 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 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 GetCustomFilters() const = 0; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_H_ -diff --git a/components/adblock/core/configuration/filtering_configuration_prefs.cc b/components/adblock/core/configuration/filtering_configuration_prefs.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/configuration/filtering_configuration_prefs.cc -@@ -0,0 +1,33 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/configuration/filtering_configuration_prefs.h" -+ -+#include "base/logging.h" -+#include "components/prefs/pref_registry_simple.h" -+ -+namespace adblock::filtering_configuration::prefs { -+ -+const char kConfigurationsPrefsPath[] = "filtering.configurations"; -+ -+void RegisterProfilePrefs(PrefRegistrySimple* registry) { -+ registry->RegisterDictionaryPref(kConfigurationsPrefsPath); -+ -+ VLOG(3) << "[eyeo] Registered prefs"; -+} -+ -+} // namespace adblock::filtering_configuration::prefs -diff --git a/components/adblock/core/configuration/filtering_configuration_prefs.h b/components/adblock/core/configuration/filtering_configuration_prefs.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/configuration/filtering_configuration_prefs.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_PREFS_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_PREFS_H_ -+class PrefRegistrySimple; -+ -+namespace adblock::filtering_configuration::prefs { -+ -+extern const char kConfigurationsPrefsPath[]; -+ -+void RegisterProfilePrefs(PrefRegistrySimple* registry); -+ -+} // namespace adblock::filtering_configuration::prefs -+ -+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_FILTERING_CONFIGURATION_PREFS_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,273 @@ -+ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/configuration/persistent_filtering_configuration.h" -+ -+#include -+ -+#include "base/strings/string_util.h" -+#include "components/adblock/core/configuration/filtering_configuration_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(filtering_configuration::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( -+ filtering_configuration::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 -+std::vector GetFromList(const base::Value::Dict& configuration, -+ std::string_view key) { -+ DCHECK(configuration.FindList(key)); // see SetDefaultValuesIfNeeded(). -+ const auto* list = configuration.FindList(key); -+ std::vector 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 PersistentFilteringConfiguration::GetFilterLists() const { -+ return GetFromList(dictionary_, kFilterListsKey); -+} -+ -+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 PersistentFilteringConfiguration::GetAllowedDomains() -+ const { -+ return GetFromList(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 PersistentFilteringConfiguration::GetCustomFilters() -+ const { -+ return GetFromList(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 -+void PersistentFilteringConfiguration::RegisterProfilePrefs( -+ PrefRegistrySimple* registry) { -+ registry->RegisterDictionaryPref( -+ std::string(filtering_configuration::prefs::kConfigurationsPrefsPath)); -+} -+ -+// static -+std::vector> -+PersistentFilteringConfiguration::GetPersistedConfigurations( -+ PrefService* pref_service) { -+ std::vector> configs; -+ const auto& all_configurations = -+ pref_service -+ ->GetValue(filtering_configuration::prefs::kConfigurationsPrefsPath) -+ .GetDict(); -+ for (auto it = all_configurations.begin(); it != all_configurations.end(); -+ ++it) { -+ configs.push_back(std::make_unique( -+ pref_service, (it->first))); -+ } -+ return configs; -+} -+ -+// static -+void PersistentFilteringConfiguration::RemovePersistedData( -+ PrefService* pref_service, -+ const std::string& name) { -+ static std::string kConfigurationsPrefsPathString( -+ filtering_configuration::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,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 . -+ */ -+ -+#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_registry_simple.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 GetFilterLists() const final; -+ -+ void AddAllowedDomain(const std::string& domain) final; -+ void RemoveAllowedDomain(const std::string& domain) final; -+ std::vector GetAllowedDomains() const final; -+ -+ void AddCustomFilter(const std::string& filter) final; -+ void RemoveCustomFilter(const std::string& filter) final; -+ std::vector GetCustomFilters() const final; -+ -+ static void RegisterProfilePrefs(PrefRegistrySimple* registry); -+ -+ static std::vector> -+ 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 pref_service_; -+ std::string name_; -+ base::ObserverList observers_; -+ base::Value::Dict dictionary_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_CONFIGURATION_PERSISTENT_FILTERING_CONFIGURATION_H_ -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,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 . -+ -+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", -+ "//components/resources:components_resources_grit", -+ "//net", -+ "//services/network/public/cpp", -+ ] -+} -+ -+executable("adblock_flatbuffer_converter") { -+ sources = [ "converter_main.cc" ] -+ -+ deps = [ -+ ":converter", -+ "//third_party/zlib/google:compression_utils", -+ ] -+} -+ -+source_set("unit_tests") { -+ testonly = true -+ sources = [ -+ "test/flatbuffer_converter_test.cc", -+ ] -+ -+ deps = [ -+ ":converter", -+ "//components/adblock/core/subscription", -+ "//testing/gmock", -+ "//testing/gtest", -+ ] -+} -+ -+source_set("perf_tests") { -+ testonly = true -+ sources = [ -+ "test/flatbuffer_converter_perftest.cc", -+ ] -+ -+ deps = [ -+ ":converter", -+ "//testing/gtest", -+ "//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,115 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include -+#include -+ -+#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) { -+ 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 = -+ adblock::FlatbufferConverter::Convert(input, url, true); -+ -+ if (absl::holds_alternative(converter_result)) { -+ LOG(ERROR) << "[eyeo] " -+ << absl::get(converter_result); -+ return false; -+ } -+ -+ if (absl::holds_alternative(converter_result)) { -+ LOG(ERROR) << "[eyeo] Filter list redirects. Won't convert"; -+ return false; -+ } -+ -+ if (!base::WriteFile( -+ output_path, -+ reinterpret_cast( -+ absl::get>( -+ converter_result) -+ ->data()), -+ absl::get>(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,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 . -+ */ -+ -+#include "components/adblock/core/converter/flatbuffer_converter.h" -+ -+#include -+#include -+ -+#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 { -+ -+static constexpr char kCommentPrefix[] = "!"; -+static constexpr size_t kMaxSeparatorLength = 3u; -+ -+// static -+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(); -+} -+ -+// static -+std::unique_ptr FlatbufferConverter::Convert( -+ const std::vector& 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(); -+} -+ -+// static -+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::FilterTypeFromString( -+ filter_str.substr(separator_pos, kMaxSeparatorLength)); -+ } -+ -+ switch (filter_type) { -+ case FilterType::ElemHide: -+ case FilterType::ElemHideException: -+ case FilterType::ElemHideEmulation: -+ if (auto content_filter = ContentFilter::FromString( -+ filter_str.substr(0, separator_pos), filter_type, -+ filter_str.substr( -+ separator_pos + -+ (filter_type == FilterType::ElemHide ? 2 : 3)))) { -+ 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_FLATBUFFER_CONVERTER_H_ -+ -+#include -+#include -+ -+#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; -+// Conversion can yield valid FlatbufferData, a redirect URL or an error: -+using ConversionResult = -+ absl::variant, GURL, ConversionError>; -+ -+class FlatbufferSerializer; -+class FlatbufferConverter { -+ public: -+ static ConversionResult Convert(std::istream& filter_stream, -+ GURL subscription_url, -+ bool allow_privileged); -+ static std::unique_ptr Convert( -+ const std::vector& filters, -+ GURL subscription_url, -+ bool allow_privileged); -+ -+ private: -+ static 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,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 . -+ -+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/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,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 . -+ */ -+ -+#include "components/adblock/core/converter/parser/content_filter.h" -+ -+#include "base/logging.h" -+ -+namespace adblock { -+ -+static constexpr char kDomainSeparator[] = ","; -+ -+// static -+std::optional 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); -+ if (selector.empty()) { -+ VLOG(1) << "[eyeo] Content filters require selector"; -+ return {}; -+ } -+ -+ DomainOption domains = -+ DomainOption::FromString(domain_list, kDomainSeparator); -+ -+ if (filter_type == FilterType::ElemHideEmulation) { -+ // ElemHideEmulation filters require that the domains have -+ // at least one subdomain or is localhost -+ domains.RemoveDomainsWithNoSubdomain(); -+ if (domains.GetIncludeDomains().empty()) { -+ VLOG(1) << "[eyeo] ElemHideEmulation " -+ "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 {}; -+ } -+ -+ return ContentFilter(filter_type, selector, std::move(domains)); -+} -+ -+ContentFilter::ContentFilter(FilterType type, -+ std::string_view selector, -+ DomainOption domains) -+ : type(type), -+ selector(selector.data(), selector.size()), -+ domains(std::move(domains)) {} -+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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_CONTENT_FILTER_H_ -+ -+#include -+ -+#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 std::optional FromString(std::string_view domain_list, -+ FilterType type, -+ std::string_view selector); -+ -+ ~ContentFilter(); -+ -+ const FilterType type; -+ const std::string selector; -+ const DomainOption domains; -+ -+ private: -+ ContentFilter(FilterType type, -+ std::string_view selector, -+ 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,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 . -+ */ -+ -+#include "components/adblock/core/converter/parser/content_filter.h" -+ -+#include "base/logging.h" -+#include "base/strings/string_split.h" -+#include "base/strings/string_util.h" -+ -+namespace adblock { -+ -+namespace { -+ -+void RemoveDuplicates(std::vector& data) { -+ sort(data.begin(), data.end()); -+ auto unique_end = unique(data.begin(), data.end()); -+ data.erase(unique_end, 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); -+ -+ const auto first_include_domain_it = std::partition( -+ domains.begin(), domains.end(), [](std::string_view domain) { -+ return base::StartsWith(domain, kExclusionPrefix); -+ }); -+ -+ std::vector exclude_domains(domains.begin(), -+ first_include_domain_it); -+ std::vector 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); -+ } -+ -+ RemoveDuplicates(exclude_domains); -+ RemoveDuplicates(include_domains); -+ -+ return DomainOption(std::move(exclude_domains), std::move(include_domains)); -+} -+ -+const std::vector& DomainOption::GetExcludeDomains() const { -+ return exclude_domains_; -+} -+ -+const std::vector& 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 exclude_domains, -+ std::vector 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,57 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_DOMAIN_OPTION_H_ -+ -+#include -+#include -+ -+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(); -+ -+ const std::vector& GetExcludeDomains() const; -+ const std::vector& GetIncludeDomains() const; -+ -+ void RemoveDomainsWithNoSubdomain(); -+ bool UnrestrictedByDomain() const; -+ -+ private: -+ DomainOption(std::vector exclude_domains, -+ std::vector include_domains); -+ -+ static bool HasSubdomainOrLocalhost(std::string_view domain); -+ -+ std::vector exclude_domains_; -+ std::vector 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,39 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/converter/parser/filter_classifier.h" -+ -+namespace adblock { -+ -+// static -+FilterType FilterClassifier::FilterTypeFromString(std::string_view separator) { -+ if (base::StartsWith(separator, kElemHideFilterSeparator)) { -+ return FilterType::ElemHide; -+ } -+ if (base::StartsWith(separator, kElemHideExceptionFilterSeparator)) { -+ return FilterType::ElemHideException; -+ } -+ if (base::StartsWith(separator, kElemHideEmulationFilterSeparator)) { -+ return FilterType::ElemHideEmulation; -+ } -+ if (base::StartsWith(separator, 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_FILTER_CLASSIFIER_H_ -+ -+#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, -+ Snippet, -+ Url, -+}; -+ -+class FilterClassifier { -+ public: -+ static FilterType FilterTypeFromString(std::string_view separator); -+}; -+ -+} // 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,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 . -+ */ -+ -+#include "components/adblock/core/converter/parser/metadata.h" -+ -+#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 -+std::optional Metadata::FromStream(std::istream& filter_stream) { -+ static re2::RE2 comment_re("^!\\s*(.*?)\\s*:\\s*(.*)"); -+ -+ std::string homepage; -+ std::string title; -+ std::string version; -+ std::optional 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 " -+ ".]."; -+ 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, -+ std::optional 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::ranges::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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_METADATA_H_ -+ -+#include -+#include -+ -+#include "base/time/time.h" -+#include "third_party/abseil-cpp/absl/types/optional.h" -+#include "url/gurl.h" -+ -+namespace adblock { -+ -+class Metadata { -+ public: -+ static std::optional 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 std::optional 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, -+ std::optional 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 . -+ */ -+ -+#include "components/adblock/core/converter/parser/snippet_filter.h" -+ -+#include "base/logging.h" -+ -+namespace adblock { -+ -+static constexpr char kDomainSeparator[] = ","; -+ -+// static -+std::optional 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_FILTER_H_ -+ -+#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 std::optional 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 . -+ */ -+ -+#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 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& 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& 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_SNIPPET_TOKENIZER_H_ -+ -+#include -+#include -+ -+namespace adblock { -+ -+class SnippetTokenizer { -+ public: -+ using SnippetScript = std::vector>; -+ -+ static SnippetScript Tokenize(std::string_view input); -+ -+ private: -+ static void AddEscapeChar(std::string& token, char ch); -+ static void AddArgument(std::vector& arguments, -+ std::string& token, -+ bool quotes_just_closed); -+ static void AddFunctionCall(SnippetScript& script, -+ std::vector& 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/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,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 . -+ */ -+ -+#include "components/adblock/core/converter/parser/url_filter.h" -+ -+#include "base/logging.h" -+#include "base/strings/string_util.h" -+#include "components/adblock/core/common/regex_filter_pattern.h" -+#include "third_party/icu/source/i18n/unicode/regex.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; -+} -+ -+} // namespace -+ -+static constexpr char kAllowingSymbol[] = "@@"; -+static constexpr char kOptionSymbol = '$'; -+ -+bool IsGenericFilterIsNotSpecificEnough( -+ std::string_view filter_str, -+ const std::optional& 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 -+std::optional UrlFilter::FromString(std::string filter_str) { -+ std::optional 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 (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())); -+} -+ -+UrlFilter::UrlFilter(bool is_allowing, -+ std::string pattern, -+ UrlFilterOptions options) -+ : is_allowing(is_allowing), -+ pattern(std::move(pattern)), -+ options(std::move(options)) {} -+UrlFilter::UrlFilter(const UrlFilter& other) = default; -+UrlFilter::UrlFilter(UrlFilter&& other) = default; -+UrlFilter::~UrlFilter() = default; -+ -+// static -+bool UrlFilter::IsValidRegex(const std::string& pattern) { -+ re2::RE2::Options options; -+ options.set_never_capture(true); -+ options.set_log_errors(false); -+ const re2::RE2 re2_pattern(pattern.data(), options); -+ if (re2_pattern.ok()) { -+ return true; -+ } -+ const icu::UnicodeString icu_pattern(pattern.data(), pattern.length()); -+ UErrorCode status = U_ZERO_ERROR; -+ const icu::RegexMatcher matcher(icu_pattern, 0u, status); -+ if (U_FAILURE(status)) { -+ return false; -+ } -+ return true; -+} -+ -+} // 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_H_ -+ -+#include -+ -+#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 std::optional FromString(std::string filter_str); -+ -+ UrlFilter(const UrlFilter& other); -+ UrlFilter(UrlFilter&& other); -+ ~UrlFilter(); -+ -+ const bool is_allowing; -+ const std::string pattern; -+ const UrlFilterOptions options; -+ -+ private: -+ UrlFilter(bool is_allowing, std::string pattern, UrlFilterOptions options); -+ -+ static bool IsValidRegex(const std::string& pattern); -+}; -+ -+} // 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,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 . -+ */ -+ -+#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; -+ -+static constexpr char kDomainOrSitekeySeparator[] = "|"; -+static constexpr char kInverseSymbol = '~'; -+ -+// static -+std::optional 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; -+ std::optional rewrite; -+ DomainOption domains; -+ SiteKeys sitekeys; -+ std::optional csp; -+ std::optional headers; -+ uint32_t content_types = 0; -+ std::set 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); -+ } 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 -+std::optional 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 -+std::optional -+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 std::optional rewrite, -+ const DomainOption domains, -+ const SiteKeys sitekeys, -+ const std::optional csp, -+ const std::optional headers, -+ const std::set 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_PARSER_URL_FILTER_OPTIONS_H_ -+ -+#include -+#include -+#include -+ -+#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 std::optional 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 std::optional& Rewrite() const { -+ return rewrite_; -+ } -+ inline const DomainOption& Domains() const { return domains_; } -+ inline const std::vector& Sitekeys() const { return sitekeys_; } -+ inline const std::optional& Csp() const { return csp_; } -+ inline const std::optional& Headers() const { return headers_; } -+ inline uint32_t ContentTypes() const { return content_types_; } -+ inline const std::set& 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, -+ std::optional rewrite, -+ DomainOption domains, -+ std::vector sitekeys, -+ std::optional csp, -+ std::optional headers, -+ std::set exception_types); -+ -+ static std::optional ParseRewrite( -+ const std::string& rewrite_value); -+ static std::vector ParseSitekeys(const std::string& sitekey_value); -+ static bool IsValidCsp(const std::string& csp_value); -+ static void ParseHeaders(std::string& headers_value); -+ static std::optional ExceptionTypeFromString( -+ const std::string& exception_type); -+ -+ bool is_match_case_; -+ bool is_popup_filter_; -+ bool is_subresource_; -+ ThirdPartyOption third_party_; -+ uint32_t content_types_; -+ std::optional rewrite_; -+ DomainOption domains_; -+ std::vector sitekeys_; -+ std::optional csp_; -+ std::optional headers_; -+ std::set 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,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 . -+ -+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/common", -+ "//components/adblock/core:schema", -+ "//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 . -+ */ -+ -+#include "components/adblock/core/converter/serializer/filter_keyword_extractor.h" -+ -+#include -+#include -+ -+#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 { -+ -+std::optional 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" -+ const static re2::RE2 filter_keyword_extractor( -+ "([^a-zA-Z0-9%*][a-zA-Z0-9%]{2,})"); -+ const static re2::RE2 has_a_following_keyword("(^[^a-zA-Z0-9%*])"); -+ const static re2::RE2 following_keyword_consume("(^[a-zA-Z0-9%*]*)"); -+ if (!RE2::FindAndConsume(&input_, filter_keyword_extractor, -+ ¤t_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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FILTER_KEYWORD_EXTRACTOR_H_ -+ -+#include -+ -+#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(); -+ std::optional 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,397 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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 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 -+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_), -+ WriteSnippetFilterIndex(snippet_index_)); -+ -+ builder_.Finish(subscription, flat::SubscriptionIdentifier()); -+ return std::make_unique(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) { -+ auto offset = 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())); -+ -+ // Insert the filter under the correct index. -+ switch (content_filter.type) { -+ case FilterType::ElemHide: -+ AddElemhideFilterForDomains( -+ elemhide_index_, content_filter.domains.GetIncludeDomains(), offset); -+ break; -+ case FilterType::ElemHideException: -+ AddElemhideFilterForDomains(elemhide_exception_index_, -+ content_filter.domains.GetIncludeDomains(), -+ offset); -+ break; -+ case FilterType::ElemHideEmulation: -+ AddElemhideFilterForDomains(elemhide_emulation_index_, -+ content_filter.domains.GetIncludeDomains(), -+ offset); -+ break; -+ default: -+ break; -+ } -+} -+ -+void FlatbufferSerializer::SerializeSnippetFilter( -+ const SnippetFilter snippet_filter) { -+ if (!allow_privileged_) { -+ VLOG(1) << "[eyeo] Snippet filters not allowed"; -+ return; -+ } -+ -+ std::vector> 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.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(), -+ options.Csp().has_value() -+ ? builder_.CreateSharedString(options.Csp().value()) -+ : flatbuffers::Offset(), -+ options.Headers().has_value() -+ ? builder_.CreateSharedString(options.Headers().value()) -+ : flatbuffers::Offset()); -+ -+ const std::optional keyword_pattern = -+ ExtractRegexFilterFromPattern(url_filter.pattern).has_value() -+ ? std::optional() -+ : 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, -+ std::optional pattern_text, -+ flatbuffers::Offset filter) { -+ const auto keyword = -+ pattern_text ? FindCandidateKeyword(index, *pattern_text) : ""; -+ index[keyword].push_back(filter); -+} -+ -+void FlatbufferSerializer::AddElemhideFilterForDomains( -+ ElemhideIndex& index, -+ const std::vector& include_domains, -+ flatbuffers::Offset 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::AddSnippetFilterForDomains( -+ SnippetIndex& index, -+ const std::vector& domains, -+ flatbuffers::Offset filter) const { -+ for (const auto& domain : domains) { -+ index[domain].push_back(filter); -+ } -+} -+ -+flatbuffers::Offset< -+ flatbuffers::Vector>> -+FlatbufferSerializer::CreateVectorOfSharedStrings( -+ const std::vector& strings) { -+ std::vector> 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>> -+FlatbufferSerializer::CreateVectorOfSharedStringsFromSitekeys( -+ const std::vector& sitekeys) { -+ std::vector> 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>> -+FlatbufferSerializer::WriteUrlFilterIndex(const UrlFilterIndex& index) { -+ std::vector> 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>> -+FlatbufferSerializer::WriteElemhideFilterIndex(const ElemhideIndex& index) { -+ std::vector> 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>> -+FlatbufferSerializer::WriteSnippetFilterIndex(const SnippetIndex& index) { -+ std::vector> 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::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; -+} -+ -+// 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_FLATBUFFER_SERIALIZER_H_ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#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 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>>; -+ using ElemhideIndex = std::unordered_map< -+ std::string, -+ std::vector>>; -+ using SnippetIndex = -+ std::map>>; -+ -+ void AddUrlFilterToIndex(UrlFilterIndex& index, -+ std::optional pattern_text, -+ flatbuffers::Offset filter); -+ void AddElemhideFilterForDomains( -+ ElemhideIndex& index, -+ const std::vector& include_domains, -+ flatbuffers::Offset filter) const; -+ void AddSnippetFilterForDomains( -+ SnippetIndex& index, -+ const std::vector& domains, -+ flatbuffers::Offset filter) const; -+ -+ flatbuffers::Offset< -+ flatbuffers::Vector>> -+ CreateVectorOfSharedStrings(const std::vector& strings); -+ -+ flatbuffers::Offset< -+ flatbuffers::Vector>> -+ CreateVectorOfSharedStringsFromSitekeys(const std::vector& sitekeys); -+ -+ flatbuffers::Offset< -+ flatbuffers::Vector>> -+ WriteUrlFilterIndex(const UrlFilterIndex& index); -+ -+ flatbuffers::Offset< -+ flatbuffers::Vector>> -+ WriteElemhideFilterIndex(const ElemhideIndex& index); -+ -+ flatbuffers::Offset< -+ flatbuffers::Vector>> -+ WriteSnippetFilterIndex(const SnippetIndex& index); -+ -+ std::string FindCandidateKeyword(UrlFilterIndex& index, -+ std::string_view value); -+ -+ 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 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_; -+ 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_ -+#define COMPONENTS_ADBLOCK_CORE_CONVERTER_SERIALIZER_SERIALIZER_H_ -+ -+#include -+ -+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/features.cc b/components/adblock/core/features.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/features.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 . -+ */ -+ -+#include "components/adblock/core/features.h" -+ -+namespace adblock { -+ -+const base::Feature kAdblockPlusFeature{"AdblockPlus", -+ base::FEATURE_ENABLED_BY_DEFAULT}; -+ -+} -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 . -+ */ -+ -+#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. -+extern const base::Feature kAdblockPlusFeature; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_FEATURES_H_ -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,165 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+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 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]; -+} -+ -+// 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]; -+ snippet: [SnippetFiltersByDomain]; -+} -+ -+root_type Subscription; -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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_ -+#define COMPONENTS_ADBLOCK_CORE_SESSION_STATS_H_ -+ -+#include -+ -+#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 GetSessionAllowedAdsCount() const = 0; -+ -+ virtual std::map GetSessionBlockedAdsCount() 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_ -+#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_H_ -+ -+#include -+ -+#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& headers, -+ const std::string& user_agent) = 0; -+ -+ virtual std::optional> FindSiteKeyForAnyUrl( -+ const std::vector& 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,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 . -+ */ -+ -+#include "components/adblock/core/sitekey_storage_impl.h" -+ -+#include "absl/types/optional.h" -+#include "base/base64.h" -+#include "base/logging.h" -+#include "base/trace_event/trace_event.h" -+#include "components/adblock/core/common/adblock_utils.h" -+#include "crypto/openssl_util.h" -+#include "crypto/signature_verifier.h" -+ -+namespace adblock { -+ -+SitekeyStorageImpl::SitekeyStorageImpl() { -+ crypto::EnsureOpenSSLInit(); -+} -+ -+SitekeyStorageImpl::~SitekeyStorageImpl() = default; -+ -+void SitekeyStorageImpl::ProcessResponseHeaders( -+ const GURL& request_url, -+ const scoped_refptr& headers, -+ const std::string& user_agent) { -+ if (user_agent.empty()) { -+ LOG(WARNING) << "[eyeo] No user agent info"; -+ return; -+ } -+ -+ auto site_key = adblock::utils::GetSitekeyHeader(headers); -+ if (site_key.value().empty()) { -+ VLOG(1) << "[eyeo] No site key header"; -+ return; -+ } -+ -+ ProcessSiteKey(request_url, site_key, user_agent); -+} -+ -+std::optional> -+SitekeyStorageImpl::FindSiteKeyForAnyUrl(const std::vector& 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_byte_span(signature), -+ base::as_byte_span(public_key))) { -+ verifier.VerifyUpdate(base::as_byte_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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SITEKEY_STORAGE_IMPL_H_ -+ -+#include -+ -+#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& headers, -+ const std::string& user_agent) final; -+ -+ std::optional> FindSiteKeyForAnyUrl( -+ const std::vector& 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 url_to_sitekey_map_; -+ base::WeakPtrFactory 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,178 @@ -+# -+# This file is part of eyeo Chromium SDK, -+# Copyright (C) 2006-present eyeo GmbH -+# -+# eyeo Chromium SDK is free software: you can redistribute it and/or modify -+# it under the terms of the GNU General Public License version 3 as -+# published by the Free Software Foundation. -+# -+# eyeo Chromium SDK is distributed in the hope that it will be useful, -+# but WITHOUT ANY WARRANTY; without even the implied warranty of -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+# GNU General Public License for more details. -+# -+# You should have received a copy of the GNU General Public License -+# along with eyeo Chromium SDK. If not, see . -+ -+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", -+ "ongoing_subscription_request.h", -+ "ongoing_subscription_request_impl.cc", -+ "ongoing_subscription_request_impl.h", -+ "pattern_matcher.cc", -+ "pattern_matcher.h", -+ "preloaded_subscription_provider.h", -+ "preloaded_subscription_provider_impl.cc", -+ "preloaded_subscription_provider_impl.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_updater.h", -+ "subscription_updater_impl.cc", -+ "subscription_updater_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/resources:components_resources_grit", -+ "//net", -+ ] -+ -+ public_deps = [ -+ "//base", -+ "//components/adblock/core:schema", -+ "//components/adblock/core/common", -+ "//components/adblock/core/common", -+ "//components/adblock/core/configuration", -+ "//components/keyed_service/core", -+ "//components/prefs", -+ "//services/network/public/cpp", -+ "//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", -+ "test/mock_subscription_updater.cc", -+ "test/mock_subscription_updater.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/filtering_configuration_maintainer_impl_test.cc", -+ "test/installed_subscription_impl_test.cc", -+ "test/ongoing_subscription_request_impl_test.cc", -+ "test/pattern_matcher_test.cc", -+ "test/preloaded_subscription_provider_impl_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_updater_impl_test.cc", -+ "test/subscription_validator_impl_test.cc", -+ "test/url_keyword_extractor_test.cc", -+ ] -+ -+ deps = [ -+ ":test_support", -+ "//components/adblock/core", -+ "//components/adblock/core/configuration:test_support", -+ "//components/adblock/core/converter", -+ "//components/prefs:test_support", -+ "//components/resources:components_resources_grit", -+ "//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_url.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_CONVERSION_EXECUTORS_H_ -+ -+#include -+#include -+ -+#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 ConvertCustomFilters( -+ const std::vector& filters) const = 0; -+ // Asynchronous -+ virtual void ConvertFilterListFile( -+ const GURL& subscription_url, -+ const base::FilePath& path, -+ base::OnceCallback 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,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 . -+ */ -+ -+#include "components/adblock/core/subscription/domain_splitter.h" -+ -+#include "base/strings/string_util.h" -+ -+namespace adblock { -+ -+DomainSplitter::DomainSplitter(std::string_view domain) -+ : domain_(base::TrimString(domain, ".", base::TRIM_ALL)) {} -+ -+std::optional DomainSplitter::FindNextSubdomain() { -+ const auto old_dot_pos = dot_pos_; -+ if (dot_pos_ < domain_.size()) { -+ // Find next dot in domain, for future iteration to consume. -+ dot_pos_ = domain_.find('.', dot_pos_ + 1); -+ // First run is special because we found no dots yet. -+ if (old_dot_pos == 0) { -+ return domain_; -+ } -+ // Return the part of domain *after* the dot we found in previous iteration. -+ return domain_.substr(old_dot_pos + 1); -+ } -+ // Reached end of domain_. -+ // Reset in anticipation of future calls to FindNextSubdomain(). -+ dot_pos_ = 0; -+ return absl::nullopt; -+} -+ -+} // 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_DOMAIN_SPLITTER_H_ -+ -+#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 nullopt. -+class DomainSplitter { -+ public: -+ // |domain| must outlive this, no copy made. -+ explicit DomainSplitter(std::string_view domain); -+ // Returns reference to part of |domain|. -+ std::optional FindNextSubdomain(); -+ -+ private: -+ const std::string_view domain_; -+ size_t dot_pos_ = 0; -+}; -+ -+} // 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_FILTERING_CONFIGURATION_MAINTAINER_H_ -+ -+#include -+#include -+ -+#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 GetSubscriptionCollection() -+ const = 0; -+ -+ // Allows inspecting what Subscriptions are currently in use. This includes -+ // ongoing downloads, preloaded subscriptions and installed subscriptions. -+ virtual std::vector> GetCurrentSubscriptions() -+ const = 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,489 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/common/adblock_utils.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); -+ -+} // 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() final = default; -+ GURL url_; -+}; -+ -+FilteringConfigurationMaintainerImpl::FilteringConfigurationMaintainerImpl( -+ FilteringConfiguration* configuration, -+ std::unique_ptr storage, -+ std::unique_ptr downloader, -+ std::unique_ptr -+ preloaded_subscription_provider, -+ std::unique_ptr updater, -+ ConversionExecutors* conversion_executor, -+ SubscriptionPersistentMetadata* persistent_metadata, -+ SubscriptionUpdatedCallback subscription_updated_callback) -+ : configuration_(std::move(configuration)), -+ storage_(std::move(storage)), -+ downloader_(std::move(downloader)), -+ preloaded_subscription_provider_( -+ std::move(preloaded_subscription_provider)), -+ updater_(std::move(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 -+FilteringConfigurationMaintainerImpl::GetSubscriptionCollection() const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ std::vector> 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( -+ state, configuration_->GetName()); -+} -+ -+std::vector> -+FilteringConfigurationMaintainerImpl::GetCurrentSubscriptions() const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ // Result will contain the currently installed subscriptions: -+ std::vector> 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::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)); -+ // Subscriptions that are demanded by the FilteringConfiguration: -+ auto demanded_subscriptions = configuration_->GetFilterLists(); -+ std::ranges::sort(installed_subscriptions); -+ std::ranges::sort(demanded_subscriptions); -+ // Missing subscriptions is the difference between the two: -+ std::vector 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)); -+ // Subscriptions that are demanded by the FilteringConfiguration: -+ auto demanded_subscriptions = configuration_->GetFilterLists(); -+ std::ranges::sort(installed_subscriptions); -+ 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 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> 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: -+ 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, 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"; -+ 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 SubscriptionUpdater in due time anyway. -+ auto retry_policy = -+ is_an_update ? SubscriptionDownloader::RetryPolicy::DoNotRetry -+ : SubscriptionDownloader::RetryPolicy::RetryUntilSucceeded; -+ -+ auto ongoing_installation = -+ base::MakeRefCounted(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 ongoing_installation, -+ std::unique_ptr 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 ongoing_installation, -+ scoped_refptr 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 filters = configuration_->GetCustomFilters(); -+ std::ranges::transform(configuration_->GetAllowedDomains(), -+ std::back_inserter(filters), -+ &utils::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 FilteringConfigurationMaintainerImpl::GetReadySubscriptions() -+ const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ std::vector result; -+ result.reserve(current_state_.size()); -+ std::ranges::transform(current_state_, std::back_inserter(result), -+ &Subscription::GetSourceUrl); -+ return result; -+} -+ -+std::vector -+FilteringConfigurationMaintainerImpl::GetPendingSubscriptions() const { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ std::vector 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,119 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#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/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/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_updater.h" -+ -+namespace adblock { -+ -+class FilteringConfigurationMaintainerImpl -+ : public FilteringConfigurationMaintainer, -+ public FilteringConfiguration::Observer { -+ public: -+ using SubscriptionUpdatedCallback = -+ base::RepeatingCallback; -+ FilteringConfigurationMaintainerImpl( -+ FilteringConfiguration* configuration, -+ std::unique_ptr storage, -+ std::unique_ptr downloader, -+ std::unique_ptr -+ preloaded_subscription_provider, -+ std::unique_ptr updater, -+ ConversionExecutors* conversion_executor, -+ SubscriptionPersistentMetadata* persistent_metadata, -+ SubscriptionUpdatedCallback subscription_updated_callback); -+ ~FilteringConfigurationMaintainerImpl() override; -+ -+ std::unique_ptr GetSubscriptionCollection() -+ const override; -+ -+ std::vector> GetCurrentSubscriptions() -+ const override; -+ -+ // 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> loaded_subscriptions); -+ void RemoveDuplicateSubscriptions(); -+ void RunUpdateCheck(); -+ void DownloadAndInstallSubscription(const GURL& subscription_url); -+ void OnSubscriptionDataAvailable( -+ scoped_refptr ongoing_installation, -+ std::unique_ptr raw_data); -+ void SubscriptionAddedToStorage( -+ scoped_refptr ongoing_installation, -+ scoped_refptr 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 GetReadySubscriptions() const; -+ std::vector GetPendingSubscriptions() const; -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ StorageStatus status_ = StorageStatus::Uninitialized; -+ raw_ptr configuration_; -+ std::unique_ptr storage_; -+ std::unique_ptr downloader_; -+ std::unique_ptr -+ preloaded_subscription_provider_; -+ std::unique_ptr updater_; -+ raw_ptr 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 persistent_metadata_; -+ SubscriptionUpdatedCallback subscription_updated_callback_; -+ std::set> ongoing_installations_; -+ std::vector> current_state_; -+ scoped_refptr custom_filters_; -+ base::WeakPtrFactory 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,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 . -+ */ -+ -+#include "components/adblock/core/subscription/installed_subscription.h" -+ -+namespace adblock { -+ -+InstalledSubscription::Selectors::Selectors() = default; -+InstalledSubscription::Selectors::~Selectors() = default; -+InstalledSubscription::Selectors::Selectors(const Selectors&) = default; -+InstalledSubscription::Selectors::Selectors(Selectors&&) = default; -+InstalledSubscription::Selectors& InstalledSubscription::Selectors::operator=( -+ const Selectors&) = default; -+InstalledSubscription::Selectors& InstalledSubscription::Selectors::operator=( -+ Selectors&&) = 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_H_ -+ -+#include -+#include -+#include -+#include -+ -+#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 Selectors { -+ Selectors(); -+ ~Selectors(); -+ Selectors(const Selectors&); -+ Selectors(Selectors&&); -+ Selectors& operator=(const Selectors&); -+ Selectors& operator=(Selectors&&); -+ // The final set of selectors to apply on a page is |elemhide_selectors| -+ // minus |elemhide_exceptions|. This difference is not computed by this -+ // Subscription because there may be multiple subscriptions and -+ // |elemhide_exceptions| from one subscriptions may remove -+ // |elemhide_selectors| from another. -+ std::vector elemhide_selectors; -+ std::vector elemhide_exceptions; -+ }; -+ -+ class Snippet { -+ public: -+ Snippet(); -+ Snippet(const Snippet&); -+ Snippet(Snippet&&); -+ ~Snippet(); -+ Snippet& operator=(const Snippet&); -+ Snippet& operator=(Snippet&&); -+ std::string_view command; -+ std::vector 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& results) const = 0; -+ // Find all rewrite filters matching category. -+ virtual std::set 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& results) const = 0; -+ -+ virtual std::vector MatchSnippets( -+ const std::string& document_domain) const = 0; -+ -+ virtual Selectors GetElemhideSelectors(const GURL& url, -+ bool domain_specfic) const = 0; -+ // Note there's no "domain_specific". Emulation filters are always -+ // domain-specific. -+ virtual Selectors GetElemhideEmulationSelectors(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() 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,551 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/installed_subscription_impl.h" -+ -+#include -+#include -+#include -+ -+#include "absl/types/optional.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_utils.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) { -+ // 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") -+ -+ 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>* 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); -+ }); -+} -+ -+} // namespace -+ -+InstalledSubscriptionImpl::InstalledSubscriptionImpl( -+ std::unique_ptr 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()) { -+ 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& 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 InstalledSubscriptionImpl::FindRewriteFilters( -+ const GURL& url, -+ const std::string& document_domain, -+ FilterCategory category) const { -+ std::set 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& 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(); -+} -+ -+std::vector InstalledSubscriptionImpl::GetSelectorsForDomain( -+ const flat::ElemHideFiltersByDomain* category, -+ std::string_view domain) const { -+ TRACE_EVENT1("eyeo", "InstalledSubscriptionImpl::GetSelectorsForDomain", -+ "domain", domain); -+ -+ if (!category || !category->filter()) { -+ // No filters found for this domain. -+ return {}; -+ } -+ -+ std::vector selectors; -+ for (auto* filter : *category->filter()) { -+ const bool filter_disallowed_by_excludes = -+ // 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()); -+ if (filter_disallowed_by_excludes) { -+ continue; -+ } -+ selectors.emplace_back(filter->selector()->c_str(), -+ filter->selector()->size()); -+ } -+ -+ return selectors; -+} -+ -+InstalledSubscription::Selectors -+InstalledSubscriptionImpl::GetElemhideSelectors(const GURL& url, -+ bool domain_specific) const { -+ Selectors result; -+ const std::string domain(base::ToLowerASCII(url.host())); -+ if (!domain_specific) { -+ result.elemhide_selectors = -+ GetSelectorsForDomain(index_->elemhide()->LookupByKey(""), domain); -+ result.elemhide_exceptions = GetSelectorsForDomain( -+ index_->elemhide_exception()->LookupByKey(""), domain); -+ } -+ -+ DomainSplitter domain_splitter(domain); -+ while (auto subdomain = domain_splitter.FindNextSubdomain()) { -+ auto specific_selectors = GetSelectorsForDomain( -+ index_->elemhide()->LookupByKey(subdomain->data()), domain); -+ std::move(specific_selectors.begin(), specific_selectors.end(), -+ std::back_inserter(result.elemhide_selectors)); -+ auto specific_exceptions = GetSelectorsForDomain( -+ index_->elemhide_exception()->LookupByKey(subdomain->data()), domain); -+ std::move(specific_exceptions.begin(), specific_exceptions.end(), -+ std::back_inserter(result.elemhide_exceptions)); -+ } -+ -+ return result; -+} -+ -+InstalledSubscription::Selectors -+InstalledSubscriptionImpl::GetElemhideEmulationSelectors( -+ const GURL& url) const { -+ const std::string& domain = url.host(); -+ Selectors result; -+ DomainSplitter domain_splitter(domain); -+ while (auto subdomain = domain_splitter.FindNextSubdomain()) { -+ auto elemhide_selectors = GetSelectorsForDomain( -+ index_->elemhide_emulation()->LookupByKey(subdomain->data()), domain); -+ std::move(elemhide_selectors.begin(), elemhide_selectors.end(), -+ std::back_inserter(result.elemhide_selectors)); -+ auto elemhide_exceptions = GetSelectorsForDomain( -+ index_->elemhide_exception()->LookupByKey(subdomain->data()), domain); -+ std::move(elemhide_exceptions.begin(), elemhide_exceptions.end(), -+ std::back_inserter(result.elemhide_exceptions)); -+ } -+ return result; -+} -+ -+std::vector InstalledSubscriptionImpl::FindInternal( -+ const UrlFilterIndex* index, -+ const GURL& url, -+ std::optional 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 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()) { -+ return results; -+ } -+ } -+ -+ FindFiltersForKeyword(index, "", url, lowercase_url, content_type, -+ normalized_domain, normalized_sitekey, category, -+ is_third_party_request, strategy, results); -+ return results; -+} -+ -+void InstalledSubscriptionImpl::FindFiltersForKeyword( -+ const UrlFilterIndex* index, -+ std::string_view keyword, -+ const GURL& url, -+ const GURL& lowercase_url, -+ std::optional content_type, -+ const std::string& document_domain, -+ const std::string& sitekey, -+ FilterCategory category, -+ bool is_third_party_request, -+ FindStrategy strategy, -+ std::vector& 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, -+ std::optional 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 -+InstalledSubscriptionImpl::MatchSnippets( -+ const std::string& document_domain) const { -+ std::vector 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_ -+ -+#include -+#include -+#include -+ -+#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 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& results) const final; -+ std::set 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& results) const final; -+ -+ Selectors GetElemhideSelectors(const GURL& url, -+ bool domain_specific) const final; -+ Selectors GetElemhideEmulationSelectors(const GURL& url) const final; -+ -+ std::vector MatchSnippets( -+ const std::string& document_domain) const final; -+ -+ void MarkForPermanentRemoval() final; -+ -+ private: -+ friend class base::RefCountedThreadSafe; -+ ~InstalledSubscriptionImpl() final; -+ enum class FindStrategy { -+ FindFirst, -+ FindAll, -+ }; -+ -+ using UrlFilterIndex = -+ flatbuffers::Vector>; -+ using Domains = flatbuffers::Vector>; -+ // Finds the first filter in |category| that matches the remaining parameters. -+ // Finds all filters in category that matchers the remaining parameters. -+ std::vector FindInternal( -+ const UrlFilterIndex* index, -+ const GURL& url, -+ std::optional 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, -+ std::optional content_type, -+ const std::string& document_domain, -+ const std::string& sitekey, -+ FilterCategory category, -+ bool is_third_party_request, -+ FindStrategy strategy, -+ std::vector& out_results) const; -+ bool CandidateFilterViable(const flat::UrlFilter* candidate, -+ std::optional 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; -+ std::vector GetSelectorsForDomain( -+ const flat::ElemHideFiltersByDomain* category, -+ std::string_view domain) const; -+ -+ const std::unique_ptr buffer_; -+ const InstallationState installation_state_; -+ const base::Time installation_time_; -+ raw_ptr index_ = nullptr; -+ const std::unique_ptr regex_matcher_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_INSTALLED_SUBSCRIPTION_IMPL_H_ -diff --git a/components/adblock/core/subscription/ongoing_subscription_request.h b/components/adblock/core/subscription/ongoing_subscription_request.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/ongoing_subscription_request.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_H_ -+ -+#include "base/files/file_path.h" -+#include "base/functional/callback.h" -+#include "services/network/public/cpp/simple_url_loader.h" -+ -+namespace adblock { -+ -+// State machine of a download request of a single subscription (GET or HEAD). -+// It implements observing network state and retries. -+class OngoingSubscriptionRequest { -+ public: -+ using ResponseCallback = base::RepeatingCallback headers)>; -+ enum class Method { GET, HEAD }; -+ -+ virtual ~OngoingSubscriptionRequest() = default; -+ -+ virtual void Start(GURL url, -+ Method method, -+ ResponseCallback response_callback) = 0; -+ virtual void Retry() = 0; -+ virtual void Redirect(GURL redirect_url) = 0; -+ -+ virtual size_t GetNumberOfRedirects() const = 0; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_H_ -diff --git a/components/adblock/core/subscription/ongoing_subscription_request_impl.cc b/components/adblock/core/subscription/ongoing_subscription_request_impl.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/ongoing_subscription_request_impl.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 . -+ */ -+ -+#include "components/adblock/core/subscription/ongoing_subscription_request_impl.h" -+ -+#include -+ -+#include "base/task/thread_pool.h" -+#include "base/trace_event/trace_event.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_subscription_download", R"( -+ semantics { -+ sender: "SubscriptionDownloaderImpl" -+ description: -+ "A request to download ad-blocking filter lists, as the user " -+ "selects a new filter list source or as a filter list update " -+ "is fetched." -+ 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." -+ })"); -+ -+} // namespace -+ -+OngoingSubscriptionRequestImpl::OngoingSubscriptionRequestImpl( -+ const net::BackoffEntry::Policy* backoff_policy, -+ scoped_refptr url_loader_factory) -+ : backoff_entry_(std::make_unique(backoff_policy)), -+ url_loader_factory_(url_loader_factory), -+ retry_timer_(std::make_unique()), -+ number_of_redirects_(0) { -+ net::NetworkChangeNotifier::AddNetworkChangeObserver(this); -+} -+ -+OngoingSubscriptionRequestImpl::~OngoingSubscriptionRequestImpl() { -+ if (!url_.is_empty()) { -+ VLOG(1) << "[eyeo] Cancelling download of " << url_; -+ } -+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); -+} -+ -+void OngoingSubscriptionRequestImpl::Start(GURL url, -+ Method method, -+ ResponseCallback response_callback) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(url_.is_empty()) << "Start() called twice"; -+ url_ = std::move(url); -+ method_ = method; -+ response_callback_ = std::move(response_callback); -+ if (!net::NetworkChangeNotifier::IsOffline()) { -+ StartInternal(); -+ } -+} -+ -+void OngoingSubscriptionRequestImpl::Retry() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(!url_.is_empty()) << "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(&OngoingSubscriptionRequestImpl::StartInternal, -+ // Unretained is safe because destruction of |this| will -+ // remove |retry_timer_| and abort the callback. -+ base::Unretained(this))); -+} -+ -+void OngoingSubscriptionRequestImpl::Redirect(GURL redirect_url) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(!url_.is_empty()) << "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_ = std::move(redirect_url); -+ StartInternal(); -+} -+ -+size_t OngoingSubscriptionRequestImpl::GetNumberOfRedirects() const { -+ return number_of_redirects_; -+} -+ -+void OngoingSubscriptionRequestImpl::StartInternal() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN2("eyeo", "Downloading subscription", 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(); -+ request->url = url_; -+ request->method = MethodToString(); -+ loader_ = -+ network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation); -+ -+ if (method_ == Method::GET) { -+ loader_->DownloadToTempFile( -+ url_loader_factory_.get(), -+ base::BindOnce(&OngoingSubscriptionRequestImpl::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(&OngoingSubscriptionRequestImpl::OnHeadersReceived, -+ base::Unretained(this))); -+ } -+} -+ -+void OngoingSubscriptionRequestImpl::OnDownloadFinished( -+ base::FilePath downloaded_file) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo", "Downloading subscription", this); -+ 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 OngoingSubscriptionRequestImpl::OnHeadersReceived( -+ scoped_refptr headers) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ TRACE_EVENT_NESTABLE_ASYNC_END0("eyeo", "Downloading subscription", this); -+ response_callback_.Run(GURL(), base::FilePath(), headers); -+ // response_callback_ may delete this, do not call any member variables now. -+} -+ -+const char* OngoingSubscriptionRequestImpl::MethodToString() const { -+ return method_ == Method::GET ? net::HttpRequestHeaders::kGetMethod -+ : net::HttpRequestHeaders::kHeadMethod; -+} -+ -+bool OngoingSubscriptionRequestImpl::IsStarted() const { -+ return loader_ != nullptr || retry_timer_->IsRunning(); -+} -+ -+void OngoingSubscriptionRequestImpl::OnNetworkChanged( -+ net::NetworkChangeNotifier::ConnectionType connection_type) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ if (!IsStarted() && !url_.is_empty() && -+ !net::NetworkChangeNotifier::IsOffline()) { -+ StartInternal(); -+ DCHECK(IsStarted()); -+ } -+} -+ -+} // namespace adblock -diff --git a/components/adblock/core/subscription/ongoing_subscription_request_impl.h b/components/adblock/core/subscription/ongoing_subscription_request_impl.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/ongoing_subscription_request_impl.h -@@ -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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_IMPL_H_ -+ -+#include -+ -+#include "base/functional/callback.h" -+ -+#include "base/files/file_path.h" -+#include "base/timer/timer.h" -+#include "components/adblock/core/subscription/ongoing_subscription_request.h" -+#include "components/adblock/core/subscription/subscription_downloader.h" -+#include "net/base/backoff_entry.h" -+#include "net/base/network_change_notifier.h" -+#include "services/network/public/cpp/shared_url_loader_factory.h" -+#include "services/network/public/cpp/simple_url_loader.h" -+ -+namespace adblock { -+ -+class OngoingSubscriptionRequestImpl final -+ : public OngoingSubscriptionRequest, -+ public net::NetworkChangeNotifier::NetworkChangeObserver { -+ public: -+ OngoingSubscriptionRequestImpl( -+ const net::BackoffEntry::Policy* backoff_policy, -+ scoped_refptr url_loader_factory); -+ ~OngoingSubscriptionRequestImpl() final; -+ void Start(GURL url, Method method, ResponseCallback response_callback) final; -+ void Retry() final; -+ void Redirect(GURL redirect_url) final; -+ -+ size_t GetNumberOfRedirects() const final; -+ -+ // NetworkChangeObserver: -+ void OnNetworkChanged( -+ net::NetworkChangeNotifier::ConnectionType connection_type) final; -+ -+ private: -+ bool IsStarted() const; -+ void StartInternal(); -+ void OnDownloadFinished(base::FilePath downloaded_file); -+ void OnHeadersReceived(scoped_refptr headers); -+ const char* MethodToString() const; -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ std::unique_ptr backoff_entry_; -+ scoped_refptr url_loader_factory_; -+ GURL url_; -+ Method method_ = Method::GET; -+ ResponseCallback response_callback_; -+ std::unique_ptr loader_; -+ std::unique_ptr retry_timer_; -+ size_t number_of_redirects_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_ONGOING_SUBSCRIPTION_REQUEST_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,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 . -+ */ -+ -+#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" -+ -+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) { -+ const GURL trimmed_url = url.GetWithEmptyPath(); -+ if (url.has_scheme()) { -+ DCHECK(base::StartsWith(candidate, url.scheme_piece())); -+ candidate = candidate.substr(url.scheme_piece().size()); -+ } -+ candidate = -+ base::TrimString(candidate, ":/", base::TrimPositions::TRIM_LEADING); -+ 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_; -+}; -+ -+std::optional 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. -+std::optional 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) -+ ? std::optional{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,33 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PATTERN_MATCHER_H_ -+ -+#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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_H_ -+ -+#include -+ -+#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 installed_subscriptions, -+ std::vector 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> -+ 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,119 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/preloaded_subscription_provider_impl.h" -+ -+#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& 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& installed_subscriptions, -+ const std::vector& 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( -+ 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 subscription() const { -+ return subscription_; -+ } -+ -+ void Reset() { subscription_.reset(); } -+ -+ private: -+ PreloadedSubscriptionInfo info_; -+ scoped_refptr subscription_; -+}; -+ -+PreloadedSubscriptionProviderImpl::~PreloadedSubscriptionProviderImpl() = -+ default; -+PreloadedSubscriptionProviderImpl::PreloadedSubscriptionProviderImpl() { -+ for (const auto& info : config::GetPreloadedSubscriptionConfiguration()) { -+ providers_.emplace_back(info); -+ } -+} -+ -+void PreloadedSubscriptionProviderImpl::UpdateSubscriptions( -+ std::vector installed_subscriptions, -+ std::vector pending_subscriptions) { -+ installed_subscriptions_ = std::move(installed_subscriptions); -+ pending_subscriptions_ = std::move(pending_subscriptions); -+ UpdateSubscriptionsInternal(); -+} -+ -+std::vector> -+PreloadedSubscriptionProviderImpl::GetCurrentPreloadedSubscriptions() const { -+ std::vector> 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_H_ -+ -+#include -+ -+#include "components/adblock/core/subscription/preloaded_subscription_provider.h" -+ -+namespace adblock { -+ -+class PreloadedSubscriptionProviderImpl final -+ : public PreloadedSubscriptionProvider { -+ public: -+ ~PreloadedSubscriptionProviderImpl() final; -+ PreloadedSubscriptionProviderImpl(); -+ -+ void UpdateSubscriptions(std::vector installed_subscriptions, -+ std::vector pending_subscriptions) final; -+ -+ std::vector> -+ GetCurrentPreloadedSubscriptions() const final; -+ -+ private: -+ void UpdateSubscriptionsInternal(); -+ -+ class SingleSubscriptionProvider; -+ std::vector installed_subscriptions_; -+ std::vector pending_subscriptions_; -+ std::vector providers_; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_PRELOADED_SUBSCRIPTION_PROVIDER_IMPL_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 . -+ */ -+ -+#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 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 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::StringPiece(regular_expression.data(), regular_expression.size()), -+ options); -+ if (!prebuilt_re2->ok()) { -+ return nullptr; -+ } -+ return prebuilt_re2; -+} -+ -+std::unique_ptr 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 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_REGEX_MATCHER_H_ -+ -+#include -+#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>; -+ void PreBuildPatternsFrom(const UrlFilterIndex* index); -+ std::unique_ptr BuildRe2Expression( -+ std::string_view regular_expression, -+ bool case_sensitive); -+ std::unique_ptr BuildIcuExpression( -+ std::string_view regular_expression, -+ bool case_sensitive); -+ int CacheSize() const; -+ -+ using CacheKey = std::tuple; -+ std::map> re2_cache_; -+ std::map> 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 . -+ */ -+ -+#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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_H_ -+ -+#include -+ -+#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 { -+ public: -+ enum class InstallationState { -+ // Subscription is installed and in use. -+ Installed, -+ // 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; -+ 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_H_ -+ -+#include -+#include -+ -+#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 std::optional FindBySubresourceFilter( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const SiteKey& sitekey, -+ FilterCategory category) const = 0; -+ virtual std::optional FindByPopupFilter( -+ const GURL& popup_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey, -+ FilterCategory category) const = 0; -+ virtual std::optional FindByAllowFilter( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const SiteKey& sitekey) const = 0; -+ virtual std::optional FindBySpecialFilter( -+ SpecialFilterType filter_type, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey) const = 0; -+ -+ virtual std::vector GetElementHideSelectors( -+ const GURL& frame_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey) const = 0; -+ virtual std::vector GetElementHideEmulationSelectors( -+ const GURL& frame_url) const = 0; -+ -+ virtual base::Value::List GenerateSnippets( -+ const GURL& frame_url, -+ const std::vector& frame_hierarchy) const = 0; -+ -+ virtual std::set GetCspInjections( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy) const = 0; -+ -+ virtual std::set GetRewriteFilters( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ FilterCategory category) const = 0; -+ -+ virtual std::set GetHeaderFilters( -+ const GURL& request_url, -+ const std::vector& 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,352 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_collection_impl.h" -+ -+#include -+#include -+#include -+#include -+ -+#include "components/adblock/core/common/adblock_utils.h" -+ -+namespace adblock { -+namespace { -+ -+std::string DocumentDomain(const GURL& request_url, -+ const std::vector& frame_hierarchy) { -+ return frame_hierarchy.empty() ? request_url.host() -+ : frame_hierarchy[0].host(); -+} -+ -+std::vector ReduceSelectors( -+ InstalledSubscription::Selectors& combined_selectors) { -+ // Populate result with blocking selectors. -+ std::vector final_selectors = -+ std::move(combined_selectors.elemhide_selectors); -+ // Remove exceptions. -+ final_selectors.erase( -+ std::remove_if( -+ final_selectors.begin(), final_selectors.end(), -+ [&](const auto& selector) { -+ return std::find(combined_selectors.elemhide_exceptions.begin(), -+ combined_selectors.elemhide_exceptions.end(), -+ selector) != -+ combined_selectors.elemhide_exceptions.end(); -+ }), -+ final_selectors.end()); -+ return final_selectors; -+} -+ -+bool SubscriptionContainsSpecialFilter( -+ const scoped_refptr subscription, -+ SpecialFilterType filter_type, -+ const std::vector& 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 subscription, -+ const GURL& request_url, -+ const std::vector& 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 subscription, -+ SpecialFilterType filter_type, -+ const GURL& request_url, -+ const std::vector& 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> 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_; -+} -+ -+std::optional SubscriptionCollectionImpl::FindBySubresourceFilter( -+ const GURL& request_url, -+ const std::vector& 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; -+} -+ -+std::optional SubscriptionCollectionImpl::FindByPopupFilter( -+ const GURL& popup_url, -+ const std::vector& 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; -+} -+ -+std::optional SubscriptionCollectionImpl::FindByAllowFilter( -+ const GURL& request_url, -+ const std::vector& 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; -+} -+ -+std::optional SubscriptionCollectionImpl::FindBySpecialFilter( -+ SpecialFilterType filter_type, -+ const GURL& request_url, -+ const std::vector& 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; -+} -+ -+std::vector -+SubscriptionCollectionImpl::GetElementHideSelectors( -+ const GURL& frame_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey) const { -+ const bool domain_specific = !!FindBySpecialFilter( -+ SpecialFilterType::Generichide, frame_url, frame_hierarchy, sitekey); -+ -+ InstalledSubscription::Selectors combined_selectors; -+ for (const auto& subscription : subscriptions_) { -+ auto selectors = -+ subscription->GetElemhideSelectors(frame_url, domain_specific); -+ std::move(selectors.elemhide_selectors.begin(), -+ selectors.elemhide_selectors.end(), -+ std::back_inserter(combined_selectors.elemhide_selectors)); -+ std::move(selectors.elemhide_exceptions.begin(), -+ selectors.elemhide_exceptions.end(), -+ std::back_inserter(combined_selectors.elemhide_exceptions)); -+ } -+ return ReduceSelectors(combined_selectors); -+} -+ -+std::vector -+SubscriptionCollectionImpl::GetElementHideEmulationSelectors( -+ const GURL& frame_url) const { -+ InstalledSubscription::Selectors combined_selectors; -+ for (const auto& subscription : subscriptions_) { -+ auto selectors = subscription->GetElemhideEmulationSelectors(frame_url); -+ std::move(selectors.elemhide_selectors.begin(), -+ selectors.elemhide_selectors.end(), -+ std::back_inserter(combined_selectors.elemhide_selectors)); -+ std::move(selectors.elemhide_exceptions.begin(), -+ selectors.elemhide_exceptions.end(), -+ std::back_inserter(combined_selectors.elemhide_exceptions)); -+ } -+ return ReduceSelectors(combined_selectors); -+} -+ -+base::Value::List SubscriptionCollectionImpl::GenerateSnippets( -+ const GURL& frame_url, -+ const std::vector& 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 SubscriptionCollectionImpl::GetCspInjections( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy) const { -+ std::set blocking_filters{}; -+ std::set 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 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 SubscriptionCollectionImpl::GetRewriteFilters( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ FilterCategory category) const { -+ std::set 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 SubscriptionCollectionImpl::GetHeaderFilters( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ FilterCategory category) const { -+ std::set 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_COLLECTION_IMPL_H_ -+ -+#include -+ -+#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> 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; -+ -+ std::optional FindBySubresourceFilter( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const SiteKey& sitekey, -+ FilterCategory category) const final; -+ -+ std::optional FindByPopupFilter( -+ const GURL& popup_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey, -+ FilterCategory category) const final; -+ -+ std::optional FindByAllowFilter( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ const SiteKey& sitekey) const final; -+ -+ std::optional FindBySpecialFilter( -+ SpecialFilterType filter_type, -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey) const final; -+ -+ std::vector GetElementHideSelectors( -+ const GURL& frame_url, -+ const std::vector& frame_hierarchy, -+ const SiteKey& sitekey) const final; -+ std::vector GetElementHideEmulationSelectors( -+ const GURL& frame_url) const final; -+ base::Value::List GenerateSnippets( -+ const GURL& frame_url, -+ const std::vector& frame_hierarchy) const final; -+ std::set GetCspInjections( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy) const final; -+ std::set GetRewriteFilters( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ FilterCategory category) const final; -+ -+ std::set GetHeaderFilters( -+ const GURL& request_url, -+ const std::vector& frame_hierarchy, -+ ContentType content_type, -+ FilterCategory category) const final; -+ -+ private: -+ std::vector> 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,338 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_config.h" -+ -+#include "components/adblock/core/common/adblock_constants.h" -+#include "components/grit/components_resources.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& 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; -+} -+ -+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 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& 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 recommendations = { -+ {GURL(GetHost() + "abpindo+easylist.txt"), -+ "ABPindo+EasyList", -+ {"id", "ms"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "abpvn+easylist.txt"), -+ "ABPVN List+EasyList", -+ {"vi"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "bulgarian_list+easylist.txt"), -+ "Bulgarian list+EasyList", -+ {"bg"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "dandelion_sprouts_nordic_filters+easylist.txt"), -+ "Dandelion Sprout's Nordic Filters+EasyList", -+ {"no", "nb", "nn", "da", "is", "fo", "kl"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {DefaultSubscriptionUrl(), -+ "EasyList", -+ {"en"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistchina+easylist.txt"), -+ "EasyList China+EasyList", -+ {"zh"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistczechslovak+easylist.txt"), -+ "EasyList Czech and Slovak+EasyList", -+ {"cs", "sk"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistdutch+easylist.txt"), -+ "EasyList Dutch+EasyList", -+ {"nl"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistgermany+easylist.txt"), -+ "EasyList Germany+EasyList", -+ {"de"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "israellist+easylist.txt"), -+ "EasyList Hebrew+EasyList", -+ {"he"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistitaly+easylist.txt"), -+ "EasyList Italy+EasyList", -+ {"it"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistlithuania+easylist.txt"), -+ "EasyList Lithuania+EasyList", -+ {"lt"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistpolish+easylist.txt"), -+ "EasyList Polish+EasyList", -+ {"pl"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistportuguese+easylist.txt"), -+ "EasyList Portuguese+EasyList", -+ {"pt"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "easylistspanish+easylist.txt"), -+ "EasyList Spanish+EasyList", -+ {"es"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "global-filters+easylist.txt"), -+ "Global Filters+EasyList", -+ {"th", "el", "sl", "hr", "sr", "bs"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "indianlist+easylist.txt"), -+ "IndianList+EasyList", -+ {"bn", "gu", "hi", "pa", "as", "mr", "ml", "te", "kn", "or", "ne", "si"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "japanese-filters+easylist.txt"), -+ "Japanese Filters+EasyList", -+ {"ja"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "koreanlist+easylist.txt"), -+ "KoreanList+EasyList", -+ {"ko"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "latvianlist+easylist.txt"), -+ "Latvian List+EasyList", -+ {"lv"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "liste_ar+liste_fr+easylist.txt"), -+ "Liste AR+Liste FR+EasyList", -+ {"ar"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "liste_fr+easylist.txt"), -+ "Liste FR+EasyList", -+ {"fr"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "rolist+easylist.txt"), -+ "ROList+EasyList", -+ {"ro"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "ruadlist+easylist.txt"), -+ "RuAdList+EasyList", -+ {"ru", "uk", "uz", "kk"}, -+ SubscriptionUiVisibility::Visible, -+ SubscriptionFirstRunBehavior::SubscribeIfLocaleMatch, -+ SubscriptionPrivilegedFilterStatus::Forbidden}, -+ {GURL(GetHost() + "turkish-filters+easylist.txt"), -+ "Turkish Filters+EasyList", -+ {"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) { -+ for (const auto& cur : GetKnownSubscriptions()) { -+ if (cur.url == url) { -+ return cur.privileged_status == -+ SubscriptionPrivilegedFilterStatus::Allowed; -+ } -+ } -+ -+ return false; -+} -+ -+const std::vector& -+config::GetPreloadedSubscriptionConfiguration() { -+ static const std::vector 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; -+} -+ -+} // 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,120 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_CONFIG_H_ -+ -+#include -+ -+#include "url/gurl.h" -+ -+namespace adblock { -+ -+const GURL& AcceptableAdsUrl(); -+const GURL& AntiCVUrl(); -+const GURL& DefaultSubscriptionUrl(); -+ -+// 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 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 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& 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& -+GetPreloadedSubscriptionConfiguration(); -+ -+} // 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_H_ -+ -+#include -+ -+#include "base/functional/callback.h" -+#include "components/adblock/core/common/flatbuffer_data.h" -+#include "url/gurl.h" -+ -+namespace adblock { -+ -+// Downloads filter lists from the Internet and converts them into flatbuffers. -+// See also: OngoingSubscriptionRequest for more details about allowing and -+// retrying downloads. -+class SubscriptionDownloader { -+ public: -+ using DownloadCompletedCallback = -+ base::OnceCallback)>; -+ -+ // For head requests we only need the parsed version as result -+ using HeadRequestCallback = base::OnceCallback; -+ // Controls retry behavior when download or conversion failed. -+ enum class RetryPolicy { -+ // Will retry with a progressive back-off until download succeeded. -+ RetryUntilSucceeded, -+ // Will only try to download and convert the subscription once. -+ DoNotRetry, -+ }; -+ 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, -+ 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,287 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_downloader_impl.h" -+ -+#include -+#include -+#include -+#include -+ -+#include "base/files/file_path.h" -+#include "base/functional/bind.h" -+#include "base/strings/escape.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/adblock_utils.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); -+} -+ -+// Returns a version of |subscription_url| with added GET parameters that -+// describe client and request metadata. -+GURL AddUrlParameters(const GURL& subscription_url, -+ const SubscriptionPersistentMetadata* persistent_metadata, -+ const utils::AppInfo& client_metadata, -+ const bool is_disabled) { -+ const std::string query = base::StrCat( -+ {"addonName=", "eyeo-chromium-sdk", "&addonVersion=", "1.0", -+ "&application=", base::EscapeQueryParamValue(client_metadata.name, true), -+ "&applicationVersion=", -+ base::EscapeQueryParamValue(client_metadata.version, true), "&platform=", -+ base::EscapeQueryParamValue(client_metadata.client_os, true), -+ "&platformVersion=", "1.0", "&lastVersion=", -+ base::EscapeQueryParamValue( -+ persistent_metadata->GetVersion(subscription_url), true), -+ "&disabled=", is_disabled ? "true" : "false", "&downloadCount=", -+ GetClampedDownloadCount( -+ persistent_metadata->GetDownloadSuccessCount(subscription_url))}); -+ -+ GURL::Replacements replacements; -+ replacements.SetQueryStr(query); -+ return subscription_url.ReplaceComponents(replacements); -+} -+ -+int GenerateTraceId(const GURL& subscription_url) { -+ return std::hash{}(subscription_url.spec()); -+} -+ -+} // namespace -+ -+SubscriptionDownloaderImpl::SubscriptionDownloaderImpl( -+ utils::AppInfo client_metadata, -+ SubscriptionRequestMaker request_maker, -+ ConversionExecutors* conversion_executor, -+ SubscriptionPersistentMetadata* persistent_metadata) -+ : client_metadata_(std::move(client_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, -+ 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(), retry_policy, std::move(on_finished)}; -+ std::get(ongoing_downloads_[subscription_url]) -+ ->Start( -+ AddUrlParameters(subscription_url, persistent_metadata_, -+ client_metadata_, false), -+ OngoingSubscriptionRequest::Method::GET, -+ base::BindRepeating(&SubscriptionDownloaderImpl::OnDownloadFinished, -+ weak_ptr_factory_.GetWeakPtr())); -+} -+ -+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(*ongoing_ping_)).Run(""); -+ } -+ -+ ongoing_ping_ = HeadRequest{request_maker_.Run(), std::move(on_finished)}; -+ std::get(*ongoing_ping_) -+ ->Start(AddUrlParameters(subscription_url, persistent_metadata_, -+ client_metadata_, true), -+ OngoingSubscriptionRequest::Method::HEAD, -+ base::BindRepeating( -+ &SubscriptionDownloaderImpl::OnHeadersOnlyDownloaded, -+ weak_ptr_factory_.GetWeakPtr())); -+} -+ -+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 headers) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(ongoing_ping_.has_value()); -+ base::Time date; -+ std::string version(""); -+ -+ // Parse date or Date from response headers. -+ if (headers && headers->GetDateValue(&date)) { -+ version = utils::ConvertBaseTimeToABPFilterVersionFormat(date); -+ } -+ -+ std::move(std::get(*ongoing_ping_)) -+ .Run(std::move(version)); -+ ongoing_ping_.reset(); -+ // A ping may fail. We don't retry, SubscriptionUpdater will send a new ping -+ // in an hour anyway. -+} -+ -+void SubscriptionDownloaderImpl::OnDownloadFinished( -+ const GURL& subscription_url, -+ base::FilePath downloaded_file, -+ scoped_refptr headers) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ -+ auto download_it = ongoing_downloads_.find(subscription_url); -+ DCHECK(download_it != ongoing_downloads_.end()); -+ -+ // For compatibility with existing Appium tests. To be removed after DPD-808. -+ if (headers) { -+ LOG(INFO) << "[eyeo] Response status for " -+ << AddUrlParameters(subscription_url, persistent_metadata_, -+ client_metadata_, false) -+ << " : " << headers->response_code(); -+ } -+ -+ if (downloaded_file.empty()) { -+ persistent_metadata_->IncrementDownloadErrorCount(subscription_url); -+ if (std::get(download_it->second) == -+ RetryPolicy::RetryUntilSucceeded) { -+ DLOG(WARNING) << "[eyeo] Failed to retrieve content for " -+ << subscription_url << ", will retry"; -+ std::get(download_it->second)->Retry(); -+ } else { -+ DLOG(WARNING) << "[eyeo] Failed to retrieve content for " -+ << subscription_url << ", will abort"; -+ std::move(std::get(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>( -+ converter_result)) { -+ VLOG(1) << "[eyeo] Finished converting " << subscription_url -+ << " successfully"; -+ std::move(std::get(download_it->second)) -+ .Run(std::move( -+ absl::get>(converter_result))); -+ ongoing_downloads_.erase(download_it); -+ } else if (absl::holds_alternative(converter_result)) { -+ const GURL& redirect_url = absl::get(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(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(redirected_download_it->second) -+ ->Redirect(AddUrlParameters(redirect_url, persistent_metadata_, -+ client_metadata_, false)); -+ } -+ } else { -+ persistent_metadata_->IncrementDownloadErrorCount(subscription_url); -+ AbortWithWarning(download_it, -+ *absl::get(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(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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_DOWNLOADER_IMPL_H_ -+ -+#include -+#include -+#include -+ -+#include "absl/types/optional.h" -+#include "base/files/file_path.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/core/common/adblock_utils.h" -+#include "components/adblock/core/converter/flatbuffer_converter.h" -+#include "components/adblock/core/subscription/conversion_executors.h" -+#include "components/adblock/core/subscription/ongoing_subscription_request.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 OngoingSubscriptionRequests to implement concurrent HEAD and -+ // GET requests. -+ using SubscriptionRequestMaker = -+ base::RepeatingCallback()>; -+ -+ SubscriptionDownloaderImpl( -+ utils::AppInfo client_metadata, -+ SubscriptionRequestMaker request_maker, -+ ConversionExecutors* conversion_executor, -+ SubscriptionPersistentMetadata* persistent_metadata); -+ ~SubscriptionDownloaderImpl() final; -+ void StartDownload(const GURL& subscription_url, -+ 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 OngoingRequestPtr = std::unique_ptr; -+ // Represents subscription downloads in progress. -+ using OngoingDownload = -+ std::tuple; -+ using OngoingDownloads = std::map; -+ 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; -+ -+ bool IsUrlAllowed(const GURL& subscription_url) const; -+ bool IsConnectionAllowed() const; -+ void OnDownloadFinished(const GURL& subscription_url, -+ base::FilePath downloaded_file, -+ scoped_refptr headers); -+ void OnHeadersOnlyDownloaded(const GURL& subscription_url, -+ base::FilePath downloaded_file, -+ scoped_refptr 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_); -+ utils::AppInfo client_metadata_; -+ SubscriptionRequestMaker request_maker_; -+ raw_ptr conversion_executor_; -+ raw_ptr persistent_metadata_; -+ OngoingDownloads ongoing_downloads_; -+ std::optional ongoing_ping_; -+ base::WeakPtrFactory 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_H_ -+ -+#include -+ -+#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; -+ // 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 last installation/update time, which is set when -+ // SetExpirationInterval() is called. -+ 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; -+ -+ // 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,170 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_persistent_metadata_impl.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"; -+} // 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}; -+}; -+ -+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].last_installation_time = now; -+ metadata_map_[subscription_url].expiration_time = now + expires_in; -+ 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::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); -+ 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); -+ 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_METADATA_IMPL_H_ -+ -+#include -+ -+#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 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 RemoveMetadata(const GURL& subscription_url) final; -+ -+ private: -+ struct Metadata; -+ using MetadataMap = std::map; -+ void UpdatePrefs(); -+ void LoadFromPrefs(); -+ -+ raw_ptr 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_H_ -+ -+#include -+#include -+ -+#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>)>; -+ // Loads subscriptions from a directory on disk and returns them via -+ // |on_loaded|. -+ virtual void LoadSubscriptions(LoadCallback on_loaded) = 0; -+ -+ using StoreCallback = -+ base::OnceCallback)>; -+ // 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 raw_data, -+ StoreCallback on_finished) = 0; -+ -+ // Removes |subscription|'s file from disk. -+ virtual void RemoveSubscription( -+ scoped_refptr 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,229 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_persistent_storage_impl.h" -+ -+#include -+#include -+ -+#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 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 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(raw_data->data()), -+ raw_data->size()))) { -+ // Disk write failed. -+ return std::make_pair(nullptr, base::FilePath{}); -+ } -+ auto buffer = std::make_unique(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::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 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(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 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 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 loaded_buffers) { -+ std::vector> 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( -+ std::move(loaded_buffer.first), -+ 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( -+ std::move(storage_result.first), -+ Subscription::InstallationState::Installed, last_installation_time); -+ persistent_metadata_->IncrementDownloadSuccessCount(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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_PERSISTENT_STORAGE_IMPL_H_ -+ -+#include -+ -+#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 validator, -+ SubscriptionPersistentMetadata* persistent_metadata); -+ ~SubscriptionPersistentStorageImpl() final; -+ -+ void LoadSubscriptions(LoadCallback on_loaded) final; -+ void StoreSubscription(std::unique_ptr raw_data, -+ StoreCallback on_finished) final; -+ void RemoveSubscription( -+ scoped_refptr subscription) final; -+ -+ private: -+ using SubscriptionFileMapping = -+ std::map, base::FilePath>; -+ using LoadedBuffer = -+ std::pair, base::FilePath>; -+ static LoadedBuffer WriteSubscription( -+ const base::FilePath& storage_dir, -+ std::unique_ptr raw_data, -+ SubscriptionValidator::StoreTrustedSignatureThreadSafeCallback -+ store_signature); -+ static std::vector ReadSubscriptionsFromDirectory( -+ const base::FilePath& storage_dir, -+ SubscriptionValidator::IsSignatureValidThreadSafeCallback -+ is_signature_valid); -+ void LoadComplete(LoadCallback on_initialized, -+ std::vector loaded_buffers); -+ void SubscriptionStored(StoreCallback on_finished, -+ LoadedBuffer storage_result); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ base::FilePath base_storage_dir_; -+ std::unique_ptr validator_; -+ raw_ptr persistent_metadata_; -+ // Maps Subscriptions to files that they access. -+ SubscriptionFileMapping backing_file_mapping_; -+ base::WeakPtrFactory 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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_H_ -+ -+#include -+#include -+#include -+ -+#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>; -+ 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) {} -+ }; -+ // Returns currently available subscriptions installed for |configuration|. -+ // Includes subscriptions that are still being downloaded. -+ virtual std::vector> 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 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( -+ const std::string& configuration_name) = 0; -+ // Returns a list of FilteringConfigurations previously installed via -+ // InstallFilteringConfiguration. -+ virtual std::vector -+ GetInstalledFilteringConfigurations() = 0; -+ // Get default "adblock" filtering configuration. -+ virtual FilteringConfiguration* GetAdblockFilteringConfiguration() 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 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,210 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_service_impl.h" -+ -+#include -+#include -+#include -+#include -+ -+#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_utils.h" -+#include "components/adblock/core/configuration/filtering_configuration_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: -+ 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( -+ FilteringConfigurationMaintainerFactory maintainer_factory, -+ FilteringConfigurationCleaner configuration_cleaner) -+ : 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> -+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"; -+ if (!it->second) { -+ // Configuration is disabled -+ auto urls = it->first->GetFilterLists(); -+ std::vector> result; -+ std::ranges::transform( -+ urls, std::back_inserter(result), [](const auto& url) { -+ return base::MakeRefCounted(url); -+ }); -+ return result; -+ } -+ return it->second->GetCurrentSubscriptions(); -+} -+ -+void SubscriptionServiceImpl::InstallFilteringConfiguration( -+ std::unique_ptr configuration) { -+ VLOG(1) << "[eyeo] FilteringConfiguration installed: " -+ << configuration->GetName(); -+ configuration->AddObserver(this); -+ std::unique_ptr 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( -+ const std::string& configuration_name) { -+ VLOG(1) << "[eyeo] FilteringConfiguration uninstalled: " -+ << 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 not installed configuration " -+ << configuration_name; -+ return; -+ } -+ it->first->RemoveObserver(this); -+ it->second.reset(); -+ configuration_cleaner_.Run(it->first.get()); -+ maintainers_.erase(it); -+} -+ -+std::vector -+SubscriptionServiceImpl::GetInstalledFilteringConfigurations() { -+ std::vector result; -+ std::ranges::transform(maintainers_, std::back_inserter(result), -+ [](const auto& pair) { return pair.first.get(); }); -+ return result; -+} -+ -+FilteringConfiguration* -+SubscriptionServiceImpl::GetAdblockFilteringConfiguration() const { -+ const auto it = std::ranges::find_if(maintainers_, [](const auto& pair) { -+ return pair.first->GetName() == kAdblockFilteringConfigurationName; -+ }); -+ DCHECK(it != maintainers_.end()); -+ 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::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 -+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,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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_ -+ -+#include -+#include -+#include -+ -+#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/adblock/core/subscription/subscription_updater.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; -+ // Used to create FilteringConfigurationMaintainers for newly installed -+ // FilteringConfigurations. -+ using FilteringConfigurationMaintainerFactory = -+ base::RepeatingCallback( -+ FilteringConfiguration* configuration, -+ SubscriptionUpdatedCallback observer)>; -+ using FilteringConfigurationCleaner = -+ base::RepeatingCallback; -+ explicit SubscriptionServiceImpl( -+ FilteringConfigurationMaintainerFactory maintainer_factory, -+ FilteringConfigurationCleaner configuration_cleaner); -+ ~SubscriptionServiceImpl() final; -+ -+ // SubscriptionService: -+ std::vector> GetCurrentSubscriptions( -+ FilteringConfiguration* configuration) const final; -+ void InstallFilteringConfiguration( -+ std::unique_ptr configuration) final; -+ void UninstallFilteringConfiguration( -+ const std::string& configuration_name) final; -+ std::vector GetInstalledFilteringConfigurations() -+ final; -+ FilteringConfiguration* GetAdblockFilteringConfiguration() const final; -+ Snapshot GetCurrentSnapshot() 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 MakeMaintainer( -+ FilteringConfiguration* configuration); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ FilteringConfigurationMaintainerFactory maintainer_factory_; -+ FilteringConfigurationCleaner configuration_cleaner_; -+ using MaintainersCollection = -+ std::map, -+ std::unique_ptr>; -+ MaintainersCollection maintainers_; -+ base::ObserverList observers_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_SERVICE_IMPL_H_ -diff --git a/components/adblock/core/subscription/subscription_updater.h b/components/adblock/core/subscription/subscription_updater.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/subscription_updater.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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_H_ -+ -+#include "base/functional/callback_forward.h" -+ -+namespace adblock { -+ -+// Periodically updates installed subscriptions. -+class SubscriptionUpdater { -+ public: -+ virtual ~SubscriptionUpdater() = default; -+ virtual void StartSchedule(base::RepeatingClosure run_update_check) = 0; -+ virtual void StopSchedule() = 0; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_H_ -diff --git a/components/adblock/core/subscription/subscription_updater_impl.cc b/components/adblock/core/subscription/subscription_updater_impl.cc -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/subscription_updater_impl.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 . -+ */ -+ -+#include "components/adblock/core/subscription/subscription_updater_impl.h" -+ -+#include -+#include -+ -+#include "base/functional/bind.h" -+#include "base/functional/callback_helpers.h" -+#include "base/logging.h" -+#include "base/time/time.h" -+ -+namespace adblock { -+ -+SubscriptionUpdaterImpl::SubscriptionUpdaterImpl(base::TimeDelta initial_delay, -+ base::TimeDelta check_interval) -+ : initial_delay_(initial_delay), check_interval_(check_interval) {} -+ -+SubscriptionUpdaterImpl::~SubscriptionUpdaterImpl() = default; -+ -+void SubscriptionUpdaterImpl::StartSchedule( -+ base::RepeatingClosure run_update_check) { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ DCHECK(!timer_.IsRunning()); -+ run_update_check_ = std::move(run_update_check); -+ VLOG(1) << "[eyeo] Starting update schedule, first check scheduled for " -+ << base::Time::Now() + initial_delay_; -+ timer_.Start(FROM_HERE, initial_delay_, -+ base::BindOnce(&SubscriptionUpdaterImpl::RunUpdateCheck, -+ weak_ptr_factory_.GetWeakPtr())); -+} -+ -+void SubscriptionUpdaterImpl::StopSchedule() { -+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -+ VLOG(1) << "[eyeo] Stopping update schedule"; -+ timer_.Stop(); -+} -+ -+void SubscriptionUpdaterImpl::RunUpdateCheck() { -+ VLOG(1) << "[eyeo] Running subscription update check"; -+ run_update_check_.Run(); -+ VLOG(1) -+ << "[eyeo] Subscription update check completed, next one scheduled for " -+ << base::Time::Now() + check_interval_; -+ timer_.Start(FROM_HERE, check_interval_, -+ base::BindOnce(&SubscriptionUpdaterImpl::RunUpdateCheck, -+ weak_ptr_factory_.GetWeakPtr())); -+} -+ -+} // namespace adblock -diff --git a/components/adblock/core/subscription/subscription_updater_impl.h b/components/adblock/core/subscription/subscription_updater_impl.h -new file mode 100644 ---- /dev/null -+++ b/components/adblock/core/subscription/subscription_updater_impl.h -@@ -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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_IMPL_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_IMPL_H_ -+ -+#include -+#include -+ -+#include "base/memory/weak_ptr.h" -+#include "base/sequence_checker.h" -+#include "base/timer/timer.h" -+#include "components/adblock/core/subscription/subscription_updater.h" -+ -+namespace adblock { -+ -+class SubscriptionUpdaterImpl final : public SubscriptionUpdater { -+ public: -+ SubscriptionUpdaterImpl(base::TimeDelta initial_delay, -+ base::TimeDelta check_interval); -+ ~SubscriptionUpdaterImpl() final; -+ void StartSchedule(base::RepeatingClosure run_update_check) final; -+ void StopSchedule() final; -+ -+ private: -+ void RunUpdateCheck(); -+ -+ SEQUENCE_CHECKER(sequence_checker_); -+ base::RepeatingClosure run_update_check_; -+ const base::TimeDelta initial_delay_; -+ const base::TimeDelta check_interval_; -+ base::OneShotTimer timer_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_UPDATER_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 . -+ */ -+ -+#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; -+ virtual IsSignatureValidThreadSafeCallback IsSignatureValid() const = 0; -+ -+ // Asynchronously persistently store the signature of |data| associated with -+ // |path|. -+ using StoreTrustedSignatureThreadSafeCallback = -+ base::OnceCallback; -+ virtual StoreTrustedSignatureThreadSafeCallback StoreTrustedSignature() = 0; -+ -+ // Asynchronously removes the signature of file |path| from persistent -+ // storage. -+ using RemoveStoredSignatureThreadSafeCallback = -+ base::OnceCallback; -+ 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 . -+ */ -+ -+#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 main_task_runner, -+ base::OnceCallback -+ 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 . -+ */ -+ -+#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 pref_service_; -+ base::WeakPtrFactory weak_ptr_factory_{this}; -+}; -+ -+} // namespace adblock -+ -+#endif // COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_SUBSCRIPTION_VALIDATOR_IMPL_H_ -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 . -+ */ -+ -+#include "components/adblock/core/subscription/url_keyword_extractor.h" -+ -+#include -+#include -+ -+#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 -+ -+std::optional 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 . -+ */ -+ -+#ifndef COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_ -+#define COMPONENTS_ADBLOCK_CORE_SUBSCRIPTION_URL_KEYWORD_EXTRACTOR_H_ -+ -+#include -+ -+#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(); -+ std::optional 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/docs/ad-filtering/README.md b/components/adblock/docs/ad-filtering/README.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/README.md -@@ -0,0 +1,18 @@ -+# Ad-filtering -+ -+Eyeo Chromium SDK supports several ad-filtering techniques that apply in different stages of loading a web page: -+ -+* [Resource filtering](resource-filtering.md): Blocking network resources from downloading -+* [Element hiding](element-hiding.md): Hiding content that couldn't be stopped by blocking requests -+* [Snippets](snippets.md): Specialized code snippets to deal with most complex scenarios (like video ads) -+ -+In order to decide which requests should be blocked and which elements hidden, eyeo Chromium SDK consults -+[Adblock Plus filters](https://help.eyeo.com/adblockplus/how-to-write-filters), distributed within -+[filter lists](filter-lists.md). -+ -+# Frame hierarchy -+ -+Deciding whether to block a network request is much more complicated than just matching an URL against a list of -+filters. The SDK considers the filters that forbid blocking of specific categories of content at the website -+level. This means that a top-level page or one loaded in a parent iframe may have associated filters. The -+implementation operates with the URL of the current frame, as well as the URL of all parent frames. -diff --git a/components/adblock/docs/ad-filtering/element-hiding-sequence.png b/components/adblock/docs/ad-filtering/element-hiding-sequence.png -new file mode 100644 -index 0000000000000000000000000000000000000000..6efb5794769b35d1317f6adf9172e75cab3f5eca -GIT binary patch -literal 177168 -zcmeFZXIPV6yC$rNQlunwq=zmYq(}$pRY0W)p%>{Qp@V4Xy(3jX;8B6l5rF_9^e##j -zkgfuT&^vR-=h=Jv&U~}yo!|5Q;ot~2D=XK!>Uo|k(f9S#Nr`U}U%7IHR8vF6@X8ee -z=#?w@4TLvSSySRly%qJL29GC7NsAulj@0SJ&fQk2omN0@a={5@oFad -z2BIw_OVxYl8aSjfLm8^s4b?w>-Ya_JId{6_*&PX`1f4cjn5>oV4nOt7qC -z9Tz7#2NQEILfKSo76^^hKZ>!CF?l#0-yX3$}Z4 -zXF!NWNw+Oq%FD(ic>hc1kmSPSX|61}K-kgOb$Qz1yX(203&Drsp_iu%-sxYErqgS+ -zR)4x#*30Kh@0pbU#g%$Fw^xNi?`}3Oge(?Zo;hYY=KB}1N?N1f+k4~skqeg>hl{Tq -z^NED~I&PvwdKf9m{2F$|Yl|2ghKug=( -zo#c_Hm^Our^VcuQhzrg)9o4^cFH%|WyLkkfpPd2=w{3qm((eict!u{e*Jw!Xe>Uoj -zl;10CGIPI!O@FGJCoP`)w4ZmeL@3z#XpuaWZhpVgm$ZC8kox?_qu0cCB);7TwIlKj -zG$vMIm0F)SjxhC15f(xAwgZ-dThuh>uGKuOZJYK&42$PE3ujzsJDF|IY!!{=Mq@eu -zh&$mBd@5*9Vyk|}AHLdtAEYQX>$?zK{5|H%X`gnIg}G%KeFMKcAs=Bo3X=bqv*!fe4-&XqWcp_YdR>W6INdb!&k?fy&aZeqHe}#~Ff&YsCQANDVqaFnmU}ze-6X7&=Y2?K| -zB9Zwep~3% -z6zZlB;{@O^S(7sJ*o1~-x$X9ABC`C<5Bvm{s(X)f2+LT`z7aG(5s`&p<5o5wwbXpn -zG{x@sC|r2Ss#A{i6R-pcPuQmFN)Yuh2fCWrM02k7N(8c4mh`ZM1|1zVwlQG>cXJjP -zL-yZ8yEdE&T6moahBeS)v|~1>TaDxeM`&)F)>_^iOi8BYt?M1TBMJ&<*u+p01n>`? -zmCe6y$pe;GPu>O`LVIsz^*A?F>-3YF>BvZ$Z&_8RwovOczwd>SHkMIQ718%wCBJ&! -zleP7U7oVUzKF-4gIcAC2svUI+g@7;B?s@=Ogzgh9Mt|l&z{6krj1-%45vFm0qD2Y^ -z40hP{ac1&=eE=j>y$=^B8@BQ+fQ4@^7(WQ&3=(#9hqU~v?qSA${g!67%UZsFI)8}t -z?4mZtKVth!pn0<v3F2QK8(xUri#eM}V7w0UrqMx|@ES4%$Wp -zZ@H+~bQcFq=g1~?agy9IwOCS<|KSQJZ!s^i@4`7?;!t1;nlws -zqp)EhFKjXWnLoe3xPtc$$lJeoKH9{oedc()0i<9?=HR_C-~>F?{(3+Ws?Mm`a@xsq -zA)GmO%lZjmpSbl>NEL95AK6QkvQ!?hvPn~XqAA?YFn5nW&J8*FVLbx03&necFd$v2 -zwwomQh5r@lq&-h4B!g-3rc#Wh8xi?wL_WN&qNorV-n}I5lerz;lu-9KJj#0@6 -z_tKQVas-Jg6W|fQ;DFp{Gt#T%WYc0}br^q*HXb%ntqPgju%sQCYF*C{EV>f^LEpda -zc*sKy8?c&goM3a)I>gxptp(>Dwh>4Ht$-WBFrveLQ-rFa0XDs5DoF9elNp9W*%DV~ -z!k9galRhoJ3Mx5E^YCNH>Qc6Q5AR_d*U7#pP3m8dU@`}W85~H_0@gLLb^iL#h6bR} -zm1Om$4>q}+1_ju@8`ty0|LO~lI_0fCzJ{0@Gej(h*1;4x@+I8NsN%VGv#CxAg7X4A -zEpK~6)=Mlyj#t@LZEf*($*kkv8f&#?B%apJe|^hHSRlJ$%W`URg!6RcyVBL!^1%U{ -zh_AbiZRY|&vXy7wop@yKMm_r`^{79$aSk@yManZ+4R^MDHm15DeY6> -z!s)!{m@4gc^n^g;*QYBOSCc{#4vFmCXP-3L+bo{;afa1Ybr53bIsPkC0i`BRPZ9ft -z8S=+#VK4B7pV^|-R#2mfuhZ}=fuhT`*)Z!qmP(OKQ7y_T`w%;}c=#;jx(u%r4Hw3;Za>V{7iRW5=0b#1&H=qPnX%r30M(&c&5=Cu+SP9EO -z)r6sh&*$E7xOo54`9&jL{2yOzGqW#iAM5#WjC&^r;Rh~l%$?gePJCXDM9 -zL4|CSpK@dC2VtA_{BKFYYoHN{ub!Pz85ep5OCE8&D1ApGdh5tOL=JZ0Efa|9N9G=r -zZ1KXyL1Q8nwa4~7vb!7?p^ZR(TJ6-%)fIKUVa=`qb9WH5{pDty(1INNI@Aq!N0F7S -zx2}mJqAizP%`~>8?fcW{ZZjw69W#XGr5!;UAU{jdxu~s2N_bHOYLe>H?F$ -z&%W=UHS2TU^Cg!#^QjZf|LX74)UG5aCjI47+2lYn9z&R=3xBG@p@QFeQ^MbZLMuy* -zUveR6_q%R}Zr{xx+Rzh2szXENazztWD>OruI9oY|;nTMBgV-fbULrUh7A&5+o&Brp -zJ<&^pliI?hi_t5=Z -z{Ruo#+|2u~q}s7g&&&8NNPE{#Y;@G`IcX-slh)T3V&)8o8{epygZ? -zqVF`R1>395T)~^Tr!zxU$P>M#I#AGBIq5Z{xwm1@ZycL|8dNuK(Im%U4h?QvC3v@XtMxxhpHzv$Nz(P -zwg2-W!HU)1je*uE1f14UjBvgds7V8iiS6D6FwQ+$%bjiZ^IyJg4AWMq4;H#*Ws -z53B4b!8v|hI$5C59`7udS)xw={hfa~#5>8rAyS&pNo)VtA?~R>7c*)*`GV*clO*Dt -z5nN1;X^&B-TE9r%7VJDFye65i3%|z2ri@)6WTYESr4_i0H^^&a4{+!VE_I~1L5tCC -zsRU_i{s?+Be4CTs!9|%-Z+*<+dX=7V%@SV`$>NJc_6Jf8y(V`p!zcdJy|nTT~Nb;-3NNV -z{AH5S&l!GR@~4}%B9HwK27ij8C*6BOxv}Cz@vbY1PHwuqbw0lXuNWaJ1b%x(q(vNj -zJ9miqB@Ogd=lphrB-*pnFSRjH|2|p&Jt-fF^*3ohlZJokGQhVF9NiZKE+Xi1;HDDQ -zqoF!~7UU#Fd0>nUQnUP*$x!3@c4d^ZD#fCEtupnC?_hOCu&O!Q{1Y&((Im*9eE%Mw -zO?uheRFP|K#sSU%+93)X4!mWj_1dL5xCqlCCHefAp{aGGO!S+y9N|}fuer$i`%_g6 -zmE#~>4W(!bgeiOqMs(okvG^(?(oAAOZ&kBU8_cgKwFqyj(S`8kr{ktXwGfuHqyFRi -zt&^(|PPQ^4v3MsSt~=|*<9w2lp94s9ZfwhTkAtvjo(OW;ZN)tjE+2NJsJo$`or?U= -z(QJak2rxg`3j72d76`TqWGdTIX|wEs_-HS=m~Kt!n}VR&Z51mIS~cmRo4}l!{nDQv -z$ZN}O7BZ}a5J3vVkK<~fmI-*3 -zqE-LLJ(iRELHciocn3>VAnmmU(lxw<YP~A7gCsQPPr~DW=t}(E508&hUzWwUwdT@ -zAQ1W5L0z$VUb*-W(T|QwJ@;1n)^4Ir)=X8V#^(71!T;J|-X#JM5cWS`yPwoPEQ4Vr -z9UU*#jkPSFD}Cm$*tDL{(D`90uW2!Yq5FQrG<=<<+oO_=r!6P)si1((4Q4c)z)d%|EUL`sxsS>^B0hY| -zPU0dNEha@&GH|zyZV(up3nC{#4ZLj}Ul+Ypj(Q-`V&^ab0O8qN)Qv4Om&n7l*Zpa{ -z-u^5J8Hc#>V8pKL=D`5;;;F1ly%EZYn9aX-NFp|K@3#BOT;yva7IXwKN^pBR*h;W9 -zu+l)J*lu|~vQFMf+d9rhDK@$+1CWw@{-pLT4{SQDbECotwM;!0?unRAno#YUq~glH -z&}bVZ9?RQtqP7A@ZKDKJ0+`D3hHq^FrEa*U%XkN9TUuifeiPom?l@kS#xtnSyzcee -zSUH}5%S0HgP52mJRpM%0>6b_50>cAy5lp|jchen9lh%L{(^Kl#hvl`~>e3zoqxlF~ -zsl1o5+d2!8jMa49h(Aw2kz_g=6ix6A*Xe%mQ2#LOiemVYqAf@S%&Jgxn?Ckb7V!1E -z_i7(WVZ6>*Jj2o@s0z-g5cwTA0<~YeN@>r81A?&cr+(^1DF~aIvG-V -z#30x5Gjgwe0LEl0cLS1Lx<#;Osb1bKDN6CYQ%x?q=OV$ayKjVTR1$t#ms^V!^ltXqJaywiIE!LuG%Z4m%qDNRs -z@URZ2PVsk|yj(TZ7NAz0m>?vE*R31?JZ56RgFU=?BiN`nJYG3KFm)wO20qKb7TR8B3kY9Z+j@Un?5>eZ0T>mNtP?7GrROf -z*XiNOU1hR!vP47pOj?TP$8RrgCJ^4=d#G&B{s{t41?B1AT}^9}-8~Y}ZyzGAs^eYd -zU%EOwAhpGDVlZsky~K*1v>f>K{zxloeW23nb7<%cwKjM=;#vPs@1$hWijL6y|kA+SI=Jltp6d`e)31d`AqlQv||A&{HUvwvRDC+ -zqlkK5ovsc1m!Zb=#31RX|AdG$dNBtTk+}QK@aLQ)9Nocj -z>wU$dbBWvGNm0IZ>w9bz%VQe7R?5CGzLwG;e|gJVzd$w+>(iz4@J4FpzW}gegOSZX -zkQ%!4OM6vmr86f5rdY?i=-&)rkLnmPl@8bbfkxmA9oc(Xp7ydO)4w?vJUn#IN3aqa<52!kUZnn6}T1U!r(HVLwvJk?U}&c4X61|6_UYF1`#Z -zv!OxtBe#Cg>PO4#Q|>)KoB;EGhw+nX1#Ol5&X+1nW5aaCLc5n7YcC!lE+>xpJX-c4 -z$Q_cFQ2N8YhbIgSI-q-Ch~$h@u?QH-OOONBliUCN;nj;{#<1P0PO{h{Zrj}Bq`$7K -z8VN1&`5SpieBS2Is#1#Z9plPzrC86Wg0S(f|&O{pK}Sd#s>8IA}a5{Wqs(yiS#f#E!4S%tDW -z=<5!A=Ai8kPk?W{llA;zreYvo>r`;5egQLcz<0J}Ek!(Wu2Ezv2&yq)tt$?gssmHjlKC3=w24iH)v%|i_7;cgwbmD&%rIkOAv -zl&xH?#1MaXJwKJ6-wi(BpG2tJmxtqPag4^!Me3eN%cf5eI&g*&Q+&T8$%A}URoz{z -z5rbG(nsWr+%Lg(#0BQNTKPhFoPg4`*!v;ikW=0*eD$!90uhtW(N~0jA1XxWP4p4{z -zqD?@HaKWVDKePb-8`)~FX`2D|eLNs~-wguf53Ys)TA$$C19L!Ae-VA -z4EmOo4EtR8Nv#g1f{Oak&cHI{z&WHbo -z@dEyjTO+=Y+uB{ptEVAy0pUw7ax-IQxZHd{sGeNI>e)Y^b^71`tbZ@~zkJs3$1{7( -zR_EqKd$tUHl#XuzE|f$ltOZ+fB;Dd>{-lo@s@>)60GQ<^;a)8vLb_CXvf}5a08|W6 -zWJna}Rg^5bSeybDvM`?r02&Tm?$<}Z7o^4=a*`q#u(2wSf4t*Anv6Y-*TZ=Bc^eb9 -z-hnyzTAYB{Z|jLMK=2OE*`Aae#Ju}2H!;J(7~CAf++iWE?EXt}<(#Zxi=0W4$o(Lu -z`38adl>lT$%=jSC#@qmZcLZDd==fBcf0;H%I)Ls!f1)O1KF~xob+|FcM+My&OPKgg -zQ7^p@R>Ui&{N5vx!adw;iAOwDBmMq=Ch83nGr5u;8)>j!0MTbGhxkw&9E1&8r&M -zllje`S3g7o+-pQ}qP+AOz)WsUX_3nxS()G@5&-x{HDO{KZ)gT|jTjP2ZatE=(>V+( -zhOg2@D}TMzx;a@-n-gn|}tUJHJ!=?sVr$g~BU>~N?+cg4)Z -zt;V=wg|kKSMMs5GQ@h=0{UC_!`QG@V3-r{CxBMsn{RE#mTzk$OxLNJ_qa-uqlSF5V -z=GY+}hJ8Lc63E-_@47!6{WNa7RoQlN5++w{(XCscPzlU!?L~7OVWl9tfp~)D)=HKN -z-5wiDkHfQ?`NkN8E`9Zc5nzs6Vef#f>x|*unA3#b7DFiQ55X4x!co&%qV`$<34iUz -z{#2I77-l()rigR*x0?)_OccjC#!sHq$JI!(B{pM_GUdSF5f<9EM2(=Uy`8vzCTqrN -zUSE1ss706&m@*b6+m3a!JdnpHYpFQj+G6mM6QzT%Z{U!`Dl~<*3fq3NIx|tMK!b^3 -zxiuIH=ur@ni9svu22}aN)+mkE20+%}36R`yoc_YP{>vkV<5S#dEeI%vw#yXGc{8ul -zQi&^H0%8pkKuPrU2%xxWf3+UYZx_+F^6&$MpEaQLAwT`q>|tV;tV%40=v9p}_VDXu -zKvgu(-+E->^99oQESPkOEmo?3KD+Yc&v)BRBf)|IMMe5sLxZEFI|Ju;^S9sX2j6Zv -zTNPqyYI&C$JSqI~!(ums2ML2)ox$VRf*-#ZVTW}kra2N?IiwtF@8zm}RXFm{7n -z!0*d@OX};6Yx475L8CCOmlwT-Oc@ri>Fo-SU_RHXjvLiR -zcHYnZ9b?7wirs(%i@vk|fJ{s*PAkRVvW+UKw>QO@cTIlEM#tfL=La#00QR%@W4ze= -zI_&-BIVKc)-f%GE5^1D&m$L;>{O~3HdInW -zce5wqzCfUkbszQ{BWrTX1CR|m&G^ed03}o2dww9i)Nzuu0Kw(g9-DURT{Nh(17P%$ -z3Ho-8g{OorZ!KBxRWVV^s(=TPI+M?XmKEc+lZUw;q1ad`9AKljyuYfoEf$2HZ$Pn_ -zDd%}WWzpSqE4a|&=YxD~Zh`u>xp(4TTjmQV6Hck8fc|CPmthr`#gO1h_LoXg2!Ka+ -zxbtlo(3cqy-mVdQVuChCuS~Z7@>y)z9lnVZvem{ -zK$ay15N(16uBzyc*WIY|%(k=DUWjcA*XepeC^C&nEN^BRNMf-|0nK#)u^x`&_8J1U -z?4uDlnOf)h;etYnP;3DJ4oiya|G-JTf_Q0vG*+y#+>y+myp7yx#L0(Z)Yfyo^~Tsn -zu^&Td7K0Un+Ue6wC27iBd+)Ag*{<>I10Rnee(6n3T*tL;QR9PipqdSscdHLOdG`UR -zHdu16YErK*tl^IP`OWlnPEg{A7j&;>hCzfUKf`(520@pScTtmg+m^GEk-c -z#;Z%3wr~e17+K5MDcb5 -zV)jcZmyP5?IX)An+!+OMx^QcJC0xqz -zIs)`|6qm!^Aja)y%G<|4Dq-?Puz$4?MqNvsb0lntCKsYdoA-BAQlg1|m5^yZKmD$* -zaBCM3)XB{JQtN0I3a4!^xezNj)}eBk7EkQqY5oS<)ZlFqgRJ8yK)Gk&@gtdL+fT!J -zu8EH74~YqeDy3;Y(M4IFEz$Pm-B}rdAT%x>ckX8G)kIf~>=mqLLgnv6;bE>z$7eSV -zsT`yPy0I&7N$^PXr>G -zfdV1@>xNdds6W}(K7i55H*5{Q61&Dg_Ps5QuQ@QaCRrgktU+LgU@Gh$%Ll-swsN!K -zS4+;KF7_y;-aN=Snvot5$#9-sd7E{f_c0hyS|qR-^5=hlKkLyJ+#m5xXT-Alg*JJr~u<;0#Q_GVBQWuC}BkOa~4v-&?B -z!Sf^DJxlYV8xq-(KJT<;u=TwrDjHBgWV$^3qbxt*b^sB!aNunC>mGm?cO7Sw3J>=1 -zl0N-qGtcw$c;=TJ45*7ezMX&+iN;q?G;&v8V$<`LH<8Z#K|^ELAt&ZzT0a#OtY+33 -zEatH@?{;!lcqC0DmOb(pMHs*oPn>Ko4n$NN|1~!g -z>m#!9G){T2neNV%Wf+CvGU}bDu-UsYIX(*<^ARrwz2dp=v&0BYrC1u&&rO -zuR76+<;fxQaP2Hs*7c>7uT>;EA$p56kas?j1D|%2ePv|9obn)r1J;=>-rYqDW!$ov -z8m;Pct?r`1Hb9nKk}+dre!ex=Y%tDpmV$_uv{&_vJ^=07TqJPCvO?mmrMl)n6-cz+ -zoR7==YJ>Pxa(|Uil78|&-z0VQY~9R#ULo6NqrGyw_YSEy4oq?r>9|!c2FwG?0;{i% -zaaR#h6s9~CWwDlgk|fW2ujS!pp&x{qV? -zvW|;LWORNf6NiZR*n0a|1iWF&Z#ll0uxo(KD|YmQP7-hRlr^4x*vqJ$mn>(jzofsF -z6i`FK2B!F|A8L4{FOHVgQJX0q8!w(;CzJ`vdO)8>$10pv#Sj$=vJ)N_GY}mW<`Ml>^9KOs>m{0Gbq*?!E$*VE3lgjPGBtfOfKbo{_& -zw6|96@p=>^k93^wt9&WRFuN_vhGnFA>#6Li6D*l`hcRC+0_hb0+_K{y-XhiZ3^Tb3OG6}DC0yZjeltvev6QSE`S -z>;wsm>CqzwmkFa?Hw($PnQe4xoFjZmokzWmC9NaP7i_-{MDlp^oy!i{x@su#Yd9il -zf(xi#Y5N`SSa#I@1o+4W4O%{k`c9G$X%}vnmrSF3Kfdr0iXcHsni$n2I5G`MC)JB#*pbkwIJMi- -zh{izU6v+jRmYXKFhQENxO9089B{OxuS5lw&Wp7KKZ~T%Aee0Agu5tXFsrYJ%uYupy -zxuv#-p{#Zf5CfdnA8uE?+{Hg&Mbd*G5cRw&+V`n3wVHLJhMH1ZHFlQ9(6!(2*aY_- -zeD2$=-dUD)khQc!+X7h4iY4qii9 -zQx|n)wi0+J(N{*ss@ZWpU9w`eN)rO|l9RBS&@R7B(S0{*F#Auz*O{(|Y&>wShc|Xz -z^qYYZqcyHfs*?!SEr|+=2d-gLMe3AL(*>J#+s>;JKHc;_gXV!oT$^1k<<)9+Z&{se -z0=cXG3+iPh{3iyT=%(P+i>Wbt8}8`I7UV!y=RsU8k)CrMd#G&)O7L?$f!qTBS!z7~ -zgVH170^m&8&kUh6>BSVUE~}SI@5aEKCrj%*@|p56l6RPqb5lwEYBV!6k>NPmUxNj> -zf;V0gNjjaIj1uz|BdeWU!}Xw+eG{8VfaQ*RuM3Dq$P;?W^h#q`#j3R>7GApW(xB6OKU(!<%BGF?5_Z{8&8qM4nSDEee -z(8e|$>{TJl3=4F91zlv#Keiv(L-@GL!`oq7K8lY?eb -zaXiSv=bFdB2okynanVi7lMl1XqP{OOJVsCpkq+$~5MwBMe*JE_NlPl|#kN^7 -zbev{x?<)Guoe1fA5m(dAWzuBbWlkgnf1HbV?D9uw5J!?z!6SFoS9PM_Y_GBN*rrx@ -z%XXlM6MZEZlcvVHgOpAkyNqex4jSx@%>nllK8vIs+5&i9OH0q`q3FiMk3nNJ=Cj0f -z;;4H!WZhMa25hQnRH_rrV3gJKi7~N<^QNN2_oN$gzMMdNE1~0vY(Vx1<5xv-a8T{? -zMGukC?U>R*kTgt^uF5>3nQso6WjLl4(>|F1!+t(6y!8Y&>>k_dTFIh~=dZb{=tO&5 -zgKJB~C(jIJx^CO%B`9$<&$B~_ExGa}ltfESy=#b~_SKU8^F-}P@3s(_M((zDbk_pp -zBpSnwB&cyQfqk&W?_eD~;lkX?FFulu=LzW4m{C -zi`59_wf05NXCu9KHywW<(P6rSQ-+4hn{0+u4YRU9t8?fCo>xUrLRNKM?!q-T?Rc#* -zh2&g04-o9tzu}^B-B%`jHkWo5{U6CsrHL4nD3beYe5g#@V786Uj`y~nt?@0#I7v*s -z`w=VE8JA%;Ip0SXKgW{8N|a37L!R52vM$}m?#VUln;eoivH5tGw?qsYyJyk=^8D_5 -zTf5yYKyep%gFyu4t!2xLG=xg{uzIGNZF#=6sBtr^Bd@oOr|DD(Vxaz#Z74q&tl9R% -zfuzAQM-oX5JuNgZ4jiC@9&fJ$1UlZPFr=DQiG*#BJ%X5vic2q!kT*zpl8ws-zb^(s -zShtzc%f+P$d&T~|L69)3$|Q-k-HuX5!B3OTriLLtS~k$#{$R!q?l6}2brkGh6Wv9c -z|AEe4*vjhrH&tbm6(-meTflhDM%>BHWlLR_aP8ty&9R7x0XM~nU6CD1hc`VsI_~;y -zp-9LQU#xk4BAWsyiPGkYbz!%Mb0!6zzZP$TJaZczS|%D%Cw_Af7VQYBr0pv>ofY^9 -z!hm#%Hf9AmN0!QuVnETXnYz4hIG1j2J$H)V9<+r%Qq0-X16BvE);hPKjV1W($ob322Cwt3sLBA*#9CZAhTF=!;r?Ok>BA3SU -zB#Q0gJLck4X-xZS872I#ScCMFiH^*7C>%pxZxP@^DP5$aM_;0PH>Y|`Gf3BfcuG7= -z*EM00sW0;3m{)4vsoI9*W;q&^vi9lO)_;c5lSjLEAF>HEFY(<-q3XCn7L>ECPh3Cm -zj`AzjW6j`Qal(c^7h -znDRuQ$YE}^cvC~)i&F4M;z;8s6g);jy5eY950?*8#!y8B{=n6IE(&oz73f#6bRH2e -z;x^&o7N*ybKjF(gSRBsKVVa0POj*Pi0W7Il!$_%H%a%#JUl#HGNM2Hew;j@VG9(v2nx -zw=h4CLbNb$KiA5S8wPYK0-+j-rX9BrM;T*S(inz#X&YM161%Mkl)5kP81Y0-I}gT -zhY!YJmo$oOlRc|=6I;Zf<^zMuv?Hqz5@26rgbQ1pXyaZJmis+g)>NK?w4^QK-@MSh -z9OeSd2oxpx6vFHAO6d(hx%X=cG3!mXULLP}_&$x)uoU*$wq{oM2BA;k{(L1X*b%z+ -zoM%Amr~t?WUzd$ExI;7@Pem$Y;uwAopT6mRoH9R90b%vNzFx@fJ{_Wd=Stj}b9I02 -zCtkbzA3f@-I{z+3Hfp={4kDFm91CY9g7qtC``dV^y6bIwU)U?^HX6}ar)?IfdKxa(f%kImwzIXY -z84x#Jn;)EYH(%IRD>IqCVci7dF%%+=esUn6$}N%+Rj^1aZcPutja_~^?#Y~xFegM+^0LtI -zPS@Ifdr>IDhvX7!8>698Ejt5dE7bxK%SoWPK+gG3l?DP|Pga|#1TpP>{gQap??5`d -zzkHq{A-Z_3&v9Ks1nbT#>i%6)waa*Z!IjJAB^9PGEyZc>r*jvXA>0^srFtXdfl83Q -zTf1P_1k=)1KfwK{fyp|BLxEeAll<%6J+bXoQHa5w&EamIQQoigV1?~>oF|%EuI+Y6 -zKuVtMFXy5Ah~tfCvWs8B(uUuDPyrvn@LNd{l9&aB<1K7X)oe>GGlZ0+Kg#a>HqsP1 -zMLPXCpBgF?&Ln7q>Ck(`vFQm2i1ntDs}7St8Hl{)pHi&$GRSm&5=>58JxdLSRwrAr -zvxmahCeXfg3sc46%BpEL_uTT=0o0=kVjVdRVs_;E>pQ3r6Aea -zS_6^e`1S4c=f=WsqmE|Y%BrsYrFWv3zeRpZIi?;>ral!wVcF1fFX~$aR}zYB-C6gC -zrR(bVd*AuaIl3l8zKegKuL15aZ3>RFr2K~#pe}~xwVfl8S}cx>^3uU#S^A#|2kY{cA8H!A=y_ -z+i$1XYY)rD7QyvrUcCU{(pRI&%6+Ni?-$~)fpp@e$6}Za~`4R+(G82jLBvw}#?S>t!UA;00Y@;vI -zW#L5Zx`Av#ViWX2aiNwGrdVnW6^+Q?&GS;x>-`}Eh6m^N6BR~N*8Mt!o+q{&>P`sT -z>Zl{>?@h7S1#ROK=O?-to7tlukK$D7Jo3u)XFF4i!sU|U4wy6N%(6ihIS&l{9aa$spC#XXN1(mD>}vTMwKyK?5WXD -zt@MbQhKl)u67rAls@~y!3cwX#)T34bGMYmei9;#9a;;KjZg@+0?6}*fQY5m(Htp!kz7v8|Lcy!NbSbPehh5@N1zf^yhC -z?0C3(z}Hn@4^u*8gCf0jnK3Y!Vt-T~-^N+8@x0ywQWpe$D>G*N9qQ`9GcaNB?0n|^ -z9M?$1DA1MF9Q*>TGlP(+m6;Dy -zMFbA)DWcrl3^0ey;=yXEBoQdC2FS&_W=uFM)*1VkkLobq}DMYdtK>0+;5oJ7TZWK2HxmZaQWfMVo -zaFgXWP1P=g8RD1ZLf3!hp{RCk)LE_Y)Hqmw$+<BeiRL(n34 -zQKl1C6Wf!}S7JXAAJUyWAvmV+if@MgWgva5D%!YTq_-(H+#0dOXKyQS_lxzJD1(Ke -z9bp_T;6S4806^!@h0&(@DJ#ZVBq@AIc@W{t#E?0)-N_i9%uxcQ69Wh+fBH^mvzUM7 -zAv=<0@&5ktJ*j@IJe5duC?nU -zyh*S*LqHU_P?QyF>L}GJ2Zxm_Q6(?~k&LE&Cs@GJB#5c}2%Y0>Qk -z0@pe#F>Nj3PQl?)+O?@So!jx9H=@#Fq7Zp&^$_m|!1Y@V76`xC!sDs;o(*i$5FaHm -zO+a{65$!lu))$Fs3^AHN0_dMMxXaT}(%?n -zxSo%bNjy8UI{hj6n=cO`np_F#n#{iQ=8F^m)nR&DZu=O_f(hvQZN2Xh*o(zzs2Z>} -z%kEu*SL(plG~7FLCBgLDZ$|Q4Sx(18CTQJ1>y%0WHwG#{1Pf0DZf>@(`yy -zi--q=Rw1#xz)hxip_V+85 -zm}3y)PAcyKoI37i7|4DJ+afeE-@#mu_lqs|AGLHERC+B^Rap -zmVly9!>Sh$d96tTvP1b|X^XMn&hVC!djk?y3jVDC)ZG9x{e1t=cLR+w;1+L{^V<9c -zXaKttiu6+y9f8FU5=tqmFq6frfY|9D9hM>=?yG;`_-h=azZXre2Z~Y){tnRhZ)`WX -zz2!6x5}+=71weMBkkJu91)24qD#+JYzQn;AGQ25qOBw?*z+$F~-(NQLTdk+Y{m+7} -z|F;XqAbhs20YBVKH#h+?W8U+8gf8alL|ghlGR2vHU#5stX-=E{FET{{>I4>qV!y}( -zCqac%jlxqV@w<#=!fvHe802en*iXu)-J+SKR-g&PbsUYhnZl?L~7%%_- -zF-hj~E5yNXZMz)BU$u&+>mT>0zRq)n8RF#dfHE*5>UnTz)Ge2^BH%3kag(q|fFSXJ -zh-gsrAIYA}e@OP^pplJk+5%1ah?1o{Vt^c32`7>@W-`j#c;s`CfuP5ydYwHsJn_^W -z&?*M(5qC`n)D_nQQHlFx_$JIOcA)(@ZT-t|0kIufQd-Q|E*t+0e|aEyW=)fM~7`>^(Sy@p(>=lSp@BwzwB~QYk9hukX#;d8MU_8Ykpikc^m(uwNL(BLgSq#Pr~2>z -z#}Q?u?2*WbvMW)M5t?=!d!{1CtjIV>S;=Zp*;3-z<5+RXipnV2Maam=-qiQ;MDOc; -zU7z>$`}}U-zrO#}Imc@}$74MoJ`9fhkBXJ`#ThI2zyfJtD`S`-U*00KVXczE!MqC2qDsuZGu9fXWw@+O~N -zZ8~I1D&PKOS4vnl?qn2DW$SX+cSH%(cLlN`#WFym>7<11>xqK9UXPbIimoaSlB4{o -zLxB5mn-cr48jv!&ifC$$?-0D0yZ(9+9$=~Z4ay3s5u_Mka4oROcfQ5^t)%A$B!v=` -zkSFQyb(V*$fv)cg*#1H*3{{ -zQSh*g^Gb%azy%|~HoH6-4L3Lnq+Jm_f^$(In}lH`2=tC#k&SmwKXxLzgZcsx30&*V -zVYm(Mvjrs)N^2F5a7ehr;7TqY??2@*e|=XVz*V|wvbC>|LQ3aXGC4CchC0apjk;e) -zgXj)bnz)do^=m~$5aN=}v^5h8frKUL4;faS(dU3k?q&28L#q734q(t35R$^Z$}F1T -zRHFoK8FbXQ24=qp%E$@RCJH254ATa)-44|7`Y?%30IA3l_I<@I*K167(=As^WXU3&b|J`@8+ -zdE#n&KH0z=lwb(XBhk&&Xq#Y2wE)^NR&>mVB3wZa#_+llvwEM|4BK0+t4UUcu3hSG -z-UHWKD#2&l81~S<25UUixh>6^49 -zr13wv>EiN?p#oZtfz#$to4!l?l{zgzTJFi>T{1>LGc3uIwHx11+kjyND5!(GH-1Gk -zZf5iWhi3qQ8Hyr6M%k~EYKTr4ihwJtP+n^JznJNN=llN`B$SXiQ9bVDiH(BPPb%UQ -z9frNjjkhY*WhR&aRpK|3%|{gFQ+;-Xf3IT&sbA*sbKb$as|CnlK*b#6kkRZHe>jrW -z#y+`YHHI9VFRXvI@h{Z)x^<=y2|L;DYTER0V@7MAj>0-|Mez&rJ(le4Nt -zfIn9$18>dElafa(P2hC|WI{ibY+4E>5Gk0l^k5`uEcgy>nMMAPDQndcWbgK;)-;9E -z2cZuls3eP4j9$jMdm{cIZ}yPZjzKDBGHtg#16A<-rFS#~3*8Q36sULt$yEkj!)q_I -z0cGNJ&UGjS*n2GwVGZ{Econ}3)i%5pz%nz4Lk+?d)IZ$H9MjXc@N-=Jdh6^*=PKG` -zJELuL&-dADzS!IO0h|J#e*kKw*V4eg-}*&+ub6bG8avfwLlXK2CvlGbP)Z!Ofp$v4qMz_M_l4?DNC7|O)L(t* -z#?Xh39YCOnaN|hE!v0uzyqhiqr~OEBzojh?%>GyLaWjq2wU%T*NOu?b@bqxflkT#! -zU@Kda>6tiVI?V6Vl7(InpbCy&S}AES6N_=Ir0Rq0XtO!n8ts9{+Uis^qWPM*#8N;F -z6Z|3_Tit$NoOrx+fsn;nzvC3;M{AK!hk~-87Tn}}SaS@+^L;qCj}q5VK~PBL7y@dT -zLTjk}f^&b%5%7nFTFoI8(1RaXCpZ8c(Af3kebb|Lqs;F-N^9C7yI$N0=LPw{yUZY0 -zz5lqO#{{8jSZ=ZSc&J6Ser?)X9bec^q(u|mnacpd=Ad8}xRU>lD}X^dA|f(-?b&c*ck!1UfxBJl~@Vn -z%{3Hzt0x!7;1^K -ziW?Tb#M0+Yuk?HFm3)n31?7VsT(0JGiu@*k@;kk!Iu%tFe7yVgNCp2(-VZ5Nm+p(Q -zm=|_ljSl(Jof-U@eMo+}UgP;Ik$W?qGmqkf2NUX=Z~&`u##hJGrrMq -zLP}(6T#q;tK|0MWKGC8fFB>$KuQsYT=l56+X8QId{n&d|@JxL=Dh@_GLc<*UQqrad -zd?A3;&S%3&%%*9fsfN^&EvpK@k2+HO90p_&3}%9Cyay}Ur*r6A4Nq&3%aRksJ%6ZJ -zWDwe@Fp;`y70^gMl<=<&ZaY@v-vIR_Xkg{DNUW8Q?YnTFG1f# -zmti(_hgDe|qbyV1fN~cj%P1wP)yYr$s*gUph-IgW_N$ZjT$wwV%~QG#IecM#*RJhW -zl;%6cdzAMfpJFCdjPdo4p~T{iqNj8~(b4*#6fRIII(UFyj@5$W4ZA;O{CB^DPaAP% -zgONtOyJrr}iQ4VZQ?^hpL4^T1yR}?qlv2(rC^MZo1R)fv;``?6`d1W92M&8K^##1m -z?g$+BZRK~6qI2XFymvPyZ->L^S@|-+IjQW15i@($G?hl4XSy<6jrS?! -zt=yj&CBsjy^{bEg{6os>sU4h5aR9J&B3BLav1nBlNA^FEe?$dHHI@B+_m)2Ve&xF; -z(3dpLtJcrX)zF!zc+Ikr>hy~hr``LBym7qQ{fSWw -zgOz;q!4i#ucf{sT9Y4!-wpul{X5MY}0Jwy^rZ$(0fzOewggcyVcFS(?4d}@I^0o -zLe=Oy;jG|0j#!%m#3L-n<-_IFVlAQ+HTXhbHGilVmJaFF;&GWy>c_HXm+C+{+gIrV7|-W88ZRA-1e?(^nrK%ee1OP%hsTuOi+Oi-`pEea -zedT?PzMgModJbRYQB3To?-C*w#EsRT$a8frfE1&X%KKIBg%`Me!BZldt{Nw`&CWy~1RDj5E=h@74*`8=Y{@tzezn7an(lBc6 -z)Od@erB8U-Ay)uxd#>p}W%QWcH0t}^VXPFSIMU12uR=B0mAQq}xEHt=u`XSLF}Hf$ -z>}1%kyxMjCpOma2s1k^wma8uHx=>}A9p`hI -zfr3kI29TPxPd~+>y3V9Rd#OI36@*-V67ZTozTfka$%$E9zq(JS996_rCq=AI3!KKO -z-x2YyQevBql0J}_*OLF(*Sd>T+^ABzP@vR?{xrzO1rDj>%~uhYyQM+h-I7OL_p$;@ -zz*Q8?^S~mQEi_R5chbIOJ@Ful`bwVu-LVNH=L2kku{@k5un{SW&hHf(Xd$Z@KwsMA;&}8Zy21PCWt&7 -z0*r~jgQr$00?F$ezPpaxmtfT4;gm@zv9qk--y8~Vp>E -zNj~OjNk7g;%gAa`@)M0m)KX(d7QrHspe{SR*GYMfrIwLJT-&$WHn3MD9?Ev8WpH2= -z2QcgETYPtUcegpFx*frl1(n4_2Q77rMFf}u5q&STc@$3(fbTX1_g_9#H~?2IgoyY!fqpT#&mDODTuL{{Jh3>AAl3uCdNWbj -z`Q!X24_RIBPYtLGWaYZF*D50P<_8&k}trPb=T$8~zCOfkB4`O0Gm22%s}fTtw8x10N?- -zl0vC{J!(}R4quo1p<+7oP(L(6*HF+g)7plw(OeV&n^YB#Gvc4*S+E*AzR_nbJxmoA -z4WZ))eLeyF!=JAU2yqc97bsu4K8zG3K4evt=y#MJdt7jqf -zr?OtZxC6|Hx@T`7SUr4gL~F6YPe1})AaZ1>}4Pt|tKSyei- -zo<~N-7u=z!aO3^)Z4le_NF0&{{$&LImC9+&$b%I>4Qq+9Wq!W};0%fAVCuA*Ms9>7 -zd^Xm2djI~u;&Xw<-hBo5vwxZItDU! -z`eNQ22eANo>6z%2aQ;f|9+o{%Gk!C!M*Q*mGH{YO$PZhMavBGz*YnKXvzT -zzqI21`D9L)9=G9()q10MHGeHPdCU2e4Y9}Yt;Q{u9ta}7u&>rrUjDd3YnqBm33k%g -zPsjO7B*t5uy@ccWc3rf*(BHwn-KyAMF6i8P^0Bhi&(C!KppATf3VJGpqa6=1LM8Br -zI;TXvuiWBfA#~n&*l;Ove>1p((qj2k%#Ew{q7$OXr?_e#Ekf&n5Uvyfs+&qAJaOSQ -zf|r-97oqpWT`kSpyYBtf_lm=`a1E!wQy`%L@GN>VchGU!26@WQ$mnk&NLCy|o~@y_ -zym2*%|8!OBP8vg4X0>WBIpuQjvF}pdwtVbVOac`Kd9Lb{P(f+|_y8Zuc27qfYTvJT -ze1J!`YdP0;X9ZvMRLkaRaYXfYAl-ZMa@kBz33mF{?}a)a0nJ6%FWdA^#OJw$I1m1c}OrM{{>~N{!kT=sF}F)tTE*y -zR#dt1j|1KKRg;t~DJbhejH4nd8y3=3y$=&Q`m6$#b|BT|h`OTy&UkkH!SoHUuNGCd -zOE;X~D#3aZ5!`S_v7IGD0A(BHeu!g2Urv4e^U9qPKIr&PCu=|Ry}rtr_PrPY7(xB# -zj!Ni@!f4kaK*i?LfkL{$Cww6DP*7*a-Nf97qO$rPV-=L*R)KG#wzO1k!|j_*_it`r -z3&Gq$v>BQzWKh)Zg+MKyN@8_I$E;rdS#7qH$bmpX-U0EUoxd#P0KR7=x&i`u%u?V# -zKpr6C?2)^D$i28`XHC2R&`t6gsC?C|0*mMs_!bGd6ad;*3k_PvTs0S;p-BxvK*c8O -z&w?hH!NzSke1kX!M5#FN{cOL(-@^#i9@)4s=rEDdDWP6%?0DP%{b -zB9_Pm`Q{Sxf#c6ut-%3c^CY~&5nQ;OnG1VvUSr;JHTx7(<_BdVV8Y{2>07l2oBlB} -z5;$|C!u|g;^pZ)nf13H@JSc}C9>23HL0Kkz3)_K!62UU!=$g`nsAa$Te;9$dG5#fm!hsiGUm5VQW3%FB3Fo6ZQ}p-MHX0ii53pecl7Kdl!~Pnk{n( -zDcA%fc=1<;S@%Ca&_5tI#>l=)E8A{jSkr!rQOX-Gb%0Af`Hn(}ErAEk=~J^VfTE%QMtj_2nSx8g8*!SVpFI0R9*s}4*^=~ -zS`yF&ZGJ?q;&Wxdb+N>7|%b1c_IX0NcJR -zDtj}kd7FF`h1-7F+0u@ou$1XUr-L0lkbh(yF0DY9?#|KYX&d6Ol?E}TUUuI_}r -z6(<8$wR#H8mr&m+gmh@;<_0DMw~sXAzex*ey_OOr3R6z{pk^+}iToi-GHvDx0R=KQ -zB(2T{3R=h_ep&oQS#b8)=(WWJXL-ZmM|x<0Z0Ml5NyKO^ -z6xM+X+R8&REXrU#+tOrLGawhWoV*M|z--6iQXjqrQn*YF{q+S;268peA$A~AWo+pr -z^Dpp-P!C3Z5~`bs%7l#xop8kqiqj -z--y2d{X6IwI6Sp-4VdhyX`i+yjfVivckoL5-zxq8XPE8td0Z?+Cy^d;UHS1!Wl$;w -zry~0O_7Nih-s5Wl^Dbe4aRKa~sQ~#kp0#l+`TOmve+yS|YNpm6>*iGrdOY=J$G)BK -zMe;Vo2_UbvzO!Uq!1{deKeQUMr>t*0%?1m9s9$%&A|Gt -zVmv;55Mq=zo{${*B=kpscrvolOh-^m0H`Y`!telpbJM)ZkUIMu??dP^L+k?=zg_ed -zh8p+!TjmktyaVX8Pp!pja`WTfTNx1j>!c7e-UYzq-IJFTN7;p1LnV}6IRF{m_oCoXkCMJ1lc2WY^&iQ#^c(2B -zi(~(xe^dw1)Bs^f`Jys@9CVc1a4J`!@g4fTHoIu@(*-RXuRo8JDLAcfTq&-k7WQQ= -zU#dxbg4=QNjhKX}5+r)ofel?JTV{GUp#Z?Gd@VyyTn!aLu5%RRFwL77B|@D-CRf*B -z4eI&CB+%#Fd}_z(Q!@wy>p-0Ar6q8Vu!{klYGAXDr+h4L -zq)(u;Mh#)kT3`_(aVPiD0q43h6rMqXAJ^;TCy>bG;och-V*CE;w -z+T)9QyCrxQ4WVX2rGT|Gbngid*@H=wTVI*Q%GYLYFBL&plAG-)UPxZaC%Zq+doR2r{gcP8jom68;Oqms#_g4PanzJfrthr5w6!e6 -zN&2_%_=LhPBb^t10Z5w4NTA3BD_3@4+gwUo%gR -zXHM5neD*F9bHJ@b@Gx*bxs1*`@7^*gY2=AJIR;l1U33T4Fb>b_4(e9g~0Mc7zQuar;r3-B1uaPTr#en4IOrffO@NNs>XX@df{Nv!+acr*b?!W{ihw3 -zTor@QQ%yf%l@uRmE&WCzdoPR9kg_~}<@2jc#dP_h^1qg*U|~)MS)v$661U%}@Vt}s -z7W9u7AxNpe^Idh>XLcWYuD~btd{0jb?sL1|fbilbUycVp+Fz{~r!w*lwc-GIFPoz} -z{zbL@ao=*6&{2jvY(tdK9+U3Y>AC`WE_*S!uMXww&q;#xiFLg9mHXH}uKu=|H2s#( -zHTyfvJBYy`DC)YQh~mx+XZ>}}U^~vPkSn={x+ezd3MfxRNMtC9M!nR0&#RT8be~@$ -z(8M4Fqn_v_rFQtD`p30J&-Guu%U^F44gcy1nDq26?_G74<>ER#D{I_7TOy!6xorGI -z@w}fe^}C~Oq}#R^En}X(GB@sferM@sRXgAVz02T~Rq}}!VT$ttkk-s5>NMuP*fLo- -ztT!HQnZo!K;_VB&?PXZ!Y>1Lpz}Qh7(T3RWUB~2Sooj-af3j2mm?z|3ZP+6NhyJf$ -zM)&ze^t8VR*>wrNhtZG@rhGG*`~qP6U)?WOxPx1zIP93iiVrzOAbugTu+0X){87S? -z#*844RWd46IG7jr=-sK-f_U#=#j8Hs{9aKKwbR;>gFDd%)0 -z_j#}7(0rb+BDr$q%IV)8he#;7ZP&Nw1;y? -zWlbMFyHg8s#}3bwj41-)hIZNT{KSA#E{uGv -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 -zv;1>B=0yWXQ1krQ6_VFsFyWFF&5C -zrCl>r_khn11Wit}b3{ -z;`TM)tf<`>USTG^;{fVUM+-xFWy)L&M~oGlWo!M&<1? -z(nvIomhia?(BkU;Yw>8g{SbcZ-r@1a>C)1u-{tzqN36H;8Z#FqQ`++z(kt7cv3>cp -zrD^N)J9$v;oOKun&A~DEM=$x|SI#nr+1%?(+sj0bHM%NJ(k^-AlhkW!v-Jg|Q`fLo -zM4Mk_c%w-Vzah`h?#)XKN&|pT_ukLjbdx1W<@?4U7CqRONv7doggqfxr1KPi`;*ZV -zysd8dxTGE9I&^300QEc~_gDyrVa_iwU+~IS0I%l0lbX(j1ys6hnH7eZZ0h<)epxGq -zA9st6cv4TyZ=)M6FSxnT_5esv^7%e}>z>bM4L)AS6|l0IAybl>67P#PZM!-+CSiZ2 -zyD^d#(*c?LUBbnkTG|s|e0e`*wJz*Nf%f|fu)yCUB_|ZP(%|4VP@tE>19QS$<5tTsnn`2tw}0?iq|vWPv*7cCx5`*{!QB -z0XGXdbroLOT=Yxc!Ff~jQx2i+0L5kMWafM~HLD;StP#a^{M`I6`SelqvbR|38J)z` -z8)sV?s`O$9@Dit)-|T{M -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 -z8K+sHZFt)Q(?n{ -z8eKi4R3GK&G_K)w>56dt@wKoKss!gjcJ0}`5ybkYzz7Y{i-*|Y?Kr1tL;`~9wjN%Kj{``#IL5z)%-@SEK+oc3#7w*6Ua*{!I#Q~vY1 -z`dNgZTb;R&315MSpa3*QtC~0GjBy!GV -zF>`{_Cg&Hd2Ijxrk@VYgey#q#wpQR#ia6D#S$r;I`|Iz&&J@)RZ`JD)C*|@lu -zkTvM7iaDcjvGpa2lo;Hz)ipGRo$AvM`wk_;fst{x=fR&}t(kTy!vDz=ZEI^1ly%tO -zCvt6vZ}g*j5t4+2gqpOj)8kou{;~yF3C8f}fE5H7jSBBkP()226n}Ocfapbr6(KcY -z4dWvs_{%SLI2c0We?@zI#dg^60H!FuidN$Pr@#m>5F@IGm%<%~jw*j3O9ou1N5KAT -zkDOdIkL*Z-BwcBc-%>t54Y&-lF5jHF@Y8I~10Mb_D(8QM$^mOD6fEzVGdlQ@cR4!L -z_`OUWrNO4zE~7~U&6(|x3II!{fX~Q~(#g8=an}o?B+v57sP*UFKo=i -zFD5w+ws`k24$@I^6Xd+-NA6fb=6qmE<2UaSIkz>{)%AcqkXXoSK-u*rNOv@(*|vZC -zVWQ88kX7Pk7dkN+WyeQ#|kSep2(tc_dfi^BLjf{nS4nLHl= -zK-~~w*x2*bvDhQ?^Z%Q_`r&RMUI4NH7aYJx{!a)lzU>HH10yeKzii(I9Cfpp1T8qoRA(&bJc3ipFk>|Nd2 -z%<=-xa8Qocvq8v0&{Ofh1K)nZ;n;@2A?vww>kcD(ENmo*#Au)2J?e+zl-0LrV175= -zgRJQkfLh{B%R>)zzXj!je(0-y(e25-I}KLJrB;A4prW)i?#@euyFoJA4Of|2;{oi#tXjXbM$@%?fy{2ml88FuF+qSZd-pGTC_pEO@ -zJ;6=oijarc0MZBkNB)QnU=1qZ4FIzJp~mQq!9n%HcIxYWPbQFiQ9t(bywH6o`K -z(SQjyg#*KJjIZl(p3T^RP>;#(F#x$B_)+DXaJDu}zenLiqmUq$Bt3c)J!pu~)dIY% -zyWMIW{XTlmW;=9MyaKIMV+55D(a%bIH{}UT><0LV{<48ceicF3g^KTn^_g}fzqCXo -z!3iJ-4kqBa?1E&B?+4@EfEr~%z*-gkP2i2POn?m@RetNuu$}- -z7s&M-&DQbH$vC6@af2_HL;-W~Dw!cK*2apMD3}m--10 -z>w~Tzj&L@B)Ss9RDE?T7xsSbsmei- -z6r?!O*H -z==Br|le0dR`CDyraq-+#*JNoGROq{}r9M5>zVf)QJ9Zl-bR48Rko^fGx~<~$3xAFvMgvw~?;K&EeUBu}<<0Mpu5 -zCy#L3zPmEYv1C`_3yvIuC*~e7=M3sheU;mi@^!79h!tnb)o^9|+n4VPMU?R9igWHr -zIZByf6l$uLAbfqdyF3QDv8gerj1jj*XX(5A -zIz7jRx7MYN08ViVOQ>@!1`Z6-^#=8f`uZ8b@HCXB=8u5ZbvaUzj(@F@zBPG7S|ey2 -z=;IZ@YFw$xG6UJ<4Ck6vYXmiCp!Qw$S@>*QF11YkQlGWb#=HQb`-j7oAtYYM!uSE5 -z$67b$W}WA*I>>^bl*B>eDg`MY-1##OIwbMITd&wBxw9ML{3J8pJQkP%vozm4X{Q% -zc4GD7`Lu>fGek0>8q1czrZ=t8B?n0SeM&$zWKP38&#ndB5U0NIrBHMg0RUwTc1}hj -zR#_ht>IqWIdzRsJq=y{4y3%+d!MfcA#Z -zsCHP@ZS8RGO)#$zsEqTOhz_-c`5LBNO)s|i6!XKyOO_{s@Jb%mEdhWH12e#CV-&M) -zBo=E~D-kgo-dAr~19AOwO#)$ajU&0|JxKd}BwUbcBQ8%NCDiR%$((TuA1`FYv0wPQ -zYSs5h+R-#hw6*0IFb&u!jCgiO$QCHZ-p}ZvQNhrPuY~LzbA$yjfmsM&10bTi9|Z+` -zO~M(%K(!q}0bMoTYC&aZ`AEPUDJS(tW=mJS5Zj=gZZ%==vHZP~r7`*1)^$X?vm7cG -ztfpcWwGMP{?eQkZGzx4hWn!z;qoJBXUGp-vvmy0a6`BXLQnxvN>ZIgw_zAPe@T;(( -z>m_#hG}CTyKT4ZK?>PZ7!B(AXE#oejYTt(IyEnr1atf{&aqUg0nl=a7=Hvt)zl{2t -zFs61SlDbY^JSP0K=?lOpy?W-fvbWe@xc}Dn&`bPZ04B5#rP6dY<4zRAR<=Gu96ZbJ -z0f+Jeim@}3l_{gv=K*6MT`KQBVgPC3J^~q5MFLa8gQ(~ -zgO}R0Fca?8jPqc`DHks4wDp1yWP}_9*PGNI%2aFu!HC20-uhjezffO;*vXB4K%$Ah -z0e{L`FO_|z@cvrIjD{Oz5Y8d%ez69U262fs>H9Jujwcm-v5xAMEo4;|77_}Q(e_Uw -zsMSuWMFk1DjT*&q7J24hUp5d((0uZ;Y{>od1Er=+dq} -z*pK}XCvn(eFKIGT#M*9k4uMhKKLqSwVs9#=6!tixVA`-6VHZy=!)cK9tpTMQ!Fmn* -zfGBYo+n)ig95NKtcr9CuBMDmWasW^&JXc;v%|Mq9>%-AOOak^R;r_dGb_oW$B9Df! -z55nLEfpw -zuK9s<7J47v03rz8ywF>c1Uw}6-9t~L$RL?#Zw^_Y2$D*+r -z;a9yKFwEIwpgfhPqOQY3hUAZTF5JRYX&p7?SA&q%A1I5$*2j1aBmWLSO}l!FHDqxS -zp;uQ~yT=b}N^Y`V7B8+jIJ`Ee=Kx`It>b&bSQCg*>wSbGd;e8q*%;?c=m2C@`QrFM -ziPqrK18OzITA8~wg+DY2`Kpb>7R0VM$ySmA)(+3o94<$<5ep(6BEzw!i+g3XeYGq< -zG*<2p9->H|B!3Jn@$XC^B~xbXJL2b1SO3$`0vI#HXZGgNZelHnBbw&ns7v4p@!L+rmjS>=82W*5fi{DUI^Z -z1N2X8Lpj@W-HL@O%AYar=IuieHw96b)T$TIR%)3RBQUu>_+tekzOSVOy$vNyN-ovZ -zThMY`?ubw?HF8x!wcM*rzn1GgMxWA>((5Jkb}q5?6&dZ1&vyUJk^UZ%Gkp^X2a+fU -zg({*>i6!V$jwDkzWY8kk4 -zr>c?hF&*kKl@(1S@FMX&k>sp*`LpPuyz@c^-d2T -zZN0Y2rgA&v2G6V(JIp5x_hbCTc_*lK^+i8s;AfMEUrdzl*aA5h0~qKv+b?nO8NcFp -z`UigHgncK#swWGRKS5r0biEk7>q@C9HIjdh5v;&>34W7z%NgqTZ_siJtbHdAMU-za -z5f8!?m!OA&Pc3848j{3zotOx+bJ^qr3`iic=5GuP0PLm-0V*dS~ua?3sZ)zpL&$aC3_4_+s5g-@Z8l^=5d@9@W*i@6q>37Z9#e -zAbnCii4Ok%p#*ZGefPQGJ?0O6H_6hZoCY>dGwebnIX=GMGiMCh_(D1bc4DL31LI{>p< -zq|C+hcbRyl^S4AMDua`fk}AP{{0pA7+tSReXgJte -zZ#}Br{+m)Bi#AM|nPKQWQ0WJ}yj{NVuDCv&majqOR!WB^f=GPhfSp3LSNz&x*fl=L -zZk!T$Xb8vXGrVDoy!Z0Csm5PLX2cECwUC_Pn7XqJXC(i2IUBwU{&duHB^Mma&r0y6 -z@?}}fFonn9WCWe?DW4e#f@<(fz>xj^X=Rl>Cf^XCya8cycX} -z+7?)rL%@9RDZfR8)yId!mt0%d@ttm(Sl-~^Ju`MCEbXX#P5xfuxChWEn9EQKWBOSX -z#6N}WqriAG*Am#Uh$#3?wP?}YUKd0O*HfjZ#4|j -z1KFzf5*_paYB2h^>k~3)>@WdyRJAvqt6311EUhE5BAee$WMuh}2h21$=0OjAfS$XE -z0ftblh<0y2T$0@PoWnWi-hWsN9;i53>EoTVkd0bzD*OE@)CN}l%v-GHISdL2=>9X_ -z+yw(*N1VS8Vhr;GC_=l*QgIV7%3)%Li+LskRxez=zm*4hE?t;TzX!h% -zHy@_ytSFA(Di|KPl14?8rYSa$P=y-oPpH`cq00`Q#;?nj_psJ(Su!T5M>!@CWC$K|7r1T?K&Ka>a-Obr76Qcs5>h-s<}eE(IsJnJ_~+mH(gi*t -zKKjusKDIOXZ6}}9CmO!C0)8~Ygd=>&eLQ&ykTvY;3i4MDT>f07 -zp35t0Rer^lAFc$}h%)KF9G~-ILy#-TyfgwOD2k+IZOVZbAid^;WGz=C)YPWnDK3pRi&vyOLF<7kh8CI3sg7VS%11-)Oy>N=EXNYi;1^$DvQ -zgHET)8|qYbk8QteDb-AVmq_ZipL#S9#M(I8osh1m#~byr?veY7tyAu@V;>LiQb1Sr -zpQvd1Rz&}>+2A2QD8=TN1xX5o4d@CB3n$;Wabx}68~nx)xcmo~t#SqUYzFg@5F7xM{XMW_)nPsruqg`8 -zTgETVd*ieUL%Xz=q@wkdFuBV|3)2V~nU6OK4< -z0#^?Vqh#j`891Nk5uK#!)&)&i@jrckK6~(DB7)aQT~`Q3ew`m3XGqsjf_ML4;K~12 -zuit?WRI596P)w3u5(mlmxctc>aRK -z1+u7t9*DBC?+PBNJ&zjGp#=JEXQJ`DHNhZy;Jaa;=wC>!fAYM3{mus@bu2$e-(5K$ -zZTvJrKacOc)lm2?Zu;Ai`}J|?r_JC>bEof!eElCBMLxDaASED*MgbOS15oHlz^Lh> -zfA7}S>w&qxVva)ROGi*DFfgwZ(Kd!|wzeO0D;FLGh&gaMk-#Iqv63ff0RxR-kIKP& -zOACBA1a$5j&iE~E-t!k+E}U+({0{%-1FoNUz>%`yUXb+#aV+6K9B?FU7`=!}AP5z| -z4j*J;_swfcwJmvD1)-!>^(z6T*PiQwA*BQ6--{ZJ~I(*i@I)dJVSM7d}&TM-c! -zML+$}>~2&UMG*!<8Kb3{V_&S*Ku>lMUOor2MqYzYF1+VQ=hM{%$Z60Y4(vArFe%4p -z2y -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 -zs$sLy8S`gK0Xe&DC(sz7(}Ud+3MEl78w9^ -zJr|WlQkR!NQ^ZAjC)_|9Yn3E87srMj1DHLy(P*;-_|Sb23Mvp0hXf19V5=s -zg>Vd|>9t;jk?O)4qDH2vt7(o2cMgH3jV+~})6OMebU5k)6V{F%uPL20#LuBy%=#@t -zKSu~%_*LaXGq%^IX|CYr@B=$h2e)-2V1Q6o&}KPKbySBTw#;ahI%9qB8(=sxL_#RpFA2Lj -zP7AX%LG+-r;wZ;6!WuPfiqs*E&JP4`H3njxT3b45`$7tr2P9y@{fpEFlYt-l2V6y} -zhOi984mY59jRESE2KRbjnQC@l(tF*sxF^;V1nYRpSjs?(LvrfOZoTb2k3x4ySdYfr -znPx|y&~!i58+t=Qg>iDSrq1vajkQNlhOST=o(LDL_h@JTKL)3V^%WSR%bsGlU3}UI -zNNy7ll5qU~B_PuVlH3`*d=*+5llxcT!p~8vGE)EgATXB -z&yhb;rcLzp^cY-U5$7|Be5V*m-empREH?F5zGa^38$jWd)fPP9{5{Jjp{2=jrjD`@ -zOK3b4om^)Q`SyAC^iSf{Z+y=MWb2~297Tm9bQ-D2OheWlL_If1nA`OuQ_< -z9_|AKr^rA%?B=;Lbd^^>TEGJ2YYd)z19Gr=YazFB -z2J;F5QpVw{({YUR%b6IckUr!9%!_0sUIg@d1B_ne(FugS(Sa8>5Hx6!+uW3Op2D!g -zP2kCbmUYAUFGGbW=#ip?G85Yn9LbugHS|X!XCLU7Z^w(QXA>#3a+>_0L=_DAA1&f=pcN -z(UsR+QBE|Sv#Y!NCEV7H;Oy94WLS9Oku%{$;`>^Xj_H=8ce`*)vw#NPBNRSvX==JX -z_jLj>BX=M4j7Z&kcR2&oOl}@vz)9)p4H=o4)}ZQ@Mj!O!)n*nJNstE9va}RFN}q@o -z>`(Vy425<#fxp38DtPhl9&OigbJk_9~sdUhsW`Fh^c-25e^d~3g#HI*sB -z)bkSapRy?!pd+;oiCVKEIe^f)bfGy)$cUVpotyCMNU6ZXH_%WMwwYl~a%}^g0HGqJ -z)Z+mAtol|upi-C+H}e>OAix@o5iVnRoAJUXfb;;RZJWU&7Bw(D+8i1~oNIHI;LZ#9 -zD2%~=OvW8Dfw>!nY$NHugcHI~3c(RmU-M~de!W$oLQ8qb_E9Mh&}tgFJs-M2bAE3I -zV-v)=LQt|)|4Xu*K^~TPI(Bj7+>-OXhsV}R*kn|W6ks+H^*zb}ZU{2>za9A@D3I1X -zQWS?=XAuHr38U6e(kX)uZPme_FaW -zn8zD9qyOn~Uje7z`pUBPix{zJM&5G#cneD10Z*50_CxYReRY0Z@RjZ`+TQjOJ+!$` -zz)=0KPt2>$v#3GwjzwISq@U^Fo -zEoG?&Fc2l7spaF{1q-X0>yeGPBLG%j0UHJe!OMs^q==;7`s$i3yb3!2q5XCmO;OR2Vcy^9hoj~SvXB)%5SGa=W$|lqnJPV|4*}U -z453dL4%2VKfB#ar(-zNf#(!meU<`~fqs#az=Y{hB&FkU%<%XvkWUfunvqZi(s%1c) -zDw|hqG34)=xVMQTOd$9qpu-S<_E2jM{ljK83~C5iDnQ@HJAW~9;lz)PIrjGSBti0d -z^yi5O)PAsp={-oHN0m8sILg5}nbhPcfP7FeOT2D+Wl(T1DKoS2#&WK!^j<6qT=?i{ -z0g&QOk-2}{fTisKx8wfR~E^8SA9TiAdvo=9H -z1K6d?Ax}5J((u8H@azxG;l~cJITAQUfw_fLcn$yEpwGC(O92b7!P+u=?rO6f+GcW< -z#TY?PrX-D*07V)#RG8dv#|b+y{2_e(HvXK2h6?`hWWvk7x-iUr6>Z4Ksx!wcAuBwi@2wp70zr3*64P%3Y^=t?m -z(F=5EI`&Pl`D*l$jB|UG@w<2r6C!oi#vJ|F8iqEub-E}gT(7HoM_{cDa -z&Lo-Pql@qs@C`(cc5rRLFT&?QvcE!-o1x88iW06d_k2lE(hjOA-ZenSolw3-bt=z^ -zWDUYwv&B}Uvv3AxS9S-}Yp`uitdB9c_udJ}?-QN(`&2+n-rpOqK!ukjW63E>Cntia -z;?v;vl){rpuB}9PNwDpb+r3SKpd^;9U^t<@!+slBuV?g&96t93xLbR#hXik~xCq1X -zsAB%E2-u+a#V`J{3EVpp;ZS9cRGxGV>((~k32{*-xW#o~#IhTr`uqE9KYV!EPzR0A -zYW#Xft!R+^v)a3fCqxF|E~PmGjicPHAY^m$$>H2C(Hgf*5Ack}AiaNaZ}r;K`(L3} -z<@8tOkh(1j+Z%%t9A&bW&Wwvx#*Uw&SeB*s67}qSGxYt-ipxhY&xY(sJt{n%esz#u -zi`|2q&6hJxJbxNFJ_UT5w0>mVAS4kTA;}!DT#Vycx93~sgQjtb68*i>($f9HxbMX# -z?OdQ~qZ}sZo=l!{%wD1?^trZ<!&hnVma2f7VMbxT4J}LYw -z>3u@ -zI1R6{J7*SsYY}4$tJ>O}qmMvwCS(OLB&X6BLK?Q%`(O^-pQW=h9tU?qU>ny_dGP0| -zA7~*u1hjDba^W(-50RMT)u;AoE~@z|!iFd}8W(h>*yzo$@}BRGq9b=jFV;NT;zkO( -z%L!&R8_sO6NZB-;b|Or%7HMNU7=a3Su0uq@;W=r=@EQ2a=)TT-EY%2_cSrvUd7Ik` -za&!p`z7<|b52pa93_q$~jnaX23qJuTQs`CM#`*2A7d)*CXZid@;MZ0)!u=m0n@D$4 -zRg&SUORw95Q9l5-uJm|Bpa3Jw6;!EmzqOukZg;eRL*jzlyu>FDHQ3qdqX^Cp>NWP; -z%dWt4^&ID44^GKvk*@4vr9U456^nj|n6{L=w -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 -zBxuv7fmA(T9q^TY69|4 -zPw&=cu!@>GEDgI$Q~Iz2PWDVnWtj#~TfF#z%XdSgCJ}_LPKd9{M!SC}*!A9CSMOoC -z4UW!C8hF*3rw>Wez7tIE9xZRc&Jrm1MzDEt3WG=r$WXs+V~UQ};8|}Uj7+5Z{^)#g -zhN1}ab!K(REyTCdxc9#4X`xY}B!b=xDHF{$q9eCuCsEEVd=-CecToojR_8i(TBa=4 -z={<})r7qhCY`cPPi8Akx(vQMo%xs|tkKpBUW@j48?(E?)gel*{1<1v -z7Uq%Jb|Q5F3-D3Q-Ru~z6-z{SE^bomY&%8~BhR!@c -zQ-Sm#jAhR$zFROUdQoc^JqJ9Zu`CEHHrqy>Wr>J9Fv@z0rz(!e9ka~djpS%+Q5ber -z(tB&h(YD)HuW!|P_=;HIuLj-oPPRSC-^eWP(h->-E9h2ZW7uD$rm|Lc7>mb_G#&R& -zoV-A|$~&zp>5jTg75Z5yy=2_teM>4xlOd$9W^zN$(eepwt`xV1^m0#t^rm%Qv*0v{ -zQc;U|bd}$`$R%6xG|B%{w9##jneOygm>%{SAqC$JIXy+%-c%RSxT^-!Q>fjfQ}E?- -zKorJy2KUtKF}Kp*l017fFOW|1km6TybS+*&3nY3-e!Toq)j+q&Vg)X;^Py09)qqO> -z4YftF3e$jzcQlN;s>IVztWa*jG;{PE&2+u+{DApQ;*6Gz0CY057=5+KJAL;D+J}2* -zvX$5{iS&{m*MxUeqJ<5M0FAJyj0%9#>NB -zHM6cc*LyNe~V?1#z@XZG7YmY6e?;oNf*1P78{vr9Qa&-N{c4YaA$ -zBg#d!Z1Hb#ogBJW5M$axI11FzIM=Y%IRf8tYbF)>G9hwqAn|O0n6C$-Z{ogoj=5F+ -zxyZ|wZC#y{1Kz)=ebugKv~AB@4@xs~JlPR`SCNI;hZY#q%N{NG^c-JoG!Gf0#KA|9 -zXBPF&6tWDlsf6WyL|@**Pd$N;G0!NGXQgYOgThn>xH@_YM84w@cNG=BcXLa6(?R!* -z%`qWSn9CgcaqBQ8?3L#Gn!uOpwRkZDODt5Pnims9)W^)S^ToAbJ_vi0xyiL&j%+=e -zI{lv@05{zy{L&F?vGjT9TLRy4!**yg8(H5*l&W4Ymi~E7YIKT`TBd_TIT-5G(;rs9 -z&$#c{EVM|yHA>~=!Qg!50~VaAii)zT>Plw|1`+31qP@|32O95qhKY|fOEdgV8OxK*_@*XG45%KBHe%j -zq!bXo_L|~5UW9RCbVnH&C2N#>H4f(9lc?i9SEO5?vWYT<%`_b~Tr{2ap{zWu&GOlJ -zkepTcWUo8%t9i+!^Y&qSW5!ks|9lNuX$?!&!poNXsB+hzkEeBL4#=MC&W>5ddUwOV -zvL~W?J@-WATfG*aD!jQi$7XErXI7Tas3UN|1=C8F&!}x;YLw}xKbBXaof)oK;nL^T -z38^Q@$G=UPGm*J*&{MIb`fM>K-Hc_cNI08bdAIB*pDiv<0|M@=Cg0!mx@zQ}hg91y -z8#<+&+{c@2`eY$hi;MG45ypW+)Ck|ecCLf2FyW##h7XV7WewC2Ko -zEzSx%thpU_*eW}# -z(dBB~jK?t>q4OWTsRSHa8z8F4dh(uJv+t&R_myu5r{&b)B9IhHhW;z~N=BDZN@y}q -z@l7J6rn(~*P&TcbULOSKhc5pNFqU6cU)gK70gVI6Ds5EjkoAnGP&g0}k57Ve6w0Ca -z2aZEDp98jM=4U51%qx^cVvC84jP%&2{Pi^GhuRsq_^tE|S2d*ldgR_48_>B*%+AP;Q -z7_Vbt0wqvxd*?uC|NFD>=%vui^n8e7voF5gmqowVr<63ejC8fd#H+hQTER+O6SKvx -z-RefVeFu8-g|CK>^;PK%g?!8r2n2Sj`|jG}m4_%Bpkxv_0(xd=y0BlPF!@2i9S04|=ueP$HG2$C7Wp|Hy6qUF -z9#WPZMp-O;e5hoCssr@uc_N*&$cVRs<77hAH@^=&aBIU!u+l;Bjg|s0pheNo8uaRg -z#o?LXRmV_W7FDAGz6i?7M!uD$y{L%?UjUUxPft&rb=(UH5ukF_0A@4P-2rntShDViBb2V!=tW1rlv7a9hLRDMFvRsja9rhNa+-? -z8BDvj3*Z$Z@=&8C(0`21Wvu|}0#YhLr|aiG;Cs9oq8L7HTEAd#hCp??rYc{2C+wLc -zFby2)QgqSvU&hD>X;vuRvRtrFzn4Y{uvbWBcNS2x^b!aBvj$o&D6>6ESa=J=8JIg+ -zk>#%;kFN$(q-q$YIzRXG--F)|GjIDi4M1XZZz@9A3Tkg*7n~R=*eX49x?R5>OM&ko -z{F-T3H|k_U|Mfo(1;6#Ln#S=Y%SnXlxw_^Tj?GeK`221Fo>3Ki+Io>>6p2*o;^LAo -zx+!^HH49(O1f^B0x!U>znru%4fHPz%=^9&@kjjmShzJ!xFGrg~U0s0h)T4C}H5Rmj -zl3FnVJ=?E>+&unCoO|J+!7SF?mlDS?eF9)ifq!wK12n9Ol}J9!R|QiND9U>ccCi%v -zt#);=4yX~Fhu_{hTS;dX!`d=|Kp(ftuD}zbFt2uJJL(^8b@K(~Z~)C9)$yb6>~OI4 -zgJ3dig$k~$R`|1FmjS4qYN#_aHo)mCRnM;6B(eZ#`H7k0^8;XB-81$e^UZ8e;bRP# -zFGwxyNyG%*eAoUe7(qDKM?-FHSPe=erX8PmrA}I9psU31g3wlsLkdjK(g)vlxvX-e1f0tP#e(8I4`Ma^c)DA$3E| -zgMp`q;Pq0Gj1_n5( -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 -zC=6MX4s0}&jL6t2?k^T6{S0?1kZwcY -zg`7@0=KaLZeP^;(+Et$|2P9j;x#h~9Qqn}p>^@A%_J(OJ@X058tl=AJ!d?0}XXo^fZ^TZ4C73HH -z74S>qEJ73Wj&jC3S=9?nZZqmbAeVUoZZGaR;Kj~Ik#+`x+X{|oroM$jziZKXH#@pm -zyj~D|(fs}avsHb(>Jv7aX@r9kcG4erv$}pgd-n;`FAKBd#3=yuS3W;uEJ%3Rcf_w3sQYKu=nd~YPQqTci#YlkAD(aRV3B1Rxrg#M_UW& -z;ctwM;)%0uW-xsVQ@o->?XMFieFPt~mRA^?%R%y-VS -zqZ(p1St1qv2c)S+bS0zDTfInApjGcjNADbF4OYk^!x9>!0jCuQ&%;sfWQ*HK76`>F{YjKDxP71% -za<RG4-X}?;TvY!0l3WCA_I!8umLce -zT=k>x(zycM*;Bxi(@DMJ1-c&I_FV|&W$cqd#*(LOI?scX#|~xq2GXXq=QbO(w6s`( -z@yaQWrG<)mFRx#sDNh5M472L2sw(9$I8_YRQG2gJSI5Pqcsd>x!;ExhS{5mx=OwA8JBAxQ>gv -zKnC-#$71zA{yp&f^)C^yb-QTqx(l#I0${tb@8sitz543rwlWK{SsjqE`&q$?vZOq% -z0btkx7Er@Y8^BCXN?%!=o6NFo651o-V!v*<>IgD77^FX}mHr+~K`p@Rrm8AxHy@G# -zq1jDbTnT~}!5zNB-F@w^ZQW~WqrC%^%pm0i%lAd<+YH=xvI~~+Za|Lb*Oc(^@P)17 -zHcgg}mwru#ZMjn+b{21Koi#jcy+_FSQgTm~F?wGM+R)HYOK@ChgglGN@*E^ZxEB9M -zge<^p!Z*cyw`I7sq#P6dZNt)^ftneFh&*hT`1tn -zjLt0YABWR5r1V<%BNr~m@-1-&>*E0{^If}WxB<)6+T{aR>%W%C$D>VwUr=y0rFWI- -zdtw({?YHC&X)6p!jv**AB4R(3MI_(@ -z3#D2dUmX$02mhh;S5480b%|sVzql26<~HDz?v1?E&as4m7Vyviq6#bd@lIdEUuQMA -zFu+R0dVPq2m%9l+s|fbn_{Z=CntMr^sm+u>4KlXejQ%2JO6T?6|?~*7z>SG>BS5APQX>=gA*n8z5)dnC#tbkx{ZbCyDkC| -zh@^(%t-@le&ac$Szc;+74!DzBj(XwN!4^f>yBRK;JgUzG|Gh=g3%f~xOkfBA<^lIu -zJU6m{Z9|4tU?lT(u+~;~D+mn;92DxTol6A9rmm(mCPJYXbmG(hPwB+feun1)t7Qnd -zEa_mEiJdb#e~4V2R*XukL@;zEtuI((AV0%)MKy$p<`sO2yI9p8=rNYFk+~I$U`543 -zhe0jniL`_c_z~(D(`Ep+7eRiH@^^z7fYZ|%8{@ZVHd -zOJAYkxR(MV@MLY5VSEg9%EGBWl`E0XCQFd+h%@|Nq@dpy#y=&8^j}fBhfW -z!j$O#Ri}PKm+LnASa}7C$a(GWR(ljS%!ZM9CC3V^j7L8#Q-{we-nzE8Wu>55TtRm=JZV`fg|J51O; -zrO(lD+)WwpsmwqNyuV-W(gW*XpB)^S19&cHAxoIPKKiQz;HDk;Om2eX4}+FYZ_n-I -zbV$?zo3&Lhqd%5{5Lp68z>#hkiJ#&LJXM -zLTCk@*8*IMp18&euq9vq@4tyCZm2zi3pw*B6tRv&e=O2M%L9w)6NI_%fE?bg% -z=C9#e^<9TdklJ{0y1giNsq=J${T*8U#C@KiqOS`J$bQ;aOFUhc$icead7I|gtkwd%gA}FwmgTd@4n*C2fu?k`S2D(`Ytkr$p;{IYzyK! -zLaSS7rwb^=KD&zJSuT?nuH`yk(@R?~pZRg#^_$lQWM5)uK=~Od?v$+OKG|RrKEH=- -zp@MB675xH(PxM9^Sf_a0j+n8PBouwW1;Y@f>XiENQDk`+7w(vj1`Eof-8;mA -zyJO@Oc5#L<9s5iP<2TGnlnc?n=DYa(Rv#&Xn-2|fT#N1rbsw8~gq0&)Otk=Yk -zPGFEU(Isg=0;5rqNMXN{_4L5sq#ZJ8q=SkSp_=(vv#OWo;O`$ZKPqiF4=kZ8tOjmr -zsD8`>*!iPl$2r<`Xj4Lc_>Y6b^Ccv7MpWE&t07@_z98D&{|m -zkPVv~;wO_~xO@JhQc1$VJVO(j&^f&v0eVo6<3fTv3?kD&p8c5&OQjjeYWQWZ?7iT3 -z4f(Jrb&D0tnnfubb`UH`HtoeHe78rls4yXuhxu`iDHgb%4Ir3Q^4;hAJ(@xW&A5{| -zzMQngX5khZ=cp)qMCjQNBKQcfkooJ8N?S=>H@nGnk2tba2Q^i8HhD71J@mP)n&xZ0 -zSIsO$VQ1tL0LI^j*Iip$TKxb>?myRqO34N#BNPwU_9J)htykzkO4?VTC^>>0d17{* -zQPoRP>qK}MymcsuGlVw=(|o5Ccvq~;6a8tFfiTfQyQn^ryFwZot8>jvb?;=3Ohxir -zJ*QIc;2cu2!|QF5*W!j@9OdFfBxc#FMkNYI=@m9N%%Pa}CbLK@v}*LETt>n@mS(4F -zp5Cf&li#2I5-3B5rf&g{C<%D|pp@0WwV}3GzO+PyBuV1Tzd`YMZl*jF+C6Z5>NJf@ -z(Lm81fT(=2GZd)&!KUy&n4`Tes^~v@99&3B7u;ST)Xnd?=PlI}_{PI{D=Q`H&Ix2f -z!X@|%D4%u%-BYI9rULjhPOmD#cb#d|xwBQF!uUZjNq1NqvZjgGR+xT#{5i4hq_WOJv3Pde@PWSq_Yh0b3;|4l{OUG#4%5?;$IUjOK$zAx94 -zo`DT?T~f;*RHPBX9g-eD9vRplNcLNV3HFfht|fTM4#~r>-ik38ZSs)rD-Im1D>R?o -z`MEm&R}n~ad$oPH&m`ZfqINN}K{blX&;RU5wc6=cy?DFK6;b0ieh})&bqkDOsEgCr -zfvab2MvT})V83i=lzBpf)!gYT4B_4Dw&F+@k+Xy~viX-AfL!shF!CKY9j3W5$q%D+ -zK=AVh@)GRPFC1SJ^jwEXf|oE?;$pu)Wl6QTXI{VKEq`F`Ga^Iqj+cBIje_lcU0Wv; -z+JJ2n-JBCpsJ#YI&F8^^fz$=$?&Bz16DTv9W_j6Hr -z@cN9lDSaq_O{Dv6&UC9^0J9BbMs9uv_Ati}eh -zbob}Jd7hIK_MtLZlh$kCh5{Rtv4v9g6Anq-bQ26?(Xnf$^+sHRTZR%xSYhioh`wQP -zcKG_O@i6~toFEH%fVG$5(&Qj!Nb^akW5$5XzOcOwSfl!RPO(Fp2MTJ$aNh(+I(LSc -zpK#18wEH!~E(4 -zET5m{HvfeKmdGWsefx;tF8vkm*uYnz5yiZ(PFpb^h!BV6mTyIyyQ;J{FsuqAZ_5lh -z7c3K#V!}Nb3|Pu8DE$@TJJeugv_eU;pj=Og4S&(hy=zGOa4Yu`%7YntyZ|owJ#)kz -zWDAPnkDB)k<(YMz2o&XB^7uRj{7{Q -zI_x%R5u -zBoalH4>laG+|EpnO;Biu&F}BwkSMe-m1m!Y9HdzbkkNWgjo5mD&y=)c)lbL(AU$&( -zR$SKMB|i6geOj^22&+VjM)h0ZFiG&Is?D$nOZ=RSkM^~dC0LXYZ}ZXV=Vj?WikKzn -zJyCt6duuEp@(cKE@!w5pwu$e3zw?yDG}cfrcN9}y+mIoS94My9hZ)9RqqW{@bdMzp -zhmU8}dgassm)tw$+yqjD^2UFUba3PGMIO*au1MEiQCd!UsH_WlUy~5z99mco0 -zVS7`IcETv;z~Rp}CC!D2dKIdVB#ooOGn>P;2Nt*M5jnBwpw5rd;z)jHFtqf#~)W@i&=Yntj#3;r#DT+=b4*YZ#0ww8Ef_ -zbf2q>{CYn2G-z%=K)u{b6eH0@* -zKQK#T*vfKdg%U+uXMGk?qGu{g`pT% -zc&~GrJSE1UJCcA(xaq70L}E=0X3LLoPwe%gYJD8g$**BKQ=Xb?9-Qmz-1w||j&be( -z-OZqx>ut@}^FD&8ns3=nh*tdB>#%ni|V6DEWZs`YsE&N_l!uEqqG0Qro|`rXU7X6QS*CXolkp -ze#=^4seOMX_Fl6nxe7j^vUvkHV_X_zBRB|pFP9lp{-mM~JPQ&R&BHbM7iA%$~ -z5f!uy47(`QOIi{<*+q9zZ3I-J3ZM538r^Xo7D|De3>*TJKEC<>f@G&zvQ?mhlINcd -zn|>^?cpME;vc_--v6++rh6*C2XicNKtwX0fs3?9T`_xnM;nOW3UeFy)G8>2hPo`mK -zy~(&d3)Y;au&5Mj$XmqEb#43saU-S-mm1&(ExNv>@U}K+2Nw6oMGn7Hl8D3L$AIvi -z1_8G@Bh+2SP5tgMM$sg7&HSg`u_NUwDU`GpHnYr&58>JQ6clsLps*ce?Gl$YtA%}i -zc^c15kQ-VV!Yqi4y~wgBwgW{Z6a!gavpM1QV%8rD7hUrnDe{(VODL6 -ziupSSPh6m-LGWd2yrTJuwG#6`kVZdWnk9$#O#O)i4u?ldB&UwITzzz;6%Ua9H^&_o -zC5gAQC_uj&77c9tLsqJ{MNIA1Buj5eWva!x#GnE(yEx2+CRbg2y6 -zc37M9#6}d}MBJ-Uc@OIczqwaen%CxySBZ!D@+EV-VIontoS+`pEcJ8ta&Mg3Q;T3* -zAY7El8iwC~uclP3)o=!XT%#{O7w?o2!69y5%f2_xfp(mj}9SG -z)u%D+#C`b(W(1R2p$6sSgMA&HfgCLvT+bJpB<8xuO=A_pPD2X -zy`CIrw{Qt3V`mz#M-7G;7*Uw$`A{*o|gL*E(RHBZ^SmS(+^} -zp&gya?P+b%pT2jVa9Ba=Y`GYHQfdMP5a&u%d#|D}l5??_z4*cfqul5>oX~b6|H>m; -zbD(ei?d-{Z04~e2bZ!Mp9ijQqu>y^c2;7}DThWdxI2Ej3J1+KAu&mO_vJ82$Uqa05 -zYadeB(&Mgx7U-0Hp9-jfZsY_Qo5DQiB}~APi_I?!@ZGwFFBpFf*1%C;cZO{eC2gvI -zBlNB%I;;%h6%Dq%O8Tcp)B6~Ak0&^Ox)eD&I*y53Hu?Z{&4lAObi0BHEK4*oFXrgJ -zgOOJIF`PBHwJrbT*2Y!`iXM_;JiNod)lh)U&dCajnKvq%fd=yT5w?mOEyGH4oQxJT -zE-+ea0d>W^%)3<)C%d#4YWhGEQ}gA-e0`jxiAbBh`XJT4$Zxb^n&G%4Twx@Ims48S -zO8x;+#mlqT!?>OH_qhEV9sfS@y=$-aQc*My1Z^s+90I5}D4-P93LRoy(f_(szrN^i -zG-zd-g_uB4SgXiT?!Oo%`96ArM2^3`u-DK8p;6zT0TBFpRgmuoF3b!lLiv}1wk&Ba -zGZ1qIHv+bhm7U!JYG+G`_-qoO=Un9lXwa6e5X(>;v#;1dywb3^>y#yRaPVZIp!db) -zxvPu;O?mLqY>+X%SQ3=}kBW8hDewH7GWNGq`6&x}#7km~*G7uEzg>DUuA?+7tD2Es -zJ?gaE5!Sqm88~(#60vjAGop3G#ri&ViD(S7_MA^88P5FB=}is!zd{r)wSs~Q5h_wQ49`4L;C -z;qo}cZ+}zJZJ^xtV-@?m{e{)Z2&0 -zxqq$S__dP;B2S1da5nwZ}*cD&a?Ria>)D~cfa-1EV98msqia_GO?91K09+| -zWZL~M_10I6tHFr76c9NC`*T9pw^(LWCW62iB7Q`i4j3Zj5)B6&9oy$)%50zai4yh3-)Y~N@Xh09A8HPHx;=4RV32U_!oPY@dw`Z* -z22f#zFnjYXZRy~YXS~JTwG{fRD^@(zhIYx?JI_evI5;|G=dyJma -zis+-TX+Ed(dI4AVJA -zyW9Aa$-bK62#=+k>W3n@K7*9T9CB_FUvxv*EndQPDzi7jwQ4dSG7P^!$VOk;8|#7} -zAIlR>Avjv(0S^gDp`yoFJs@ce9Vq+UoTNY(xciir~a2N#3@ff -zE^`x1BU*!{X1kbMKn6WD^ZjWyWPs(Sm_H6s7Aw8A@I$tQsA6)D#b9wE-ao6E6S*%x -zP$C5G6}5eXk_f=N*mRF&s@Fac^(E{6(8ebP*<>RR0x`$IYoIcFZRGZGx7nBJc8}{2 -z$lNW&t)DTu&v;~g)8!>IU4NrLTqM2itnc{7?xc(W%) -z$ESWA$!_iZ5|ag5hPTS6J7`^D{pg)aoVt{xO&WxWrek#_wAe#}Mg6g1x|Yc(8*~IY -zle)UyweR-CeE132vO{$@Pz+ImGx19p8g`+e(S2M770S-qKH)-6YvwHf^nU -z<79$0_l%x7z#9toE`czPpfzJbi)+@>dq=Dp6Q1C>dK(twC3E=i$@;O_&-dUC%0yA-RIEbDN{Ex)7l^!$^qXX!K$3s3Fg -zP$haOctfmt15&^{J?%1t6wBa%lujFvn2Vl0Jj-dr*H5ss<954kN{5W0K=LNV@Tx3EL(rqivt#v#=&D(-mVD_sY^AK`2uGNE;_ -zQ&dzt#^a|+2(Sb -zQ}tk}daj8-78=jT%!y^^CX{HfTKDEe;Tb>W_6cqR>TPm}&;^ -z#^suCE>(IUV6rI|?|+&}q%Q8VfoWS%`P_h*$$I%2ULy^q5ZAH__1IAmG4IkO6Bd%H -zrUMVxHFNs}JrQ^|0V%?o0f&?nO|U_K2^c0}d1v!V_Pd^L8Vuw50^1)%=>{1Rb1e3k -z=}s!SaUBi%@KSJ?$$?3`le39%Xo@MajQ4q8?avMUIxjPZ+Suny%PBxr)G)R&b*T-c -zBhD<&QB#t2uZvFEh|TiL@$F7vWp-2lIQ#0}{sDbu71KQLSmWpjv(j3{BfK7MDK@0) -z$=uhA@iPj6gzn;x)tI89&vps1igVyD@i}lP4_xGPgV%8BA;0RJ>dnH7H#s*o0h!luO5Q# -zG{5@bkQN(l=S67`ay-KwePiGY&X(=>TtmwaHyGX1Is5`a3N1Ng*v`pT| -zWZ92lY@H|iYRYxvEU%jQ^G6@O)V%mEBwaUHpGPR% -zV=NVutf1ABpcL}l-@dt=A9Lh*0#lt!g7kdq-uctvZb6zmWF$Dra*Wj3hVmVA5#F&8 -z2TWpc4Y$W|2lx|`SXn+8Ltnm_IUDmx|tj`=7dL0{#^VZ@wz@){uEzyV!B(d;zh_Pa~mIQHlF;P)Hd6dUp>@DR?~&iiT6;H}eJ=VK(XpB?q~|R&O-~wZx51 -zQN4gcVud^aJ!%sv1s*9OpG;mIM+(pBA`Tk&-f -z-$bO-d=RP&dDXwm)IUWmLxbirUhNd#k6ia5dhF|#^vD7?%+5C-Cza;U8Qm{BpDpC) -z5J8x_SCqAKnduuhZ7Z9Zs4T~M97>duZHP>I*+0urW-u*KhdnA3&zBW?=lX1_oCxly -z0ShZ8PBr&>(goLJB)J^w_}MW`Q?pCQIjsf$?Gfge2V8#0Te*~;U*t46acjr;6CrdJ -zMb~WJww69HaLpL$P0sGkzt;Hg<*o)7y=hw${f={FLTj8*AD6jM(9ue)qjOJ1$gqCU -zD@^I-b`3Snt4?q7z*L0Kr2+>k;u~yH-i~AMVlZD)fydsq`5+S`<%uYQf>VvLX -z_Ij(cWm^@}>H5X*0}nY;s1z5ZiEYvj&!@KNIkj~Q2^P8?DUqy-8%z~4$|$8oEpV4l -z-h5qK?DD)JJN>o$MSDrh+n;dmTl)(P3-@*!ocY}J$>q7j&a^J#QNBSs`#kr7l3Pe=g} -z@h$y0#@d;flw})pgL|X!b1vV%CE(s7uI6S{^%fSta^3o&yS2fD|P73C?LN%C`QFeUNN5;mU>R}lE?;9qaX -z+YY!69v9(xa`ahUwCpw>Yx^uje$u;}ShWSS-b64YMSqZx0`VbQP&Vj!FiG%dhnX+- -zpNpS=uc`>#p1TjpCwWW?1{TVy!o_i|+Pt#_QGUsXabM8HPtcL=@(>&eC*6A{1{%us -zGC+7Q#y1NkUY=TVNND;KOckcqv2e1{&diB_T-)rgIXkyI6Z_7Sp`Z-uBGN`xg{W6> -zv2u%LdQ8}Q2B*NW-`_<{|IA&E;cpK9-e4DF_W99lR7lF%t3(ODA1O>7Y&vO_6TE5E -zpHc@-o$VqGC!E{)K%;3(Y1Yc#gUxx -zgL+A4kJ|9cU|n@1=^(04n!J9-&PSn4X3ebj{^7?Au*~nlGC$M0n}vq)lh@nUTstdB -zmmR*Bu4v+9+vlr*&hhs^#W@>oz~8@DfDQJ2uXsk->@gp|!R)xT-N1a7nb+H`;V_q0 -z;3DD6r8V+fhttaim7NEnjGH$%&i-ot8f3Xp{&FoQX&N0N#m;7hBzyd`L2L-_ -z0oLT0Ks3%(T1bi8OO}-mhe3JT)4EbzrA)1A -zwfFl`RPO7u?SfWn{yIswi!Hr1dVm -z?bS+o4n{4Sr40&;H(nX|eO^Ay*(KI4+Tx0i0TomDKKhsQccu4sQ{z>mTaQvEAGvGa -zq@Ov#E)e-ZE27EUqh8Vf)WV6Q)tiGZK=6tYPeCA^0@Q-+Dw}Cs5@cykkpi8Mf8@hN -zexj&7Gks~h-5F?pts&GoSDGy}pKw#~n~W~7Q+&R6e=L}_#hK&# -zw9UAHKvx$&G@xMr(LRArXXhBnB9PxGaoi2Fq%LHjmBRnC6}%#EfAI80mqr6#!{o9w -zyEeRI%|l$8*(0|jrwr%rt)U%*pH%MQ*ax-IM)*=nm!h-|(O$g>oqvXB`rg%&iQyc) -zG2>%<8ZZCtaN=xs;97r7A`?w1ynvG9f|dkap_kB+ofb%jPx=m?Shfhw_YQ~3 -zu&vDq-R0T>f7eoSq4oJ85B$p2}3wiPIC -z$PhLSthF+~soxjK(3QsAvbN0)#&f7B!nL_t8*`n02Xug>QN`$1&N1P#8lz#xOZ0FZ -z&(Ty0L4i>K$KLqGOBZ6p%;wt%JxJcLQBH5JZsAwS&GhI6$@T42)_CGAER;t=}V;${7=rXmrQtdZ+ -z9fS+AbEDcCG@OpUlgYfFxJ8FetL7#-n^ZL~jr4j&B@)4qd636k0 -zX?KOMeSSL(u3arq3$?Ko&Gzh})8&;Aj3OvBrYJOX}HE6oZONo -z2b=1EhA66&bK{j&dg&oY@bJ(EPLn0@f%Wf>Ks#_9oae&ln;{Rx8sS}FI{4lKPDTKm -zGPmqK{kc)!)oOYXagl>)$WzVuwjv<$_7689;O+J%!Bp_ns)XBOHV+g%Lu8IO*ed8x9u(MS<->ygWWG($V{y_EGFigX -zZ{u{VgaIsU(`|KoSs${L{93c$?2HL-a%Qc(<5($A3(P6ah>7VB_l!WRZ})TO2y{mk -zVDq%01yG$KK>bKhPBq7ui6;D15X^m#Z1<2qA~gdxW#6I^gZVcGMYpWCY8Oof>_YZ) -zEbc`=KJM|YM$j6dtl0v+i7!yHiK6%t@LCMp@}06lFjP|MRHr0Df~2pOiPXXS!3Cc|!$KIcRJbC6Tyts9x!$5r+ -zr|;=^AJm5+*(x(xFTWYUtnR78h;<)isza_nBqoOd)7N!B>AKo?lu6YLg2^;ROGu#W -zDYM#OSa~D0bCnC=m#piA`EDKsuw?f_LC=J=&2ic#1?ZPfc}j!%ok289%GK(fv)Y#L -zCZN9Tf(Kgj -zHUGRoOdR7#U2{+mLyXvy0Du37Oo9GaLdv!l -z!2_BF7+_xROJKcQTW(~Z>@I&LpJm=oJd?_m{yvE|=uO`NkA)8JZpdt<4llydn*<=s -z?u4F$2EvWdo?uJqgg(q19c&M*j6ZkD~1}kd% -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 -z3z40O7)!#~mn=is%36vvB_%3`v5T@~mwn4 -z^aHQo^Jn_&J?E96x63~5C+Vf{f|lQOnmcojy{;%sh8V0IU}Fo=R$C{!XMQIg^A2bq -z7(lT6cdpKV0+d+K$RQ`4Ils)MqgXj`VTENOsr}A>m@^lA5Z{ruj0e0Xg*9GUk4K;T -z^wdNY4i(RBikz=@Y{FO)$oQDeIxm)vC`{~+3YdU^iW9=`a)iTzpEw?Qcy|$mgf9u^ -zqxb6}q0oW!NMmp3>|pYF4lb4>(z;)~8_~LDs-v$e5XwRCGWTNM?+KiV1QbT#?$d3+ -z8W0oHNAIQZ|Khz!c_`$C_Fk^21l)Q2=jXa*Xga(Q&-V#H(~pvK(d@5l&z-ikyUfxT -zNF`bO50b58-a}*Y2?#Zcp(3>H%`~uAs=V%CL{K>-G;Da(B>wL0*)h>?fzO?hk2QD# -z%S0gd`Vm%tsnbG6ytvSUV!Eebj%6FoINO*>!fGEbB0tL%lDg -zPLg-V95;N2!mE#bbBLh=K~Xcrwt3gaq&L!g(m$^9995*bK1}e -z#M)#Q;31Qve%}l0mb}gl!#?!wdX;P18fg2mtHAp8?8hKYye+CPRnvE^gRoA-a(P6i -zi0u|)hz&x!Sk~o3yWeiE&-i=&NU@O6r$3Z1u9W -zbhs`pWRLoWXC|k$n!R(3(nH1hB_RF%Q0X0TjdIWV1+MU8GDm={({J+t_1AdgT>*3b -z8lG-+?)$oJipNlnzh)58eNZ8;bzxv?C-O%FCw7$O+#&?NQ%OVG@xs{(-b1Xd@Gb&~O-^p|TSx(u!khLI=Od?e80Qlv=I74ow^j_@8UG~jJ1X+c%0+C!%^&nn -zt(;~U6vp3o7DzTxM&`F$ex1>qCO39D-%bcJL21{$GqZVR$6r1|s#V|EBdMcHi=R)6 -zKhjN6e&gBo0}!TLGIIeE2f#q}o$-{Wr6QDik}lX4;Ks`Shi+i`_k&$?+l(ynL$KPH -zJo|;f{#(I^AFdtlcH(8vaWmC64%H;o06W2EVd?sTx1acK)W`D?gX6`sg+ENg@~g=^ -z80Zc;GW|2!lehsZ;2`P0P4;YHpUi6qlPh+cRGu)r373+!YzeRoTA}SPg -z?ydiD2B*|(4k^!9-!bDmkutZBz2y(S)M)nn%y~uN4gsYrR!$S6xk4RsRF1h8PfO~| -z=+;TNcC+Qnj57r%e_|_UniP(C)hkXi!+5wXIU5tyMqR;BdaD_4DV|MX2$wX$Quwy! -zy4V=#8IWGZTA9S#KG8C?Y9*?;;`@!h#*Q0>+ -zeiI50fTWV5+FHC|cprb>9AisJmR8X=3)LX(aTU+Xey7&h#noEz&YRRkN`?|MAM=-I -zU#c&*sKk=eVe5H|pAJRICQdWxI?N{@#C#swWTB>+n>Yn -z{aGm*J}EX_FTG28U2-o^+>CB~zHxqMRfGhzB3GsG2dp!;Q2yWJYUR1f5(02%glyu|t -z#5b$a;b8u|v3xUamu~6gTc+$dJ7N!zSz%nwT+E@WAbRe^fQ~SRYZ*3<5mvL$V;l6z -zg=Cdhp&&ne)$MsF6R;?GS1Z@h+=lGOR=g}#nSH>uNBsfvCEdVy^w50`L^fslFyPDj -zfFa+3RMyTXS>*FAR7E5Hq~Z!bR-~u3cUQFbJgTbZE983>oj;UF9m=uF16gf0YycmN -z@zARSga(`yjss6Q3Fv^B6#k;2y?}MpkE#KZju)wrx+QAMl-?N0K%b?U6tL5>$I0T7 -zxOO7I^(CDJTiyT+)g5Y2FvL-`j%BClLKa@=VUCExyXebVK_E~4zBAF{qHI|Z;fv6@ -z{npJgPj!LSL#2AeaN%Xr25w4z;o~HGW@moK32E!kXO;Kxb{dX$AlV+#{QLoXZidsH7|6Li#A -zzVyYn>Sq2l;G7pK5*^77x13NQEaALHv!$WM2iS#bAU=p=S*FMGXu6x1uQDv4O(f=eXI`CZ)#cQG{Z8Hv_dW$vx$u}dUc7pg(j@#*1|QCDE~^l6WNm$Jp42#9(S^~Gqz-IK~S`fYkVMi^~& -z+iDr3X6!Uxas-^J+)vcaeA -z;JdfKH6aJ!Wo}c&7x|mgw#X+}3DT#PSYzM1bmqmkmuI`&h?f;gJTe@BJ0#F)&)bwf -zuDGYw=j-fL$5X?e+ZZp>QHtQ4(4KJ!NOQ$Q11I(p@VOlK_zIl+gEaG>31}^k9MfhM -zT8ftp$)(AAcJht5MBcMcx}vl3FPk@wSojX)E<`4=s(kJA-g{Ke=^0yWd{RMZq5U0^ -zu2-3EHUvu?1?QN%y=Z76F}6@>HsBHc-bzp3$!zLJT;u5AI>Ljo&-J60<|CxbXOF6j -zHkORH2IvK9`kn-$-P!_a6Xj|AqH0gp!B#Y%&b{8*tr<3lj=!S>TFmFPuFF5D7ytDv -zJ$Ck)>6^Q)xWFX<2-}7+HI(e=qMY@{%G1(hGUe|0DVWUmE9-WMqm7vP?-M<$b&fB# -z(VR7sI?kSUe`5xK{cC8L_)^<0$ybQ9i&yhh@?~xTbnOC)KwH)tvul%U(^;-uI22YN -zUVo>BcZ-FenR$E_)s#%?Xyp@f%~@=h*51~n75d~=%;dAK(Z(+D2CLi@2?ZH3YWdd+ -z24eW4gPnkr&~792FvEW=-1yx^t=)us*J@&qbzJuvl*{~>C1OV}^Ha@e*fmG+k~twB -z?{{Fwe2y6|Yq7+#BUX}dQ)TJkeHpJECu+T4lv`IyL=XTweqmzKFT>b)4Q&u!i6N`hN{+QbL@?z -zOi~jtmvT@3{Ant_Mqr(p_+Ei9bM^2xgGPPm-BG(i_`A2?5VB1 -z+8E~iii3(Ofnm+T{k>n#e#gBR& -zTsocy;7^#&W|#1jC9+HG$n2SzB8)GU0Ur`qaI`b~P%DtQom}(FVwziw=Z$P_q?5bp -zN%J|FEac|g!MU=8`A-j3c|9g6Q@^x#VV&?BOeJvy6$QWXPdI5|@BP+BGIxh^+L{+C -z(j#ddAk8dvD6LLh`fDP`Wh_~@jmK8E5Gff8Su7>P4Q>_05R1M -zarFR3AJ!ZDj4yRoateT~VaGc|K;EoA(vwMLC99hB^rW`xR{I;r6oM>nHH}hx_oXrmdIx3R*bG-#gj!sQew% -zSXRoLQ;)ZbmEemzoR5ui*eWfPLNQ&?gtf6GDJ+0|{8fA|@*vNB&}_ZX$z7EahBolV -z$U=*>xf@giVFl|)_+?h!+N^$+jrvNn#;51X4prO|CzJ}A2;s%%b23}43EOp!jVTA- -z64-3iGfBxiBX0s3o~46U53t;}o9Utoz}UV3mn%g1kS^mt`G2Yb0J(hJ^;#`YxBt*1 -zz2W;fihxlj1g0x&hdfE!Ow)J^Og?74d;EjtO@4T0BqLT{j)w^W=G7Q#e-T7f -z^5%I}jxHF0=s1KNOEn`Lb;2~Ke=rP7Rv;F{E%-us9)a?SibBoA%jm -zM+w#1r?La`X*25M=1S|+gX89In+Y&mUNkEQ`qlw%2;~#fIgt);886)CbK+d(0{jEEv{!}m{SsF(0Gn%Q!IWo+$0%@uqGY2p7%jc^g($GfsG670)$$fZ*8n3>I*A?5A -z2ORdRlV96HU4X)vEW_6MomT{kLvrXo`FFfr`-34dkOvX9)~*n>mfV<=b?$gyKfW?d -z5w|Y1#J5$M%Wu5?7n1I8k -z^sUv7Fo8$1neSV7het~LS}>0xKQijdLLe*Ab3k8PM<+|oZ;S!w_;Fk_C#&=aBiEu}#u -zb-hgsG|VK}0!u`)7AS=se9FFlNs`9z&V1a8a5L0LstEN#3KTb*W={mWQwNCQ6U5K~ -zn$y0(S~!L#%Y6t}0K5!@+LkE2B-xiTZ_hX&5OZ;F+4vU#!aM>OW{Un49qss4bo5-@ -zdJ))5<9EWuim&WG+nCIBAAqe&!>3TWSWt;d>rOiaLJV$_Ya*%u;E((J2gGuKe`;KR -zBxr`S*;&{4m7zuJ*XMb77u0cyLVtwoh4Ze;6@6Y|s0NcIFbFE5I)UAL4)|w!({h^Vc-cF-GiUKhF$_RwuBo`fSYC-xNIYueGT!~tCg7_E -zlo4hkG-VDnerkMCBgyq|?R?O{bKOTU&GoHPmsI7^UeJK@N8!b_G>mjgFcF{B>uF&I -zB49s<@GNiy2yqEY<1a`w5jY -zleBlKo5osOptWjWleYF-C=7C!o3DnV?I>D6FKXZT7ojqJLp;W;|3%8LLO>8Y$O~Ft -zLeh-YhP9mXKBs=Bz$tQK*c%3=7ePnC`IC_s0-+(+1b{X^U8}?&`KAEW`l|||;svx{ -zfXMTqK-)TMr~-6Rvm%DtT(hLMUUD$9e4C}-@eJpo@1 -zqE4(4g`q!akG*8n5`&@ha%iQdX@gOGUdiIx3GBnj3|rW!$6&bEt7{(y+wrR~^H|JA -zVjKn*^ZtA5txUje=icit4|pHqyzbKLiA;1W4z-5f!0d#(hk(9tXbe>uPPPZ>6yP3A -z0{^(V3Ax5{BMT@3?zHmOJKY*BXykT<)9;j;(Ufmny0*R}&IISP;0Itn;6CM9 -zWNiPz=V0_y85BM9VB(P+zR;j+0msi)-)!%;`{f8f3nP~;!UXp~dqx+4rwXF-U4U#C -z%RF&yM||P>fP0_7F#NT^?rniJw~Ob0P^6c$Jzvra?Qza#_`gjeTWa%Wc%*?&so!NfrOi*=SDiy!+TKYWA>);&(mYZ -zO^$@VOxKMZr!ELDTH=R(4aCAXbOVVzDwk^kG_*gJK|*b>K(6v#j9^EeuaGd?d_v>R -zHLP2S^)U7PGBYBIcVJ;KZMBNbydEHmF{_58;3f#SEM#M5Xn~%d2)pj>SAs9&Rbg0L -zR=k##zEF&Uxg!sKq`GWvow%Sfq8Z=R>+?JXpX03)xCq70`I^^v&dszRv4mt)OjIG9 -zho^^=lz~0p?33-EO?z)3*Rin01TXkb7kg8R=_vb0==b~3CU6#SI(IsQY~mH4LaL$8 -zDI$@{ko=Q2ca%ZS(+}cLi@f@ -z$Bf3%-e{P#5R*OrB{R`ul*({=vQJJlcNF+5oV%gtbyP7X%>~q!JTv?kXGT6kot&BP -zjl3OqI<~%QRaqWyxsB%s=AXMYt6N0zW3t9^a4>zXy_&FMm(2Rwq{+UW-;5c|#-{V- -zIe-!a0oUna5WnSOJPhZTtR`9dNj%I(%gg71?f{9$dv1h8t3CV)v(8x-3~Dhc-pYKDOH_^9+;5bz;iAm5ni_sosG9!fJhOJG0uLhSshICl2* -zF71BUHZPLb=T>D4@>al^R>0v6CY@%2R}7pCE=Fk>mn@C&dH!S=vJr)2J+r(}SOjzb -ziT=|L;f29 -zGx)crgD!aauGsu!MgQXFkdCTC;kfJqJqA-B1ta?pjz|uJGE}yap3Lk}%|rf{^pr2uLqkq9zREkeDf~v?7_Zms?aU -ztPtTsZGKi*_zvby;(Lw}>Ta-0HBgq9&Y@E22`C8h&?D8k01YR0_E+@>0hqU*-;A#~ -z7|hFqFwc&Yp(e?fbEgBP%lRsC>bc0(pdUk-AMKMXMJKWtgD2|)=SD6k%I^PJ^#-Ma -zcy2B|OFzivK|6DQ^s@j+%6dfRzdajK*zvK!dexl==V|4?2H=uK7alCqDjx|y5-Lhq -z7>UgTw;4QSomes7x;zy`=(D;Dejtqheps7n><65H5NLc8oVBxkm7D(r2*0nmNtG*f -z3g6}nu`x;tSG#FSlW%EISdXc{)EJ<@4_-t;pXcvF1wW+pj7q!Vq$uU|>*k&sb6ATW -zi-ZH>@2kcV4?4amSOf8%&K>{q;A#iCw5Oz#eP$1Wmtn6U8Oq3r+V)wE5NgBkp>mkK -zE;wh*L6cv7pF-xZP^JTX&wMetNnjrQMujDukr(;5OG|J^(9Tae@v#C9MJ4KusDk8WH#Au+?=`Ez` -zXHnr{T_Ctm424BW^z}z6ql#UKn7m<^+c9u*&AIqz$GR$V3n}sw;%0}#**=_)G^&E! -z{Ru+Vd8pFsg!+NOp)@J^joWtL@9TQ(SIb`sX~c}{&G3_ixiPyMo7U%$uZT$;g9GIx -z5ZA3I;X`G&bMjsD!R|+f<@ekoE-Ud2o^|qi;|+i4QZg+VV;RW`D7d@mt;qu%WBEDA -z(a|o{9O@QVql74_JrlR3DZD>*4R&o?jF@%8Nz=YP>%bnc(MDobfqfTI=y`MB7|h@% -z4LIloNHY*>Mw3*n23_G|x-9eya}%yWK=w6B-{nzxdnJDj)P}(V{7~3ZcAt;CT!AU; -zjYFz~;Bh2okRQSuOt(ZwGLj3!>MBhe%CvBTkC`6TM=GL -zQdN^x_pDRKZV7kI9(^}`I_FfDxW8a%`dOK|A2X#0h16;4mOp`H;Sbck9gS(_l_1~1oNs4 -zfBMQUMFU288$A7)`bfMg*9lq@k8Pk8P}>}VcwIzXx3Tx)`prU*t_SG#wm8@RUduTs -zG~@msgl1X^0ka>X&T#HO9)ZLJ_#7h(C0=)nZ2Yx7BM_Bx<*Bex2P7)-C1Tr^fUW)) -z;&#(=9SaE{>_D$Vhu$EZ*%YkR2c8Jy`O&mgOudlC52z&Wh8{kTSvZ_vxvcDX)gJI8 -znnP|8xeY|&&0OrlCiEJ+xr5cnn>?En~+X^2e -zI#JRoD1!J+s9UXva&m)ba$u%j6agEAilE@8VE)SDd`5P`JfZrpq&rO(ToD{_fr0u2 -z@}DPIt%p)&gDX5{y!_?aT2vLaj~VW^3hZ1^MYUeXpwl%B6e<$>m52W|9&{Vw1KIuu -zBB<>W=oe%=T3rdy(@ZkQu0G`NX*Iw=jpJ7AaefHcR|XOliyZ@H21G(Y+!_c;)5E3g -zj-$Um0q{ap*+CP`)jzm{#4Ru|ZZH(TZ7*XGJlXfBaV+pc%dlez2Uh>Gib4G~U4K4l -zf>+fENg%>9XGEbU7o9Fdpnt!WDym41+Ueg3=heyuRY~xhsO|2ZP~Z8BDE7|>GmwGj -zD8B7{?9Y=KAYL8mwi6c&BrQBNHyw=;+#-kn3&iTElF1Fq>CB2>QJb{rB-EDam3X3^cSRap9<7$J!h-BCk1&l!)Di&1h-U2IPJAi~`#{@>`RS=S*0dcS5M!;3FR&*YZg9Fn5JN6=?Gj^UL!HDB -zPsr6A8WoFl*7I;hfEPnOuLWKQK?N4Ve+%V(nUcPnSjqO+;^l%2=Bq3_NXmo`^u_O| -z&4FAuA?P=?F&zermqm;p1}i!ehCBQV`%W6hEEyuwGI|S@!Y=RjH0){$*y>;$$Nc+Q -zEol2==0`^gQE3`bO*Dkrp0R>;;j&$)ihF@|r0@%Li?GlfMcACnKpapW4P0Q|8!|g9 -zq7^(sAC9D)ejQiqnEveLELLJ=^1Ov8T^}ux4klVtzziEFG*w0$!A=994|s^uD(CxP -z^YK9~wX|H3QwGd+CG5$^oO_&p0eFm8X<*T!qm%oK3dD1Me~agc!1L;Taa%qA+_y;+ -zC@p%?8jDI|p~8^}-BHaTK+OXYqQL~3k1KZYK@h!xU!42u+EJy@_FMSL+KTmUBi_S6 -zNp=V{Gi(Fi7iXuSWFZC)&KO1+^_5-wPb08;fj^&qMZ$Z7x4tSD1$MyK>!QLdOxQo>banUs^Co}WWX3lifOyDCp)0GXg>m7! -zNfb3jFwy`*RpDOV(=ghZpBTXcRdXE04?mOAn!)JHm&-On-QBb3 -zk7O4Mhheb`-{cJs8iQDp>hKzWKiU}uWMIt{(YLGoevu_KjyENQ!_ldBzd!!7OGM#3 -zW&rT!)KBmyjl<8@z-#Ej`#4~R@;2Am(o$UG|1>aODI!gD)#Z5{U5 -zv(gbi!Jjl$Lh{hM+X|OIAuF{BZu<|e33@|Bx^?2M|HjQI1}osyleE5)gHU8b6Al%P -zj -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 -zq;nLIMzP{;hD6;ngo|i9|?F(WkUa}+^$7f=) -zsH&Zm*i)v>{Imy^V>}=0+;;awgk*Zw#X?57oH~7&t}~T#G92bsfxjdsd~|G0i!Ys|2=w>YkC#v_lekPsF#Y=3LpXgm--u=a1|1`uz9F)*x=;qc8nCZCZ67^StMUUI)C0xHCqU6U -z^tl&MKyVGNZg-m0R!62W7N*bkVjmz-htN#oUfLHhY6%d5hta)L+Qkud7CSpTijAge -zMp~s2$qyhrbB3_41jt*?b#cPzLczfg>Xv53cS$;4MI5&D03AB3yq!S>*oGtK -z@y^xz39KUu7gRptwA}|mn`F4nckal-%g(r)A9%ii+?yE?Tiv1V`a=R+i6A;G&F@JG -zz+KUQ#Y!t#fq5(m|0Sih_Hr{?gWdVma3lUVIK^e42p{wpWjNDqhpL1!HsiF{aJ*I* -zISess0T0a8UhTOW445K}-860E1Q<1QFlx;glMk*g13EeQTI0I<87>1D3$keB3N{bJ -zs)m5C1xAU3Fi(6iPu%ph?eMV|oFvcbckEbw0)SURD3LaS0J^SI(lWP28r6W4_fTsu -z+)Dy(8>SsgfhUkf=$T+b-g=rQfG+j)^UMWT@ftL-mrD9^biqWp!j_|StsFq23|%h; -zh=n9t($Z)k|7%s#|K;fzn4MpV%wL=45%L38KA?pG-l`@j*9Ax)v#ZG_Vk6qzGCsN- -z0M$ysY=;u1IKWb&iMx0#9a5pcuCS^(nbZy2t3kP5P88~(p&}pl0QKO%a5_pjjE=3q -z=#gAVvK?f2$-&G#>mc^)1L!s`j^;XB{Xnd75NtwP}LZ*H#DVLpFt-0 -zw=vibs%d3rjw2xYNXHBB%a-=&7Y6y;yuh>YA-tBG=oG*D8@>ei;N=JBuZ&t8@RNSE -zmW!)9V~K_ -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 -z@bwfo^K`M{`}(&Or6ixK`&8<%_@1bEH -zIR}SpgFm+xQ=!)AE1|E#Pf{Vf`5`?@|5_VaR%(yz(YZ6==iH1h>stmA{nd+!TRu(} -zIE&QZoIWAq@K1g?97vs|lQB5m*W2cU<~Vr$r6x84ngzIp?gu0Ce1WhNN{HQG$3iGO9! -zX3&4Zvf1vO8oUEmEUb$TuVO?tSU!uu6W;Ys=6XGq9q3F -za0a~6&-t(Y-fUz7iRcgP#rJ71&}n{o%@<)xlXOCFY?5Q_QIWTkG{zaaX89)@;y)Tv -zOfENvSw^f8=LMRItbs&5KP&-PnCwO+u&k5n18cBc#3V*I-uW0A)M+qT*Dg$z30c{I -zWhs>-DmHh4Y(8H^hc{^cUIyj~pVLpV`(GZ_?q+IPmzge&y*I!38P+XlBO@Pk<-P5m -zh1;I(!llxzwc+3r_5gqLde0)6FK?;?izRmQzy{7gBYn1%+NH@{9t7u}6KwAUrd}c~ -z-wSTo#hjSqcLj+uys1|`PC2Y{0Rr4hF35Ajm-%V@V+xU|EAm<$vfqQsJFgqG6qJ76 -zu}vqnosMrlVaQth5tL83UzUAmvKQ_)OV@e&2eoTqs`LO{U@hEY>jjrs7;a0nCkSTA -z(X_p{;C(05$7!*@kCX3pwATh*vGlnCzCBv0WnfP&g>A0y&YvoK89ZSN0~8NGa0>Q= -zulvx!5MH6}J%LMo4&Qq%lB$3BmnXzUB+*ivx_rI@S%Bt|FQlMv2Bio1a%=w&E?Aly -zpahkEkD&+kYY~gTu6zpvT_Y6w{hyF6NcWP86aiD+2jbkgJtsm_RDfAy2c_Y+A;z*> -zm>Ch3aM~zHbb*JY-8Qi_nL4dR!JR{g~<8x_?aUuWJt6 -zP{qzCq>n+lF5}b5_`<5qx7U$WE6_O+p$5J}*CjYw&cWL2yVAH8r%EHoof`05n~Nia -zmd}6%E{m_cZ?-y*tXKnm3KSk!A1(EOH~wdkWV@!xOy_p@B+iYc>h{L*1Z{kRVO_Sr~bJF34McdVSXpICk#u^BeEZ@&Eqk -zJnxNCErM=g*6rhkz-%%*8`oNOfX_1gx6ZcDR;<;E|*HwntDXLatd9-{eA!I99g#|@lJ&QPuS-blOCZJ1AHFs3)Q)-uQb^NfBG0G|&% -zeZaIDC~y%qA)t^+xc3u$Ie)n6bEEwnVgHCFxQGmJ;7{DUpkTfFF!Cf=54?{j)3Gm# -zkhRza{Vs`}-22>nOTR#IUY6EH@NixvP+L4Mr4Cn{uQf8bfG(w=H8cE*UpVWvz{LeukjcZ -z_|U<&nWR?)TVx0Ie0G5I-UdJFZ_}+KQgjH2&cFRQEjqE1NHE+{OS&wJh&ezpiWW -z=J{;WPeuMdKB-UPQzgyejWCD&VA^DuC>5)I5*J$cRa>vGi&E8wwZB7?8xDY9D;Ade -ze>?zIrc3n-91eb3smV^S;Xmbi0%MX)gRa*5dP8AlYr({7ww0ZP)trp>reH#g22HX$ -ztQNTT>dZ_Xgb!}Y%5I`}#KN_3IZ_2i6hFKlIYZ#%ii!b&0Ubw=aYD22A9_az@q5I^t=zV11X`P?G>w*2y152!= -zWC{o7Qw6>^A9|U0)mHxXfhsGEq)4><@XkTk;riZqTq*&6W+~&tUR}Z=lZnd)vfpEvD)X*^X=)+;m-=l-xRw8?);n!w*Lm-&)|EM^1uTLYd)8NkHKL6 -zwV&h2VBGQC|Nb-w>cnP9BA)3!^YOatWHZBx6DdUm97$kitV>)3G!iEi^@d^Tm;RTH -zn5fY_^5-EfY59-b;!pJNb7}1IGSRMGH -z4?o{aT&r^Bn=xf*p8$jQZ{xi>@OP1O)hk%!eQR|^YH@)l>}q0Yvlkv^9sF0}&`Y`1 -zyZ^FznBzxb6j`?>Jzm{Osx){QOp~?EI>=Na|6-m|{$FivJ~|hwi@XQ^|F~7D3K7Ih -z_FA3!aQOr1kZuDqTF~N=ZuS+^#!Uph<>WlYkqtRcjH4v-B0)L54W9XMD(he1vXPNyUY84;`=2k+&>@+$@1v%5y)$yYZEF}5zdS%7PY8n7 -z$4g_)>_fl6l@(99@3xGQ8}RHSHyFE!;}>+(&f63^PeJ!}_7v-sfGO>?5SKbVev~Hu -ze(r*FJA>YQFT+tEhBr)c`%f6MDV_C^1z}fOEHO2}7ab;5dm|`{agqewC*UPcJ{<#J -z7VYLWwnLE15!wR0cyskH6f_e#{4XFQ2gJs&o4f-NS6bKmsY$Iu2%3SOaNoqHx -zRJ~Zpw!~m;UV8S11FL6tz=Z$(;`39RZNIDa9lqRpzrU7TR;X!Z3a^>6G;{Y9WTvh_ -zd#5a>$EB?tjIuDC>uCJITbE$`99z8>-X33dUy7FxbJnZd)?Q%*0ub> -zx0JOI9?Z$CvJ$NO_sw}j^k-94Io_u9^@i}I=ycoO6C`tgA0^PUefdB?TbI4yDhn{- -zN{+7GGn^|QI=Zln&S@LmZpWSXx#Y}RV8kjFqVLM*%6HxzkK=vm49+5x!D9lR`V$W> -z?!o9<26#(dSRzwDby`ib)&b}-z4K-k0DUHC@)J`6T<=hTLdtYRl7&1@o_q`#f-vG} -z+ki;hJ`@j$1XlsHb!G1tI8^V26Sd{%?9J7|&HBFD#qvvCaubbi9Y`1@FUU)uVB(y# -zvH|^E4#z01e7OYV;84(wm3D)G%mxIfBFtPqCb9ChPeOBXYSd?gWq;aTP#-j0{)|j86hM_H-mGoelQpd!?tVFA -zwQh6$M+p#NgQ3rdyd>}BGBCAoJ8u|e37pkfv>045HU+r79>7D!;tAO#Q1x$_%?8@* -z={2e5#u}pAUyl7lfWtTYtdqI}V-DUrpZLAiKdmpEKkE=oO!{*J3|`*BO?l;2Y&{S@ -zjj!0$k3w6c7?0&)#=1j#N~F_4WXr&DFp;Po -zGU5CnlVPOO$FwdgaY18JEXJ&Pzm(}m$R^DCWPq;F(Q3f5hKd8%U7H9E%IjbCM=FRJ -z4&C1&tT;CrSqdk#b01?eU4^DYHM)iX5D)9lv~aJHL3Ld=FuIX$avNOb1+$`7P6hpg -zYvV^taq|lE9rTc!`nX)M(q>ZI<1OfA_wgL0Du!Tw{7gYQtKn+h>c>lPSO|rMEmsW~ -zk(9R%<;4{tk*}RQu|x5kR64ycQEkXbT$}fUA6(G6d}XHHdP7ndK!s0}4|8Jn09F&& -zr|82JEMa)j6EXvluF(?~L#b4|jn;JY+_UQHR^ArZL)qHliAIIxd3W5%Vf!UpkNzR3 -zx{yN5>>*&}hk2Meq}kU4oWo?N>WUp`+t|S)83<%wRfQ>&5*|> -zy0~p%;ze+rfE^euCXYEG*nzF#V*iMNUM-hYyq;Drbl{TXSX-`VIIbbQpssA)oDCjs -zHgX+Oeex0A82_(2&9hJ^!!=*eDvIv_KJlNM!?!pkl^8g5l?&&G7k; -z`q%o(irNgLdv8z+YK;K5e9^1|h>P`5JHo!n+HZ*q5$Z#ZY!5zG;|qt;lXtd}%ReX> -z%zL9wny!7nS{J!_O7#O+NCV9GAtt_FKnh(BjE8-{)@Cv)MVD1(7mv$09u;TLCVijR -zYqbdzKwG;uh6#mN6k3}JRgN0ba(&?AjiAEW_Xy`r*2n^t8B2LB!O@Ee#bzrZUb)Cv -zJuo~Rk~5CVt7a^ck_WzPt$>2QViBX&K9x%n#;I2sTSP`47|9SKYo1%`$&KMJLFvdX -z0CFDO!`WxDdo)^gg<%y~^o|^E^J#3Fm9Mu -z+~gO@C|%J4RJR3zs-fru6HINJrmA0`g+Yv=@#;fe;AVw;e{ocJc~gdO3|EwIIQeXM -zwuBhDOTO{9GzHx94ASdwo*0H)0bQTe(g~gizKly}MXZ62J^B3o^>!$!V94gLrjD~p -zcE%29>%CDf_=6k-SwRpxeRiO-E^SU`iPQ5s>`-%WWl-GlA17vrhy4@)MmTq+pVvVv -z_sa9_yVdTlfR9w#Fc@N7C&uL_n+xqZD!E{&%ENJLV>ULhF`Ik)mzxExhGub4HIQ6O -z`!K^Ia#ZVHlmy3J$mHb_00sAI{lsJE4dQ^AJDK}kv{co0j$qy{D~9{1)iOLWoIBXB -zt>NSnmIBydb4T+|22+kQ9B!zb>$1gD3RM=* -z%zEidHSmN0e!YxCyslUG$~A8u5-!B6bxUp_uv+`V@Us`Y4mZ{c*qvGwHA_VBPrr@0 -zyKS_%-rgaNU6;+RbCoP*!8Y#3dwmA&AwXM`Yr@^15;rH6 -zl^eWnlx`tNYFqbVn~Ay;-@1P*Q$Hv6HeyT|b8zN{rukrS_Sfl(v$mhR%K%An7#TfF -zN2G#{)QT%U)7gyWc8|SZ~RWkua}y+0RpH=8|rLlsb!!F8B{T4NoB9LDawX1zZTrPUah_)N;)B -z7OlfPhl?ynmU!w@sLSe&wQCca(g@%w#~j1XRkzb7-iwyQ+dabJS7gM!3+spMEaPhn1-6&N -z72`VCnDY#+c21>u+o+U+*YzPY8u#L9!{2Z*bodkFI3;xAjfG%W>Ykvzh_4epQ2XuT*8!XsOf_JCk; -zrx&e3rw)&TmrqdMD-U6Lv%u5zFrTNE*GkB*WkT%J7eBEQ($;4g+VTQWbBN5UwhlU2 -zz$-eNTOYxp3x_NrR#T3oZPrykuR(i0!%w^UK;cYe0Cq>3FE;)H#FF;zwFt`A)z{8X -zcKNkf4-mU-CA0!N?#h$eWMy;W=fzvEt8e29ZM`?9YrMkus0=0*DxPeJ3I7(2lu-HW^(kdLvvGm+l5LMDSM7ZeYI=7>S;`eaYa` -zo4YHU3rxtdHaW_K7S|-W@abcK(&ch8j@c(kvq%~XAZWA7u! -zasFSis6$WpU`^7WELjBXSx)YC}q~tSbMLdsC>rRTF;C8b38JVUV=ln -z+v>S467#BIGZ5MpXIPt29I0|cIAIQi0mHJ@d*Z^|mF~PfQ=2R#A9aUMUN)(-=6lto -z?bpEqQJwyw0)LYPFS9-uL6SD#EVRO8D~mh|j2BtOUbeCL@CymWxx=aUYhRg%H7(Cr -zfaKdfr(z!KXTtKjq+t;|OpMtOnuIE0Yn^82W9=1&IGsW{?bt*`T@8s7nHn1MV;Rse -z>)@DcGNHk;dp=*>ug1qcNmxGWA-`R^fBM;4hVJ0y1i?VieMlNGkH!}V9|cEAuoev0 -zHNH^k`Kr8GbaK97@-K*#3bWF(<5Z+ztZ$!G4qP&>9mnv+XaRZy*q!%D2$|@J8i3G5 -zCz`;DAR$1HE%qAG0V*9jq!YrrSdG|8}k$_ -zJsBdXJkjp2eK{fM2^zTGw~!@9*+Mo{~G4%$6zR)8fWB^G0k&oPbiI!baW(LTwD -z)q29}c4+t95)>gvPNaG=2##3@p45HBH=iW=1pCHQPpJP1;O^EP&8qi%h!h$OiNWoM -zWOqNYWUy*WSLd7kp~WO=)q3B)_Gj$l9GIc{@D7D8zF=Ceh(n3qI;!S~g<){(3|YEv -zGbDT!P%o=lkq*PX`IHuIk39G0TAS%mNQ_z^YdNYjXf|q@TyCyi%Vi#aM@vkSuT>X| -zYmAkWNW_V5g*gSnexXvzt!#Pjz0~%O!mTE~jJbvVfon@*lab!q>}JhY&dS(mcm0VH -z&@>G;vGV2pzP*ukQvnWZcKP#{m7J588Hyzqcdw?-Uy|!iN@4ZcRjMa=J5zEE0?*Y{ -z#aI>I9ZBA;UObr|)gWKhv>sM6mR&vbqRI+*O(V>X`f&@VD6e!?@!e$m-njp6*Fcyv -z11Um0@4!)}JO!#OsKHDP@4hB1=8-l2L4Xu-$sut5#I|mR#Umpla$37QYKApVUK)Si -zE>dvSVg#1Y;g;Sk-j@_`_R~0h;iFPgap7TkO|hUDfYGN@Ps{U1O605 -zA#`b -z&+}MlA^L#I8u|!>C;&B-9r!(qJ?9Qq9_M4u#aFVY1yQJsa@B1*5f6@1o;Vb*v+MBK -z&Hb{UuP@KLj0&%#s#^oHPKg#4gFNx8*u&9=FW_;S9bD?e_Y0~GUyXKXcLm;O*IvwP -zuB_5}QE!;DfG^f!Bv{*o95}IfFl(=F2rp9ro|~6DnXV?oHT?PUJ;|$AX@Yf0JdlTT)oZr%$ME~O?mNq4*k -zjsPu=J2Cra+_dKw;3OKkxe2+_=+)4L=*#GQV0TratRgu@yCOMou0Rl9Y}qm&TkG5F -zAtr?3EOGSdeMKOe)H*4Dy`OmRa_~;cbjKatW%cvdsV4dbNNr^?o(&3Af@P>}SWAil -zO~>PFO&98KQI<+eVc1GG-TuzHt8k-$Fq`4yuA5N>@ -z9UJ$l^9m(llU|Av*wl64NNdXAJ;}hIR)%*ymG&Y@&@UJ=J5<{jS^CHERQw+X`GSl& -zRGyF^m15VlcO%9&5pMmm3+!~7k^`kUdC7M4I6w=71>`hfJy^m07Py?1 -z|9-;nhn<#z4|q#l`v+@vl7m&0OqI3+5J(reL*e!;_N0IQdFu^>-ow1Oj`Y3O2cqDo -zA)tEfMT(R9&t|;rDNZL}1xDczm}$7lfz3^YppgQieX<#!0x(b|ZkESi0P497G@YVG -zpsgts1G`kcrPXAitxWLu0|bUVi+lQ=F<^le$pLeT+1M;A{;BCt=1&JVH!ZB?^&?*%f=~KZvGG+ua1R9do?EFa -zM5swNcgZh-E@~l=+Y~DR{h2iR)rI0f*0J+t_Yd;A&}w&91v;EotT;_FXEv`Hodn7t -z4Jd1mJXbb>olzY0_MS|m9(?YBs~>FODA`d>SElvs)2BcM_tbXC=%o6xeLVM$a;}`e -z`wOt|WeR|{IlI&V`PnL(IlmDAEB7&;yKHc7+2lj({(zaA%d6u+z{>iX)7Iy5^2x?R -zTVHSNT`3ewK<3K9aV!aPK#3G62Q({^D0n9CR;~Z)5$6M+ws>9}lruY<9~$J;LpAlB -zvZ*HGdwt&I{tZD&SX_W4zjw5hwK-ze3_TlMzc>{LR0=BhT9U(Ow?1^J-Y7n6?%tk# -zLJY{S`+?eK(r{YPS|_J41*fhZPg((j(%SFc3F8zg@w*UO8sE=|07(zDC -z`6Fl|_|$K_|9?x6Ua_oOZo-gRHzpic6YTJe9^8%4KOZ=YH~e?}?-&CWbDL{VvULCn -zNzw;H^`|)h5tXFzc>bLJ^p^@@IaLjV4 -z$Q)>;mm{Qf)RGvwGKMCRG=rVI^_i@(H?-zehe>IufFda -z&fd!+E55tf -zR#(#;v%*)C3qG7W($9mNd9m9jUY+4<)&6zVd)j03b(=&^$4>|gB7 -zB?Lh6ge^#vej@00*SUnCtd$094eBS&ElL<=qGJap8V2-Yv073B{;{ -z0G~~iYeYh=jHh4YeV}359x3} -zSWRiejZuB4606|QwonSauAUroNaEgs=%n0l_Kp=!DTCpkC=HQvTob{j0-++xZr`Dp+9R+&!m%DK -z_Tz%5x-sb*fJ7}5PS<94a&~6vb~gd(_}C?7TP+!~UO7x2&k*A7wD?HsXDMr4Toj$8 -zHL$pN4y1T4Gm!QiUfkVnv3NP*22p24D1~IcrrSoaCQN-TsH>@J$MNo)YDMY8TJgqe -z-z&L;2a|d!>K?^)DbV}0^2uaHR_iwYcws2oUY6mKG4L^UL+VopdU95?`D| -zLGl8Y4`7zovrdan_~Oo(>q|fc5rVY2vD)ZP6bV35`4JV6Wt`N(s;3N8AI>%@bKS&{ -z1dn$xcHnRGKa-XTV2|S@GPdN$Gu?W(y#6DQee)q6w+_iemR!L!g0y7Fvf*vFXV6Wg -zK<5||SvPPwu-KV04sByk`+&w&j+z?T0SC*3yKNK1wR=ek${o=G%fmwU@eU8>H?6`= -z)$gN93kg-x^#C<$ZtFAL7EuzMC-?ja)v+c| -z@!S2guqM;Lj|xv-Ev{MoR(0@RNdm60u>g;=_vb33xiF&_eTXxl_1)-%qS@2xiWl!E -z#-e|+2sosittIH2QGK=FXGQ(FiT=JwxBC5~OatM8L;k7Ut3fFuJ_h%VgrAB$0g75x--TQUrOD_P8SE*!_^l<0N7&u}Xt@Y5ZD1;7cnA -zkBPa@Cd~6o8I{}VcVwNbrcr4>mRH~=bnXGJP>q~?Z=3I>|_1GmKNjB>ValOV+yieR$B8#EwE{>x*GLr3kcG?fgM6o;Tga~=k -zI^Zcambsjqz*!kygUBHIhK$V0`7C_)m?L&Dfl_N+;@-i^_LP}R$Mq5f+q>gB!ubi+ -zMk7ic*`CiDGpG9B#2v&NP3swAncUm-gcT-aGozP7mDf0vkBE7X3rpNMOi1b`h=u70 -z-8|=_gy2Dm9{ySG2B2Jb2X08UeMehk{L%h;;sr@6=Y{vX3k(Rkn-DN3iTHQaAaMFL -zDg~FY3cGly>_=SR$@6c8tmEs0(*Ub*<#m7?w)KcGQnl?$O&!Hb7E36sJ^hn{6&&J#{p-JYU{`w4A#w5?l#$d-i -z6OqZTW?5t6=M|<(-wP&330e!&&P~t}8Q~yQJN#a`nCa#FQ2M72ZgxbtRPw&M$_lwF&3KOLAeC%PnB0?{CL`$@2vRXno>f -zo!cDA*70n)!=#sfPBxSDp0&(yPR2g2K1E7;vgYE~{iNmq&K(SQTYU4eaj%I18xXysb{%j_FmLOPVlfOAwW5@th>!ak)5&?udHBetc1gzs#IW -zdq6AUL79^Lv&7<$>qTu<8i#iGk7Oiunseu5&5`gSIG!R=LPY{bYAW9Q#6V*hH%Ga)G_T-g`>YNLu -zb`Zm*Wp(L`RPXbsuaUbMTVT$7BB@sJ@IWSR45uNPzRabVl-NNRj=0?-T9T1xRIk+w -zJi>Jv&L!3g21PyRd&YD!YKr$6lb}v7&zPCutZt69u-5}M4_gBT_9D(lsqgM6?#}+^`4d!b%S~kf%hP6txevNrVmvgIA&GDAryV1J!0?B=bq7hfk)& -zdGo>*t)~okr8bj_GgPS>Tt;2<*Had+mO`b3C2jaXWU~%kn3xrZ)?B%~19prsNL6+u -ze!g1#ct^N%W$$ZmivjitPA_92%wI2w-y91KYUO3RKB{9G+xk51L)-1kxXgXcZa4y_ -zQHEy~D;H}|Vhw-_QQK-~tUK2i-(8%^mHgdh!{5l8Ft@P2y8s|f7}CqT;$a`GUH6Dk -zL~AtTF9P--;pny@QOorluQ -zcVNd|?Yy%qesUAfJp(wIBC*130n0-YH={gW5l6YayrE=%R@YpzJZ?L5lGW(YxaVQa -z)gPc`xDiggtKYbtS3f=m|Hz&immF0$+)R2%)Ewn8)5o*&x#IoibA7KuX6js5lA83q -zy`pV2;VHI-GnFVJ`Zo2jeIV0eJA?w;jf+~(If6Wg5teNrjt+Y(;W*Kfn648&Wlkug -zxtQ-R=?2lrOM$RBCczysQ4VbvJTHo*6=o!z?6)LbZcXJ)j8Dk8r0MC&I(vsj++$j7 -zUZ&lrEG!*z(j5mS8rNt=vF;`bLt8%I0TOkhXL^`8qhv>TZ+*P-L6qai9;xS(rTd9h -z*r}-tjqEoL_8)~Zn+obZuu2OlR2Riulif6%)tKDwpOKj>f3u-lWq9nVX)PV2_QXZd -znWGz1wvT76|i< -zz=@B2X;0d4qf}DnC$DZrI|m`Uq?(Q@u3G#)F??}U+IoFi%Y<~flQ -zVPy|0l;2VT13!5^!Ct>iqf&Y4WcQ|F_o=R&Tp_Z8@a>qOl3j$gqBep?1^?>JvP=EX -zstX!)qnxtp5m6FsmJ4hZR2gH;UZ%n`gON+bd=4fnd6Nd*`%d6(6Bm~7`>b~ -z?=09&#h4n`Wb!U-xAmJcCE~8UpL`N(FH88Gax`OOmc}v+)Yv>3Dc@$oHu6d2US0-{ -zcLRTF9M?VEQDNwTf!uPG#3S5T -z^Ds&1iRc1@jT$bkrv2o+Y-a8|E>}z%i%)0Y1ZC|B|vA?^u7ooihex)p?b;<35J- -zc5fd(EnqFI$e6EiU3biWR~Ds$>N~IMfp})7*IK6c#e32^&W=jX7HQNb3* -zR;Mn*_4a#UD$@30sCyz0H~rp+zJ@w-V7hr6au-D~1tp;mV^mJA7^QKv{}5Vf2TO_> -z+_|<-pleN3CJ(tW5ZaeWs=6u@n1?G2N$%6WLxNDFGxm6v24uE=QPoca8U1bWe=Bl; -z;}82Rkt?q)iMO+bnnnE)oyrx?<@gn>_(V9^Tx6Rxu1K6KKX`BS5bw(&cnj9rZ5g#dmIf|~ -zjFb=t>I*!9)07^29#IWK^_X`u_23kqsMT{&j}N|xvC=^xMklgQ?y`;22~6Z7Ds8@ukd -zh2@@3-{zh%nnLj|*&c@4csIGOcew?UW9Alj -z1+6VOwWp6ZE41}hpB~qBZBsgPBJ)Q~y5Nn;&RYq+c(;j%*RMF?8QDR7QADDPN*3zO -z4sX}4_i9wN`MP%HcFugO9(-bEk_qCuZlT`4A$pZe%EvjlLMg6i2*F4E8I}L2Z8-=7 -zcJ}$4;eP=7aF2%=2p;@>j}VTweLyAb`**YXoL9YAR=-F17AjH6UK{1#`?c=vu?viB -zSht$rT-iwv3gVhMB%>OjzYZG|!xpkFz5dRQKl4oU9SJIX;H+XH9}r^&_m6=i%rPq~ -z(cka+_se;sIF`F(*VVHa(#lQtH4Yqr7efeWc5c+_`-5G(&FGrvtQseRqU%C&j_f8- -zeI3a$d!1a$kWpvKHHZMOw;Q!3(U%CcZia%(U&X# -z*Uu)xZk*0^_BF=sG48?&xz~~FSK^Cnf|KTVOr*OOeX2V5`o|gV+9Up__n~oCISQjE -zwjaQyL}V9&@4L>DiE5t*(azH9H$XTUxF>b#9h#17q5n~9@u~9eRYX`ny&KiHfT$lc -zACP-`0pB`ClU^AK@RTqMFfE6m>V+B&egoQEpoitFvwKo8M_NNxUuSO|aJYTpNeMtS -zA&7d3TX+w^AzPQ_z?7Aj-&>40yH&LrFA0ZvOTlH5m1!3J^T5henPpF~`0= -zoXKHdf9Sf6*oT<6rTYoSs6?^^X0v4@@h8Bq&*~9-vX}%-(^!FYS{w}R`6Ljjv@{zy -z2pCI*<(-03N&}CzCPMrw^r5mD0;Di@ek=f`v}NZzq#P66fD`B|5=wm=`X)9_X?w(XVbH<3Cgj!iOOJNx%6aKX7xSl)c##C}g)F8zNf(1XNxFqX;JU -z6@XCLN%ZhLYwbbaw@-CncyePs->ph8l?w+Um_1fY+Z -zrQT5qo)B@|c$yHEeIWoF|A^kDCTjXXE9tQ -z>{c)h2(^d;NHfgFA_TX^pZuzATL9_}*Z@inj_-4zotnR;U6ePFNMlz$HrJP8`w+T6 -zYZV;rtpE()4#O-GVyjNh1GjfCC?b290zG2**n6-WhxZ@M7R_hL{`#rF6@ak*dQf*} -zZg~Q{i{^>0Eug=M7&YF_cPjnB(s6S;pjXsag$z~CcOC9yT}4Dx ZstbbUf^A>+t -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 -zdUj%d`2}>w)seo8gNE=CTiK-^y6VI>&E -z0IU2$yvjn4cqeX}_vI(BwJ&loZQcE(lQ0csU={MhpSqwxgNWJQjq7xGV*yFbt#x&l -z;_-*H;==lm9@=-1g(>p(Wn}}t?`WBsME)KrA{N$v#e#kB0+TpGQ*;h}@k0cl?lHCc -zV}HNowL<=%ef)n&(4q1B=RDj2LFcy{0+1~9M!|6=TxLZo -zezQ6%+kRW{-#f{dvuKBC%3QrI_(yK|*9`o-5Ed5DDfs;_1uL}3odV%ki?O9rZisWf -z7~xo0W>DL!{jwWAb}w>rvbOFpT8~k1f1A9=8GiFG9l@43I$oaKB4txP{FnvCygj&M`@dEinzBER@<0Cc -z1+(e2GeXNooc_P*Mb%yYW%AVjR*SOM*t!)gN~kEpZ53b-T3`74_ILQnQE~@KaxKDW -zW#I<8;psKH=b;|N=I57J5a;TB8I$*Wrhq)Agis7@KB?i)VY^|3W~nrwnoqZ~I4uk! -zp75dqs{eM9@xS!hq~o9eR>rG;lYi#6>>l?&sdIpj{t-zkRM^{s>P#;|)4kvqH+P3Z -zOOMMP8JSML5MG&t!t(f^RUKc>!lGuaBPahodw*+q{=X@4Xx(`QzmMi~+x#^oL5pZP -zY#x$tSVU*xVxQh)hsRtC$=9pFv0Va#3E^Jc1*Ux)cEcwpIWj?4^5q=dL6rRj|L-?& -zItDZPTJ`g`KZ=v@H*CjTKZL%c)V{NTVg}`qE*PvYtZ=taLsRF_q#41(wn)>zr~7m` -zEGat9K(Q^ofpKBBPPXW|@_yQx4R|(U*hhp_qyJR}`4LVu_Qc)rRHfn10$Vl!hg(F? -zYkTE2m5BtO!VNIWj{hqF{ly3F8e>gH_S^9O7QRy{hM8X!`aUqI`v=kk55|oCw|~j{ -z@w{|`-~ZBn-Fh*UZF&+Im1*OLb_Np#*G%{T;~N1GzWrO@^WU`|r~KegkPF -z$ncf((zZXswHE?Qf9tuvT!A4kWLI^Bq5HWHKK#d}MKLsiLdZ9v;+j9!0N&O-JWe@f -zOB-PRV5~!|2I>72g%6UbMLKxuYF6lf<$!OjqCrD9dK2+{JT4vhZ4bskzPK>pRhl6VM~!4Cl7rsT?$IbO>CBLKNw`U-v9qIu$w|eTwaMh@g$KGG0 -z>dQUemNLLKLP4)fbfe&XfNR=ksH0>7&PJ%X$8e+BEdX<;8T-gR#S8Z@4uHhC(PDDb -z+yWHLM!Qad0}Gq~%TdP;^OoPW@RMQn8~=^&O_DNFbD}_1nSHTkQ`};FOFcgsQbLl(kL3&>6vXyN*|L_tl^7ZM>GrTwP8lb -zZy_>zN1SskN^zZp*h8;jE2cp{9+4?fg=dis*9#=h6@o+zaPahtKWC6?^6%yPdp-Q4 -zlKICnRrDzjw3{@t^(Xx;?ZwM(FHhRi_L@Bs)`QwCAY}j>D4mtq5ij{r@I#y{V#h$E -z`2qo@WT-VM0e);#0CZ%3@osloCbZDCz{g;>C6G=Y`3fB%ZkA0c}9N9R;8KUE&zgNq`lmLGTu-S%&tbN0^91mu?8is -zl>V!Wdr=LnE!2|Ox3-DCtyqE{d5mn(Fnp%a-T#s8GVv4lNRqD7ExMlg()-C>r}_D! -zvI{IF&&C2VNxf=1 -zt$+hE+M(kJOYKZBrm?-Jq6qxJ^^P$mkOa5xFw{8zz*yj9E1f@tL_0i$+}23I$zj!L -z0%PSpnuF4_H~B-9L` -z3pfViNVxuk3AeRQ5^F7V!RB&YWLZs+`I-3x_uvDeA!?zdVP9nRcc9IP1ME(|NIfYR -zEj%5LMB+RKs-;VSDc$mp@IWZK#wWV8yZ=M1(uxy~^cnU1@cRO&;67A?h`YNjK>XC+ -zCsTJJ=T@%eaI`N)ZLB(3P@G=N6GV3t428Bssz%5AQs -z81Tz-N>ID#98*rO!~mx-dBW6}RU}}xCWZYBv?${tVJz>d0nXhtkbUb{C(D83bR}I$s*3gB`6b?RVG;L$Y}55&O(L5ra}i_ -z^qqrnwbRzwj;9Buxzv|HviQwHJda%bDeR*~(7PaLG#aePCLMFo>jIfM4!Clit`VDIWqMMaUp^{?(b4oiVSnK6n_t@e|DP`hPN!387FT^aL1r+M1x7$ -zp(9lcI;`Sn4NovYGhHXViUW0l;Qe#@W5RZPstKu5eBT!u*f+%6+(tR~ib;Z}6@M7V;ecVKkC~)4Q?rHwx#ksM@wF_RQg*iN -zh+e-2`%q&F&jzT)HD(7wNvL8%@%qX_q-(NFbAW2~JlFQ_C`48&@>`U~Y`ebU?5R2Yay~X%+$39t(YUmmrfA^n|{m)){dOw5!*G@l1%*4Co%Vdx-zAIsI3$y$y -zU!Uicmo5)!z#SS2e1LiR%OQ5o?3-DHI+USg(u?FCH -z*;kUAiE&NfskTl6{c&?2E0KHDW?`cm3fzOTn)LBHw*h9yKi7J7f)ue$jUusBJ4ad~ -zVIBCEQ_RduoFC!_$0*F1?Q-0vffRmF{Q@)NaIyFfM!{xgZL&&P)q}glId|M{?te%; -z-OH6w`wWT#ZeowT-m|JcPWUurY8sN5`8ICE1Jqaa=c+e+Z<4-;%D -z_sSWav9d^LY$7JGoN{^dfX@HN-kZl$*>?TI5hX)WR7j~VC1a5aiA>3maoZVpLS!sL -z*v1kS4T{WDW!T$PZ1WT)Qz*7sl6j0|Ed17ihU@-a_x(J-_j%tx-p~8~ak+M!`#g{H -zIM!OnI@bEG?=pO#z&iZ6s$cZg3A(o>Y*Obl#?2)jrs>QwOVOL`^0yBFv<9TfVB1@QVN -zx4ZAa{O9)7!1D7=oIAOl?vxPCfHSR9S1$8;aIH9(@#aH5ulMtAyn0vzvWBDI*&lnyxqTJ;O^k?`BFE#$#$`nHsf?23ZyO2CJW%-YsHUX!mfF5Y*@1YK -z=x+M+LTnG4Ox9~;J+UeR`~8Bb_fqaqno>MBYcS9#I5BIRFvpiK58P#Sk7Bzj_4Hn^Sjzx(^Sm -zA@^og7(@fUrTm3!O{=JD1X8d47v&2ssBw4XZl0_&gIWyeS<_la^M$#()Zb(wj!&d6 -zzBI7gB<^b*&B(o`ZP0f?^YWe}S+L_2@-}22PVHE3)Km=!EQkt7wyw1oz@KZ%@c88< -z#Vl@8yG?al7+Mz0L?;VBmSSi8)Hk(un9F=+6(OAh-YIG8w<$Qel)+Jm{ay<@=)Sdd -z%<(?nt%3C9#y~vRBO3q+-}b0+j;Gn(yNxoa=8Vaf0rx<;%Fwuo2Hn)X-ssvzhw)Ew@ql9^K!tJ@+<^k7ev(uBS}%0TI2?L$ilk!zWZ3<`uUeGV`?C -zEXbN14GAgb?1R-oxd-O&%yI2MZkiE)w^CO28`o(C`fk-=OQZemPn4^r=NBNV}pDH5*kl=bGYU;=BB>(`~LN_j70cXQq<8g -zhPxcl;)Gjbj5!Q -zMFwhSqqqU3!}(@)QW0y{_I(u(@_iU?do%Z)d|xUw?I-zqC#CP`>(AOJj#a(Z_!uOV -zYZAE6Ly2VfRV4CsrKD(kW{x@`H=k$o;~RYu9T1xg>MM0l`4&(Cs63K!JlDwzeZF0S -zTki-smL1C`Jt}T5bOc}2qWKScC -zg^+1wrPi?5)U6TaKC*Iy@$y;xLL7&bcx&-}bvaQ>57YcvH=wL}{JiiM#pz(ncMsB% -zmHrYlRtnhqVf3kPuO-RU_t4csAToX;t7#Je$^B%(zm5z{$KQ%E6$Ec--fHrcqG?ux -z^9P?BUEmUyA?6X`+L2Zu{dsm7x9XvI)uMA^O^*0T6C~%XJ6l99_98MZ5=t4Q!dyGN -zxoigF&%Uh!?8CFG{ey_6G~~2`=;s@o#khlO`yy%)0mG%(-42cFlk}LXz(D@D&ap{W -zmVjQowVT!6gsicZ^+g77sg+y3M+Yt1vkP+loC^(Q~}vlToY; -zrRO{s)NCS^v-_6^a7r|dOz!+}gvajKU -z?e-ys4MV8Y5rWRkP0EX6`;URoDA!sMN_;FTue@C`H?xLGhemEZNUrFssG{t)Ew6XK -z*l)bE@^*!(|Jh4B=yXphH3?oFh*0X6r@y`JL%9F>o1!D0^FjiO@8YkjzYHDK`}}r5 -zH2RUh>_RM*^S<82 -z;XVHPFDrDg{=OeROn~1~8thOWRR9O&o+XpwucmugOOhJxoVXrU?lg;R-iJecAlHk8 -zfsO6$T~-tMjNR3h&3xbe=G99@BRA@Yq`Y%3AGKB7=X9{$^{b`KeA_k)_n)Z@ueB)o -zf0f!pD|yZk=%**~SfP=MI9Yej?V3*`4u8F=@j^kiHuG7p`Br^E-bD{2tD0~R9Qt6! -zqk@=LHYyTM0h$@SlD_BkLK?!RWtUjlDT4*Y84HsN#twvA0nbslKAeq@z25hgnN?wi -z@9UkH2{Z~OTLYOU{YK+Fswnqwc^K@c{p_f8D4&k(k%64Rvv;^Z^I=|IXR3OfBUipJ -zhROJ>#&!WGlS8HB5QPtrwk+|aP`7)+D_&zJYRqxE%FRgLk!SV*UC@#rlMq|19$|0l -z!|umH=Z?tk-k-z0m;TIawimI_yQch_5X-lsoyl%{zLN==kMVs|6Q6|rw_J|U1unz# -ziqPziC)yo?_Ro8d9x;h}oZRnXA|^;%AGQ~lD`*<~um$*kia%FVM@-+%Oa^KK`PS%f -zbuMSdHOfQn%`52>xaM{59K3o_wQ(O#$LTSqpWdX2C$tWBFhf44$X?#>Q6Kt8H|hqv -zeOpd<61<>|nO=TdtY)~Cg;|!Lh3}2*rpHIi=r}KHGg}0N#d|y3Dwb5}ZTiT-&lxZo -z?A>q5N3C+m_Jn2}E7y+GDzZ{aK9k-%XqY0GzhwMKo2==M5dB^w`aQzUIc=bu$!w<8 -zHKkeC^-@UPd}>M$bIeFl!<^R8aQH{Y*pwhlXv*=Uk29Exf;Au6@AX_qNyGWz97SpK -zwOa9~_tMsNTjB=<{G+xN-HNReWs-_Xro5SE+Sh9WS6%Q*FKwoJbR_cubsKH{;`1qL -z$J6=o{Jt!VM2leUQxradswXFJ(w%tv0X2EuQNECpcz3Az_3dp2riI=XUCf7}Ragi0 -zNT1Ekw7%_qcp6Uh@Mo%?6C!T*wSGPPQgduy=6RjIY{FIZSIsj@#f+k7AMCpc&sKvG -z-aKdJ@5`b+M;N|{*)TGnO=!_Cxc-7@^^Bw{w;G)mYncC#PkqUp#7pq#qcuk%Ed* -zYMs|NTE?Pyr*T&2lhic09#LDP>97eAKAqaT>nKgPapL*exeiLN^SVjLSabmIZG562 -zy%dX)1bdSuAluMXb1>qHG`&MygQA$tI8V~i~{V>F{p@DxMl}+BTf|g -zSq0iYQ%Fy4yOYS9N}DTGnK)$Ep)qiNg|~kgy-&TKtr$I$^|Gn+98JU7}t@$&Eyx`9Apcex8|7F~ppbUS3FFooY_CQ*ilL -z9lHZmJbR_)bT{?Q>EDLEN>&im>KBpEU#zDc~;)T4_A -zDzOBD7j%}){>TgcXG{VD0-Bzl*)r0aVOBjbP#0|rfv5xJYY+02%BP~iwZAF9%a|Aj -zHzp76cQz_*Ryao&3A3i`#ibOSWBmo1PudvdU87i;a?blD(0axS0`}v%0Y_KX;6AmT -zPZF&*(7(+cD{HKc6wk&RO5QjZapDCgr!Cl4M&^b}*wISpyIX#AMU>Ewv90w}L(}&D -z?i*tFdP;qx?ym_ohLoq!qMqk1m_N&vsr~(f^BCnRWsI+|$BWqGF`1Z9LXU7)L>$ii -zk~X1ygXg_{E9&vW2oI0g#XtF_8-F`K)9AQ -z>MtD0|m}g9Rt%&m;^#3 -z2(YM(8YX(7N8D+=LnA;jB+TQXh=@pQoZMY2u*}J_XzkDgV+<%=%EH~XjSoI>oSfW8 -z#Ut=(+4{|wbl!7&D(SWs$=m4;GE>r+(4scc&=116`iWrncxV2N8!IO1OpIQ(jW!l* -z2KU|xOIbz5#?G!M-%(%xY3US{sYTGb+#_^tKcNsp!2RfRnX#zyyUS+d=2l2UUEt@+Cx%2zk_6);V8iprp}~zW`Ek3~)G>Ha4j>)VYe!U{d!>NL<@YLG_o^ -zT$LCazxO7_q7C++xv4&LswsyM_+3$iHrNQ*&*~Q?aHa$vgQOT5Clswj!rO+mGFKg@qfk_qP~X0=a4>opb) -z^fda2%47AhGQ3_OHhE*7&^$gcgEHu_hzs4V2@w~2>HDiw>K6`o>A2*-6mI|z7~_qZ -zp4)`e*#ftB2E|@A9bSGQUh*dU2IYCEGFj5P@~|Gw(J@9A&?r#fPt0xMTSVP?ObDTl -z_sa7(nsAq>DfzV3;rj)`-5+1NQN*cs-gAPKt{!%e6R*->C9F+#kq1mp!|kW-G@NQz -z;Zb)U*}tONl(^La+RNL>jxlI1W@u;Ve7owCbn>{!X}b6lH5y!laI-)2ir{&rgE1%z -z>?@$T3GdE!&r-imagMDy^l2B(GX)re+@p7Du+#hnHx8V;ezlZJs7!0ofc)$4Zoez%yt*u?Nej120ltT5%SvGc{awD5Uo$gE~18wXkLnT5<=6W#t7~_YOYrLdgnQP_*##U=C^ID(=iCclXtQiWTni7Kj`cTgr1R@#)dZaL0JGX-fA1*P6v;-1 -zrJYD=<4u8`x5)cI1um}6#X2tym_H?!-f0>wZIFqVAJvQ5g3{EZ_b&LZqm-Yk+V@*k -z6sOc-qsW9t*P@h*Zoup*DhlR1QryWgt&)rnY0EHEZP>#`c^QVT@}2g=k5%LMSO7Mn -zyuD^pF@Z1(L5=s8NHn;ol4rg#dNtA|>yoN2)kVFtZ`UZ^x(@a@=|9aAW>D=^G&J5V -zFAsh{D4N)+;x)8g&+5g;w>Q9ke#Yu8egGSE!Ye-hfL^BgLx!D)V{&%b9sOw+CYGr) -z@A|t34-8xlrlOo=tY0LY8A55M-5l{xVqv;&RaIB6D#prOaG!&nkoJuIVjLGPgg`uc -z{P;jYNSZR2GQW+$3AR5C?5eJEqxm|2z*yUN1$|Gz)P4VwBaL&UtS4JDl`ynffwx3?+>)@nXL7}Ef;kgHgQRG~h -z<4J29ttObEiwgx|OC!rsE>`RQwu!9@@|yd@x|{MW`OwwC*|8b^vbD3(6h -zxgMUtc&7xgi`EbYZOV80a;&b{W!fSiP7Y(02ct6)68&+J*3>hX1H-Vzv?VKZ9JzQi -zNm?W$>GQSP2+rp$MX -zZC$l9GVtf6#0^v;#mImQz;^T6=W-*3yQ)11`&s7J^XV77fHvuEuprK+GvDdVn)e}< -zZPS^Ynp$gNVeyp>^(1x#!nXEm!D1_jdEG_j-7hArtgQINlsQgZi5c3ly;Y^SL@k7n -zj#1J3u~7V$^2Rw(+g|G7k&%?FtfsKAuzg)vv7fX2o_VLZWAgV||0RA|nRQ-+bTtv8 -z4Y%l7FTQ*C?)?`&33_DEuU!;?1$hpG4d$sBVUptHr?mD-ySqa$3&L_?rPwau^s|H@ -zLo=aNlUtcX#G=vV)nz>+Le@OTQS%4vbVe4K_{Vo)6v)%522XKi$rM?@x?vtP?^n|S -zdr;+aX3^s2|E|Hkw74#hWw-p^KqY0pts`GTts!Tdjv;D?F6miJ>CN>MILQOd@x#S! -zrOnwfMudB_uPl-l!7gxmN^ya$;*G5lS(OllEU;oP2cEp=LY}>|y>68sn2`59B-lHU -zyL#uaJrfn9f~S<@9DxUm$tMhp;9Q~clr_Z6biOpSg<(%$7A@0?RKg@#gnFp1laN9B -zrwiNhOU^YJjM0MoXiz&#PzJ%vr6fBwrpeE-O|!;%&~QritvY!$+@H#tp%~+>M6-zf -z-EMpW>Zxn}E=(j<2;)Uun$u1cBAL}i#Y@6NwzL>*mVt+C<%O-eCCT){s%B^@g9=}4 -z)Ti-!343a=&ygbs_u^G*w1gO`0(qtSEbRt@AHo*nOr7UAW&t(6ek!1T+G&OygT0VX -zN~AkCU87anC}pziCa4)RGc1F`Yl_udbE-*sP6TmzOBJB4K?G+s8$2OmWmJ2A9VHiN -ze6VuGCfH-RC~0wL{C65bZDJ;BM%>iVp?{?p;6nKv5LuzV-?I)avDYV1=~pQywyrI> -z9o*oh5ToA{Scigs-qDX+i*E7P%)<_A0@D__Nt6OkU9y~0p=s5@{3s$;YXe+FNUpxp -zLX)Fc5<7x#A>;7-w6HSmAHW9)YYH{%xC(O~FjC{wU|ZUJMPHPclIF2GOq72}1T9P< -z{{z;d1+P@S>{>v=8KNHC_S$8r2eKn@%p7d6}X9cEnT7X>dnu=9j=D)FWm8D?dBzNg3l8)4MTW{w$LBU?@zn9aE -zL3XkqYa^i^C;1jFaMoyt^0)m$lzCn-6@STqB(qLcaIeZ=BAmo5MW%C)QNDqx-fkP* -zi-lvLvm-=4eyy$)OFFIf<+XnzGy8|Dua*bjAG#IsQ$yFJdS$qO|8W@3eXv@8eD_~& -zwenB34e-W7oOZPTySe}S?U2lg{L>x&{=3JgVL9pjA}A@SN?HEJTyB}Y7=g|nelPwo -zm={md=G}vLe2t4KD*9+)X_=CgbZF^@5!KJ>VDW-hW4dr)6FGKFyKg}s{K7liLPYK3 -z8CZp0hlPjVb=`aBpax9x!-lFDMulKxLHZxP2itNTRAQtld~mo_|u&rcZ+*-fxz$r{2}zG}*Dgs<#_ -zzc7W;AR$OXe#_$4b*nS#6^Q(z1HDup7h;YL>M_0kGZa*VGTyOsF49QhyPIrv;(*o$d8oUuF(?;~Qb09H3NIGPZ3b0K{ -z39bsOUj_^)!t8!g0;s-7Kt_~D&A;_wudSz`(f|)NY=LXtnj-wNafyj=AGzH99!Gwj -zU(hO`s~ExfiiB)CgUp2ya)MS&7@B>BBKXeNfe@wxN^ptJl+LjEnvcrKh%{^PwLV<7 -z72Mdc2e(aj!mV849a~iQu3vHFq+*6)RWZ?&VxCg!@Ja3}*V2X3=GUr6*2{7(QSxdh -zr$axU -zYFKLt!dptI6wLPmYMj~t+R1}A71$vMqvEcmbz8Mlt8%p_+JCbMUM(Q#!-@gA3_vCbh7k?2hB<8=iAiqt_*&RPMJ_`7ezoTw{3s-^M)emhJJKA6?}IJsjN -zN<16&w6eW4+`4alACcMCg?p5GzGlamJx>vTCZ -zYbQ{jN`%qs8@-WAJSBew?*=S{qrl-&XaKDD&H?T0MZ+Dy%>VFRsPNm5ZnFmMkW@(l -zHXBDky6C~Nt#cZIb8QflLYz&+*G5lekG#`WEOIo=H=T~7nY>s&20 -zTy>seS;@Dr`ib7`c;{U=(YO0SZIYPOsLvW0M3NgDpIxe_lZ!kdx;ju>HsU)H`1VpgW4?0L=FI6hN7Z=z@TY -zP+JcUH0@!tXCxOMMnd|uDOP5qlYi?#xU_FM+cVfQnYo -zYC#}bTO_3PHfjc$lEop=_ysBVxC6n0tX-5FMo93O&F>;DJpR~=Xnh+}5+vmOOQ;uZ^%3i7xxLpMrP2r&d^!k><836Oo~lrtmF{w$ -zFEjIdFrO!3UHh)~^3iB1!tSK;VKQfPx8=$H+lKodSb!bXQkA@MW}VkQ*yZk)d{#qF -zr7ZlK@oM`ChoXsT;P<(v*X-WI-j$9zCJ2Ny3TCp<^Gs-v69}p-<`7PN4-FSsv0lUL -z<$VmnP#>Xoq)pFcA!eP&oF~{P4!KT*iCB&TqVgp|K`puge$O)FD@HFH)!_*!G-e31 -zyc}YpESmS&ct{I#J)ZMiY%}a2%3jHYW8_9j>ImBLYXUyc0E^~N?A2mS^vsdy9)TQT -zPwBffd>?PB3zmYAY7a2Tyax_o56w5>SdQ^N9_!4{Za<~7cLFk;#$EHZPNN_JM=>2d -z=45R=f>G+$G#>Zqk@4N^;1y3e&^Kz3Ayb2}pEkUb@1ngeeB5Z$S$2IlE7~xW-E<>L -zmEi9DR$qx1+ehrj)t$i955J95)2NG#JaILy{i|4h*Xf17vZ2pimCp}dGujI*y_R_Q -zsA(mGlpgh`@~y*UVke2&uM3*8j)x!su8o`Jy-b13nZ85EQxX6$)#&|=t1GJvAaT(K -zgA0HO%CYFKafD{Nx$_NfiSCE>=?O6AoZ1oV*a>kO-46*OZtDnF&S75UhGh{keaT^6(%94IFIDd-;_=jCNtDKQG0XDIO?gEv -zJ3SyMW88+rDGy^FBHpEO8QMN(g@v)pi&p`F7(iFmcyLcqzf1oJ6xWPWmW@teUOm;f -zBc-{GSqiM1cB@IIBu^4{X`dKbuj8g{{NM|OoAhe5B$CH0+!}JO8A={+?H&QJiZl3P -zIYhA;6|X+fSvV7ePI!z#yNf<)8EY;+cIt4#9l$CUk8G}lF&@@-NZq1`*+d-kbZd#| -z{KDy`K^_K}gTs!f;m`~hVb7+|khJ?CRM)m$5A&v|Qt>ShJ^8AxE$v5p#4Od463*!c -z_aTVQLXzAmD6Lh+K3=qmo#xvuYRF9d$Dk+V9o;vrgYBCF=6SvJ)t$&0UQHi3pNkg6 -zV+OAcCnaPRD0UO0mI)>AvJ;}++c39z=rJ)4!1R-67CD@o>FhmJ+9-*RDhgc}D( -zI4>TfMz}xHnjFeE` -z#kfDi%3%~?`v{7{(eP{a_dVD+{T?X)cw_NMDP7T=gTTNguSUk~hv>e+8HctTk$2!Sn@@LxAiR;5)8~Bv8wJ3oaE$T|2PuHmv?6!6b|i*#QBLaXy|!^1NsN^2c?}}h_v0^mui74PD%AHXMuwU7L(k}8}TH+v>CSW -z+II=It=*Qc3(ur5BZ_;ovWz!xEvh=>l}i9MjwDhK-E9i&*RP{*T9|!T=X9xlR5F2Z -z-uiN~83WSl|G|FgT8gE&}*`|Yl=}t9+5x|IadrIv;A3RY(&C|jBJjktn#*VSg%x`lfZw6 -zo%PqpZ1@5_702gegEr&X+1U-AC+yNPO%knld^hZyhG+Qj1@&pFJ%7t3I4xOAj-Y5n -zox2p8^hDq8Hql+%kN+Tbao8CV%v+1o~{uOjStxQ!em6=J1R_ckixu -zPn*J`1lj+>V??r>o>*yd6hI<9+;~&E$piEohwk#|L2}9pIx1yF+EpFO0$RZw -zvmQdy!udMRrmX7FF=)Xa>{^>1<)H)t->)cu`1WB2r`jPfw~A-q_K4j~n+!SVa$AZ3 -zLaHgTNDryOJ17xFc~=I4{a%d7%D%SlX&l>x!#v08>5ZU$tA%8V#U3K0Yj$DDO{oSW -zSywTf37nPyvxm~__IH>xu&!WZEvnC3KO$N?lR77$S;8oU^zDrjx2OvH_GUjMYZudg -z`ZlT~(W6PeGyIfNiuz(`UHWuvM5($@0d4b!ExSjh4^wAb6nz!h5ncP-Ea;MNn%E4S -z>FmjA(B<|@-Tdgs!hDDp8pe-#vCwFX;W5#f(<##ZWFj^^%91!Ru-mEQDu1`-19#rX -z2%B%!l&oVDZbqF*dkVqq*`eLCQpo}^SL72kF|3v&m*+3p7`Z`js~qR|+W9WEDM;)e -zoVvUjg(Vw;i`%?ipltXm1)=l;iL;w{Pt$|B#oWr>)g)yjM(ewXfw`eWRJcuEr2zD@ -z!d4Q#OQ)$fgfnVcC#$s_cRTlrsE^sC{6AQNzQ&PFy|jH72>zbl@_U-F{ddU4cd|^h -z4=0ahY&kh0)w~yDY)QO`X+h~GJaSVrdX;H1&sPts6ixnV(iZ%uhkcY?&DT-VM}wZ1#M9aS?xCVK}jyh(g~*; -z8Z#F&c;1j^dSfkGpf;o&{+56A$YE_NX$Vg-M266D*2jBB#iqWcxCTBgPs>?SpLQahC6uN5FDO3)l?{~1)GUj!ECO*3@BC*<3dTxwVhwrWc>#EM8ktQ -zb{61O^NN$(E;@lT=b$$?ESiDF`@pB2CJ3jpjDf1(VTOPD#I&tqGcH4>WvW)Z4i4ViUKln+CW7L~n;sb4 -z_#a~*Qe2srXVfu}?+PGy+96y2=%j4OWrRrU0rg -zX@~OyCDf>mK0c(Gtn@^{3s>e<34wE!@eUy&zyPG1RBmO5WEZ3iB5e(ChhjE(L!66d -zaN{B{&a{JnR(3*SUaskB1Cp-L-h)yx -zaWD%kefNI6Sqp1ZpqT_1fTz@9c&`iRcKM+56av|09UfTCO8-g*hw#BAChyFWp6*@E -z6?#ey!tG+W*~6SF_`wsU4+I*)twv$Cj3s^OSEH3)1US!WyU`A=qm+XQ+x{kK?gq?= -zQlqbeg&Rag;;7YJXl&9zuU_k&A8<$LMMA^vqjyy -zW;o4z!w{%%pFq#L0w4LPkbz`_PwzuEx}dxa4CoiWBLIGXIb!;U@d3Q41pJ|wI+#!A -zA{1z*L-PZ@Cjtihx^OG$SS_UsaC$rD^39wbxT8pUB@QdO^(EDq*w|N4DQMCVuTlmf -zYmhAXVg959G}vmqBlj58X|9RA=}%ah-SYOI0jZx~SdB5S&d;B}s{+`S?grU&tK4T}{?vI6BbOW;9JIkM^r+W6paof?oSuegnVzMJipu03 -z`ju!o8X$qjpF_Nfjg2k&#fuj_%>!|;)kKukgbG^&$u!DjcF>86A*kW)2<;6%QZJFZ -zh(!BC1h5pejiD}B7{xH(z-w-Q`sT%|v~xaykbjVa*MF5Z1!zvF`K{Z3%krVg{0*JV -z+{%eJJ-rH-`Tuq8f2?c2Y|cJNyRbdH9m5Xx)CF|AQIKK{Il`?2R=`C1oTro=7!lqo -zj`_PxKO?wnBLCm98-~l5%@uu=$Wgvbn$o{y~ -zvaV?w1`Kn`0&_h$*|75bcX%f^ZYw(AeutHla&pJNU{~%ed+tP8SH{3Uu`3T!OD?Zi -z3U3-d{hWHJSnPIpfBtJT9%C{$H&1`A^rUT~yR3c6&JeaJmhI*5y&2A|o?=M?Pl -zyk2lHZ2=|{YvB25O42-dZDlN9X@JW6uB7s%(s*) -z6_^04p%2fGL5Itkf{13YBR(s(8M136I6h+Ls`Lf^c!?CM?R@7i08sJ8S~}zXY4oBM -z`+s{XZ2l{QAaJYUaJGHh1S56eT2a8^9YZ#8~MmE8%u3{RKiiu#p!_U7FM7zQ%>;FP1bzo+?ZwujNln -z!+#HT#Emb61L7!9Fdko4ZkA3nY41kP04z5*#2!i4g$;jhmnop+I!rXLQOehFip9}( -z(^@`V4TZ)kZ$`iXH1^fQ@wg#c?(TMy}h@r -zhumHBJ>*yY{rwTz(0#OGeln3GTJYo^z)=B2A@%sJL=N0&*xk0tj`h)eFVeq+<|JSC -zxNqv4HeYm4-dbf~L{+~ibs+yBNu$j0sZq3~&8*bSan5nC`*MV7w;QgX9gvuWp}=Us)`UzOIvwPX1)E7=A)vR!Xnf%)D99;<~yOa;(H1uI=(# -z3uCb!f;i!U`lzaAAV)i2?7rCH<2o}sv}BSEHYN=ICt#45S#WI19)T`%?FZzSt~;nL -zdsdU}e5%w>8rml~SJ-@QK53Hu$D1Ul|F_u9T<>)q{(t2^ -zpqGU4(=tm?tT+~|XH)r|{(RJYSm2vi0P|S$gZnS8Db`Qf-U^O#*r!VCxAjn%;8m`) -z@-(E}Ih%Uvj5gtVjy?LeV{H&W|Azj_S%vIt6vx7^quyg3PVt){_QHn~R`E+KcKFv1 -z1ri7(AtCw|a$jH&Qo7W5n)YH3j}f3L)n(mz{2$GijGx*bU6?!1c>Z-%Bh=li0r0Gxzo-(j=bUF;$jkB7P_kFK3L -zAWctAw>@WoNdQ;Wk>~Vf_x#d_)5z-SQbGYvMqZv6F)1lYdq<7{nv9a=O^waXde+t{ -zfc5YGsqr+KN*AC_xxnko0K04+ab^NjsQo8$f7B;e_b*VJ{i6KW;Or-aECgWT|37=) -z`NP2fzt9JR1Mc!JAxbL7M1Rm9(}-MJ5Dhgb4 -z_SV2`IOZaOsu8Ma;n4F|K7g%ni9gsYWBX4mfD}Ng{tw}Je;TG=2IseVfW4mKKe~V` -z=D`$2nb+@2`2D?q_h~hNu!^~99c|69u8%qW@C)y1aJR`dTp{EuVB5 -zVYMXkcI?LojCa%vQ!&mhQ_^kcO9wWhmbGwI&dOW$G7UDRgpB9Fu}{CSf=QO`!U&IH_@Ai3ETfO+PD -z46^QND*=c?y?_KQJ_xzFj2?{S|q5{K4}ozXLyYZ#`Ms3&aQmWQ)y)TY2GI6|F{ -zC_u&lc#0YQeljBr!eB`sN)=TX9L-FCtL6vWgVJ4FSwQRtv -zKz+q=ZlOlFF6X;bWgu5p*=F%feM7y>krv?o{D>HinA`d9o#A{B?|3`PaKjUXSD-p) -zu}Nkho}c3W69C|wg4yBisllv-MYvuVg50pORJQCA54E^x*wgbvn -z-dYgP7|1k_fOb{KWJ(ZtR#?%qF~a~N(_=uS4Gu6OaSde_KZA -z_K_RAJHG<$TLa<*3BLN{>yzT6iyAm`5^85RbuMHf&&}Aoj^PAOj)4@<6}FD=X^$<^q=0dJd7=Fv%5Fy@Jtc+t`#6%o%*w*tqmw4 -zBn}X?PD^G5BCY1zB#UpJ>o=#P1fh)p*$#u>9+sxjFDtxuddM-HTogpH~@>UP4!3xIjq -z@M^mmzIx;>NgYVL3n_D64C=b(P#3CI;qsd4Obe@7M7QBAke;+cwrIpV8;+$5fyA%V -zz!Y{QK&Cs1QX=wulqd?yN8#GpfOS(Jx*i^O7p9!t%j$dAz%-|T4W2Y_zmqM -z9_C%(SF%T!>QVQ=u_B(%Ttr(m$uxC;=pgIUgOS$)p?&pb8(zau8(?-9pl`-k>QBWz -z(Ctq4o^s3l9tH=pc$=@3pG1q|*9eI){i6UDtaUut`MH^AT$2rpq(mN?_^*Noa6SyYeJNyZAO8@~ -z-34eE_0V}`1?q>Wx<>-GoA?xUH<9?%)Dk5lt<-p_5tvJRqfUTp9Ljj6G3qU+Dx7=X|w-r72DuUp}qD!FFuItpoj5-y6H*k6fO)B144~-Jf -zX1skGEn$+h?r=sQEsll$Blrg?_cY(6Qi>K9g-l>J*iEX`?ZYJEA!RAPg)~ZQfpNDu -zhsyWYZ`^#XJKy8h1OQTGu-$}pk{L-Fngi-S)~ORB6?nMYjpQxwvQvk6v2Ml8Cx{yD -zz9{>jZtFIpClEpoYtF^Fj)`O+WBvxMB;PPcS>#{94ec^1pv8$~6H02WRu*H}Xpf0m -z+SAFNxB1`IFqsfcn**qB5ZUwpD1Hwb;%z0K%*%~$cmXxyPI?3$zHch+V?5_EEsvlE -z_rr)lZf{_OFZW|Dg(r(OSj;V+$Bn{#5D2kEq+tdVN&L5_NTb%inCS#h)B-z0L4EMO>C$?Z+AIZX)3fCMc9cY(r$)OzM^tl>l{2U8u^yqwFm> -z5lOp7JW-YKku2;&&J4~3J7z&=Bw>lQy+|WuxLnh$Y!)fpzSmrmbjd-r-zAke&Lu&2 -zzFQJ9_@^LQXez?VC^5oLM}XmUdR-bFIR%;;o4>ghb)ELV5<}lgc+^WEJFE2 -zcpPjC-qM&8JlDzg#}S4!-^`Hbo0cYH(u6%^zihsf^;7Dz%79%yVhmd52|S*v!@`v; -zmn_<`dx{8w-yo}Qi*Y8VoX3bHT2$5W>j{5<$Wg?D|BzMek{bUdqdaEHGW>Imr>5Qj -z!4BNkBAXvBjs%Z@sPAI7A5YHG>vv`Hr&DHUm0~%FX0`+|Eic1*Yu_8U(g=TPAI_;f -z5T0W*&wq{O7SA{Gk4p9OP?gwN9%m7Gl78%k`U#P#0+*%Kag5Kq`K)eo$Dxw6loSKO -zYxH8cF@rt2GTRQGyVg9dCt_L1tBmPOgXukhnL-U}1@M>)sS}1OEFZppIPqi3WqOjc -zhk>nV!&t&c#+dlr8XH0f+3h*O5@tBxu$K7wKcRK -zw!|JGm>Kg#X!#^X-AyuvPQl#S0mP_jQHZiQjRI||E$ug#>EXVtYh-(&=>jckJwzW) -z1WWV0Ef`CYv_xb)OYs@@^C55fY0TbK9tVtd($Y|92EZV}Gcw>x%eqAt)+UXmFT@X! -zcxb>pPxI+5785vGj`Dg=tGp%TsU4&3)EY}Nd&g5T-Ayb#9)Q`?tX)f+F@4ei6T2IM -z*XpWJIikUI>b))iK6i_AaciU*>J) -z>0rVKzB6+KM*a3LJXe}CRHqBhOH66&&3y!qY6kp`9EWG3nj)i670}Cbk)?#lrm5F| -z35y5i04L2`Pr?a9;V(T8MbotyQA>B>GHfTE?y~Mv+D9lZ+W!`NmbmQFGz^65Ury~d -zDL7rWvRywe9+#tjeM(syhu~7d%6DZG6zd&G#y+hE3*a}#}9ipQp -zT(jtgn9YYo2#%O~2RQR(X%!8VXr)=KhI8XjeQI<}A-I!;MOmI%uy+G1Zne*)#qYCD -zdm2PBK?G|T*d*04y6#|=nsV*U#KEu8mrE(ls+qvl8Ih+nNI_|(WH|m6`Y6S|C^f-= -zu3q#Ba6xpu?H`U)M?n|ppbYh$@dO=e(pRmPq3B7%CVe~ -z*rdM&3ibOmeahL7;u+dhsmq{lp)lWe`qqXntxd!u1RfVS?AADUO`WFtRgtRBR3A?I -zHn&NFa-YI}JVV^$DC0H$39lAf^J?D34>Ed(po#zj6kq8EpI8v;-wVpGrl`oHL6(8+-Ijr7$LCtKf+XC3T%WZv!t5#rw7ZSCx%Fg3F3Z&kVY|OpL&)8mxD`5~ -z6?O-8ri@}eSNl&us;c~Q)*w{n{!1k2wXRg1HaX)ADRPVjt8avw$|t|w_H62A -z+Udur?q+3r;($n|Xu+MSRQ~d- -zpNC%U8kb9A5H^yN5|Vz$fVZaI9JC;oeTybZY0M~@kf>E*4mydm?LOVK@W&G81|ehkPsXYfxu>52^_eMpvcN0CL?jd0r(Ldq3^nmy}f-*^A)w1 -zD2^gg?7!Jd7Vr-TF0!#gS(KL4ACx#OIc!f>!gdu0Q4+K*ASNkk(_gEL2yu1;O5?Zp -zM5xn&rD-h+`GJboTD;?h@tN80JC-||Lxs)S9_*EYo!?lsh6tb?;UIb{uU<$l9B{H> -zWrf@m7LF_hQ4t6dXpU&XT>GxW$#mtFuA?GNN=h8_A2{HIikdz-p -z{0&y}C)ngOtTkI#?$d$QSCf^4vm1}_X~_>fhdq|?`9R9Y@9Nr(->n5%m*>5^kN-yw -zu11J2uc7zhcE9~C9;ltyRUfaw`Tr~}{u%nELRgpn*Xro<7La@+I(zrkRSZNWvp+;H -z-K>OIw@MQ}Sd49r^nm6jkfyMS9oYe@a1+Ug$v#$yGrmUU8Z(*|Yj}%9R0~T^@Z8L) -z_82~?>))0PnGl)OtXcQ_qzVuH9jZd~DnAdbE6dD`kP?5r9$@lA1=*SXPumLj;!|em -zueiaIpm5#;zxBv!90bQm8VO?@6z8!8ILGW*oy*sN_z;Cnh{}0x)|KA)yhg9ajfECILcbcJW#YMi~aD}j)_q|h5HgIysTD02I!s5>I -zg%ywqnguO6l<8^$m?!8fCim11VC?x!b*VPuGIVNs0ZHo5h86E`qsUQjrVuw(;A+*l -z$x%?NC|H^6SrM$rCLx&JPX2p8t)4!tW-=YsXSkXzZJsti`=@mMLgIirp&Ii_Sz+bB -zK_Sq>{0?Tv1VLh-Zr?u=C&1HX4|v;v91VlX(k!L|mL9Z~{HB^<0fkNBcIp)bhK^Oz -zy43g&S21a;LuDqdXTjcj%HQP!xbnF4Z>xG~@k5o@xhRRNpSebc1UaZRH1N7oP|9!~WZ<$8n9hD!|!abwPd^3THXrSyOOVDG!8uO(c0gr)*2C8-?nxBj=Z~4$gOe<;=065AD -zh0Lj_zZ|5Z06v}qp;V6yID`le)V~}c?&{eCg;Iq}3nPx;p$7F_8xwmA(O(Nxx{T_1 -zopHr>eTtg6Jy65r3Pqn*wGpByr{idl7AQz-T+2gV79Y>w=L}?A5UY`1yxBV?}>wa;VfmM?|cVT*%jL1X6y>C6ixwjr` -zHQDwZ`hD206huM{Snlj1CULqUdZ`ike$1etm>!=BEgx$OZ6K%Oh1=F!0+IF_lmTfM -zY1Z%Ad=Nau;O{$qe4v`czE!$}v)gj!2dQ{Ay5OY*l-i0?&p{4Q@XQWAg*g}KW6=hE -zEv%q(ZH#qpUoa#L!dnxdmiVQknc9iPUneJa&$gJ^NpZh|Z$Y~^kkt39PB -ziwe7ZtK6b}Mawgz!Y4#hAfM2KXw;fR=@Pnt)FQ`D8+7(*$sHD6EAX_BU8>nR0;DFa -zbu~#K2VN$d{ym6EHG}giZ?BDY4LkIgg7J{*nccR6kFH8z#zY_ -z$Z<@!{Yn`_`}6|vb&l3D7G+twblk1t*h#$!*WT6sBh6oFj=-rf+Yd3%Ag9Bqgx4Bc -zOB)>y8jFBBZO!2ChCJ;iT%Tm0Rpsee?Goo4la|P90E2H^^g+db|Q%b3?=wB;-th3(3ZGX*8p*4c7gQ -z_8v_}5-RA2s&pG@!5s3vgXzpJBM$)Yk3uJftBac7KRhYwJ>`M$KiDB%#W@$?@Nb2_ -zC4J>F$7i6MR^{dspFD$4EkQjK>CY62ErOrj709aqilz_!E-r8`Ln*^tiTU0~Gof|( -z@_C+}%cVPtEC$nvqg2z8Xy6OJ7v2|n_t{`Q;QSglxSro_; -zgNv5nCNE&LA0zd;bC#l7A(b~0oDybi3+t* -zF{7o3V~S}c_U%Xu6grZ>9neW}>)Z(Sf-cq5->xn$Lov3Q)0ahPgphgsuk}igp(nLK#bFSo45}wZ~X;hT0#omIk-%DA&3z;+)U5;a#UI1l -zi`KFMvA}9t#hPVJ*gH8!^`9hz##?z$ElSlT@wy!ePbao6m@`*j4$~XAkoIi@o6&bO`pR7Tdu0cPxX^1--qDkVD1Q7- -zf$>VFZ2Iex^t=JAkf)=8OrRgo$miUKte9++tH@SmEk!&FtkoTL8k)ZN2r?rZ?1)XE -z)CjG?a+vZ`XvvSLlE>VV83)~x7aWcqQ~$=FBMRkY#PXT`Lrw%`baSK*8i&!KsUlD% -zs&TX44afC!c^Rt)H6Ev@1mfRxL8+bIYAViLA{!i9REp0bSAL^Fk{7e^MY0oFrofCL -zt@gB!hRJGht-A3ZtJtwP=waos?$t=H??Y-HAR!w@@2(BG4t2ChEJJ`*vI2?-@9bj;e0U;4CszmS<%tNiZw7>{EiW29^I9}NAw -zurt0UjnOEa)+%H9a>&gOnkb8tf^lFp(0eh#`~~b}kNC-S6_QF~g>}{A5_uJuJ0x17 -zn&elqZ{wv|NT&_O%+Jrzrf7bzOB#OT+4J(b80Mw+bZ!mJ*(#0Wh%Pxup=07Ys;vX_ -zcHS>th0>de)Ms&2R~T1xsUn5}SgvCz`LbmK4NLT^|KuKi{)nTW_QCqRI!JT07%Zb^ -zR2UKyT-lL{)bpDkmv~YH{Dwv)vcu}54EQ5@xCwXqbB5K?zLr}f140dSUqE)YN_kN-nT?d -zhZ?@`11pC(R!kleU>MNEHV{{=kHT2hH+GoC$BDS^6kmB9xT;dlZS6R+vKC -zIC*5Qk}zN|no?0#$xs@1+nkfVwyKeT*PPwKaSzBi{S?fj-A8IMP3W>Q*OZo(4J$nj7zj~DGYRQce5iojjSRHtg -zcnZ#H-YEY07X#L-+A~y-LKDQX>fv8Dv -zTkysVtL$)ziJGlE+eRQ+nl`3HmPpOYMo$?$!q9&I&7$zw^uzY6_sBGrRk_2t)(@%L -zzLj3Y#W}lkCu02Wrc-@zgkb*2{&pjDR`x8uFGCsPVN79FfHbEfx5nD3R8 -zw_pjzJ_m%K(g`b9j@qfO?7Sr4V+IQJZ`_37&qwIr%}!4l%r~mmK`E9ZD^_Xsz4kq$ -z;1lk^ub)evOqx5)`CQvPjCHQ&x#t?j7Ke!g;%zmgFmn#doT)*LlzAX!v3s)G_qcwN(o%ih;Gj=8gz -zfcHvv37}EZ^f*KGG57i9`kP}&{P-`=Ty{5N;VO(aIv9WP^^@cXU5T8MqU^{ZK|G^> -zQ(UAF+JKH$*H4%=4N1W+{2n(;!X*JH@Y -zktjWMhj(lWN+o1Jrh(t#oVGpl1{GsL+wK8?mWOq -zO>h8;ft@^pe=uuFS7+2%>tYE4T;%WOZi(6}TriADjaiunvyj(%`dVV9_$+_t-1nqW -zd}H+H$As2Ag7V(u>V3k1op6dx!5R~8kv-zYm3)TCo6V*@l%`(=Op%(xUA2Ajcxphk -ztCCxintp0P%k5@0F}kCb7h!+eq)CN`TU91KndKDN6x|fu!~)<|>ujoIw9E!2RyUnma5p4;Stk7um9K -zF;}Q`zq(-PVgkW?F3v{aZmM^KnxBhVX$sY$PH|D-%vrPT-&h&h-|?TG+Y_*Rj!%9? -zeC0wF*awwsc7Z(9)9QIbB7zi5lYdDv6{waj=5EOnq3T)9pE{ginVwvsjnC}w4bU1f -zCT614&~!)L>f;WrC-IW9CyqD1boFmNE(XOh-wa}V#FLv~laHGlrlOA;ULLRM%-tW;XE{Q1d(LoTery~psTB&=DD{8W{z%;4J>)^5Gw -z$K#U%8EO;@^qX%GlHTHZ;teU_oU{5{rmtJP{GH4Zbqc<%74w^)$%)i}kB$qpsUX!t -zT^vEv^w8jlP@4{$oe(!0be-zodeX13KJ$WK{T4NT65!+nPgymlV{W*2^s|V=@>Ta^ -zO~_^yh5WmG<- -zAFeTLc$A1i@r#qZjU?jz$0$W3;I>TfI@EfRgfU7;4r8j`)0GYTaS8GEaIZtOsA*?2 -zbOFYgKZzvb&d|+XPZ*ay}*3pA>|TGZ&zuu(>1+ -zGnSq$)Pr{a@$?}T)9I^{W=qE8AaP|{Mdn0058)ft=vZSTY1lL(&4WZq@mL#jl_YFV -zi)TL@J1=Xmg4c1Bbq%Q+$gHNQNv2|wgdvVk!lWvJomz}vY_x1F__&_}P^k&(<@8=t -zT57Hl?G=5h+yxXnw?y817T|J`8&%7nPT$1}c&sZN(-VlrR86QHc8gc% -z*-RgZZh3UVZ*n>ni=W)VIkfAcqHiM$Gig<1XVx*%v!PIZUw54)lZjnieE#`}F0h&2 -zgY(Amsnm}calB2ck9AW^o+U+?S+&4U8R7JPdxK={K{zja8Bmx+A*jprNJwIkciY<* -zQHz>Jh^N4M1pAA9XAy?;rYGJ%L=bJX#H>5GJq$_T@@fQ{vK#gBqb#S}LFcjgC5xAC -zjH>vaP7lPXgB1Gl#im8K^*>XMe_7mcU|G43%jhKgcanN%`-OslW^v*lP#8;mW2)nx -zkn8N{Ioa(EiqDmEzBITA(Ntcdgte%~#Ap783Zd5J%7y1Si_Q3=Gv*$tL3%cZoVGpV -z8$H&dr7#3=B6Tm2zbKjeK#@UbBxIy0X}ZP&Kblmbhp%g7GII*jAgr%)d{`6x -z5JC@}CqH_I$buhNktKkU*=h7CZRnUE$0Mgbzt<{05HNo6%&^8DFOXcaG9=$$(;?Kn -z3h^d;0H)FF`X=>>c_Rwg5PICsj+Iw$MPeuMf5$S>{Z}Lup -zK-vvaZ-*lnV_5#jU&^_O*T#4&jXkwcB8P-Em -zll63N&r6E-z}h3;$~W7s6Vy&KO2CA@miahytI-88Q(wOGe$E#{Kw@>g!*N)!L7OO5 -ztpV)i3US_D0M0}rL=FhCTCyXqP#$x}Ev08K62u8-H{h1FCDX&=%qSkLhfBK2f43S- -zNC~r&ffrD4Y{?EAr5$uwhd3BoAei{9*OH0)C!T;&EQ{~I1u_F(yB>_q{Q1>{I~+Yr -zSb2e9$LH7vSc`B2b3xvaifQ}pnCSlie*M!uDsE&;glJa^<01G8rI*fBB~W=Eak?1h^2Oh*TuReE4a)KRDZ@62H?N&LQr;)Ln+@Q}61*evd(_j(5M}Y`()z26A-gq#= -za%gCBr!Tzw#`3277`yRKZC*R~<5E5(1ae;D@s%^CjBJ7A!nAV7dAWC5#aa>b9!llY -zp_QW#M}8CIhMYSoe?j&cv^lHe-KE#7z)mPXJu-^qD;61k3GPQ+g8zymL!7!sWtTbC -z#A?~Nu3EeKD`W|R=ea&^Fq!!c;{+H813}kQ#i$FRThtVKkCuMx{yshaSedG -zS&z?4=w*;HXmPKRZE+LEU!zO&_q@&0f?xtjd+b&*g~^@_h*<{dy>kepyHwT*{7Z6tHXU -z9ANAJ0~Z8QEC8xDCUY~l0-|<6{O8{QVk`V0ZBQo*Nh7GTFZy0-Ziaw-82NMJ3luD~ -z%AsDg(ZYQ!;&NTVHV4MR2`4ikl{9r&iMvPgGvlyYlhB!aD4; -zh^O!Zn0tk?e#bvFUPQQs^VNl*Lg#EsEc!*-I87*X1Ry;_@4lY>_1!-R|0{CRE&g

^eZP9y -zW&Y0tCeAAa?Co_W|NK7j(j~NFu-$zE7<394m}DN=^+9X6RumM4&-{r|lduF`ocav6 -z_wAz7h^^f?mZcN8$UmxDdMJ&SEO%c6zF%D9kiF?C=99{g1@De5?vYwlZMF95m@14JkF7a$ -zw`DrIcUCK|BQt3zr#Jdu-^fUKLPCO|*-1y#3NBu8TzomIHODm^@Fx0LEZOhBHyn%K -zg#xsj(6w7s_pvRCb#XI}JiOu&KE$!HvA%(U+aErB=o=iY^ee<{!v*5VD>O7TPTARs -z=jG>H;e)(ZLz7=F5Z}Cc)7RgB3n0x!mwE~0Afi`KOii7WsP60Q3rfGTo)c9iuu)Vr -z3=pu&CMMk7q*SdG(ATKB&Uc=1@$NQ;UUiVsX>fKrc^s>jgsu|d=TJdaZBAt14gv7P -z-1H|*>k-^ci|u@xhe`?45^ZA`hBdBL0ljQ -zyl<%hKX`HTT^ag1yoFV_^G)c?YC#vn$0v0NI=$z+JXoMnNCHb9RzLp*x~47Z+i?b`>6` -z1D_|fVR491#QO{x2kNmkgkT*F| -zD)6e)j$ItYWDFM}sv>6Y!>?C(=`Fv(*iw%{n=$t@VZ;mv7-M2csNvFlTL`d73tHJF -zQyajuWv=676FN#W(`E9BGcLk&Z*LjE2%=z|tx5MD=?3CDVK5=C3Fze^KZ1U(+EHlU -zCoo?RTWgnqSEIBcD`(=0+lmnF;Lk&WdG)~CQQgjhusNpCiP?tlP%{BpuRquB`*v~P -zDsvr6r0+(NojRN-0q92HRy49KM`6RaLU-a~upzz^x&li}4a8Z(l)Pd%bQae2Q3lJ6 -zYOzEYK#jL<-GU5nzroMB%7Tiv(R1Dc2ca3W)oC{Qs!)M=Bcymc<5%}=M;gyZA2@K} -zSy7SB_3PKKF3kysCb$hsgHLVTvgHn)PDk5}NWzZGzJ$;uK)TsGl^GZuINB(>yztB0 -zjt~xVVwP5P=^vcPifdF4NSOSWU;gpzHR{wqKL4{3<%fZ#LUdhjQg8n;3>2qoWGkyBC&6HSYZ_V -z;211V7x$WV>kb$i8Xh$;5C~8*``K>RzWAIRHCVY*?(SLJ$DEqvIM$C0w&km}!-w~8MWr$q4z1<4a@N6HV<># -z!Ru*Zv3Kv@OSSujN+H`&5OP_JV-Mp$u1A%aw-RA$v%I|YoalT|4agppS1BsZ^-*8l}U--z~jvb?lOsEo9=;R$FNBj -z)7<7QT(`nd0oA~FDl?8U5NURU3iyS00UG;TCM|gqQeJndy0kRB%->?Na~`rdwCOXY -zisu3{)ZF%TZ>qTo+NoN#&f*W!V042oY|MC76+)xktGNh6+{?5SZwB8}g3_fJ*6@#8 -z;kY)qy+=fh#W+zCzO>bU;PNylAj`z(Gx(+r&>xP>u)!y#DLR>|Uy7hY17wH(ltc5x -zOakNrk%vX0{9CA@|M&YDfW(pbegQoGVq&x9`dttyd>3-Y+CAc!fi#4RsO>^n)PMw# -z-Hm5TOGJgsg}wYXT+}FJ2wbEfR^`>HPN%INp$36C8-&bHI`@h9-^&u4DF$hz)wO1L -zpVr)9W;ZxI??4EMdek&T6Sya7n(K@|t$DZovGoQ7BDj0C;Z?nteY=xAaFeu$0H;iIRY$dI(EPuP3oRq=SaQoUFISV4d78y$9Sco(>+ -z15{l#blP+^+#rWe=N7nz6(J$D>t49;nRAa8+zi7-Vspn;&(1O_bSvR3BrZ=KP6Q(Wm(%Anm8$EWIOCh8D8s -zMGCvW%9kDin-t#qtCQ_`k2H3Ak$!dyspO6*Jv@jp{WL!RF!}{nQyfLgY_P -zwo^ifDwpNZCn<7iKS~w-?}a>&@kNqhMOX2N-UUheObUcG*jxfL)Be0I6Xy+mEKBib -zTj+r0UUV1yGA^D46YX{UD^v3A>MuFZMCw(-U2&F^H75Ezs-yu4dl(}V=CQ~2jJ*=>Oy(5LyLevEs4GAYXr_3k -z@l51!pMm~nH4%i^GV`75MY8%+RedQ9H=gQv&Wwcz2=|Ln%S3B@+Q(n*)XdfRE(P}| -z!O(OcV#I-?I+%~=LwFt08rmI -zkvkEsUYBnnHX69<45tlSE`$oKWiB)Suzi@j5DN+z@FS5BU9eo2O83KJw6kodZzGXW -zncaSXY99)L7_*l^6HH46;&EpV01SPJ9IxLSZd+a%>!ZZW8fqdkz6Rm5gLqQ@LlWC{!&zUQwp`$48h-Y;%54z1k^#QJK5`zzU8l`L;p&bdr}p_`&VN4-Jou -z)S!l+Cbj@zz1Y-XGar)7Yacv*n)$%n19*e|RC>?tP&Yjj^tEphBK#a28(I(T(>0|U -zd{YJ3ycIog;#!24uW1|wjuj#j#^=I6J>I>3s+~^lnn~KI6`yA#$F)XZmuV^3PUkU2 -zCO5V`9i4=nZQ~?{dru4%iH136uFOYIE?bdOw#rBP}$Dfo{&e}33v_I`K^zNl>4S>1*nB2F21=j-;;M>)7E0rZ_BsB%@z1Yb2 -zl8mcxvvh~v*%b_SkTzpk;vbmT<0>7YRA8_D>lg*ssrs0d2vzh6DDrSN8j{rUAyh@b -z{v)SPostA*c}~p0*O^&&`ofLvhUva3nWw8FYt`UT}fjQ+|r!A&Tc$V)V=czoOap1x?ksF$O9!WbN3`p5p17JJy -z=*Rswr2gt^DebXa7J+P%c_Efu2nCyC9-}@@1lK}U*-X1hfQ`P>3tsoJ+Hn6}`S(>t -z`j5ZwzfiY}iAYQk$!Kd0Y223}YOV4*6(UqTr@YvS!S0m;^yC`jFH!A9k@Van;g^nG -z$+Lw3%6r4M2E?nF!-|l4=yikoqMCmY!L+uBQPwAE`3;p%cl5ZbJ<5LuwKmU$kJ)BB -zTjWZl#e!rfFZc-zPD#_Wlo5i+PZ^vT!gcrV-7^6{q8D&2Anz`q>rBS#WQ*$z1lNH? -z=mUH)oA32(PgM7_I|>!UKiqi5U+yIGqpo;(RM9`GX-TT1>-#(K?7U#;;J?WA$Tth- -zaO0SRBBhE_PH#GfffKRV`5zawWM}zgzUICcf -zp8v^Mm*qEQ=&7nfRfrSCy|82?S5A-T@$;1j~Ha@@_D^i4#5){$jH^pO;PM -zX(-e;!JpQG8!1YUe8ei#Y-b$Bmh2KwM?K)wD?ybsk3zB;g~Ya&+5naVkrhyprs*}L -z0fkV97)PDpD+cgNy&S`_r6m*>f=||2y6abvKvgg^z~ANzp+0xW+`mCOyZGpghPwJD -zBuj9r@4Dl{Kn-D&K>i3x3TL-~5)lzm+PPEQH%2E$7FOQK9Qdn^`dUD%fT14-Ay58n -zt9)KDw2o4vaz5uKYzcHc2*S4P#WOQ4&CO5O+E)1xLa38N!L?kxSp(B5zX8pO`2jpOLetW(;0`EnGx6C3q;YnT{n<&7-omqj>#OaT*b=4>=41$% -z94KINNX>)?tK+Y4#RV3l;92f#Zpdv4v)d^PTIVJ%ez3AwrunPnS^l&t;HprK*QjOO -z(1c3;7C3A{pw)0U?fkYuoRJMv&vv1NtY4B&lHrG_11^#%kM+FtrjQJmmFwx*2D0+k -zaC4YSDc_jm8#isb1qG^Tinkxa8((%K`dkpK<$qV7liTK7HUEqsQLR>j5n_3@x#V!T -znLLhNkAEIZeHeuc7pfFY6M|$yXu8=)x%Tr{1;NZ;( -z%0wVL`q!u8<1n->T?fIi^M7oy`MVimsh7Xlhjb7OiOa@#>+%l#tCqj?O)kiKJ@oyE -z5$?ad|F@>&xnL_K-4BH>QT)Gq@SoHDYaoAX2-a2-*7go#yUGUX+~Rld_N5Vuw2}J` -z!iyDhuLC+Y&h7#cP-WhO3UL}Ri=H4%DLw?Xb?esJjt()<+~w0s8kWa_yN8A#L{Wg6 -ztYtFIzn)Ev!JS_*HQcrZu*h#t{Aqd-pGI|ech@#H3f#YczZOo;pGF>cclUS2#W?pX -zS8_-z#^JOF7Pp~-gM(}9>-kJfO!h1>L|7K?4ShpHReI62wY9%2JUjR75hj~c!}|OC -zdyYA0TLb%YYZlxO>?;WjKK{@Y&I1;0AUb+KO!@$*AY0^}eV5JxV*!4c8p#mI20Iw7 -zkZr+&5?wS*UMvUkI`Z5xXRsQ!>;vU)M`+d>)Cvq@9rNXuUtaetYxv>2pmbny0jSgC -zU5<12L#Qqtr?|HnA;y6*9|!abu3h@@kg$CwvUYz!Cx1lLAQn{^$zkaGu%5P5gd#jl -zXV(ru-<09NSvb7XpjRIJx&p>{1|iV@Ra=N8dV_cbAGCUGa{`R|Ff!_SL5^j*as=Mk -zbkuv%m -z@$qH&&$B;4-msi)!S3lvEW#lu8&2eVx}bKZZx4MNbe^&e2e%3`&APC-GWTq>RjOYZQz7=*;r -z08^WB=EEOmk4|2IAw96Qb9BT`jdX}XZ5HW5G{^EV?M|XrtrNZpRkun3o=iwJkPPpW -zaO`OfRn?v=pA7IdBWFO5nK}Tebw7%J$q`?_o^ki|yexsxzxv-C4V#dg1;>qExoTBn -zN{W;*7wgXj{+rFxUE*>6bq?YV?!Ro7c^{Oc6vBb}_IU0$0ILE}CJiyV{{KY8CCtw` -z2Vwud0;7t&e?S_JUL730BSmPAWlj?b^smCVIS>arHt5yW^RP3^ulyt`Q!qqOsy_gH -z8?Qqa9P-+d_fktlI|Qu5Wl_}Xqz~M-t>idbI#lo=$3xWthu832=er%iBn;rNxzrs5 -zCt&<2>_kZp1GRkaK0?EJlI8TL#uWiQ^#NhHh@}JBHBA4%qPZ&|L%%@H=gG -zJZ6bJjn2TQd-*9-zXNml_-QhiDd$TQ18J%dq^W6nV{uNDIy;ZkVquV3^$s9U?2wf$*5ZOHFq)4PY&Ju+yv}_~ -zY>xj)WaMptCn$rICd(5cFMe`+Np3{!UMHf#5B5jJpI*8C4%VknPdZLMPVSyAFk1F5 -z{f9&T|B6!tui|+R3d3or36SAX`|LB5jDuCqI&24}vopY{d;%0Z@?qfX@a<>$mynHs -zROp$SC;!reUh)#}-MhCNl2QO%?!vEAeDvticVG}f6mNsrW_K^J^z>xNI!fZcNdMC{ -znY0&KSvRh~gal=9A94@{F@L!^XfH7)OU5%!)P6yb5kmQk!@|M@y*4>Y8EqzZc6Od9 -z^!7MvX!!VCm7qor+!Mp#nwo}<|G#*4dKNkM)uwUYFi2x@z~7<$$M&V`*#-R%?WH7L - -literal 0 -HcmV?d00001 - -diff --git a/components/adblock/docs/ad-filtering/element-hiding-sequence.txt b/components/adblock/docs/ad-filtering/element-hiding-sequence.txt -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/element-hiding-sequence.txt -@@ -0,0 +1,32 @@ -+# If changed, go to https://sequencediagram.org/ to regenerate diagram -+title eyeo Chromium SDK element hiding flow -+ -+participant AdblockWebContentObserver (UI Thread) -+participant ElementHider (UI thread) -+participant ThreadPool -+participant SubscriptionCollection -+participant RenderFrameHost (UI thread) -+ -+[ -> AdblockWebContentObserver (UI Thread): DidFinishLoad(RenderFrameHost*) -+AdblockWebContentObserver (UI Thread) -> ElementHider (UI thread): ApplyElementHidingEmulationOnPage() -+ElementHider (UI thread) ->(2) ThreadPool: PrepareElemhideEmulationData() -+ThreadPool -> SubscriptionCollection: HasDocumentAllowingFilter() -+SubscriptionCollection --> ThreadPool: -+ThreadPool -> SubscriptionCollection: HasElemhideAllowingFilter() -+SubscriptionCollection --> ThreadPool: -+opt if document and css injection not allowlisted -+ThreadPool -> SubscriptionCollection: GetElementHideSelectors() -+SubscriptionCollection --> ThreadPool: -+ThreadPool -> SubscriptionCollection: GetElementHideEmulationSelectors() -+SubscriptionCollection --> ThreadPool: Injected script will be generated for extended\nselectors like -abp-has and -abp-contains.\nSometimes the standard CSS it's powerful enough\nto hide something. -+end -+opt if document not allowlisted -+ThreadPool -> SubscriptionCollection: GenerateSnippetsJson() -+SubscriptionCollection --> ThreadPool: Injected script will be generated based on actions in json.\nSnippets are allowed only for special trusted subscription, they are\ncombating complex anti-circumvention cases. -+end -+ThreadPool -->(2) ElementHider (UI thread): InsertUserCSSAndApplyElemHidingEmuJS() -+ElementHider (UI thread) ->RenderFrameHost (UI thread): InsertAbpElemhideStylesheet() -+RenderFrameHost (UI thread) --> ElementHider (UI thread): -+ElementHider (UI thread) ->RenderFrameHost (UI thread): ExecuteJavaScriptInIsolatedWorld() -+RenderFrameHost (UI thread) --> ElementHider (UI thread): Script will be executed isolated from site and extensions scripts. -+ElementHider (UI thread) ->AdblockWebContentObserver (UI Thread): on_finished.Run() -diff --git a/components/adblock/docs/ad-filtering/element-hiding.md b/components/adblock/docs/ad-filtering/element-hiding.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/element-hiding.md -@@ -0,0 +1,11 @@ -+# Element hiding -+ -+Intercepting network requests is not enough to filter some content. -+ -+On some web pages, eyeo Chromium SDK hides content after it has loaded by injecting CSS or JavaScript code. -+ -+This is implemented as follows: -+ -+1. `TabHelpers::AttachTabHelpers` registers `AdblockWebContentObserver` to receive a notification that the page is loaded. -+2. `ElementHider` generates CSS and injects it via `RenderFrameHost::InsertAbpElemhideStylesheet` -+3. `ElementHider` generates JavaScript code and injects via `RenderFrameHost::ExecuteJavaScriptInIsolatedWorld` -diff --git a/components/adblock/docs/ad-filtering/filter-lists.md b/components/adblock/docs/ad-filtering/filter-lists.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/filter-lists.md -@@ -0,0 +1,42 @@ -+# Filter lists -+ -+In order to decide which requests should be blocked and which elements hidden, eyeo Chromium SDK consults [Adblock Plus filter rules](https://help.eyeo.com/adblockplus/how-to-write-filters), distributed within filter lists. -+ -+Filter lists are hosted online in plain text format. eyeo Chromium SDK downloads them, converts them to [FlatBuffer](https://google.github.io/flatbuffers/) format and stores them on disk. The [decision record](../adr/storing-filter-lists-in-flatbuffers-format.md) explains the reasoning for choosing FlatBuffers. -+ -+ -+## Default filter lists -+ -+eyeo Chromium SDK comes with the following filter lists selected by default: -+* EasyList + language-specific rules based on system region (e.g. [EasyList Germany+EasyList](https://easylist-downloads.adblockplus.org/easylistgermany+easylist.txt) for a user based in Germany) -+* Acceptable Ads (maintained by eyeo) -+* ABP Filters (maintained by eyeo) -+ -+ -+## Bundled filter lists -+ -+eyeo Chromium SDK aims to provide some level of ad-filtering on startup, but the following factors (among others) could delay or prevent it: -+ -+* Network connectivity isn't available -+* Filter list downloads are restricted to certain networks -+* Conversion from the downloaded text files to FlatBuffers takes more time than loading the first page -+ -+A collection of filter lists (known as "bundled filter lists") is always distributed with the browser. Bundled filter lists are the latest available at the time of release, and will be replaced by up-to-date ones downloaded from the Internet as soon as possible. Downloads will be retried in case of error. -+ -+You can update the bundled filter lists at any time using the `components/resources/adblocking/update.sh` script. -+ -+ -+## Full versus minified filter lists -+ -+Minified filter lists contain a subset of the entries from a (full) filter list, in order to optimize for very resource-constrained environments by reducing disk and memory usage. -+ -+Bundled filter lists are minified, to reduce the Resource Bundle size while providing an acceptable browsing experience. Downloaded subscriptions are full ones. -+ -+The [decision record](../adr/consuming-full-filter-lists.md) explains the decision to use full filter lists. -+ -+ -+## Implementation details -+ -+`SubscriptionService` holds a collection of the active Subscriptions installed, which is used to create a `SubscriptionCollection`. A `SubscriptionCollection` can be asked whether to block or allow a network request, or what element hiding content should be injected to a page. It will check in all the lists individually and make a collective decision. -+ -+All network requests are paused while `SubscriptionService` is initializing. -diff --git a/components/adblock/docs/ad-filtering/request-filter-matching-sequence.png b/components/adblock/docs/ad-filtering/request-filter-matching-sequence.png -new file mode 100644 -index 0000000000000000000000000000000000000000..e11074c9a52c6041b406c4b5b662694d4b11cfbb -GIT binary patch -literal 140660 -zcmeFZXE>bw);_F7iRhw4??ewVdau!vNEp3E5M}gH2hn?kAUX*m5k?So5H)%YqKw`< -zqmJ@k?!EW>z|k?M@5~W(*Bwg=gMoTL^*)Prb|Qrbi))Jc*ooJkj}&-xd#MJ`;^d -z=6S#o`Z!UU`{PsYkL2t2k9lY}@}A+p>n6R&#G3c%qN(f%U51`K@e21mMy^iV<{TQ0 -zFSi67oB4emPapRSkln;QxirVeB~X{gB7YNv!_JKPpFYYH5=hGr)rS?le9Q5lfBDZ- -zF_S+=eP{h&p4@?e+qe4rnpVyK={Ejxa&BK^y#K@f{_`$!t8ge0#>O6;CjXm<3<7@q -z75jhjHh{Aa8ev5vzw%2`2>kKhf@|6Kb_1`aGQAg~ERhX9^9MbGB!pCa_$08skk+JZ -z)W^MxRXMqavpxthopa5636AU>CJ8<^Z`t>7{0_vsm9FL0m#J8CmmW<>6|D_>v!r-cgq5{9ro$M_gVHXcLU}8*OZuU8<=`NKb@MrqM}d@$e$@| -zJl~j?81T`lK@Dr#lKU^eGYEffV0r`#WXhq}Hh2U(ZDs0cM5_q;J`q2ecCQebO>t}3 -zZA7=I$(;|m9CUF-%wAue1ecf4Pa*?tsnbaw{Q*Ub7JP1BU2t$Z!=JFclhNExZB8#X -z>woAnq77=-mD!KdjkV&=r?PrMUPr3hbo3R{$F4TyH^DKYb&-RfUTyK3a-!~WA5x(v -z;w`PQ@*e%bc=zgjyeZuL`h0wUcOYQL9s4Mk=B-6sQ0?eb&Gwv~M)VGti8S@&M7%m|gc+E1{o=o2F}zgVDdRbo0HeEGnMFR_`| -zY(ILh4+0bMM^DWX3;J|v;ugN%i~1A?S4ZpGTmYw)8_Q8phJ?BjKl1qOfX(Ij+B`Q7 -z>9d+u{*}LBucgXeay|cKzmti{Yr--D-bSt&O5JqYZdu+=m0{U2AIo%o+WA$k@>xAO -zP4J)LV)O`z&w^XF-~NnGA!;0f+NHPftMg5@&6-rimd*U25=oqXIuEngMbGAqU)v2)xH>?hDNy@DpB_Iq5?(#+rs+L3 -zg`^@~*}rFDmm+UEL`S%Lc`_^4up6*Th{xo=SJL)m2Rtr-Kn>6HLwtIAAv1iTcM*r( -zUM9+r$rb9xy|6X1g*b@6wb{bz91q__;9dw*=q~J}-f}Sxl>P4Yhj_{RTi>Ugqmk~9 -z1z=DA40mnP$0+C5`L2JGk;QF%W)HceVqKy@0Hyh8Hyq;7EoaQ}kuuoP7h701?oj>q -zFXkv&8|qPsvyIjCOz}Np_?VGHnB3*MZaHz5TQ^Td{thLcz?k`2w?VknP7PaKeCM`r -znQv;6e$Kj9Zs{6g`}>qCFYL;vQwo_FW;i58s5c(bp@J$+jTu7p(HsMDBzAR}TqJEX -z7I7D$W6zj5J!&VbX+I9)@j{t?0CrrlI5PpBq55hFshJP=re23O9%gYfx8=YSw&z1TFrzOC!sLXq#ehxFTJ@Hvi7MR(OE(4EXgs)Ng|yJ9n(OS0$V -zf9ldopARJvLwrXv77*<=CzCTfs6v0ZTRR8n#3&Wpe#y`O=9&Y&19*(y7UFBUAuV`v -zr$6A!DaNWj4t$j5nJd|jCFRu;*B50k -zdPN3$VF`Hd%8=|2Tg8E+PTo&-c#;>Je~Cf;^s>?0)#kmkxt1YBVv*1!bMN`Ec?A;R -z$Kp$GlvvsL^vaENog2PX3U1(T$9yY52sEB94EZM41@FaFiAeLzQl=O?*QP;21J9$~ -za*5`i1Rt1K$31*`DXkuuWX4{mM(?V8dNvTqXxb{bWl0iqbgz0IfRnYO`zZRIfmto@ -zr1Oy&;?0J=&hMq^b#1z{;nc=aVaW}db$;B2M=mrA`7r{wfgBSVFgoc_#xOPPyZP0i -zSadieqiiI2L6kFL4=)Q7LaW7+Qg5&>V#Ht-~6(unO*nrr^b$F-riZJE? -zO*PVYlaG4i24}5>8P4~T6s}tzV(<)>EqXEZd#mob=+0Ul{TJ?mf}@u;cg-p6&pJh3 -z1A0#a4oeJL>}JkiYD)H^oK*=!DYxU>yC9d&%kfCMCiKkiOM(R+g2?X3p2(N@ -znLNxM2mE3~WcT$AZQgO8v&PNWTNxdsLHM>LzDQ(M`x~TEvm%Wuj_<`H -z6BF(A3aBY+iws6~EP)?n?7EYyD!$}`ai&D6z_(5%XeFY>BWD6-dnUa|I}mxIp9Hxg -z5^PjyNakpvx3UnvqlsFvz_ub-uAY4LMde2dHaSk4_W?S6yn!?NQ)DW<7D(3CzIv1H -z-J2shR~`)m8Ru9LclE(6t5v63rE77_%D%-R^v}FEjb|kf55;S-UQsqRa7X&k6z(WpR{{%egqtNfOG6=oVhAg}LIQ?8&q> -z8M{Di>yO77F^3 -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 -z_*&wWuhpCCh?!cw(aHs -z3CeQ)Q5*wuD<>eg#{DU`Hq(>I^L1cR2qzNxmz>@K;PHaA`Mf4$O@>xNl$tzE3Tw)- -zLYuNa0TBJYg_{3POh9=zCHd!}Uy`;7{yr6Dy;Cw;Uq0)0?TQ1AL8fq^KOPIAYWU2yO~?+`=ny&m|B9F3KP*c$a_m2R`{(Ph;|Jnuj4F#D_1_lY -zfB1V9$majSD7sLWacQ#<>+T~F8;72&6O;_hPz6Ar{@zjo_=JU-m -zCb0>NkbSM`(4y{R(fM1_VZZ4-;w|aYS{_-Ya0sp7;BI$zNK3J4WTQ3Mer&1XvctE& -z^@IaC<@2I(G!nZ7n|{eP&^L6 -zP|~+bUHQ^dKCVZ|U06=Iv=SM=HA;G~6cqCMX876vL3Tt4b4ssOKF+zH;ErS`oT%3) -z@jV8Cdakm?AWSQWrIh~{Q3=7KYETHGS48rb%C0Lf{|H;K1oPb7L%hMuk2vg)V{XP2 -zr(3Idi_0VAb|RP{w@DLU_Z+WV(Y1mhg?a!Phz?zxI_jR4_bdyC_&nQLiPoxI0w4u -zC*&;4AN-w^`#8aQsj^_SLnLdgV4pAbezDtDnc~;q(9C+Vo{@=_aQQ6Py#9LkB;%(0 -zK(b)PMCbb!e0F}EkOKi}Vvy+LF?c)%_$1{^xrW68zPPuxNn$z+ffhR^i1-Wj7rNPB -zzt0MBCgq#sGR2agN)6kYzEYD|{y1DN7cuOTSQIMFajhHe>m5)}uST9=F;W+{BBFxPcu}L&$tOAz#U5J& -zd7{&{nf1h}W#tL~d_5n!vdj~Ma0-Ki0-hkA>VrOY4pe8{FgTyCby9O1xyxSqME*l- -z@-%KNXL@}>9Z=(!ibHLJ%HSY62`BCXua84@qw#Lt8U@yY#0B%Ri_@Mp{&4}KI2*Bq -zo=67mor`E3b|E61t(c8>-|7@(V2v|;0K2^OxtD>%nGzH--bVG6HmO;e%w-Yo>sq&O -znw154luLv&{nK8Fr;06ZtiUT-Pv<@Z+MS!|gm$&Gb=hI-1IuGulTU{`c5bT%?99RT -z%ogU)%94K(ZR{UzzXVKSAYk>`h01a08#uq)^`+r(MhDSF{E8Wg+uhjm*TZsRo>SGK -zdsNI|Unx-aA=B;Y`b3VBea(P4ltIAUnu#b_a#1l0s+J_x#0tO$`!Cp({1Y}n&NR&K -z;0%u90PtU~;?Th^^aN*Gyu>Wi+26o+HG|v^`v(7yA8o6D@GNFnPELB%c}4%IHK3z* -z)8*_Q+75q|mU^Q>Dt-FwE&%L%_!HE>cx^mGd@ua#4FPXh-+`zYffgSoURe~skqB@d -z*0~PLqXmz}dP%9-WO+LLn#7Utr?1rRebwCNF3Nl+dA$1g-i}DWX++QXgql)rf6e37 -znW3frGJu;#bpsuDMbL(tq1_9I}W7_6KGNX1@)r;AL{=Xpx#__ -z#|#i{15RglVbuMA%JTCZFwmKL4~8&O>d0N9O1E?+*FVC6;dU6HjOE0=8vuM9wECu= -zzxHk>k|mYHkK)>=RwB7WsvDO1=Yff%ZbpSLm;LpYtsI?>_;QqLD>12S0-ni5(f#ElyrXa#&>q9uhjVndmtO@YX&>j~RK7fJT3cpQ6yj_1X -zUSJ(B!TFcp@8IzC6 -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 -z)y>=a{ -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 -z5bno~U!sw|X6XdJXXr!nFN1xrlmD -zL~S_d+LB1b!N*QYUVfj0AJFHtVLL2^O4f-zp>c6OlCGTXHY~wi4~k}L-S28XS2kJT -z&<2<9^K&+P?BL%6hgcqU+dGxyk$vyMCZx0A`{Zib)AG9ckWZ)fi%(9#ag>!zj5}ws -zW4`+VNE=y4#14Mot{6iUExLF3^?(Nov;NVnTs6L{b=$?Yo&7z98JNaB{hjoK;-FjW -z?1Gu_P&QVAEi$zv8@eQ|8n=y1B1%47uuyr8Za(O2kuI4?UpTTpQ@`_D{Ev*JEFcJx -zc_lpNp;%Nrqtu*T8mJuuSmyl>6(O78TTDfd$|;){;uWY3U|m_{O?tBS`y-mDLV61| -z+Bvb9^W+l#dQV#Egk?oHJ%dcrC;e^MdJFm42vO%`#j&h{fDQhs;M2~Rqg(756$})b7R^(`Z -zqV9Xm=T`UtRo-x-gbf3#XyYt{hFfbE_j!P9Yy(G;OL=&9xN}6uLWsc_9~X;-oALJ~ -z;hRs9KgXcGOZ)>Wr;U;dN*F -zXnTZO_XT*VxVRi#gadKdO}-=RX1~qu=_$Kf$^k>0YfmLYJn)YP=4PG7te&f-*pi5o -zo1Pp}uNdQf5xi!(dm|)Sc!|SUz|t-)V(G5r^s`G|GHianOuN7MWUaE+^;oMX#n&Uq -zF5*AFm~|)qNUv1mjNQQ3`owVo5qv+DaKGcQhmdpC|J=%%&*lCVfxpOhg%>WWt@&9r -z7r$N&N^(+Cw5StMY}Se78XokjX=voHdx#Wwda+{6L -zHTH$ITE*241g_H&zR2Y>Q2JbIvoYutxbBil9!oNJ|;UeY=0iR6i8eVAp-OSWC$zBmXJ~D?a<=m5id7z9((Mr)+n- -zt3>QV(=y*5)EwDjTuX%Os4p_!do>lK(v!CIe6x4}H}TZdK|XdmESY{@N~j_ta6`nngW>w;-4Q&74_d -zY&9#X>gV~;5gh{tZQDLwn(d%3OMFF%m9mc}hvrrC;=T;p(kU!4^J*WKkqlPwAU{?W -z_hfeM^}J2n)a|PQ!<|&+w>ZM=hD2Re=Fii~S&^P*n0g`qggGQO&h&3dgjbe$Pb~fQ -zp3XV_Oi#~BX}C{fyXK$fF7oRG2WD#CU5yTFH^o%~W(re^?r+_3iWZyxVpW{zCZqTJ -zBj=A+pySU{iV+-PJ(^OSGeXPu@|HHYA$M>(pZ4ByW{9~d9m&&ZJP}NE^qHGazP+-E -z#?Wx-0K0mQvHy|eb;O%pwGtrbc?}#Kf+Xc)&dI!of -zFn*0+j|21R)#YJ{;!E61Djx5G3ph#h!#oy#jF%OD&jr7;+ik@a+N*cl_ORMmJoI-#(R*?X);5>=RR}6#vQZ+)>mcd+Quu -zp&OoE%4?Iy&L&V*&a^PbFJrtDnkEUoJ)Q?E*V1jBI64*NFq1>r!soY4N_hQ}cgUsY -z5gu*Zwx5*l4Tf|JAs@evQ2hu|ZytG{?m|xHc9iF0r_WnS -zjlOto6HZ1&PWaq&%cQoAHcb<=DzPHq*QzF<`YB4{Ie)102VwNuqaVTxxR|Am4gH1S -zXjvgwhPNNa8jn$~6Antv_v}NBUuan66!q`JUombg7|{(YIU`DJKFLI@7q+;B^dJCe -zSDP7QLW{LCpWBq|86SAS0dyN~(KL;^EmaAat{5~Q=r4VfRD3-Ui#tKZFumI#n; -zG(_)Cnl|P~P}tBh7_!=N$r?;v@Im6p<{qOm%p+ccb9Ay7CLhN3-Kbo@AA(n3>Pu$b -zU6L_&jcuEN&!8zzyR=7x^La|=5Ff=Ety;$@#1h|#1{;C@4;vv`c1h`%p2fF;r?=wl -zRL`X_w?!|lYn>nO6d9c<@V^bFBTN5QSM-&s4CV*5#l97%eSh4i-z9a$pO@zsR=Uqx -zx_(S#?ns5)P*stLdIKg%s%#>G$Nxn~96OGp_q_D{`(2x5pLG)Ruj6Fdro1H!*^3f} -zD`NBVx$(7o^zK6Y#=F`oYo``Hw4MEG8t2(gCO+Gh>wXZ#I3s$wy;g#j@KrC50`8>9 -zunH|!gYyEbzV=NMo8O}dI~$#hl3Yyb>K#C6C) -zb}7cjin6V2XzOH(^1?@D-DJhn7&RzAp^lJ7Vrh2+TFxp{c|QoQ_miRa**0SH4w92w -z;a%}qy4k*T*3#Oj=9J1{>v*mHU_rsc?kTyYj}wDyH=qc#__+XC^5v+@n&DS#TxfM| -z=Gh1)a6{IMeU+r8f!e!6Yk{|^ewPB@`?V_8L_I9Ur@F!1{;9g1-8&7xn3qJKIXCXf -zsKaCT+bo#l7^5$)_(!yIE?(2)g=*NAlkS!0ypK{@FPq)gkL(mVUR6y-KNmw?`@)`5 -zcu@VSUEig;3vNmmgg=KGxHMe1(mrX6DYb-hT+u84W36wYq^{i3UeM%L;DH}DB|79$ -zDSGHLfBA*h3Nq@{8+at{mr=ul1(r~`EPw02D~lGC#iPGjeC6tLtpl0UamK64NEvhE -z|6C)z=SGL!7KobTI;b^{opalEhnia3gPK}}|J2m}r+oZB<>UV;AO9z$gz2)T+sc<3 -zsBsp`{dLihI!vmJXaK5dNAenYpG6h}&1he2Bo&a4ts>kY!)#9gyP$#pbaxD?S$(*A -zgy`P!TLZKYbgH(hT9r_Zk<-V>FAaNDO@`^O;Xhrd_i$O;*)l}6MBqMJP4SGIFaC+;;C{TFDBBe2&o&eikZcyrxU#2X=n-2o;}QuC}5;|4o} -zA9c^AiaOlJBD!s^z1-co^s4${$1)TjA3p$+LvS&L{N1O5$I&r1iW+SWT*->X{)9 -zs6_pNf+HjHTFRxPnWoJkR) -zka|Rfb1g#Bv%>8FDwrTUj8sZ<#k=JZdS)_GN8q7=A;h%2Vru$rXq?wT1OecoZt8t{ -zOn?CCN!7G%L6c#Sg)%kakX=;PVGG&lF`v{F^E?Qk#yK7Y~>DAq+Ir -z0VWb~g=qHy&u7&=fRo0hjSA%h9w-8Vmgb}QMc}nM@;ZH#kDoeK4u0&YgzlTF?=(XGs6I&(i+_vdILCJ;-HWz5pYs33gm*z6^t9wo2 -zS(HR)PCP#tUsV863}*I&{@%{7kqE;O#+C5h7-VCta9P(fn)m=6DOZejHPKkS`rXrq -zh}KTz40&w(CWqe_oiFH6dV!!W!>SM#BcVb24(riCEQj<7V)QoNB0KI>Jf3ueO&tq;7SLhWw`7RCf=A&q#wQmU595Q9^V4p} -z4pft#kT+-O;%Hu;KSX5Dkpt=}XZ(eNA3~Lz -zQ97F@jm%T)4HT5@o2WyrU(LyIZE7HT%2b_9JQt|CW~p#JQVCh7bHM*Gn1>Q5fz2GJS{ppazpI6YE2jv&)P8BIdbdCK9ASI8y^JBfe -zW{G~+_Iqs}!H;$yP}|wbgC1P`zHJ0|+Ci@JWQ>$18V5jI^T`Q2>W5sQ+Z^9Se|Y1C -zZ;U7}o#6ir>A%iW=k6`~ztsZ#-#241!1*yUpWi~x6>HyqZ=$~uVSQ&HM5V-G%-|sR -z7{+_?n*+g^2FPLsszs(^fPZ=f9}%W=T>rFj&$R^B1F&6`su;Mg4*P2<0a-~7P+^lG -zMA|?E@CY{U$mUom1y5$d9rM*zKOY`UQR1tcZ~shnb+9@AQd!vpirL}|Z1Mm8D+dCO -zeBu8cLW5hcs}LR+&}3XbzgDNzf|FF17C@PdK*^a2`_Nlb;UPD>%`vuphHik@-1g}H -zO$Ai7PX#3Bdz46#ZVu2uH^>~&C)~id1iZj9P@alp{YiDX1&7 -zoB<=j^b^=!bAUFeh*y&P6L7(6B}vKerTKKJYMsjfB-RL^i0eJDjW0ykgG?@S&I8tQ -z1OQexj+ZUm!W^B}+T`orb=-fWY4+j!F95Hxks<)f{)ps=NiK<%z!BgJS7!J8A~lEs -zj#_3Vv4w(VV6{sv{5AK=+Q;q3%}RCB19VrVGxQ7~=&MVu=C5O>L2y*o2q0Qqx}GlV`;2wNc!^O3y>Ek -z{G(^pDc@+@)?0s}Q}Y23Md8((6@YHPrniV5fH~4fS{8(B+g?{U&n%qeJ$`q2B9ID8 -zHGO}hw1?nKi(18x*UKV5GD2n!jh%d -zS;`=P7SA*CvLhYJlH%Qrv%E(s3Q`cf)d-N@MLMKD_--`xU}sR)N>*K{!~<|~j=KY? -zebz63oy@XCsGX2r7ZjM*CkAZ%X>iV*X+N01s$Ee3T0F?kvWMfxVj-+*9BBIEV)8C> -zQBTVPM+pY#8gHWDKxlhCs{RazJ_Q{X{F9uq02LwBo*2yvs6^PNp5Mt^+iNtDpuw$# -zI5mdV+;Ld>LQjDpRmN67uOeu#y<<+Hz_zRU9jJ|yasqG34g@?v(7Oe%l#Y&~LtPgN -z)i$BTkqsf4^F{c+5Sn*%G0D|ST=viQ5$GswM98JlfYYo9PR0#$wa?wcmx2sERa4L -zhL^{W@w7p|12aE??oMleMdFA`wJs{C^ -z(1gSdFjo{nKPg{uVJa5ESP5JQbkwuAQ-{9&x~`15GyX{cy`%n;ru0jT@b+sqhX@Sf -zm=ct;yepSjFjamQN0XY3Fgo8HLYinSe=NeDtGzxwuJ3KqqwBf53@~cpM*!GeDe;*2 -zCJ=rfW169*eltvY_QF2x>UEEYo(DY{Hg7GyTaB@6j{K!n`LTmXx8R_ANPb!)@@Y={ -z4IW>l6~KzrSABmLp;gydp&gI{VWhvrGlyrTQf!CBir-6&Rvo4YS}1&YU3yF3=b1zM -ztA=7Njs3M0Nir+9IWf!@*P -zQqkLZha`N76-V~XxzDKwFqBtUz4{)$4Nl@)2Nbf<(_93H_jYp=!R=;1N0t$!F~=yo -zw6ksCK^Kv|5UHobJ4mMy1mSe(N&$dgpT=7qKg&VQ*}gxc?D=$s7swj6Nat0my34ZudDtM6{t+2jt5O{Jl|DbC`x-W1t;~U9NO-XE&v^8De -z?E!G;{Aps29oy^&m2Ln#nY6AN@n@5$iM4my(gNzzjW~4AJ0kBrBw>8nM~YP}ciifL -zXcRAuoU)2aKUx%t;M8tx?i!tU)wtYgy|YqR_k^aCZuTh0iN#M;j3dp*Nxu1#hhV($ -zZfreEa!T2>Q5j0#*(6_o_JlDY6TPxScARb{y!=iN|aeCDCNO~TJmiu -zAWp(J#6hP)UX_Af`_U1JZG+$_X++V0#Si)qi+B>=AUV@FZ06;xWHmCvsQihp7k^5k -zHDAn|4o(P6C`YJ$H?c`IK-EJPnj58b=o8~{T5rbZ(c3Sn@^m+=>2cYeRN2|!TfjlF!!Dsk4ydB1 -zkySwC_rq2gPxT#mKQ6S1p3nqBZbXpa=&t~K^~yb=+Ua#8ra!Puh$^t>}q|v|c;S#-4EB<{{pS -zXrNC3?iJgcb#5zQKWH%&KO27hu>guQNt8oU^@}sZwWA-Gz0WyhpT>lbwQx%g{JL8g -z5Wl5KKA^bQ*HhuBDt=z#<>DG|59jB_dmFaj=tja1bG|!lZin(elp`e1p!91VN?8h6 -z`cz@{sIf?_YT5-ad>!4%Z9mIt+kT}|R#IBG=889MPU;@C)3_Km9a~eZs{!1-Gpohz -z-a`3+LC0msk&aP3#nmL3#UKm!0~Tz`<_9-ydQoK4o0LS))gwGNkrQTFC&J)UEqc%m -z*O9g@rFWQJ+m4wlpH;ql&B?~lCfL^k_{on`bx@{JEq#QB4yPRr1)D0Vl5?7sMrw~) -z1#RfrnFy#ha}VPHc+r!)_GR!A$DHU~hQKOmcPqL`uC0t&l4JELPuBOs6L(_}06%T3 -z4NJ(^02Q$$=0p5ji2YMO%*vzhW87|2!8nVTZ$fYb`+KJ -ziU>3kBIHDWK79Uc&s`9o3d%Vq%S^T(Vg8Lfv9|0=pB=w3sCAEH87{?0iJw74&+Z$= -z2&*!I@PM^U23Kb?oM2~JJ+3+6^{jXD%u+fKD-7N123$?VtrP@eEZ<;snXUCIT4ZAD -zXHlhiYu2}4pOZX*K;hd_tX)6(Bg{nl%jw|ci#QT%f%rbgUvr)E<^r`nFGjTcMFuhq(Jt%4~Yx%us}YRV>+SczoBW^sR+S`{jC -zJ3II2RlWx=FK7ghazwelredekqFT$n^QF3=BNjJ65tJI}%je|8*~&@E@n+;{+1=sA -z2WKULj&S3UUA7ba_RRzK`O=PPC!5!Ci)$WWSup6>jD0Itn|__G4}7>gGnR(!uVt;Y -zZ`q|LID6qG(myjj+wsgFj}qIkVy%aFyX@t(q6Sp@7jly)3@W})rHN&Rvv#&+SqnA<;O|VbC#GkZ*;42ZDhqs(1N=fz|i%nvItHr!S(3N3Sr=`TZ -zEZgQ;W9-C)m((`U2%gfkvSYL3YKwL%o?}>HYnU7_-2p_nLBYTxEtFBNN}#5>Q(`XA -zkSz|EjKSI=4=N-oQPv(XYMaH -zQR&zeN(Q*D+KSqJ%f72A@-FwsHy(e)2ie$WT#0=`#k@1@bq#)fdxTr^>|pNI7VaMH -zjmd!Bf^Y-ZFD#b{$tbZ@PtyxxZ7WpO^?jX(4>wHFKHLac9xw?#=|~*A6%b=Ttie%- -z6>-ONsU4Lh`0-TU?*wLwZ&9tAxthI&`3$%Jg9%)Eh?<$T`29O;mQkrINL=nKnAx!{ -z-?+dbnSnTwxVw>M2v;~r{rHO=hszujJ%WIZSXqpgSI+ -zeJ&5bRI)F}y#1L=f{L=Mq>Qm>c3?ed;$l`RQ7pc|UbE}xd^q^=Gv3=2Hna2%tOi~3 -z<#TJl&LtN`DKunDTFuHU6R%go927`fz -zoH-}kj?w=Z&{B|E?9UH2lq*s0&5==9?}^th#kkM``LB|VK`c^_cn9GYNVGz=9NDs8HJQLbAu -z>ToELv1g`$XO^J5+4({O*$IUugxO-{zWujwj3VO;rCD}qYo-2?NRMCD?&9l5(#`jR -zFU4j_3?~q*i}|KmL~N{F`1GF9{WD}_sQP(5$BxyvM{liw=_+o$`Czq4I5cbem+%4~ -zARa7IbT!6aKduNmj8|~@2Ns%NeR*WN`B;Y1`4EE&=r%gjN}!UsHoS+dIo%|`+3DmGuazX$Kyj9g_sPQTGtEMI5l1c<_*BrXo?N;m$ol -zZ16~ai`WYI-n2o58<8&=zQNqH&P+QTc6c$9n`Bm;LntwNTCw6%kazM$Lh-HFJT}MI -z?xxseD~k?$`3msPfg;vRz0g{2Z-9GZljRtzt5i#bABe#z1PkuYJ -z*E~X@{T3nV&g$}WiQEcE^@P}T?ctN^ZJfh3h3aY|)6ybsS(osyNXp<|PRDjQZoL+? -zv2MldSzF^?qpQ!9T&Fgk3E7JLCh~b77M?@w^y**y1=vE0) -zYp9Y!vU&`x;P8*+3K~PuX%|M)uj@DYcVg6+ilFcF8hQq<>uJIP9-8$IibirKEiDF| -z$3&UZH8R3;hd(=rsJ?v342*g_Is@OwW?7up2aE;i#!Kef+faYzgri^D -zw#*}~Ra~ph6lFcKjCY&sRw&Y!Y+A@2B3I9La;BG#E+=@)iQ@=>?G~nuPZQNBy!@_O -zP~;~;z)aTDd7^T3KI^!dSeElx`5`f-ih -zvzThF@-NP)t}Wh`F5Z~*7*Efx5XG6719eobYbBM(DRD?6s#O;k)&@&#yti8t8s*ID -zb0zX?a(A~hWJ20n%EEvx&s0JehZP%Nl>70lxs*g+;V*5@|2{_f@b3(Uu(F@2fgt_q -z5RFzRFby6&8j@J~uOZOZGawzAXLxCNen+M3X|QyaQ&~565GVLAulW7$@ikg3LcmoO -z+x>f{!tgdinC)AyxL=w6S$>{_4VlgV{sswGP~vh+HRNJv5#&0VM|0}ImlHDa-$2U) -zYltiuon#wvh4!Dp8$C#aw`NS8|L?Depn%q1zj!jryjrOLQHS=sS|ov`CymwySwpfe -zBoO)8^%?*A=$ameC=_BDHq)F#{`o|*SRroZ;b6I65rY{ihV?aWPM9m*GAZKhOiNEftTv -zn2gNuDW9t2{>;=!n*k`3l{_wT;h;fG6erR?(eI_Qgb48v%vHi5{-sA)NYDGe4V5Sz -zWaM8zu!WEgvPd({Vvv*SwDhbqCz0jKJYfwa@P2aiIu{{|cv3&Ju-nXtweQ74s78DB -zYUUsfRfi7+)v?-9Y<7+OkonOz_P7tic^}rbL+j@xG{v1(h8(jbXMC8H7Hp}ayr<@oZb95uZ?fK>B2W_6`g>oShvXLyN4=U?kFquIkk -z;Zq4r@b+0T?6IjYUC5Zt2!J(-Zf>B&?uot=p6qmNUvl+lDygc&hor42o>oy(N>ypW -zODlz%D$0iiaQ)l*O*~W}H9X0^5{9!VbmwuYX_ldO7<)_9!C>zZQDRck25ufbgnyZa -zeeGukJ1OBI{BpSBXBso<5$|$*GMa8 -zmZ7KrUmnUKWNQA~Ki-=lOk=_+^8fZ*2bX&Xz%p3z(f7}KPx1Zm^v$^(0~7} -zgHNeo@#XRBE&kmo{}}!#BJV>ntW>bp_s}CAa)ucc2l!_LikKvQqzN%&Sza^HwB@*p3X2Phv%&gFIO^2 -zq?a}`W?(;3UV9DuB<)+%1K39buz6aDtF2)l@xaUMZ=duizbb$hIWHj*`%^nZr(Kg^0(OX3DZuMHqUfF)xQEl3a)S?|1?Y1HsW$gno( -z@z1-&!oWn>lc|<{|D*GY!CZe)cJ!T=IB|>50aQY^<6rMY7(B6i*oYBvdPNS$T#jyz5R9`dWjdPW@>&>C6`|Mo;rg*gin1Mv{h9S{{#G4P-0@sf3Pjs2xGVMmUoUrcmz>t6GLJ^AiNf_kEpipuLjXw32x$XDxe9@`f5q^uB* -z7joOQ-iAQgu-UM}^e&)e)Tf$b1?CJOrj@7cHcIne>h5ukXGhObmly*TkLP!Ux^)}c -zBAX#x-p1rG;s|%>+B2xH1nq4?#o-U{0IPs;5J_2=8R5&cFENO4diBETA+sT3^o>?nX|R(2+@opi!!{1cXpY^aAbyY? -zU%NIjS9oEl*ygQiCT-pqMvOV@?$)~9P!D9PHi$(snKn+Xg#<^1P)f0T!)y?pWsc{avHb9#WL1Fbks;3vz7xyY+b=kDXrU3 -z;*(F#A{tS>UfKt(#wq8rMpKWPCCXTJVIAhWbK^M@hN5YuE1{uoce%@IYVj7Y^W# -zK+xM;7i}H{MAcSu1=IvT!;4q;Ja -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 -z6?8jx*25HjKA(*mg52mNjxpPCcRww2 -z*GaK=p}wH81X*Jr3Zw6QdMiD_DvtRM#Pze%)p|WH5v^Jq?IBu&3T0+=17<0?(zggI -zuXHpU`$qPeX6W7BA4}3x@;sf+B0KA8y|!n79n(sjhl1Mi$65m&y5@=$oyn8QCj5aI_K=L@TjZVxl7ZJ_zoONJ$vsnLAoS}e@vuqnOd0EIly61 -z>W?z@#S#xY7)a%~mhsQYZPe3Y=Jb8!SzS)#AJggSqQeN^Q*c+EyZtFY#JZ>EaGyoy -z5=$l)RPfh~^~8;oxe@Y(d9{G_D6oDPlZUnDl!iRDiYy(5JTVMoP~u}hr&xLZw3M1x -z#$XFZ&I@JB_+f^SNmHRkd?UtHPmbbJ>SWpziMN_Z&NXV1AH$fFZe20`scn``K)^{n -zXLR1GPBc0W!Rr`sM!DqD754oS)eda@wvz}aQA)2qYk9$dRgVi5?M=(;M4?<8K -z6{rZ$4-gm($M{CFp=KbO5t7{uaWIVz8ZcGD&UhR6;yy@Q>g5qXS&y60OA!`IKAy0D -zTU9;7pQk{fj8gGRzVsyf8m}yy#Ngc&nTLd!b@(+U>DLULDD=e>U?7FvvaeK{W{ExNB#oRjUm@1ayJri3 -zJjjRzQ7k6eI^&;0#!|Ph-<{56nJHVUD8mA_kU#{ZUH5G60l~svNIn&&?t`K(4E!QU -z?>m*C6^>g)c!|R4hrQb&B*?q4bwLcj9Rlqvy2(V?vHbU3h;*w!wBpNo;Y6nu^RP~; -zcid}AX2DEM@V|`ojwtL42pftub(t(CpMoHU8J&>?x|0^7K60S0xgkDZrzZEOK%JMg -z6}C=osj^$_P5yp+S@v)1+!sRVyfNpTAhtISv&~iM*NEpW<<%Np=p|)}OuwUhBn!%v -z3Yq@)KTZE3UmRp0F0!y_m%)*#B!)jy_+(|?)2cVmC~ZCh;`Z!e*)W|IkKI_|Xn=v1 -zy-IHwXorEuetar0RDcBWGe;Zgz1v~LA=z2~d~1LTrR+i}RiU7HcjuVa)0hq`hbV4$hDQE94FL_rhk&4lusI5eXhKXiy31Q?2A)1#>0BfcssxF9(8Ezg -z40E}n^1M|{!W@IF;+>PNubeQIP$VA7SDvMJ8Qri>7{Ab8w4|4&GlEo$q=uEdZY*}g -zZob#$TMrRbQMgsdZ4+a#l@(#uETfLaOIOK*^suE@M^FYR!D5IiEJ9@M3w#>V68BF( -z)mp|c#pbz_iv_4hqSNzA=$QV#rPESusoXBr>+&T_tlYDs!HlPRIKWJ@G!nYxuYa?xq_{Udb&$Z5bqlA)ekt(tw`!hQSaxgXc2 -zL0o>&IRnrB08ct*V=;CqbMrEU+s^S1pFFe7;vk5~D08^thAF6P# -zp|_KJ2{RLYvyKo<43LG*cq!T?&htDxB-m%#-Y%`!49EC)+*Z@cq{IiSAaeEXx!_ca@ZTT*#qec$ -zKy74lImsM6wWK!^9iH#ax9BS{jd12&MxvEbg63vBg)nz9x}el!O{ZKOJ8sYElsS^I -zyl|Rs{bWbw=P&hx;JtFZrHYD*kj?1lab-V7WVlo$jWHq}lg+W_ci-O(+-x2oKZHMe -zltCO*F!L^riu3B{LBU<+`Nn~vrk -z?-k#MXj*lpso0!S$VQ(~*PH*ID`e8lOz+ll;7^$m2eC=w&hYSLmX`;4I6=L|X!75|rwo8hqL_yrMmFIVC?4Mz6BX!9$Yd_avoH{#(BqJAiH+Aalu(== -zWH^^4hpZeLBGD{SPf7CeKUxl&rK@F=7rxXMYjSXRG;d4t<=!OUmar8!x-OGI3u3O2 -zn}(!jli(x@x$$C~{gxXD(P)ns75lAg@Jf2t)(Xaef2U4gAzC+MST4%4 -zVu^FeG$mG=aGbbZ>*FJhcaiPqP{D{+i=kKJ^pYw?be)QovDV+y2Z)M=Vcxg`_pCUR -z*YOWM3XhUz%Vm&vyF9cUjG06k!~nML#BiuyH6?%1T6S^nTLv6vOfas8%q1i!3D_^& -zw+L#zr2gR(uuH(Fn;HLn^nEzvbX`ekXyduLn)CO!-4L#`gpm2U>++{mn32aV?%+Y8 -zPkv2EU+QGc+7nf9}ENh*FqPNtLLcDib2MtOO8bL@C;<@>kY{RO6XhZ$|Qr9qF= -zYesCtO1=PTd8sX48zQBMJQDuD&v>A*cU3G;%|%xIT_KD{QrN!ro#{Ay?QcGWfJt*fiQQD+EAG1qSMa=jNP<|!`@nY?TFg%~;Vc%=#<=HK^Axb?-xmk9;4mf0?NqFA*3kLw!VPI@ae5|5aFZWE)CVaUNoc -zk8_W~xQo^IWb0b7xv$+eUdz-f%GyX>2eod5vfstK+w)uT{Bg~}g}?uJZy^c}ahd-( -zXb5O-4QCFQC31GpVc1-@e=f)@aSTBRnHyWF>vxsKyRu3qnbK2JPpe~#`Tz2exegYR -zl0g@Pz~4T-m4y0L9;yvz(?Xvh%u=F^zbrS{DcuWEl>pnjDev;pyodMpm`Tn*M)9Xt -zv^PK$Z{-cpBTsuU=!9Y9hUJRULcyYDSUA}U;+1t+rmy{KjukWUZ~>rNp6g;sU5Mn~ -zW>Roq$nW#@_oaV-@?}V$^@rc!Bmdh?@w^|CjIT=kfH-$|*)tH*%d2y6cQ~D%H%&oD -z$y|W!IQee_ZfPhFdO3b6by{N8&>esz=3h+eJJ4KQnFc|~n6Cv?eGa6tfY9eh2q5?b -zMd%|(qaeW#7d972N#6YA1gEd`u8OLv>iPZ01SHUfj=aGoxOx+EsBM1aEKx*#bs;R~ -zc9V^{AXDF-BI|TZL2qYsMO8z?6rv^H5zS92@H$DkP58& -z_L|3Hp|9{GYO`o%lF1Q;buG- -zWT#oKih?^C;Qn3x{6EmBkUpwH -zAsEspOKJ0MZ~bEd+~l}eGSyc@A=xN~CDEp_gp<7WEkwMb@`y?lPSEVEp -z928ph;UIzL9HgVjlvOm6dlw+qpPK1^Ron-IKYWBVCnerPw4(v)@1`FwLX)iy2zx;t7gS*J -zqKoA5yk!x$tJIB@71VP#9_oL$o+y&FnU1=(!)(3-q7~YSPVT|2WmNBjT;Pu^D!R10X8JLi(INnBQ0HO>QR -z^rAqmPPG(4Jx1^Frzh?Vqm -zf3)NO3m?3Z^Eoc7#$XG^kR20hEOnTh+;wJTN7P7oRRq7`qpxAdyWh%Fgb_nEhK`iB -zW$+(I!Jm}y-;aks-n*p^%T~Q*>+~1e?_V^c_n!-j{>?3s1Bg}?bbU_60L#DJ<1hYA -z@B&F8_&m`Kx0H$^IC@U=!87{vA4h8ZQzE@fkW{Q36~a^met6>%3Rdk`do#3fn`I4z -zj!0EU$3n)ReBC7?2tvWq=s(RFsf}Pb+_4=Wbljt{+TeWs8b1xt&@Y7CYM;RnAK*hk -z+fc@jG#7$*33sGm9#EsU<}eTJO(+^JrGo1(PxZDZD|9qgK|UGJI|&^GC(2}Fq#?K} -zax?Kt$Z-@;P#el-_4@T|-p%*mj^zE5_IBk60#Q@n1zw7_~y_)d$)!Sr7?oBw~^Cm|q*z8#fH?#Tl-bAQb_ympG41 -zIHGB9!vqIx+6RLRx(mMx+pr2e!?)?C^4OhNp5V+mQ4yr5S_|H)O|28Xsx6J;55iD? -z)`L<=p`*?94hqCx_?yanfdA&auBGsx{Y!Dkxqh^rxG~qB{EQ@~BkVHx1@R46`K`NtCWA~@Gf-M?#nZ3m?aH#&%DBywFMPiP^|J3`pOp8 -z(n>(}Q|aJFZ)RBH2DlIzP*KMFN|h2WH`dBE@ZHu#l%f?lpEiQW*YP;=4gFv_Z^eW5nmhdGv&VpD -zbGe4X0vfy9=?_3}yd}pe<0A02qW12D9i3m3*jcU_Tr}{FlV@`;Kq}EbZZDRZNI<== -z(Odb@j}W!#gM`fk$a1_Z+e4@cCYcCkiEY-Md~M;2*})JL(utmPUIjn0l;sQfjP{4H -zj?#1Sbj3#&B2D6QkvbiO^P_Yy%dD@^1DH&TS>~hsLlo66?fIT}JH9H?


$f}Czh -zllS>P^K1uCQ~!d=#P|MqV8k0iInj+tyFUJmJ*bb2{-tSTlst%;%j9()vM3sS)S7T* -zae4t<=`!;;_n#VNfTTnYA4?oDXIBpZEd=KV%ZmDgqX+Fjmo2`H#W^h8$y#!@3toY` -zv~-&mAWdihe`WSEt&b9vLi%=MG3&PEHGX7vt1A8ALCymeM_hXS-|Mp0Ri-nowX%Rj -zCqEXtzb|4I+9kmG86aPd-x=qEl7P9J?nt$!ymqP@rKJ5ZbF=Fj;_ -zS#o!+ab=IJOLHT9j7##@^$sk~ZB?;o>^_B%wqvgm#PN5VGFgJJY;iI-4IfQ -z-~CRT(>dRBzQ61GUBB)6{&OxdXXZ1X&-*^_^W4vU-wy)AwM|sD>tQPHsW`hA_$OIC -zAt{TKpxC^5vfG5}4NMM(Yx8ZH>6jZzH&W&z?Pmk1HW>tKS56tv!&wckz*V*A1V9(q5zv1JLy&AEH~>ALr>nA-kU@k=oSYbixI-wlmgf@A=?hW@Prl -zyY1?BfLj(AXO^f~i}<)Mg1K73PVfut -zHUjYw9O$(Y{N&tURj+CVKIuL?b+gn2{-n&=dEHxmokrys-G?Nt^E7$ywL52 -zIyK)THKFeLRD*@i`uPdPLP*T&9>ryKDQN7wMmLV=xxCV)1*?MAtYNOw@nAx=#)x~2 -z1Z=Ovw(+0yd-St=&k3h{!Kx{9B>;$mcs>tPS)sC?m*C9NdsHwlz09I&y5}deo=p$< -zMoz0`|Bv#tVm;?ZHfk$5K!b>Oi&ZMtE{FK59h0Rw_v2okvE3LJpod~@;MQ0 -zm6ImpZ19^2CbCeOHb*-?9TAEP4!< -z|H?fGNIElH$63wjD=7a!F5LJDrA+K#A3OQJTYK$2hq4=iiiwGHA8cI_Z~W}usfvm< -zal?%Gk}d_-=-ujv1B-=2l=ntu_wJe*g3T0FaiQk^`}YFhw0MGU5fPQE#d0+Axx)WC -zT#(zvmfxw^9T5!*UYxHwtJ9E~2imJwrq{Sjo||Gq8!I~EjLR!F$Ewm$k<K&pbvanG8U~%jUdq+76KoaxlcX$*ro3cwnkg*t70rAB;d7_y?VBD~k2>>$ -zf!%_JzBS!t1)VM3bGEh~FZ2|8&#dWB=(|$Ho#3Vzzqdc?3}1&2UZoT2UC>lin{yf!!j{|fICV8Z -z3m3R5h3YCqilev2+?QH~WLIQe3gW=cr-g4(>gDsR25>0xDTc~m&s4{djk@G2k<_&y -z8=1``+8dsF-V7FusfO?gN)?WxM -zPA-R?g76W;+-8k>E62-Tv`|92 -z!*g&zyytgmr6r=7zdVuOtyR9vVV{3q?>!q4y)v}xlTEq?S1?w*0jK)O=*@Lyn_nx< -zpfbboP`ZmA!iw?q=gaIfzMpof)KggZ*#;45k0Oc%?}TW(&GZgDJ?;|wV&J>7L(;aC -z986W89fHD5nPFmQq#T>oal-Kx2y)zn8X_P=;5mIZ&$eA-D8azY4_d2orW&vn$CRydtIkheg)r8n~WS;$4 -z1CjCb9+aGUsg!BplbV~Bd5Jb$?h6Z~5@(a~ejKGK+cg<>J@LM!E$5qY({9I`N4QN( -zeFQ4AXR%-$VWCAkHF&&C(Qs@nDQ$ZVE{?9Te}6gll$E${(+`&+Hqrz+hP1(O*&Rx`tr3~3GV8J9umZ40d; -zyi+5p2_ZgN`sYwA-Oe&On-hwy#1!Oy3pO@Jm%Vw`{up}ufa8g+Gj7vP#@3wCRk~47 -z#Ej^^EY~&G(C&HK$9~0Bqi6#w1I>zy3DMP!l1i@oG_TmrVFmuCpC8{7HnPTxe1WrU -zjcJsMB>MSg&&;&IEoH;!e&~u9W%TKZJ~~7#MkyKMkT*wkoiA4Q_?r(VVP((2%|GmS -zwa-nR+b}Z&_)sABVKOm1lY&VU*tn;InybL>lKzf=p=` -zw5!_sN{fxng+Y8?l&I7gA$Vee^_ciz^~0Qs?eUBTWA18U!{%uG>$bdxWN{+>d2FMf -zpk!G>WKR=?b%;AvWm^P23W2g(n>J}8x-*DJ& -zphtOWs{f%1J~)=OCMsyNvO|iNp>m+OuU*V_+PWH;qB`IvPMso0oMbv|@>tv5`8^^yR1}eGkQDLEmaKXUXk+9ZlOWw_=*2 -z?KVRARcNk$R#6%k`ht?061kK6Uauj3w%iG=uwqvCFrWABkPBqW+z`)dmaL*Uxy{e; -zwf661EZn_MAhoH8u=BE<`i+&ZrW`W?6`-f3NM*-+5bG*7Cx%u}VfMZZJXmemUE?D_DkxcB}JP&G-0H@Wm5^1LgD=l-gq34F)t^p;U_7Kh?i?L@G$ADNgc<(j$|+ -ztET(z3AU{sE%};L#F%T*5Fa3)tmoF1PD^_lkCy%zw!sLUF)Z -zNR40UcOnV(-<|FbiKfe&c?2=@SqqqB`S{~c2|^?!cyJs`u@u^?YRUBpyS6FQ#FyEo -zUC-5&T#z8LO1mN6eeNgr?PrwHDOyHc3wGdkNwox#?K<3a5VB&8!*96D1?ja%^_Y}_ -z6WVWnQsuq6v%fdx2_i3SclS!j+QlS43YSStsqFh>&Xs*!CALG0XMb;of}!TRSewyG -z8;n7{y_?QS14WQAXaBytpTUl34E*FtWCRWIbU8!;FA+wwNriuGkAYmtcI#NGvF25| -z_ELGnZkg%+T$~_15j7BHO_n&Zre9Wwb7#-7J|=(BL3PLeeIMeI3Y@trWlrxYC8c>N -z*^Hh~-(Snuv&)y($t4OJt0Z;zlv@$>R>xqlNNJj}4oWjk3fGOr>f*8FZaz12JV -zGqjJbR>qUI-{)sIX40bUVlp^s3Dq@LR}MrgKg=mx+t6yaXOaZ}34m)3cPEEYrPt%z -zt-m?Z9NiSs9j`N-I+U}d&fRt{1S}zMD}D;gk=3o&BfqCL{h5PO?#>zeMf|J=NwniZWxtR0p;*z~>RT<0nJUN@+ -z_V@(yy%qeZ-f80+PE1;Kb;a;-eH>}my`?y%_ZNUakYd7Oh$k;}0KZ8{Y|HmFzId+V -zpJhQ`@sdq1`6~{*C~9*Oe9Nfe22A&Cm~W75{hI$nCN0QnWPOE=F4g3v){)1*R+IQn -zr_i|3%+1#s+HDM%_aV{|v7j<~GNav&k`Jn}3_TW-Gy~F8Y?bD|j1m_7J3wvS4Lq?8 -z!iC90nxpNTsXl4Gj;+##-JFgV`WaALR3`-CjVk=>+*tHg?p6oTi1fIuWX_`%{_?ig -zSMiP$UVoZ26b%#8r}7=pxVK;A3Xef1S#OJ{G)g>Bj><*L!fDQW?hxqg2c$HH1jls` -z1bKhc5#5pD92IYn@&t0Rva=^UC-Va6Djv>OA?Q)GdgME&;FrDEIdiYDv{vcwkUH23 -z3TuHj>CG$O^;%Wt@BzjH_gxnN>Hi|JgP=tH;f^&~1LMw%g0jZqhy3w$AJ>t~VVzPR -z<}+R#GY+owm2ZLYZ-eV$ZDBrvm(oL+TT`M9lUI&sNSwGr8q1(VURdFDRp-*u$LHmZ -z4Jsx;nTd8y0Ka)&V9H9D_dB3h4T$XYT34CoNcY)sA?OPZ*vH!I)Q&t=UCTqAoMZ`nVNot;V(FQhfb8~YVK~3bX#_-jL;s}0emh1O*HReqB0qC{!CpGBH>SM!*nzhP{y3aKV)ailgCE^dDB>geR+95+?rZWEazPBxzN39 -zm$F7kFj9;f`C3xROE8kQ1%H|ohr_gcJ -ze7H+i=q=)2=p5Lnab5BrYodcq#a*eU9;`}J5{0I(D75={BHLxp%Q$X -z=lrYxyKd<>Df^b%*Y;KEw+nhsmW_QZ9vzEN@(dl*@$;m)(4UG1Nu(dCQq@Bw+mX>Y -z+Q%0wTjFeRh|DHZu3QcF=0n62Kui;rK73a&g1$~xo=1zbzKkbTcQ+jk5s@RLb-v;G -z%gizaQT0|S{T&H)uS&&&z@|vKS6x?Oj2j2KV3hwO`wgV{_;Id?{}yroKwoQ`9oaK4n!xBvC2g;6Nzf0f?N$uT2PAzGH;E5SD@D -z_@hrn*1>2GE=mtXMDq!eYTPPS@F6(7o;GF3p@0|tVmPpvpXBzSbE=k%{Q&W31qr^Y -z1Q(~fGH4;b|T8JBfyeL$blBVK+$5{B$CbKyW -zvU%(1vaub#w3E$8+yomz$T`&=8s<;Uoba8Zjc0R4Krg|_21KnpND>trI}h?0F5!**PlFfI -zl}*PfHH1gk_-&jCRuAeSMEBR}zAmiTRYLjL+RXcH9hrfKV6n<=X1%I_)Ua^wJg{A2T0{2vNnAECZr#-1OJ~EOp!sx9&x2%3{pslH -zLjXiwUGt*p9#j-<%*l&d4Q}=ibQme;yLUCb4mAHO88#;nTQ-jJ^Nmg~A8-4E3&4B( -z-D0m$%&?U6lMZ!)w7qc29cbb$aL@j{F=u%3zqrII5FK{$NcUdY5eW!2u-;^VK;CRuu{QzNwghcBt?QC+E6kw! -z>fG^Lm#Q0UsoA&(u-p^WOP!4%BvR=A}xR_}xxPvbH)9gO6ElEEKwCTuX2|fwvjH -z9n!1HHoqn5i4oQ*sXXaS|d8v6aUGM3x%&kznZtM6A-=O!m^ -zd2hM_9~-qz=e_Tb?eI4Y$|1!QDwv(CCyfQ2SgdH=okJoB8Qm$cNNBs2w5o4J%6%uv -zy`Fd$rLl22eVB-K=iZ@_l_ZnvcptZ3V8ib?+J2`zUMg}Ofj=bbzYNFVKHL>dUSf1_ -ze?oN~Zf^=2s)w8E(8NmvCi(Kp!4K;>jglLV9}{VZ7Ne5etDyAl^a?7JA+#H!!tfbF -z&cV5Q)0wcxhH!>tujdda2?$I^mj-Yam~7tsI=2(nXfsNz(h)ts;57W0w(R(5A6b$? -zZF(yO<_$tV>@0Ae{9T#(<4%cfBLgCmQIKa8sCJEQhn~HFbYhj!TW*Eauy%i^uby9s -zj0g#C#3zNih&6eFp6aOoyfZpDbxelb4g~eRKdG-;i6xJu-!Ci-_2Kk3F3eGrzpOi=IAG_ -ze?EDBo*zSg`cZ8E>5v-of}ig(6G?C2q_dTOQ!W1KOu^+m0E7SgQv3C9LysT4^moF` -zTqK+JRiBYKguJ?C-PfaS;v@(Po?l#&hYEXl+r>8W+rjUQCN!U02X{@L0gyvZ!MaGg -zxjZRG=Yj&?TpaDIe<@uy>^rQD5c*R3m&&DB;oa5er;3=?U9QJ*ML%6&yuE91?>)-` -zULe-}I4OT8*-*f?ZQaTdbAsldh3ez5uFTQ^k=8*kJm=xtl93qJt -z$eD-Tno1sz7(4CNr*WO5aO#-sf--Cw>}Vihx&R>Hd0)q3Dxw3Rphf17UIoq2h|@eN -zB`v-Q$EA0A)nBsRzXpUK1EFLok_v-dtyMDs1mkC^v+tu^o!-uU2yuDhb^p2+_!GEolThW59Drva)m7U9KfQ5oWMq&-$D-IWHpy1reXSv5YfvF -z^D*F;(~G-R8mn&zHQv^}ov+MKmV*(noyP$mtY*44p6>XBRz)1dVQtxS;La0ieBj-= -zi1@7jR>#UmC#LH3IBFkT(xnPw0WUj)3uVG0jkwN=6&j9X)V&^IoIny`yqNq}xAB9H -z38QgV+F=MZL*Lpc(4hR(MRAo(j8# -zOn5J}Vg{(EWjl;z{0KGuBRcAp4KE_K=5?{dHbP|9VmjCx+?r8;<$$<7ia9n&=Esz~ -zvQWP0490sA@)vN11J7qPPM2D09n=}7V5a^uCifEpTtE(3#Z`Yy(>g!jDVt8TCO*GW -z>?@KVKY8-R{&lWDJKJmUH{BVF%x8D$YTuY7i-#99p9I;`V=r)^+MuKj)>F9%3O7*) -z_L~->f9g=*SR7ONbYb>oAHASYFhr_|*1q_LpSvrj2M(@{G*3HvE7I}3fXY+I>=qiv -zbfp(w@f!F{!>>wZU%`>=UEut(?xto?&0LkA0?@N1);cYa7}Cn@VtU4TebR8pl&Mxo -z@SV**G{%HEq*XZ|>#GlM^znzUSt)>2OSlt<*u*JKRC%S?i8*SS9rL7L1iA@cg$G)^ -zN2{HwOIlH)3cQR!?YsI+C8hH{R#FNY8r0EhhMq;xq_GQ%0z4c|;E?!WQ4R!^f`3X;rv_*dly6 -z#mbraD$jZ^)U(<(G=vBeQ%7*seMuPCnu6_IDt -z*XGm^5s;bf*;^cD$=+kG&LyQKq?oN3HnylgDfeSKHj$AJQ;Kq==?E<`YyH$klhnCu -zcR9`FA4TLvyIxFM%rDeJ@R*vb|DlV5r2D7W>VV@)5Gn56_TAd*%$B$Je*zX*a^;wK -zDDyS0;(>Gg07;@6tn1EI_cCUveFqBlY`y=|`pLJ?fEZVw^k=?|O7ZSNt -zG~?bIjia!13FKMsRR&C7}nv~lp`*d(aBY&?WjJmu!IX`-?o -zB#E-s{*i&+Af2$UvX=d5e}5|r_ak-cuW_~r;DDf5mn$xm4OfD;BQAnBQ|Ju4G#Z$j -zEf|94qjmVXdda=?axt~XUrWOTYtjd3QROI2AKN^m9ns_sq|@qYM=eoC6?OnrNG!d -zE}U8`XUZ&Y$8;B(kEf3*y+<-KHtuhYW6hd_5guTtDGg} -z$ASO!l}bVQ;GDE-lKQy6vsc}*bza$vchF&7(z&u~W!EKmz{j@0Bh4SP+U(kvfJk_= -zkICVaN`_nd!;f&uBjK+4U78o*JiQU;+C?h+ydY^=rk`E$w2GXwG)QX$D|-gl|{QO-VB3ma@AkXL&Qf -zR3!&hm^%zNUT=5?JaJypg2-1`CJ=yexBxopRK$ -zJNB{5!=vJg96L`OFR$RMX$7=iTyL9zg>RS;pKC?6J1~dG~Z31;sE{ -z?>xSp3(wH=>)}>|R=e_+FN8{sRE8;S;PVbyf>D{7x}$N7r`Y9Yp-!|w$_dL=;(9ZO -za$NKHq}PEOrc7FV<;8@YPau^$_2_R6@5j`$Twqwn`I1WoA5-!bryIvsS5w+k&Sc(J -zW{KaFTUB3YZn`HPT(f4)^-57j!0tCQ1;*oW4}JA=(Y~*yB!P}Av0uKqh2JDu9K^hT4R-F^ -z1Fw#Lj~N(r=6&%u5?4JMwM?D4s}?@!ORWKMx03YUU)>*Z-~WPnMB9yOE<<{?S!&>U -zN9#c3wKoNN*5r&sDW=wxBa7Y<5mnhlgOzia -z140~+ASEBMdzsTbrGkS9t{-88xjbq3-9OSQUYmonpHfY)sW!pM<~vO#A+k7SsxdQL -z>o%G=3TcOP9lzO;`wSvlCrv?qE{5+{7dvxEA-U__lXGqPEWqqhwjAOQF4Dew*XIxK -zQa$?_bo+U{%r!jPJ1~dcQK6)M5iLT`Q6TRgdNH>O(xa}iHEBu|6Yg;?!h`7qv!%Rt -zA>q*%p6@akLqv>0+lD~tXO#SZKMqtwbkFXf@2T_8BpN@RO{IcFreG&2NvwW5i#FIS -z9Bvu#{UuA+qo7!C|Fs+N=S-gf6<5WqWxHm73EUFm{sWDOB50>Ux5cu*d|Z(?@%L}w -zWM~6q$QB@}C)mrQRjz-dkWcAl$P+aauV@#MHOBLK8NXl&50MxOi}+0AFH{@6i=$6; -zDnaCSy?OA*MJqd4mL~|Da;brb0?O}awuuAeJ -zG1B&?uHDt!)kvuNz4sPE;73(Us(H8Wn_R5U -z*YTSW)u*E_&k#&~2>mp0{$CkYBF>QO+LBY9=2$aDhF#|V)#QBQ_u2)80cRCc-A=7Od^7Qwzo={k4P)~V@JuLYyJo)-*Qge -zjWEzpZcYp1J*n&PH3$(T4Fxfvhs^ySy8z}4;DvprGHWlG_~50oEo1#~crx`a98kiW -z@$71-#6~Gk-|W9QMZfxoU!M`)^RY>&^H$!Z35+Cf5!t~4jDgO -zbp8&%{qHrO|Cj1K|92^Wr+!tiW6uxyrj_`qe*M4GQQ=4685(MM!N}(_*3KRyY&<8`bu>!2?=k2<{e(@zGp6_pO7cDQe*HrbQQST_ALzq#8UE)N@GqI0`;hi@Rx!AG`hBwIIEQKZHlLz|y<~WaCKN-I5Tjo7n -zLArB(h`n>-c8!J^!Up2yaP7%s3xu5A_&!HgXGrp|A)wMsRbv?uQZbCqcSUbU5Nrkc -z6;9E^|LZ95zbOL$o2~Hgb=Uvdi4#M*)|YAX -zoeL20gJibE^4Tpk>&tU|hVxivVz2HVNIEffc>&S}%j!%wxqFir&w^a!*bqhj$z<&e -z+0Ng-nXCOd91zg>mmu(!XFRj#AL7#Y7$kcXJ8knTo+AEVUM@|+K9v>A82Oja`{m<# -zxel6-xw@z{ -z@H7L0_T6HWfO~egloz9R6fsXEs-2%JC}b;Z -z@^Z%J7wWU*X&sCAuiqftVyI_JoqO2(D1C{xf`-f(O{d>9%+$w=YdoOH3Cd*Oe>h(A -zve;IUdkQmF3o1Ge4!U<&cB(A7Y*z3Ll;xBjMtt6(07amhtJK^#?dPU^1Uk8w%)i=p -ze2+{m91-DZXl5z&fH2qtW_sYUW}v=36N_tiW*;ZfUjRwI*30dD6GUVgm-p+_O*5j2V -zz3MfMrfEiB>V)Yr8}cngtU)__Z%LJ|#L--yurO9bS`5$rEha^TURIPLXWMF=KO?t0 -zO#E3{M)P2_rQrF~h=DNAA+@PAwzJ?Iyg$Y-o{Wpi7%3tI%7Jo_dXl!lVpN5pMn15~ -zNwWM6OG;W69})iK`R<<+ZCnWe;v?H-0&}=yLoN>&Cq9kPHb6OB%)VLPwL`!m@Wu9y8KqhQJa#{hT+7?rO@9Tx_Otrrl -z3b5PILnWcP)Cq2Iods?KUp^nmZmaAjYGXL0?2gU2DHR||d!=XE)7lGl+fE&w=X#w4 -zlEsu4*Vx29n#T?9i}YRz3%j;PGA#n~qUElt@BZ@C83UuAYql -z>|8>uC`1UEbkjS_+vt9NYlJG1MyR*Cp5SSeP -z_vMZP%cWobsfEZ_GJ+O-F?~Le+`?+(zdq}?S;fqto&$Ctra884ABB}Th9CYYuH$rW -z6^DfI``9C0vr0;cqxcl@7UYV5U64Uj8JZL9=6;!tF?DERYKQ`-jdDUPfMrs21CiN-37Xs9+W_c -z@$;gms$!=LS#04|`LN*(S-fY%?6SP8B0}qT;ZPyy>QqRT);+b17ZQ*mVS4I{ec?LL -zAft&j@mc0hw5KdKhGVAI?2T9KvGejdZ&n$I@)*hU$kBFQKrDHizP_P{hsXKz4-LpR -z-X=sG9pO9K{Jm^9Cp|j@9i0KKh6@;a_jbY#h=<)8Up{PV -zY9b_WX%ek&@D4dQxLlI66XIvBcT-+223(cA?dSo*jhhQtR(I#U3-Y*1vZ}Vevsi@f -zn1uWEEZjAKIvG#;>5;-Bq2EYnVc22~T=8T4PD{5+fdrp_j@v-vx39b~ntu&8mc+xn -z0MT|6@S^(0FIo*m6GpqtVb?AY9>b{94!jCu3ZA-)CVAxV2=O{hMdl}^V)D#Q#Q!T| -zi%|eo30F|ObGaon94GMO8Kw;W^Uu)~dkXWxEJS2U(I7iyKG-P2=)v|7lzvFuX0(G` -z$%!jnv;(J$GS6*>(4juJO#bOM^Gw7?9Um)9M&!N3DI8jmxd@&;)`TZrCAs=v;|JF3 -z_OY`MQ8(jb*K~B43EahQ{FwE-wg{;7IDzDc&5hQ=;Lj7f5M -z2Fd_=sB`FOC(f~YzK(o}P5en#g{jap07feIHQ*gr0tZD1KcSS(z9dDx$}1w5PP{-2 -z6GN64qE(9!D|F3h=#G1$pWB6IaTw2n?yJuscH9V_v5|ofn3o}|4%{IbT#DVg`wvE; -zsWR(50cD2<6zXIFG~ca=<1+C$932@<&j+)12f8Frl}D{bkt!`n411aH&Kvp~DIfd6 -zRH(o`uhv_s`UBDY_O8@Ng1sMG?TAM(RQVPMqvnr8yl&T|X7|X$do^s-q6y@Q;&)VO -zK|IyDvg7|0S;-&5r}2|EF=0#6x?X681ilrHSb)z}>#e=S}!t|-rN_nD; -zLBRaC(FeF=b_Kma>!NVq`hMcvtEu`yl->`%q77kme@c321O?09JZipaR#pE&S1v~-W3|?b@e%Yldx;aC(sa@n0CAy(3+HEfZX -z97vtwVHzGCIS6ofvQ=3ZIVpdeO>EqnVbQ<9XS~_eRp5E=YR?i&T41`<>02U?*c!ALf11gOo?WKVKEd;g9*Dm)gk@fSmhRlo_@j$l7gja+7d%^o0z+~^b=9pu67lvu&K6G -z>1^#{uc#ULm^Rg8t($vqV~lYqCQQssE9j~5sv$(q<;w*nGJFQW&TR@jUE?*_zZV(u -ziQbn59ShM#qJ4TDz)aD;vZ@?m$nwLc{5baw+)ZU3A2JF(3Ow|#;0d_JgtoxF$bm%$ -z!0ko9vgW-ruzrC2NSN -z)c=CNR(pO-3M+@nCD-?vqi1IbFZEe$vF@?S>rFQ?qvfJ9y{$#P7F3q!f^5ne@wck* -zfs&_QLB6fL*hu$9$9re=2Fa+!F8vi0x4y2%y7_HceGa5Vj^IKxW{q)eEMbT3X|i2< -zN8H&vt;&&v+QA0Kw{x3xb+U~nBiZmHSVvjPiw(@@%`b%4(-*mf^ju^b5!_?xG!eu+#(QZO$v*k}RRNQi!ejEYr?MehHOD_Wj=d9wlT$vWeXzSw_a6`C -zE%V8Pk-S~eAFI&gHBj%>f->z6;c5$>p`=Id+FVntNLem^H47Z9)P1kjB{-=ccQozJ -z*Ai#g=~66+#|%9=g65cL%w!Yer5+J)+R{YK_A~MPyoWd^P4RuL_T}|BhnQp#5?}IEKQ5}jUj2yJB~*93-3-09t0|^$!wEoY-U`r?s&(HTTu{;44;uNa -zJhcI*G@0xaZkL`&BGqU$nLEWj+*p&H-B|t9AdUZvY*AT(O>03x8SXS++Ci}6A0O_? -zklVzCZq8+kbU)kBF(YRXocvVO;V>-uQ6{j%sJ6wi-H%Z -zDCy0~Q;qxA^hDF@n@zSINuWQGyQbfI6#|B58;zX8W_8IIoZKi=%S2kl72oZN^o4*T -zGd6S>X*5Wllwo~MUr<`c<# -z@l-v{&9OH6w*rTk6<7&g-#LnvqSxBa?s(T*Pc#gi&lnpSx%Py!^0}0dl|4fHS}CYV -zBP*+=B=0Yn?*I1lC0|3qbll9k(v&4T^Sr&d?lmqxpP`8k?nxpaT`t~GZ^tmb9T;^| -zoeLB%vt7ybm^u`s?jbbu=GWeYVr86IXp@)jYoo|=H)1BaUGL -zth`k)T4rLATq#U7Lz_Z)4ofU -zjngDohcb)%V1;p21tHxbI6>X1by*;d(JW-=f1070!e6sR(f>xpxwyJJpi)@(cy^>N -zm8|o?sB{On(W$*#2bB^Gm6KD3n?z)B3i~m@u>6+U{VLtIsv^U2qpdZ2pyaJSRmtc{ -zrrFuWq)Ewsf+tEHO<7Zi8I}^FL}9sI{^HFyoK$RW=^bE4vNjqimg>5;uG2q8WAwnt -z=5>EvZ>GAfN>{k#z-G(_SP#Qs0C7I9hI~O~$?0yt8L5e;r)E!xN2HXQ|r}7v00GeG{ -z5@(cTYjl56fT{(V|60AdL}@L9@aQ6L(K>BoK9Fgv62DKUy)q+XoR~d&dF9T-9sMs+ -zt}E&p8n|E<*lWES%E!od8V(I|FsGX(Pv`}w#c>gMV*3O$89}ud%mbaW-$qB)y}MCe -zvL5WJ?ERQuq0JpLebxS3l^zKqMtMh@tIH@&DN78@ZqqI|oAFnVj1ppXOYO%1iE%p{ -zjxhXd8Pe1R6jo=oCZ$c>pm~R@XM@$&0MK%Va(K??mXYSH}9Yb?! -z7HY$!!doxg&xr46s($YErEYq)(|gb%nSC*rX#4Gg@6a@2)g<{6B6I3&oknmjZN=Tl -zijl1C&Enr7c#o1PJ}GvtEC=`zg~Cc<9sut`{#B`l0OduB@Brf1?WFZHyV#evOZamy -zoutJ#uj@q;!;y4KaC@SsSms{k_Io=2yDfg4rmdPePq#5^3zeGZgKGTrvYi-XIgSF8 -zVo{72NvI=KM-;M#Rh(985hb!??oDD(*xbE}5MUuGe96y5y^l$`RtW -zfB|68YRY_hGXG#V(OyhG+a@BkLprdCIV&sr-fpe3y#D_5;f~K2V$oal9iP0!9@H#n -zvxN%``ISZ(_Y|&?Fn$|&kXX#(D%rwo?K*&pSr6&_TMOre%T!A&G^uJ|AemPK -zFy+>dghrM$@Fo{c9*_Hj`ZzE&xqR#p(9{RI+f2If`?iBZ6z_+ms@Ws9gT+YM`|8#` -zDB-q0+NrPyNRMDOM-%8lCIsh{4yG9dD%eU&2_Y*`dn;Tkt -z<{PRh^K;oD9ivc*z;l}3cw#c~Pa+?BhcKtAEAA}*%Y}?DQeKMGXHhebaY?xOtm%GN -z958aslm}xS!zj5%fTe-txXAqYyuElZ4ot}~9y<8ya1K}^gjJ3y-s>*&K5G2L9q@*a -z$C#Uf6?Mn`G%Z(vt#v79c~-|57-gI{Cd27Km$cYTl%LLdA>&|EJ)zl5RLF0>g3EjD -z=BkXOF_RReCye&vT$GhLc4YG|XdU2RAV4_%ja`#@%p^S@cwAQC9I=Ok;D+Y$TNWWy -zZ#(^l={xXcz9Rfzff9Il3p8vlC^Q- -zBw%@HO{47Go+ic9L%(ihZ!_) -zusJ3uI1e95s!8uPR*wnvF&-)Udf6o-$ZJ8+4N-<6=o^H?K&{iBgw};WM{SDQDDVY& -z%Ey?A&#-hV$dpnVlkJIVA}PC#ARI+%6NCr4THA>9OwJN5<8N`UN2x*hzgM3RT_Y1j -z#*LJmJ^IJYqS^Rq+3k)QUy)+v4Bw$J3|@XN1X~BbEkv^K*C-uC`siX3CvV7zkos@f -z8p}Uc1HKt)>^boy)smpuaAUc!5OQsU^f54UDLD`rNy~mmf8t?s0-^HhaCQM2RbL~Q&u(Nu -z$Hd=u{wVtCqSvv5=A&J`;j -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 -zw&X{0Bu#e*8N*CdfUOZJH9SP -zu^zSw`K^KMKuf{1O5^Wd7)Yr!y65bWKJ{WKtjVLzYsI(4rif9WuCrsGYNKR!7owf} -zqV-DfjY~$|x4iisJvP4Yy+ku@t~tT*%8(K}Qjz+o6p)UT9KicW+Y5 -z)MXB&EHP$hAlT_o?@ijCtZY1joUcnt3$ORG)!ZCYX2FSl+wkfDtmzLGixT^G*y$Cc -zS!>$8OQR%tV(9K5_iLH78bSvh!|bU0bl_NTP;lG3IKM{Z5**2{bK*XN!V}Z84PRz+ -ziMbrrK(NdG;(&V!j6Q9Q)#rh=7kYH@qV=R+_24I5I>YwI&_FS>ZoY`?uPiHr0D4A7cYt)PFfSTcM799JHQdMHRuAVzD;PY7x@QQJf5oP -zf18paCSZLk5T^Ns)m{Pm(nZ`BDrbB9e2>B;Df8 -zi`?G2x=zcK;xBUGl6WXTNzXdY+YJ1-wuSW-6@!?#+=|4L_PQU8^t9dVQ*{*2NV8&rM1 -z40U@Ip>VpNTv4f0M@32^;SUr$)n)3{1V>#`jimnpW7^1%*o@4PQ!t>1OH0)ZEA_bk -znRs+&(psC?#Aj|T(&y-A)uI%_T)n?oe5sw0e;>RB!i>?-l>g+Q)ezse#V57xkM|X0 -zmRHrbtzY?j=X4OV(I4{qJs$VA&1`J-;O4EN$swjbURpYxWrt=`?g-mtCT`Pst|kmn -zTzI?17$-^`MmDQhgTQBnd6~zP?0QMdef$JM$X9-C)$!(GTh~;BM(j#nPXLelM=|S7 -zy@-PBsnM{*n$j8wTa)L_)>&^+7WIIGb+-U?X*CbSp>fIMl$N9>Qsl*^45ehT{p=dlXY7g;*DEvEBZNG -zKIt?VADLGs9b|&qrHdVSQN}xmNL1(0m;bjgW01N}RgvMXgB*EO#p% -zBm3@>yOOZk#gM=yi%m>Z53LMli?6@b4N -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 -z6+?oQvslKae+}FsG -zG?>$hi10A>Q(vVpaC_xGc5K9ab<=&L3iKD|t1Yv*3laACTkYWsRst;ux&)+Tw&=KM -z1@OlCEkIQbu@GDkvKc%&z{hZ>yv7ljCSEslmGZA-ZM}k_PFw3Y0N5pxEFc%l?tW7I -zInh#?oqvUET*9`cD7EA@B!+b7o(N2v?VL*52;M<(QmvTbp^bzExB6D#=TPH}{(H2T -z51r8-8|9LLfvRHhf`nf$z()1=N$nEd%4u8$O!#Y~w*;bN!KaL9w7k{|D|f6*A$l_h -zJ@gn6R_7AAPfLG;(R`ncFtSjrMX0n|AQe~Lj#SKjqWb&#UK*UCuw%cdd36&&)qObO -z628MAp^!WWBy_;${t~#mLWV$P8!@D#tTKUqg=UX6`^qrDO}`+9@05*`)R(jDblISX -z?to}p_6`U?I1Xv*w1ry$rVpk8>u*Ub%cPnLfX+OF`ka( -z=s5_K6LS7kY-}W8WsMbe&V~eGoyg+KOs6$qL7jkhxR32we8+QJobX+gmPd;5n*ywF -z2*M1>{U*q5OgVYoS&kdYN7&-INsZ1En@DXCV&yi@&Yj<&`W-;-P;Cu$>IHjZKDD`x%GjNDK4Wm~obLl9&{TG(K)>pXihT83@CXGj-6q(- -zK^=9qd*w{)&~8Qk{z9qSbJU?uRF;R{#~0Mxg5YDmB3JQKI%+NxuGo}GM1C*~PQk+Q -zOTa6CPaJ@S-w>Zd%QK*t${*PDJ!WIB^~^~V!iJB&L6%>@P1*AOI>DI0>$~T&kSL8~ -z4}i!;cT?zqrna^l0zQ$f3rP~+s<4QPDgZ$KT4r07Qvlu6qA;b@VxW6zvgC@WqkY`x -zm!49}LJ$z^ci~fl}6ObVdVbx?|Df(@7(o?WTty9v$P)GG^u5WNC_)FSWiT&sI&w2Ee4|Q+pjai1c_@Dc6FjXBJ_aN1{hpAJ)#WNYM -z+@7`v^U2*>x-9ig@2x>F(nI3Bka$|Nf6?at#rTB1r6|-t -z9E%y|U$L2y7>?0bTziV!wGLqEoEfji!L%f-9gfzdGb1|(YWp0{G$_6i0?1qkXY$C} -z>K5>NLpfme7fANJMC>XD!_IS9{M6@P?pNvUq -z;k+b}A+NU1m>FRgkdN(|E&0R;Vhy`pD1YG>2>VcIzs8UqG@Cml)>JidX#!vG-J -z-JP@QE^YPqR7_4jZ(oM-%ADcu%omk#@w;g=?GDJO(y{1**-NE^U9rBy{a#P@sg0IQ -zrmd2TJV4wN<%{@ -zK);X&;oIUH`u6^z32i&t?IL7)v&S_DKzc^M<+6b|*&?vMm)y`LGC7CJ$@kNcP0vQ* -z;>fs!ohR~m4!C8)jYmypnhoOVOAc+HdjOPM)Mbm|V(odHwn6-xTU@0o*}5{Q4n8hA -z;6(=B>9b@xB@!=s@M43i#!}$S0L43yM*0_SuLAIR_+>~L&5^bCkae(`cO6m|!0|z3 -zIeND!3-Ve;nsMU$1G0HkFb08u=Zn)bn~ZcK9DjnMGaHQ;N)%Ts3z{#z0gj^bA=hW* -zhfYRas#I04a-7X7ETMUG2bfX@3#be*u2lfpLF}`;r{~IAhAaSP|9-2P{7VKBpqOu* -zyWsZeFiy(^VniWavQPTfy)>i~=}`c(Glzt-)YFRVo7oTa!uC8QjYBX&-f&&hx`JU^ -zcUi^Or<1^>u%J_yc|*$fHd~bjYrHFVrIfi(uM3_eSc&YL{8v8E-|3PyoCcDa&rWmf -z=Md&{aMR&~waxTUerrEx_I|AVn(v6NV5F5|@H;GJXCkHPE1mJNr3MZMDxD%^m5J=8 -z<(9jLrwjlwJ~V~CBD9i9GwEW^*gqPzV0M;>V4D9fY%}%Z>){Ji3#f;gu4+kLZE-5J?b4jeao1DS_gxw}4Kh0Mu`523w;?RiJli&n7X`tv -z(ht=u0I2&}hPOS!`_FhZFEwCrvj2%mf4mXvRCv7yUMh=L&Cv40O5(M-4_&Hts^gf%zy!pUrQrv}i%if`)UGPvh3Lt~xJ&z@d2?|B`H*Ase^CL@!U)1Z?} -zv%FI4&h((hG$O4k(VrzqC6AeW?FPv8>F1K#qQ;KcPIUI~MSTjE%TaAlJo!7oqE^pH -z9}<2Fj32AoK5K~h!}nzTWx2|%Z*|9Pi~J6)B${MhDD5JD_gOzcp2jxYc-}GpwUai{ -zQJ&nJU?D59BA2x?>jZ{n`~kU{$;&v#{K4vpoI% -z9feDh%jb0D(C~k`+bYzTw&2Mt?;vYcB?MELPc+uhGxe&9C0_gTAM -zA|wHOh~PF1N}-2@qxA=e*f->wdf~fcqH$*93qd){>M|d=dTZA`)09~hr-ysmtm6R( -ztD?Lqm5eeu6RTzp*a{G&*1vAJ34n{AV1e%oNB{MR%qeM{PE8nwf-r@Y%Rq&4*N7&C -z#>b!2$LsS)sB&@;jYLX)9L3(yty6GJ -zygwv?d!<`Tr_yQsv;U0Lq}ue-B!NhQy8eR9{n*g??ya1ZcHIS*o_Qmjreawy+u3e6 -z5GEOV-_hTaF;-MCrY-txwPI>8jbUtHQ^j`Zlne1$L)Qy}TkcpKy -zz>5MvU1{0(;84kQFi47=N}yZ*E4B86=H5T&o>(tWXE8h`zx27tWUjYcb=XBHKlEk= -zNI4CbpMqX#>SK93YiSFV8 -z0SXsxy`uII%JP)buwrwH!n>w(CaBhLh_?+$xL%6piuTVSZ)3oR;>z@&F8lvgj+mTdij{RQt}Y+`Cu-AYge4`{zZyYd9&<-@Q=m|9-o|^ -zBO^9{7K`Ry_R!*6V70lhvMVw6AmhYp4^Z@#f^eDD%Jlo}$=CtVH=b4@ZtqWVHl_e< -zJk6*`pa*Z@nu>F#zvky>+T1 -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 -zLADbt>#oNFQJGL -zr~kd`rU8Lt3^3R1m -zA+vZ+=iC0&^hd?)k1t!IYvb{*ZRJz(?rXwZ`fZUcgQaU%EXzmwSsLAY*0RX(Sj*Kt -zttd$40z5lJncAHgytwF-SLW##o!Bw(KSX(Ul%|~?aC_JxD&GB#5d&Y2RuemFxR{_Y -zGTIZHPlh&pm`H3<^5gP4l(8Rz`Kl6^GH4XUSq-!r$D0EgUN`1=fEEKqiQj^O!~63~ -zvnG}-OihHTPIWg$1xQaCIeDWg-fzwD%ke;U^BKKfQDwytIi}G -zw{(s0#AXa*hwte53^Xc8Vp1}Au2jHGs)w)o~7*YXx=m?t(>B6QHG^fWXiB{?%^6eekZ_#$xJx+NrFe}yXvSDr=> -z&%Aogg5I?lI=1z4Q9>rX3ooXR*{&kMD5yWfL;fYp?kz90{V(V`exDPLLJGjI?MsgW0J)=-hb#}>|lgA_tlg!2|10=@i!~=sv -zQOyPNlIU6qHXu!D0HzRr+5G;7Qv{ofmW@y3OFel=M|~`W~!89xl -ze=?!9_m>xqF@5?*Ajf4e4Z)QV*TtK=H~jspKkoIM8za@(ePh$%&qm=p{MZ<|l|W`C -z-Ha=EE}#18KejAMhml%1d-ms({?8ku=l^|`Dh3|XPU{atvvCI -zvMlgo$@mCJE1?8CMKHB+F231R6uQAB*{e|AV%QYLHVS>6Ie&JT69eBt*bB&Yd|~VI -zg>o3@4J~SD?V>B@@?;Ak&U=0qnfl9M -zlA7PZY{-E?p$`dz4F9?kbpkG(IgnG -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 -zaXnuP7B<9CM=6(;_%b@;j^^?* -zSVWgaB2Z*CunC?hab7syb3)1m__6{l<28t8${`F>mvZEm-}Szz;ew|#c_^5HcKp}= -zB1vL!YM2t_nx#qD7vT*$J_fh5^!}j)XC72#|TZmn-~wa -zoj>cs5QG?!>tsKqlx@HcMALjry{Sx3@u{;N?kzk0$1BW=J5aX}OiwPa*k^}*TP;^O -zd^jqec`HF}0vWaxY8F=)a8@~|_QTvYv9;F_jN3tZ$Vt^ -zx5TfNl6a>c<`gZc#=6OoD5MxCj( -zPp8UnOI+(vOqfwVld90p&@H@Gx-K>`uCy>dcy0a}hs0F=spGBRxVj>y$Q5mKHyf!h -z@RhSUx8mNdK2;v+8hNVR^34nI9x_eqF5OBKIwtqr|04W9?l3_Q26gVq@}Q9|g<<(g -zX?geI{oOcslRo2Eh0#S;!bYKa&-`VXa5Q_x*XWnEYXvdNGy00L5{lqf2*RW`4SK%Z -zcnXY3V#>ncGV-|mSVd*=`Om;57hVmQXZ8Jw$l)Iklq`QLWG1do5U;c#{;fBKT)lQ0 -z)FjCi{a7O6`07_*U+qJklBE6Sem^w3}ig&o`%G^L2-BL<{n52{*?_ -ze8#)+J??;G>v|gRiKak%W;@{-{gX2BOG=G)t%q3-i&~^U6{>cDlUk@{=({95fJGPfC4^L-^TQq1Xyb}sq$sSMOn5;=VBq7}^iComW -z-a?8}qZ#E?90{xl22z5pt1VUQ;I3G`t>|!0$(n`fn>>9AON8AERA{fF6gm*b{orK7!z8ue# -z>cGa?O+Z1C;GB6>?G_>tp=6so3oo~GdJ^g<#AEEH))*sC^eJV;TrR$qhk@agN#4ER -z8noMjj^Xz*zS%rGUmf>#m?nDI>gh-QtH*g*du}vJAZIFW9OuWHPypYSJPVj5ImQ+3< -zQ%!JlTFe<@yZ5K+)kBj1x0lGi2~QFcA2W*{f_;7AQFkhUvNeS)RrSd+Lzy}*zEc(! -za<-ESroxy}dHII}Ladkv;tn{p9%WH40;v+!Zie=gEh}7av$R|oa7nK77+g_i2ZhX2Xe@^O)*-nbF*fU9`~J7v -zD9A^ZxUar#aY|)$N$B7iHWpg1c+!sxQ`_aagCysK-+szxYUjrWj!0smmgQ4e9%)9A -z5Uyx{j=kPDO6NyS3a)N}OoEhju5msn(Z%+8duOXPc{Bf=9OEr%M; -zvA8CDDLa2e`)T+63#+%S4t=!0e~?6QH_-AkM)Q}C$tbkbRZNcmqTymDWE!gUFTTHfmoP_Y -zGE=r;X*YUv5tB%K<>D<6{H+0n4f4}|Yj8v&l&4&_Q|Id}J$r912O^p8NIqsR0?`jM -z@(CXui^JLtv2|giw9BnabpW~@?vj3W2i#-@%uAPbJT@k3;PiHJBC<>$B82paAle&h -zEQF{bD`W^;MC&JFnn)Cm3dDVh^hjQof%IQvK^K(XzO3q -z*6pOBxzOG1AY0!#y|C|Ch}Nu?L@w$q6wL%SEh1oW2ug~CF-?Aje}Iag^?T_}@Y_Sg -zNn}x&jPdVbGPeIZOh#LN*h?IeK7lfAMC@Vx`5?+5kb{+7v{cY};f(v^mu7RDcG|O$ -z^FD~R|B#;fwsvVEh6fx4nZ{l>&<91KH>!kvsv1-Pj!>~1urtl)Il1q?No0Y*(EBC+ -z8qplc(D@D3k5*}oUpowik7vigTUxJ4h??8XuZxJ{kHZzwayj#DYuSZr9VyBQX7+R!rz(fILT*k1&@Z)&wehY=WXG|)qi+hw}oZT^vEZKi;06`$s;mcf1`R6Z0n#^+~%OD?LH_j;Rf -zl+ONmdN(}p2)&wp>goxY-~9aWO=-WjH;T!S$|xk1!fa87XB=E|bRumaM0W|_>$5^9 -zqG$)aRUN$5sUyUGkp3}2K@j!P8$6`X;nQx2#Oob&znR3jLl+@0boD#5G9!kpQ0nais17z!=H`oT2x^$P?D)r -zmjs4SYXBLX|03-E;UP3$C+Qyl-+lcl>cU^y@c(>!FDiJ8o}B!1Yvi#10E??-TMEhC8Q7@=^_Gye{kzKOh -z?G4B;`4XKt4)w+qP@zmm1?|1Tr>z0~Q*x-9e*f{*&2@MvHcTj*KPc|9f(Hos10V-M -z9)X(hW#qZ@eY+jvsXP#^Z?C-b!#7?!<$%u25No(yJbHC1NDr32!JNDS`&k+UxNTnC -z5TS%N>R{do;C>!sZ1;gr`gkMROktQXg9yUrAQ|`^8O0%zLWkr%s~;}?zZ4&=`G1|_ -z+XF{|Wc+UiL*lUG% -zceHs+%N+Qj|QYZ~A7l&2dhcc%g!PJkjPr?x}J1VM2KA9I*`ms|sp*mpaz#b(G -z2f_jH&y(~24^}8m1oMLU>LrN7aAH=jO}}SAp*O>O2L}}D4=_9WmAL_-Z3y-g!CXy$QJHV6vL&wW@{GoC -z3$293JJ1u^xX4eOfVa}8`L|FUeVkc{UyC=r0zm(zrKpa=ukQddP;1KtVR__h9g4Hc -zm0t(q1j>rdu9w|qmDwI2QPg1S?REt203;ETMiD2&`I=IA@*0qmszw2sz--&vphWj? -zeT%8w?rdTpQel`9BKV@24EgBmbnTF;q5cjYnKx667QKxatxRS!ituyoIDLEkw!^hW -zpyvQERM;0m>OklCMgznB+BGwXMd5?~$Mp@7J2xkXN+4uYjePr&DHP%bh@X>xPn1hN -zd-Sl+!i}ZD>vYqiH5o@1v}HFFPTw>dN4V+G!O4Q%d2ta76Zs!dJ56XM`KNETU3&8u -zii0c!T#k=_+ApCTfLVA=GVs_AmsL&1pdj#5id~S64AU>%#SXDM1^ -z1=vknB_P3*b(Bu8@#(b+l&Tv@r~^b1C1flMZw%h7FL33g;Qv%I`+UV?=`95oX;B9f -z0PzElmO1ANBzbC)`C=^&AUQ+af3u!u;N9Kq{3M+#aAGDBnNGd|>A<&+k@?7<4hFut -zG63(4LftlA5a(;H?QWQJWQY`_=v%{AE}Jfeg=02Fbc2vrAryCp+O>mK-G9A9dFA6d1gu=KNR6)Mt*1WR@Nl~` -z={p&mUK1V$RC7C-s{jmzfR1qz(P}8%LV|t=p+xV}LtbN<^=gU=VkDouJ_`gOhGh?A -zO!+B&OXo{03G0DV;@655nV9hQL}o -z3R$%Pwcv`1t)YdwcReVjcR9ZQr1Lh4jEilIM;d~ke+Vf98qLhVrs{|rnhF-SKvO;? -zY`f;fcjAhT7TY@RN(9mZ@h*R(xt+C<`(KX=Bi!rT1%_s%tM@zelN(RkUO42WyZznb -zS;7%0#UDAJD4%knm-PNiNM-S=h&^!qxC;3#-!2VZPE)MIDL`N%VrtI?5@@PX2oT5F -zW~7AjXS4&;Afy%~`qlqL4>1c>Fa*8k7a_XN={*jzWeVdhrw6V>PQ21sgG;UGLw2Rf -zAZ(v?OW$bIH}=7R?u=CqQu9OTHFF?j&Yd#OtdtY -zkI7jK`-#pMG3Wsp(Q7TDji_6qO;IP^x#*#IpR?O!Jmh|toCS|9_Pxc$8om!5C#nZC -zc@!n>Ks4=6HOdARrQZ`SgV@Vxv%z;ex5@^qX9WeVmP?5R#kaQ8{)4AAO^>P1Ts{IR -zZLJt=-j+KIiG}^wGj4Rqvz*ud2B}8bvpyH4#u250CN0Pi6z(dQ$q;&)K2VGgVuW^$M;;G`E7{h0KvEx@?@YK=fd9GH -z=%#(#M@$=J#s#n4Ek3akLVK>t1`f2mTSsPs-F)ecTtFu^ly45!XDEH7l|^EW4AxWn -zER?#&$6JLgey9T>67YB@vGA85}l=4O18uM1x{9|M9Y;&{1U&=di_JqAsVdnf4VP(o_ -zZFgZw4ECb%Cb@G|W^{RMo>LTw*Lc7Uyv!8NurT*cqLgJZBrlUMEnT-oG9x{9 -z;dxn=%w4(#m=+fw`n6G}%s(Nqm -zGtRcS@DUJ1(PB@sUYSi!!x_$d-{lJ%eQKj^XHpTMH2M86ymLPF%R%!o+o&a{M=LTW -zEN0&@XAw-{UDbJy7)v?x?>BWg${UH#KV*J1Y$6j;t_>-?HMQz^`+MQi4XUlot&{k3c~?HCfD@hi*<4KRV0&g{B<=OeJ0l<Y--y04R2s@AauVZx9$6{&B@f_2sF*+w9Uc3KQha}TPOXIj1rb2BlaL1yfS|JadNt8xJdgL@NaR|~D>VyDW}k%%vQM3tbc}}bzPFwE -zv7HNkJ0mQNptft=Z|8wuAmG?d1$o -zS&_{Vva&f=*8O^)>uOxz>-v6wzsG(5{<-h_pX)ju=bX>y^B%AFYdxRO$St!hXZ@!7 -zO0Nl>&iyzNLpP>DdXboKb5t?3GPR6)UB`Lo -zFRCc_O(1 -zIQI{Guq$#Wh24y|Ov||pCZ_sqiID?w`LSL)I~-v*i9!gP+dryoIL_oys)wFa#71bgC7aX6M&zVUNcH@lkMWXSBJJyo;aE4VtGafnqnN5P4w@UdfK- -z5S6p#0X7frbWLj>`THIa2z_v{n^M>k$*4ZeF)|RT6?}Mb%|;EQqnR*&ovOa$dTq>) -zUI3EPw6Djk;%YoHD#c3KI&bH^jXE=T-`T^3xeG>NInNMgY=K0g_H@+jN4if5Cp2Si -z8#tAd*w|}aPwETumxLcqf5Xn}2X3SCr(41&_eP!2s1h{y0W8CvI^a6giyy1^qH?r+ -zpfl$UZg053GpYDp-G_7N8!8h7s{_`mrK}mt@)LwPLlPcD6edN7^1aVZ*p+G7+Y2tF -z*_SfRUFp~lQXvbKbRDW0V)DL;?rp&%mN`Wayvymbg7I1J-#+>(!d`iI)G`9_UIRx* -z%I?RBwYxS*3|jXhzp!{3g6mM1hrA-wJBGe_?}&Zt5ZwWB;2_7If!gyD2B$N5aP)#3 -zg?Zo(dd9i+QO%qclsi>%@7XQ~lGF)LU;E-{ytQU=a!ZV-*^>sF#f@lD;Bp!^?xx|V -zc*UntR^p5_q`ek)UA+O3bulb}dU_+97+FX-uD$P}8wr67%lTQ4JZf8Alq_eBryHw} -zriuU#LOWjfI`Q|G3T*Uu%`7=vA|w;~G&;@G_QYmb9%1W=oEx^x*Oo|(Y#|Xp%x80u -zbr9@NPPHF<%ifFOQi@M>s2S^{9mCK5k -z#koxx=AceG*Qw%F$(H$mrV|GTux|p}T!fJ31q`u*gw_VV>IN`u`oqyLMrQrhw>e_X -zfmKe9l@U1Wma8-OH`BXl)c7>8DLvr_(j=7C38I@*@jiItfPlKUc$!OdwK4k+OEKCc -zGvn&pnO}7@#nahWOM0HS#Gr{65N$gsc%YDC6@dazLPb-?&Xp -z!`$)k?4HUHk=nPsd|@Wl*CVHArc?&EJy;^za9}c1AhG&RHInd2laAuM(pw=LI7U8j -zyWu{~4Tpy8rnI!wxshDbnRa3YpT^(~^P7OTeze^_v3Xd}#QhvO(f2{dfgH?Iu%|5S -z@A$^}0ndcp6i^9p9cryEu^E(8P8S;ts7LL(>C$xu_cKo#8jTmnEoHKeKb|p6_q3_; -zPDUs0H^?UxI;HW`wOW4V -z7}0(#vOBvBTF?yFyG~D%pg2yz=t13Bn%3>r>)R-TZWJ4@;+9HiPfFhqst*hrlA1Z( -zB3WNDsZNy0-qg92nX}G~l~iw7MLykGsGgWeDrnt*533IZ+Y -z6(_zRj=KkMVXJkIVoBRjPs2xBHX`x+@eVb@@y(JNT?=C&69$zcomfj%9bBAio#mGN -zdyLnB>2v!AM&aV@A0Jvg$3v8OwNauQnly917N16r$O1fOs}Pl7x)NZJLX@I56EZRp^@^?KYHA<9iu~P!khOdh -z@O{W04!!S$N^Q48js`&VzR3@MwMPZf(ea-J(e`H65MJ#$c%uYo{|49C*cCT5>Xn_B -zAt{k0RkF=K^R#UHbG*9Y?s^!Je*`>gCsoXFar5)9FVM7gGU(QY-FsWE{$k4I -zb3NG}(!6G8doGZ+48-I@^%TMC{_+_y}VREKpelJ1<#7Y3cg7LNI_0{ps1Gz3Pt)W -z92DmP?4f);nSN=!ou3!F>3=|FmU+7^qcq9|Kl1*8l*#A8MyZ}P9A1MA9zS;s{nkJ< -zx&KAXg+73(VmFu{3H~dSl19J8M=~u!|rBpN*NkcjH($}Fc9lxUt|80p( -zct1u{ecE*n`a$%`%Rc?v4vF8dXlqGyP94=p-|y4&N3FPc0J-0dJ?=rc%O_hg&??U* -zwKh}H|Kw5lvv)05{0WbI*}n`pnP`)Mh}IdXyxhNvzC>7>2y!AIC%J%cWZR(B)HcSDA@U7m~+4uFeB6LxVKOC*QOX -zvf!x0r7HPUpv@VfvBZ9H31PxbjS><1AE4q+2Fe8AZr~=hk(nwX-9<0Tdn!pv9Dy18 -zm=myQ?5Tg&&)(-lUyH2+5ke|reHO^rltOJMf{**a7C($BbQ=i-kpll%A)7$p`Bx)A -zRTZco$X@ds1K5+u+^;quCIf-bI%=53o;|`F9-o|SAXg!2)nJVZOVzMLQZvEfw&z^i3nWNhe!Tr8TqfEYB=)r -z2f+wy}ip#;z5W$;d%νfa#R`et@ukP$_Kk -zHrjPf?qI@9KH!qTzD=M~yR1_98KQe|$myxoj{nx5v6U174>g@m{;E9xKaBePcQ)y# -z-2lycI5%Uv9}#Q;VV3{NhW+^b=OCEjpAgRf^5bb}{$$*I|C`qJAS}cmc+4}qF;Jf4 -zL@d^=Qsut?h1HZhLNE@2yXYd-&fmKXKwSUGc657BgfO7s;SXqcLIT}=s^$Y!Sbsdu -zKRp2E8yx4)2S<9*hvFj3jy%t#HU)y1R{&PJwTl`zJ#R*{ou>*e+U= -z%UbW<2n8d6SR2nSm~vqZX^>U)0#u;gx_&YzCXTuQ&^hJMGgJ;~bM2Oq@5j&*u9y1) -zWeEx>0@xJH)$+ufNi>`wD&&{}wsBUl-$D#m@t+EZzkFqH$|MH1b6O85aycW!AQTFU -z3?7dmPlSULA=UyAPK|(wgq~ab+nq^3Cfg4M3ktRA43fuDXp@{VA_~UKEPU3ffC`Up -z_bnUz#Xp;^b|GKmOX;6IHIhV-q9X^jm+*n^A6bi;o3Q}j-Y#m^1y`)%IA+_F(gdt8 -z$IL5(s*-{p0f1AD`hle9CFqe*25lniQIOao;Ln8*V?Yk*)>LoNt+AHJw>H*S#lAA! -z-id`4y2`N1zucU`T^uUTRvnpF -zfj=$aJ?(iZ(+vPvid_b+mS!V2&t5j0x46vFN&Y@&YpnR+KbAqUh_2LvCy -z-u0+liLyV=K-*5d;d)l%`B2V*oph`(L4CmsT1>Z16J4Vl;!l4AEuPg5uk3CNVV|N@ -zKzI)Y*q0rsy&~m;_*v373SfVz@BW>ELAvD!c|{8E*7T*%nlHi?eRNo_LeH`plu36Z -z2km`h?QH^t&*-b~`%k{b20`>pVWSR*y%73{ -zob3(ssnB_ZN`L7kX+$c_gG}t*U)@cg&-q+~zPbKpb^fr3yHiLd;+3#9j(3WhWI*VdQLfcE}#`jbp=;;SphD#zM_ -zw>N}HfIG3RJZKThoSbJol6YM=UN5x1^b<)sQSwQN{{1v(XVt58e!-!;)A*Aa&MxZN -zsoYi-2gKFqfE7RNsxhSW`w?$?Xdgn`TI4}lv`QHz{nYa|a;9fSRHE5eEqr{cq2woYtQJWaO-m;EWl< -zwsF)YNlPM-p-Y$>g{E1?5}*Vb5uWvd*#?x8g;CwFAGYo8gjwa`MKYdD%0;+O=UVQ^ -z{_po2@yf){&QHLqm-SnR3cLd(X+^|vUKU8;^d8QAW$#St0%{ZFG)YEnf)X(QkCdBc -z)xT_l@iN){s~7I?j3X($7(+`RwgUj39p$21xh1Pj;JDuF9aO65!&XMs&tJOcS^!lK -z1@NjXU~esPk@6ROxiJIIxZ)?Zrz22AmURFqGpq;zY#6ju<3L~JFg&}YR*H5ceefDU -zH-*SfDRM*}&{XwyD`0s;mG8hzXu$;>=|KZ#k7W`b+0)xcVx41pAa7Qb3w>{b?6 -z-$ln6nf$U8dIQK+ooYii0T&h2yI*}eZ#WwY%OA$?R-hU^?|6O2)()+?_>DtQrQ-V5 -zUp}p}SRPXsCQ@Z!R5uNC6jq!Bgv>MV`Tbtsdy-MSf-soX4q#Hrf$}NjHA^6>uMg%r -z=w3*i3ui7TAQ=kY9l49c3)p>rQte;A%R%i+F@IHICAB(n_UGUcm{msg9HGahYeW1l -z6qWq|uD(>vgv*RiD;9Ut@o67gSW4vvXY28nPChWLfhaf8(IoCn%Bu**<>7y%cd;V< -zEbqg%n<}K*INNR$8gSlc_95V`mNO@yBwTizdqSs5DD^B9;6sRBYc=Be>hL=R=}Ji& -zz!ZO&m;^>I?lq`U1;uYU?i3elrpR|g>@F_I++TpMvbqJqWNXivR~YnJzC$3w(RKzTKmXY>$EBVEDYmDo -z+_ts$Y`j!hgpi+z;vEobIL>B0&T7eDJX5~xUVB}KBEdm7`+7?>t`u52%0a|qe@;Tp -z_o-9r4S2p7T;080PPO;vS5XIq{y~P|+Y@R2qBja>PC2EGLhMSD(+vWuab4}{8T~jZs+ZAvHVn}rvcTo_=$K9T`=fw7Q@#e6S)-D~%9qPNibzL>p0Wl3_3QGK -zl;$NHx%iw+xv;T&*z38SG^i|@Oya(h!?gk{UE*c -zliPAv^l74t9)D_t{6{)|s_x^cbS&hD8jMvKBEaKkfg^3#_KH;0SFbjGWUEgRu^^DL -zB+hOTmjaKU72dpkto18vx~Imm@07@0YNdJ-fY$`v+CB}Bqp=N^QnWy6G;Y*Kh8!p< -zwu9VYw_fI3`Zz%u{dL_uU{fj6EwEiEZ2=-Ek1CIH-r^Z6O2BKXJ$ADerz^KCV_Tpq -zi+-hmc(EQ(lpL!P<=~<0T9BmAz(f`SbXSnC)c|S(qYw$&Vsq(j}ly`61FyeKXI7W -zDO4p*%szDY)@bt6qT~?BAJ+|p!hoORr<7YO*ZnYr$ev}&Sb+qY{O3;WAo -zHhtnWq~AJrj=1Y&B?y&s+ca?Ow3Hp{7jIt&zuHLZr$C;r0Qyl_OgWG`?cjZth+B!RbiPRDy4aTWluYgfuhd1w?c~&o?Hr?fn -z=SIQBGuAWYW9yK&R}@zxz1VP@BP5=;7q$-{*`%V+A<)8_br^@%F`gKqQgBNKr-cRm -zYqUA&<3?DHkmrOykNdiLDJy-(ns+EotWUvqZaobz{xDuB`y!NG%=c#BQ*mmzCIgDt -zkBY5mwl>pshWH0+yb#2StH5v0>K=M-tJTUCdBV| -zJ!F*CB5_)13bNfChPYWgVUymDQD+xsXt^l!wd%d55m;AYJf#AlD-}cTiCO^sHCm$W$ujim0)~^ -zrKHUXLJ=jHJ?6 -z0ZHB*dU|CPw55C`70$tD-JjvGjh=!Q$XiAOAL#m_vwU>UD??yQy5~BSV5Hb*E=Du4 -z=HUJa^ZqF=X8yqlfH-=ZC@{ilo_fToN##X6!xqW^^o1YcHRcEG8q)p|aun(L<=s{Q -zut2TO$TxQ8TL+J_$NlCj06GB28~g8caKew+F6+a$(*HsSr{**(0pEW|awgyf029EHp067!UvA2Si>d5Q8lXT*q5yoVx%3b0`esO$CZ -zLe%h0alb^andq3vN4j(rb0^x#~O6Po-6YaV=FvD3VX!Nh8i_9;TX -zuQgmylm9j(r2c@jV6x#+`6$p#P(NXZJNVxqtANYyM&FNs6j3zp>4iKRn4J@_Pw0i6 -zDU68NQM|POKgCOMw+B)3gc)-dKCAQ2ffIh~CaRvw?WbrJgwnd%{ -zzWmuw-;jWkLV14XeejpF$GU@I7p1`0b{{-`3q}<&S`{Ln$fP>)737<)H(zs)(EZU1 -zAc=kTi#LUEDfBEV2>P|J&Vv;~KQ0)Sn>!lJpu`6EDK`c8=tk?x$&1NA1t|W^pM&|w -zrO<}ZW`VzK&q;)z+6|;Yy(t*C4tO4f=8}Ya>0RZrnY#W3>cKha$Ohr>{P5AO%t -zwH!7b1IV!1d?h6Gn5~F%Y#J$?if5HNpx~o3)j5T#FqX+>pi@>3SRHE7-pef$d)lZV -zBkf52d%cGp)|D_f26+9el8+vRVvmM|!ee;vD`rcw6~YAQTgO_(nZJb?UZDVD({1#( -zZ$Wl2R-79vL_@X{hVE-@8FV_*WI?{ed9H@@7V_2`xe|*0b0(Ay;3BLKi2;W&1Aw5bW^=a|>bm)p~`^00ASNd6Te6(;12O28^9L1n)| -zq~dObH5YN0j~+(P_xYBbkeZX?$0u5k%0WTy+qU}0go2iw!4ISRTb^+j5P2i+QxK?B -z5Jo$VMlKKpnt4JG2t0geGcW`BH*MnKhD3Te$f&Fv66k=Dn&&VY&fTgPHr7^BeV3-p -zfD;cdd -zCKcf{JOjh9V|PLYTC7lT?|}gr;rptoPox3Vhl!^8zZlbY^I$&!q7AqXyHiQ#_R#o& -z-tJ?CCGXdSm3{W~7u3g&SVhM(b2JzB%tPw|WYt5<#$T2);lP`G1xazy&bowf;yL9n -zP>$V$h|KgIb6e-cylr?f43x_V$N@-$gH@q)G>9vXp{|1)mQTG3x|6fN?!=1Ja{bga -zAkz;c8B2tiR{D2bh)*exgI`>c{gID;3QF*=Anlmm{21B8A9IXu74jYa8dWF17gC%VyGGn=#LJB -zv&anHLR&7hj{cv%2a6^_j(hCI@};>HNohDue`E9fz4-U{D*|(XYDxlL_lk{>X}B8H -zKzG7?$P7&I5+3xA5SS4A{=--XlfGfc?(5TF@OlgmvL#|eZRvM$Mx*i;5%SuvbpP{l -zA-72Kh7-Qo84}9|w$za3U(3%rU$lJ0P98`k_+j7S7-_%t{4{~X60CGg8PNJxKqaFB -zB^#H61~#WB+;V*SP%iQnAoK$0or+&iP{t>oP&Rif+q%;q)x06K{DLh%AbEYl#^osF -zNbSv*XZjII3gg>SY)Eh)C`whJRt=;GJOfF{EUb~;-1kiR9c!96-&?^nsg3 -zoo`pVCyC?i#Z5=Gk=sb*h4*zO_1I-={vX3gCFg@UbM`r9r$thnbE;d;(%Nzb^&6mS -zc`FHzAKaGtSyNBDY;}+s*@hg=cTL-+)2knNnW0Q!45&SDx)OlHfs%0_f{Z^Jk5qh)s -z0{{s9)%mf)NceRZT2K`zsq9=yrTm6?hk!Ro{rUrWy^LS-YtyR>t!5CZhP9(kjhM{g -zHU8|IvD+*VVn<)F0Pzf#=L1GcU)~qGQbOA^vulrF4jm|gQs?X@)k}nPHkNe$QWg;% -zyg`6YX;>pVMMl2TwYgV-;o+;aY)B9XMe}9(=Nn^e-hRFo!yBlgCosMOiS^wGDpvte -z4_Zyxb^P+F!$*dOP-6dpLiPg!qYy0kS0}p=15H0@>)tUY967^r_JI(i3&-q2Nv#&a -zyu~(oUyGyL3(saOLFUh#0f{mb93Loh>d*PcTE&o}L{$N#NercLub`Uri;^k{?sKfg -z*uu!G=L<5tMp5evL_MwFRuBeFopd#?>Z3*`XY3bScZ#K -zX5YZvaIpe{HdXAPcc4Ths~@^>Qr`DZON^y0H>`t=2wQT>sTXx&%sq!IW;j&UAdztm -z_YOgJ8Qec>2{6?ZclnMWY-fQ1<{Md0MG?tOD3L4jLMb?GdJ;0Vt#jTL?chc+QphZS -zS*D9BR{p@?b}E#7`a4R}$({y@xFN6lXl|WygqA}9QfE+b&dZb@I3b1DL6@gy#TNmo -zCg=c}K6Y8RwTFKk1u03yWSgc>r+YFc` -zWr5-kOumY*Z+}K<>4*9!nOLsoffeMs$F)s^gCsjFXERc;1tk|H?tVj>pjY^`sa2Ia -z?bOs-sK>$*h9_+NTTMKB_gQff;U)W4G%sWV|DSgOEQ5!(3PMs5{^U_!86(pnOq@&LOJ^>VS`C -zRzc>F8li`W=(5M5z8t)?Ux@qum`Tk8R5N`*7joDXAW`PKI`$Gsz)B*XgtGMe*Y&{S -zWfPi8)zV!@MVc!Q_+3t}A?5Xc=&uTCf0Mue2%Y`Ydj`DDD6BroX`oU$4>dVY<2^yk -zP}{6jX3pp=E;IO|`dqlBD6$Y0_gyN8XF00g0MF9B(EB1T(BmD5l8OM-Ee&c^khuT- -zozSd2WmHXr?-H1gss#n9D6f4&t5Aa7ck&xl)EO+QcTnT>6un&ARuE_Ea#DFr7&rdi -zo|km=)4+ANF@ApYV55M|&8;^cgzdRk7bQTbWr8LU64i^Ni)o%uKe0{{AhQlywQ^8Eb07%8 -z4(-prd-n#CoQQ5r7?%Qe9$mWln)gWMes4F)L>fy;4KHWN{kmT5N&3EJrM)(m6l~@- -z9cz$i8DyXeQSm!Ge`sUomY3vQMA%kKZhlz0v-YU7dG8mMV}|L?MUetg!K>KmtNhm8 -z_wArfT&y!&m5t{+D$Unv^RurOP65qd*qm^%qpy8xF`{E&nrOW=vF|BFD$$!Fnu0ZLA}A=GV(b -z@}$NO!?aKbu-t>si*zgVY9Ti-B1f;21NybJZ@X9ASg$5 -z6=ow*V0O>tjC1<{H&`l5N9%Fy@l=n;+KFcx-y4ck|e2hyupM9m8K+?qgJ0&efmDn>OT*72GB#eWkG -z$q)~hKf@tA4hhXkJ26v#w+k?%}HF0b^s)8)FXTb%g4K)u0c(DTk{@oZ}8!iwZhhvzOcQPY^Iz9 -z^7T7JOj=6ALQrkh^f)q*?AnA#62c|EJ$azjt2I_nH+$AyMYQqkT$ZawzvTI> -zV)1z5jx|9@TNMZdHj;exf>Yc=tnxwOtJud!$9he@-5M@%N+xJ*!(aS!jSI8F%4Sol -z7f?^}hA6ZpPBp(&)qpIZ=HEdeY#Jv6awELQcb}8M@AzSeVo| -zQWr~;BPxFWK~70crPNqjl)0*T_Ul-38y6jkIPr*d)j*+v#9f1~lc;y3?#j>-0zR4s -zKCs$_*YI9Ub64`wc^0cZKq9*A621bhL}xxdpn4sOIb~YO+Dx_Ia(Rv+O{~!-XG`qk -znREup^!wZwH7)9*qkt*&+Mygf6gezkt%?SKU7Nhk%6>JcDp%;gxdntPm35aJ49>gO -zun$UHI2B|A$TJNQqOV6DCM`#$_kc!EfV^RiF#z(iy$xg?j$F7p?;tq9LkANlHfw(L -z*tKU2l>8gv@s-aX(8ZrP-Wcy0Ea19da(W!a8wSnC?<9$oRqYV9Kl9>x?Uov4f(lShv^wO=HjW%=D%SPt2IvE=q~kz=8^!dRj&j|e>io5t%B -ze&~yoSGcWQUB&ffSL3bri7r~-uYy}ZmhevFkTQddV)W#s>NH@cB(t`Q+LgsU+mp~& -zU!+|8MWv77qFiXixI={MGUa)w84?1laX5<%?nDem$(10-Gz6I0{uHA6>ryZxO1Py? -z>XOFUSrPRBv4w#X -zs>D3zGy*Cd+>?l0d|*$f2Axa8gJHT*>6-H45=4s^!{!H6AcNwq0Q?9oCwr}zr8OMy -z$F-7}AXIma?a2yLB=5yUphQ>qkQ`L*d5PF5vkS2ThkQm;wM}?Dqz)>x!^`JO&KpAnbS)ryKhfKpCuF9#M3QhN<_Hy0fD)S!`Z9igY>HE -zE8iD~Yl`Zux3>w#6SV$2Z0g@+PBt4e@v6*UjgpItuHdEw@6^D61`f=JIKkxK8y -zOcQmX{&(-ObxJsm_FzwJU*6{FhdylHcXuza4_ -zz5P!zuQQJ@GD=*w{dkvTNXB#R?Vqzwj%GdRh6SWK$Vg`jD@#AFEL{;24qr5%7u{4k -zEqc_(%v5cfSMi+y9Cw9?p=q6(@{g2}Fbf7)mJiPtkv`VX=VqC`-N>3{WFwDLq#)g -z6;;-HG6{2iiwk)iM=R-1_`-AZPF^xcSirb42pWv -z`G@gSg^bCHKZGQ+`9NWCVP6?YNT%e2TBxtlgH`myq(~6jdG>$=`sx;URA#Z@x3;^E%VCEenj63h5gpN=!I?1tnFyONk!$e -zv?c5gx<><~ou`%_%lXGG6Yl%Rf8?A%Aa*H(Z%JtjW{M2jdMZGUF!SltnrF|Ry5`KH2u@*Y$tBQptlm$sYeON~Hqoi}|Iur}D;IhhIwi5X7Xb -zk?iW*U!VT%Rf-op_Q3AKZSbVhy}@Ju1<6T!4^>u -zG@{Ad15d~l0gAwXzaH}f{Ou{f0F5HE7QEW1Jvlnr7?%VW!Q6m<^HTi%D{%j}X+aYf -z7a{rQD*I&M;m#>Po#nsm1|Kg2dC~lxmj5^|Dp(lLZ|Zf#R4O2Vn_ucm<+GZT!3p0= -zNs*(LcdEVh7im<2#wA>YnmAl`!^^19)rvZ>_5kQr4xxJ`JN5HkD+6)TEBF1=)$eyf -z3#${TGM*&}pO)p6K`O~HEG!H$Sb0yyPN64TChvh>+dT94WbtB7rfWMtlE7}z!9Z(Y -z7Z4HI(wcg)XoG&KJZ7>xzZ=rEli`w6uYlsIz({DKue6lWrIM=m`z~6vZ?Le8d+DF8 -zOsoF9mm5SbePg{*&8=;1h9)M7xp{euUk3>aopLE87=SJrDk$6}FS$rqtCQP-3~G?6 -z&f(IfKoIey<~w6z;<#_$K3yH1sF~PfS3p>l^OgI!E)PF{eoV~%=b4$rq%e&tX=wDa -ze;>Vr3Qo0Q|1^4kzBfjFK*%~&Qd)h_-qTPG6pdoThih;jAOWFG127^VTWzL(?4G~> -z%3%fg2%WomG<-x(6^w&=x#r`4|8eG~-LU*GU2x%sy%`|_uQSgjtPs>&|D4jlULryY -zKJv9SlnMXyAuew9AKO7p$t8c`+vlgE&R^u>LD_=J -zv;Ps8Ej={(M(*yp^~th*KqAS|ZYIcw)~PxMP!QdqW``6SOibhb$w7Vm40}}&++4H@ -zb69HrjhMG@CF~s?kKR4fi=Mf2AAlw@M1+M4zQhNjM`$v=0GZFT#B-8AdI5CLpLeRS -zuWy3UE#JO35cH|_H8eEJZOUh0u_u>-HuH<(;vPdo!_4e#>cemw%y)?-JtkgdXA9cf -z*=2)OQ*1~bOd-YUXen_Z`;QNYjW%82iWK0PpTll3cp)_p#_$-tG>b0Sfw2S=#Q8`w -zlp2$SOy$||MwaVx2{82Q=PdF~UW#7p{X~OiKu!i^)Z0Lg6<$Wem$mF%`C!>hzFkq|C1^&rwJ>1KT57&Us5& -z3vEuo5+grn9Zr%4+SlFZ!$`5~1}*3wz{nmXn7SEI7oQA>)r3b%g(&CShC^39M$t7?NtJ%;!7 -z(8v4TkcAnL)mMTYkVtkaQKA(YQ9u|ek!>Z`=6ZE?)#G_qRvrL_x}of)b6g<<^tlbF -z4oLgFsZW$_2cp)v+uXnjU!|L=Z|0NY1w4qaV`)I@SnZu0T;?MY5d7pFHGESIvTxQx -z)^*i_+uhJ1`i}=8Soa4o4Ikbd+eHo^2|`lW4A06A{F@J>Ur7-{D|{zmg`a?z`w1`6 -zV-sfnum7nuft540&-@D752UaX)Pqj9|KlhB_%@{)e10ztEeo;(dsQq(u@_r7V8C+1 -zfd4T5q*dD=za#3Xe2Zr+OZ#tv-xSJ{%R? -zf{Brly}P@68<<5&+n#-dF-R$c7Pl9vsqYTb(`yl&n^C0U%JBT<%lzSCYv|`>?k8$( -zZPnG%G7}LIAx$Q&P~L*MLIj%5xpQ-__7^X{uB}yzK-WG2b3QtX}M10<^up7(k50bs7ER}Q4)!V;nX$?^PY -z*HK3Ok&4x>_LAWnYiWTv=&o=Z1h$vWjkvLkmp;ZEbsOijg?#n(#A_QXi@-=zU=dc- -z4W1rP)dESS9;~&JTrl(_6>xIhfrFP#YfQRo} -zS^z1%2AQS8#sCvT$95_o&-l -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 -zZpJ9JSE+j8LpYv4SQ+HUBsNKZV++BtDWHH -zfLM -zyU3V{|4W+xY{%~w5G*s!Whe_78(r9JIfmZDkUBJjJa+|`%Wkl*urHJn53mzS!w#8x -z%8(2;;W%2r&#NvKnL2K~eS}JHD31plhft0D>6L?p6osFEb1r)2XMM;3ey>6PUMw5Y -z7!&{(^(=mF?SIGu$Zx^CflEDp@vx5bBRXudWZW8Wz;@sNtd4<7r4()JgEarGy(Gs3 -zTRmnw(HJUHdtQxYB-DNv^)CLr{(m$rkf7Mj93c)*Q+F?clq~{6Ij3t@?D$0+4T5n+ -zX?(P#%R=CuXssnqY{B?L1jG5pc^4F~k-3Ql1JYnz6MKy4DIC6wdb+l%5L$F>flCwT~TAkC<0C$j|km{usQIoP*nq3A|Q@Fm#X3MkCk -zK*4s(PyIe$F5noNnXclsxP7t>#eXo37m?c^(oKN9MDsFXk^lRZFAiNzL|eji3Qh(E28xE;F>ejdtIEs(pm)ZC~keWvHr^XNfNJYTxZ -zRpk0@azaEBKS1!X{-|xhn}mS`ze5=VsrC*TkSj9u_AW%JRME?$XSyLjG6C7TkoB2c -zvTsnT>yn<$)9x&jB7pI&JKdL56=~ptT#5cpCa)BL`Km7h6@43D@r=9a7JI}+Ou-JOo -z53P25=vja?&68fxv08+bas}g4l*;Lzd_WNwu9PI<{tU_~g%?cPx$rG~+PPl}#t~#; -zYNHF2Wf2XykHem7^(f)l{pMm+_lh4tgE{~L)DukG_0$*OVQ=C;81;Q`%VVo3`X&Zx -zxHC?G3Ugdq8C913_Ps?_+-^nQDeN?-US3{aS3C~SK`-f%8Q%iU>{WTIqw}cpP}B%} -zY#?DTtKA0Uon_tS6#zRl8z6y@Jj9U4$lV(oF9E{3MLi9sVS18pUt$*xZK8dP3rjY# -zJh30A00nTPZ|92q^s$9@1sXCZ#HQc9IN9ABZY6y@^@5(oHE_l!0O{!w`0i5Qg{iI+ -zYsldZc$WRCy${H>^TJ*a&JIK9nkl*UgiKd@mJTk@v3r=Qt6CaY{M-f7f-h!t_h4+V -zB5AS))5lDs5&>_4l`PHZx$OGb>F!A{9F)_Ht^o&r@+z*Wg2phO?DZy~n+n7M7~<{$ -z@b4n=F*AxVqYpPdbDtOG>Jw(#|Y#&eIhxncCsLpS~$vMeO$g|%d -z=X17be%rq0)(uqy=a*rV0 -zS?b!K60WCr{%wAEwysy!7sJ%laC$x4Oy+abPj!z!9^d-~2gY=2Bf!Jw)>uB|2hV7~ -zys2fo_Vo1C+U*;5j0#Qxp8ymT8b1%&ZSR(%=zYhJ7`I8c;=e0+fBX)02ak8lkS!k= -zPGuGl!}~#@sAqQ0hw?-+YPx2q)d(bG+;=(ItwEe`tr#@)dc1D$##Xd#5>n+w-yVuQ -zbm8JX7xXf5$cIb5|Bd|T3mT(1ve*UO#qEn^XU^G3ho&A}8f*DL=K(nh8GOo3`Ty14 -zm&ZfBzJF&9(I`~JsYar*Rg^>~S=u*kmSl-^B5T&s2odS1Ce^7d2`LIQh_OvWRJ4dG -zjD0ATVVW5-QY6oHPn*uSbH2;-d%d3L`8~h>@EV%Wc7N{sy07K^eqa8<>xT9gxW`#o -zSWqKbj+O0D4S4$g?K6XhyRsSCrjC(x$oG}bI(b>{kfw`NJhLBi5icsQQHLQTT5H(W -zcV*~yRpcv!3R`J!m#sLEOW`?Ttw>em9Ed1Wh}; -zCFyca96N4CSa^__pm(qTuE&LSBfv;Yw~30c`_=h!f4p~T7mlBUvPh*+x$*muH*c#v -zxH~uFtHqGpw;RHwecL^Mj%QL7iF}BiGX_?%2-jhF#8{3Yh=xPH+Y|3 -zokDcpO505R#Wp1)ywWs<5F2ugVMFU6w0h_fu9Z}#)oRGrk2V+_XJMZsgGuXL<onhN3gRfEg5j -z4EL-UPp9n)75mU~6L(og`|#WSg_*l)$<%fApwlQ-n2O&$-GnZ8h!f0o9p>*ji7lo2 -z+Ah))Fjr4r3MsUxwqL|u4W8K~S;b}^j=&LO2}F(whD=Sa%s3qo=f)LR#j!^=%)JrL -zH|0WERc~~CI_5^t^Yf^?o2U3~=0jlR_1LdSRx;4nxSdIz9>Lmkslt!#2vzMLWpxSXD)1JRoA>}y#9hxa-dd*hxG`VyU2kSKl(9bD?8?P2=W1BFq^qP+N6gF9DiuW2 -zBEi~@PD~;6nR!LBu>DBYafq;hTW`TpAi8yDk(3+XA4`+5AFzpHU4 -z7tui8xMtni#ot&nrRwgfyWsBjQtVvJooR~Xam;^xyto`W-vG6v<;_(!E*+3${NL -zW(3toBp%;s%FpTIDG)QgaQN)OiLQ6t! -zWoFcDl(TfsPHm!I$xqW=ciPCM{CA8&#Yere52x`fOgLM8K}XV#oTwDT{6)ODY)nlS -z9ypL!_DeNDNyz=3Ba8O3$+a%x!F@98tEkB{Z`zkXoEXWB4!PJf_jspCm}%quSDXme -z#+$NTDD$;R38h&>57te;KunSrrt0ow9}S61P95}<{Z;sTGW}@C*>s$Sf&E#OvYN47 -z|Ef}QTH1o<>5lNiW2}tBJP#i}T%H3NCp*Yh4{2#m-Zh2|p$1txUN^mBx)rkCJ+mY3 -zmMjKmX1iNdA!)$!eWrHKcFy|{PN%`>Na8SqR!Y^hu3E$)cw!rl4N1vk>30P->_k@l -znQ)GS)sZTOjbmO&c3e=tuMCk>)RR|Z@R(lr(Kl)2GCmx?SCaIcdA1$9(SOT(D3jF` -zY=#$0BT_T%MGi19XZDfww9-ChW9yG6E4kMQ%clSV%88FK0+VKoDmnus+U!{ -zQhaJW49x7L16m_nPon}5$ -zN|3DH)_`xPZnjmO!5$qX#u~3r>kc{NXDeNjM7rHL8|~19;(%}l{=K3E&q_^TnR8`? -z;`d}%_B=@aG0prw$P>K^NjX+cTIr@+rLk=1qpS;*35HW(aZa(=FNGpoEr=qArY$U7 -z3-4>G2EfgpR_J($%1F$IYIF4OCuKH7z>cf@=$_J4cCIn3hnBg)U|mWQuNjyc=&!>o -zpa-0=v+4{dxE@;}N!5lTPA7A*hhbp)(Up5OWrbXAPgO49JDc9+0!oQCCX>3GF@#-s_wa0>>FJ6;hLO!;HXL4z8* -ztu$uG+lz%wS|R14!V7~iD8qi&bWy&sjKl# -zEKSz7-74+PW(Uwy!Iqzkwk;JL1>a9kj3W=@65A!F0Kko++yC-jr$5$xOnD%a7zmzjtj?I&$h|MhuUnAy8 -zv@<^>TTH@R=?6lm-jX3t&3RT2(o`77QseK!{?T;C@ZIhqmMeFIWk<`}j6^DyN=?1J -zolVXL`QdhMnnezo5XEs$HWeuK)jh2@I6fS$Ny&lqX$N-(3jklXIUN>dAE4^u&Bsdk -z*B4c6#XWCBD1Z*o3u*aP<7lIl&0sKMt>cCkx(#OAb$~QSuH>XaGruQ$!w&K;%USg# -zzQ$(D<@c)fYIil>ozOB^>fXGxdjx9YwA6Cybp7i7*L$X8n+I3%+Pa<$4qjoF6@st4 -zk$;wHV4joh^n7s0romOfe(J$rAg#kAr4%xAOZkG%FkYrE1^04~6}-jDWEm)gQ^S8X9yU_LoTHZUKL+0Fl=sU>EW62 -zL38|$XGzb3p+^Y2T;iXp(lbcEmZp2HF^#>OIJ!$!L)6WbHt_0ft3dli0LiVL<03FU -z&1mfys~U&>{nzreGen6xf{RHQXR$V{6_kD0tTS0d2AT^k`r>EpJ4na~@YQg)jLpKO -zI8~ASP4f2uhNDsaPQ%$+gMD^T0H9cMk2j1Yft%eCo>CaV)6ljwPXC0svxIwtQ1oeq -zpzKr2sG(EkWW71uf+^vTD8o}s`3`pK>|KX$=Jv3CaC{+u`^*YW%RMt1hb6;Q1nTs*JlQQ&_V={#+klJI_#WZMK(u?(I -z$oCX5!YgcX8z}^TH}S(&T~GYUtS(Bt#sem;$Ty*yYESjGd0re~&S@XmV^wbEHOexr -zqH|29e@x^L3;Z)t)x` -zzbSb=Jz`||R6q#Ft(fmqTUa1L9|(Co_(*;dJt*=*0t%$tA+N-{B5#E}3kg40 -z^`!TQ==M920`l -zg}Mv=rF3D~O2~9BloWlo2ucvp`s5aLDM<^uwIXi>)F_-#=1WIs?}BLK7E~Ish-I`U -z=^CP86V@$Iyr=~q?PGfY95)SZb&QH7?(ozAVBT-0BtZwQZpR7Qn463sK(-Ye9ym}}SF}tRik4we!pftypgG6!?#F;FUTAa0 -zh(f*2#iqbL7ZPv_9P%em6%jRZI^iPcVcS_v$&rc1W`B$9^I-$vj7TyiLeTHBg&d)YI1--#6BQeoEbhpV8JTxMeM`_)l|(R-@{9AKDwjznZ@uI4t1_eoI;|WYo||8AcNlrcwFDB#KzD6n`7!ZJA;ya9U+%T -z*cHZBK#IABpf{eS9}d(}O;iLmZ95$+B<}4&X6x|3caux!%M=jtME~+HOrS|g565PGg35vW{b;!~@#yp|anll3OeI#O$xeo) -zcx&OIcg@ZS_UYb|e{i`|<*9|cv?W?&20coD+rV#?LZHm4JXT+EKE%P4Vy5KY)l{! -zJ5ZLeL3|^kqos{8kjX28!>g!EPYJ0N0G1?7j=m!!Jlmgs?9-H4{?Y7h|LffQ&JO4) -zfR3d17)To^^H1X~8^+Adbdeb2ZrR`b9z~#bYiqg{3nr!-E -zTAWDvkyiCqS7o7W$aG>=PVD4KVUs1<&K3Zjh2*?7*gB1Qu;cwF)G9wYqWF+SvL95+ -zS`p|OcG@Li{-OuK{IL;9j-OeT0ntTt=2$cyR%Sc$$BvSN0DV!{tse=l37IO@`x46M~&a2 -zmDO5g=0P;NgH8Whffr`Pe?KPG|1o$7qHx9Qy18>( -zsE9G~jl$KCVNvk80(!aoN{rDl(fP0)AT+ZUecZYL*|O~R4E -zi2}XY3Fy#^-=_)l)!%_9-zwZcx_fG9%zu{!)^ij%f7U=l2J<)HM+zstm7N&9bm=5y -zmIV)9 -zQOrd|-@@4DEf#+K%0`0Q{5QCv+kpw3a9^l+AOqY@!XNQ%usJ=0`H)x;y)o@B7XO9d9>!*0K!|pq-gGmXyNKuQeR9Z3QTS2Q$l>mxoeb1Ghl{?sjz)uv7Cm5#Nlpn3z86 -z^52b1PMwhQZ7tLHE3xAXIHCKuQDB}O0O;_+tOwms61pn|AUQ~H?TTOeTl6C9TlXMV -zY~t3=7zslLVTxXx16CCp+8%5LHa)lVm~_FJBQI4D#`mgc_Mq~4Pzwg^;aYZ(`s9Z0 -zm(!LNBLTJoRDZ7laY4oqmiZJ=w4SSvPo6YdT(kJiMz&q`@B_VzZQ36YXn+oN@3VVhKqLQOf4K -zr~q=PF;wb@qCqn>4tv_QByvPuP`6dD -z`NUL}H*kA}prccx2*RNwul35Bow^Vx8cw2BAUooe+@BqA`k9xzL-n0Emlq#F@lUs2%6%mG+-bzW)!*FJ-rtOUS1%hI3>uv|9UouoExtF3Sd{=>Onw9{`9#VO!D}MZ*^Es!B9Vrx -zLV-*V!0unS1-y=PE@`hTk3}&;VO{{D)FoX&{2kN}f>(<_&f2!JKLTM(PykwMGwg&iv9EfxB*2$567T^mH$w7R#rZAX?ZO63c$cMfdFIX -zlJul02}Yr%G&B4k)Vz<7wbmBPb!|CK{?J{3s$Ml@M82|Q0lTSvH}Cd<*+`Yqr{|Ta -zokNI%M_XH!$ilj#f?c8J9@yEzTdif)HH^#(M2=!MZ`Rl(Ta>ogU{1f!x2^;nz -z-_f=m>>DcQlZd_Q@(vpJXzZ&eZ8yp`sn0{oX?EK`0y3he#%Sz_e5b}_b9~n+9or2Q -zhhb=I>|??LhiR&+I -z;xZdsipY+wAawkKa%m8f;9a~Scnd7@%C{Tz -z^hQZ0B0e%i@%0xuK_<`hnP+TujSMFo0s>db9*?G+ah_CuF`XSO0Bk(E=h -zv{=>S$JVNg**648^W;~yJYOzUmAmStd?VZU%)&J$D7G-vrHn?JYg-Jf&k}ab -z_^{TLuPj3A_x`5r!LR|tLZAOQ7&$xc5UV)n@sK-IfjlPP7aF4HfYp*1p|ND0C~+~E -z7JP?`99uoYqE5>O4gyGkRl!OV_|>^QIX69_`nj_SA!)1m@?aTCw2u;EHr1r9%# -z4%OE(12zcvNU)GqID}Y*?KWNJ+P8rlKh*6=(nFmA?UX(M{q|A&dVCAj#%@R=;FMe8 -z2!!LdeRekvM&*1;O{{iB&?K%-(uY$}(4`_pnehARSrv*U=3rkI1 -zxxnurtf3uAkpxN5;KbiXAZHlb`}XjIy3%P`Q`(6g3Vk -z6swte9^=M6C1vUrJm(Ey-8{wVD;vsg%N^&`hY?B#5-G0}&`tRZ$rupa3C~#y#5*~6xAcPy*^3&YR=YDCc0Nyt52v*q(j~BngdEaQ -z=2Vy)bl+|>PF?yo`}ix6J!;iunsQU36n$vXV6bOi3P1d-%TQ*3HfOgRH-~9vryCj2 -z@-z$g5?e2~K{LmaF$NIHR=d>XJ@~_eF-J|!#ICg#dKCgruIpVQ|46#WZtN`Th|T&- -zZdJ5^WX)?#QY-jLgPud=3U|Zm#B4Fg0!1Z-B?q%t&$)$wn>;`v@(C%W6pOmMQ&Gp?cGCOghxVZq -zW4lnG982xW2INTWU`qSPmbg*yZ%V}R;YdfQ7}sn9l}sxO3v1w3v_8-Fzuw`+DVN~N -zG_IbOL9WbpdiM%7<7o{OS7ZmYCLwPBkW_$^8~31InW@(kZxGkuK1!NJ6vscH%}fu- -za@*>fPAFaAOwMz-bhzA0@l&TRhS<3_dl{par2kg0oVG0mqS0zhK7N${9tTQfR -zQ96&o-F;Le107F@g?Scy5!4l`onomQ8H^px=jiFptEFEGz;W1zBCkaP@FW#`4(O<4 -znH`SBn1cY2Neh_Ek?@KDEQxQh*b+tjPW_ -zkqp(}K8y8q@vQ^(Q(pnUda%!wXB!SIw@^Sc6_We}C9F_#;sDlX(EgC&vShmkgPFEv -z^2{CWCX&DiZ4Y5uJJT1Sw{nFd&Anq4LvHC!IiB1T((?4mEFy>PO+HL-w4oJIQ%7<9 -zz$ko=G2gJU#$(j0VQ0Coj5%SY*-8oeAR|ICYdgFBsdd)_fTfXkxozTD&L#3F-tF5u -zHt$I_AbtWKv-yHho}A9XaoHybNdJ(J?LooO=aBPzEPBsU$(>DZf2Ml*PajGHD(bPl_<>qH -zf924tBaZH|41A#!w#t8Dk?BiF_ccDD(y9`oZdz??Tz|+tkT=q%8)En}`6ymbU|;NW -zJ(J1|yPSU>??PmosAAY6CzM_?rN(lq5(sj{D4cWO(FoGD#$Ry7TU95xc=Vb`k$3GG -z0(Y9$eZK*@p=l@(vhZ90b(BAkp^}VBSPfMBNw&CRvdX!^8XgdJ8zLXIJj@si2951v -z!mLbng$9y+1Wsh<34g8D+mZx;lySW(v#cnznwK2Hwt)qKyaX@lP7Ri~}zUJK}P)uWgmpc@TmWKw+A&j}Tz -z&he0>>sq$k@D&BA -zuw;@-BhQnoWGQna^)erXHV9*%hql7K=0pJ?%`|GxNHf_r -z$DqVUTKs|4nEBB;YuV&OR?|C7HtT~Up3>{pZX -zVHi*DZD1ms( -z;LWR>G!g^vKmRvVv@i!I17T7pMo;20ai`cLLsYfD^2&{QwbL@~#>BthQ~5kowZElY~XjxCv={NmOPw{wjs^Q-0_S(Qrl(SU6^(SI5FFfT#co -zHBn27?XSdZNRDZ};^7DaF=__qLnP8G?K+R9>3#?-&hGFLQl!JIz|dGvM1THCN+z5! -z0*Id+NSq!;ljPpIPT(p?>dv5dkGy@XRS1pWfHd^SZg2UiMEUNjbmT{EfPF~C@#O-c -z-4dolcK$^=3)Mp!ud0;tVcSM3=LAxY^^m`u{GZWJ?&Jvpt(|)lgme -z&xP*hN%~Eu4?DRd3UO9uYp>F?~EW -zPZ%hS7g+oztyvK%NzToE>gwiZIJAX;UeHIeiwmvyA!ZIVm%SgFb4mtmh1@up!zlzO -z*^V#{X;C(>Ym>Z=iA$S3pBy(0MENW_QOItccc2+$i)OiEp(S+zbU$hV?T^)VbB0br -zg2YMFGE3WV`%36`*);hQ1Hb&bgAx=6e?!GVYl`aM=)x(ThADM0OT!n@#*_ikL6SJv -z`fa-sPK6(S7-u8c>C8oX2t9QU0VF@pTnZX7<9zNPT<~W|Soa-P#|XZXj269e-+Q&6 -zZjGNW69{R=cNJR@d){~{f}eXqzHGS{%N{T8B)6YM$pQA$>LzlKQrqcZLBw -zr2)U^z8$TVVrAo9*jXnW=%;j?{J6_pK6&M{GEE2b8>o5)rVp=1ob+ -zMRVPLJQ$AHa|VP)lZQlsvEd95id@jl7uc(|{R$wp1%0r$v;xy`*3eipP&OAe>$4S5 -z8E{?a1uOepi%iRA(7Zx<>-%jQ%ac4n9TBBr1;+i*S!tin;%|!B7sYB;jkrU)e8fqTnj9#HPQxPOuyI`%1TXd`0o24esloZp=1+zLI?JnkG7 -z=}A!1fm&;|#1@DVp^cW;v58Y2RbSq`WQQq#=Z!j580Z+0u$GoRnVFe+OeS-3^+k0w -z0s5znGj_KpR=OZ#ryv2+m{q+Tj$Pl@pJ{496%*^;UefUB@niD!>-RM@HH8A2)6{dE -zs-!eEHS-_}&CJNy^m6vOr7ouYYv$43=vtEycA~H6m(4av2qd5!MtUG -zi{oS=HSw4+QuSWj-|r~85B4zJ$h0NKK%GF^>lpQ`o52qXe+gnJa64RN8mA)D*aOFY -z-V|g);f(JaPfS&ov4qQH$u`a%rv_sjge;|I__vy49PFw!=EZS<*SWU^zJEZ>Q6Fwg -z6Hc=G!m)Ct?I8J0*m2~DLRb?j!I!R?4Bmk$B(rNQ&hRfvaw2cCH0u-sO_0|Cj5WUq=vc!M=bP>*(zx?qD -zT{fXk1-(r)ihupXcnysCR1=?{!*#zJrO$?F48Hp5Ne>{DfBgGD{;H%5WASx5{rwFb -zg*omu_F8~6e)gLtgTevi%b^IXE5ud9WV|d2orn)knKA|Bj}_7#m&`y$X~sAhE$M!s -z0!&tc?KlUhTkkoRM!g#qyhRB-Nnu&Lg1!W4sp;GQ0N7y)#2Ajarf=O+(CXv2ZVaZK1YfG -zcV8a#&OJfj`|1=0ZKX(^{eWzWkqZNGN$dOmE)UQlwvO}J_!__?(*g_-NARB*hZCl- -z2VmFogc0&z8zy8mwoilgbuOn4busw+Zyq(4g+;qV%}4s5T^?5dIoR?pMI@S`P)HyN -zc;Ihw-`M^*lunh=`5O0ShUF$sm>}x4agB*NiUF6*`t>ei2yN2Jf%InUt=~?bp1tCh -z;222$!1g*W28_ANyBh`DNGV3sisDyh(+g#_BjOJqAnUUjwC}#KHjJ`}J78Or_bx^P -zaS%s;_lXwj2=U3u^TvxBZ5;DM2>*N5sDZ(krUl-L3!K0&k{KrtK9}-(8h}vae?-_W -zJ$TpoHjL!a=|6^lDbG&A;L%S!CZiGg3faQJQv%%Ct{2My^}~CpM@3D8qq~IPHn`y+ -zd)w2-&tNk>Z@Hur>Iom<<0u)*ME~O!{lDTC^^H-T#aGM}F;OrMQlDCQNThreS20O6 -z*E{NAbRQ0wqrmpkHeT8wi!M^ -zTiEFmTHqFzDNmWEbjZ}09gOKoQ!(WyC>b7~Ac7GkVNAWeMBxK#x(u<*IeV!Hre6%& -z{t&LM20mm2%lZ0cmA*JQQ*B!UWs;0OR)U@sq!PiZwa#2~6lCf}VMivn=7>s&!tvrZ -zCjumZe+<{7NR*GbafdCF4%DoQ0eB -zH0llppAp`W;*8idb?(TO__V`Cw*Ye{b3#kI*0-f#k-wdMnKf>jQeXBRebP;}X!Ay0 -zb&P1Kxr4&-ve3=HS%>sQR4N-QiHCJ~S+^G4%*dEs8SJqxWyno{ovvhj6g@1u-cLT< -qu@FfavM?E%^z;AzdSs_=*qmcW9Q?KyFAA9e|2D2QTSGB*2>)OBwC76z - -literal 0 -HcmV?d00001 - -diff --git a/components/adblock/docs/ad-filtering/request_filter_matching_sequence.txt b/components/adblock/docs/ad-filtering/request_filter_matching_sequence.txt -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/request_filter_matching_sequence.txt -@@ -0,0 +1,28 @@ -+# If changed, go to https://sequencediagram.org/ to regenerate diagram -+title eyeo Chromium SDK request filter matching flow -+ -+participant AdblockURLLoaderFactory (Browser) -+participant ResourceClassificationRunner -+participant ThreadPool -+participant SubscriptionService -+participant ElementHider (Browser - UI thread) -+participant RenderFrameHost -+ -+[ -> AdblockURLLoaderFactory (Browser) : InProgressRequest::InProgressRequest(request) -+AdblockURLLoaderFactory (Browser) ->(2) ResourceClassificationRunner : CheckRequestFilterMatch(request.url, ...) -+ResourceClassificationRunner ->(2) ThreadPool : CheckRequestFilterMatchInternal() -+ThreadPool ->SubscriptionService: Matches(url, ...) -+SubscriptionService -->ThreadPool: -+opt when filter match -+ThreadPool ->SubscriptionService: IsContentAllowlisted(url, ...) -+SubscriptionService -->ThreadPool: -+end -+ThreadPool -->(2) ResourceClassificationRunner : OnCheckRequestFilterMatchComplete() -+ResourceClassificationRunner -->AdblockURLLoaderFactory (Browser): OnFilterMatchResult(request.url, ..., result) -+AdblockURLLoaderFactory (Browser) --> [ : request->Resume() / request->CancelWithError() -+opt when request blocked -+note over AdblockURLLoaderFactory (Browser), ElementHider (Browser - UI thread): collapse whitespace left after blocked resource -+AdblockURLLoaderFactory (Browser) -> ElementHider (Browser - UI thread): HideBlockedElement() -+ElementHider (Browser - UI thread) -> ElementHider (Browser - UI thread): GenerateBlockedElemhideJavaScript() -+ElementHider (Browser - UI thread) -> RenderFrameHost: ExecuteJavaScriptInIsolatedWorld() -+end -diff --git a/components/adblock/docs/ad-filtering/resource-filtering.md b/components/adblock/docs/ad-filtering/resource-filtering.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/resource-filtering.md -@@ -0,0 +1,28 @@ -+# Request filtering -+ -+The following two entry points in `AdblockURLLoaderFactory` are important for the resource filtering: -+ -+* At the very beginning of network request lifetime (`AdblockURLLoaderFactory::InProgressRequest::InProgressRequest`). `AdblockURLLoaderFactory` may pause the request, and then cancel or resume it, depending on classification result. Or `AdblockURLLoaderFactory` may also rewrite url response if there is a rewrite filter which matches the resource request. -+* When receiving headers of HTTP responses (`AdblockURLLoaderFactory::InProgressRequest::OnReceiveResponse`) in order to apply CSP filters and header filters, and to extract a Sitekey. -+ -+When classifying requests: -+ -+* Request URL -+* Request type (content/script/image, etc.) -+* ID of the render frame that requested the resource -+ -+When classifying response: -+ -+* Response URL -+* Response headers -+* ID of the render frame that requested the resource -+ -+Within the browser process, `ResourceClassificationRunner` receives the data, makes a blocking decision (`ResourceClassificationRunner::CheckRequestFilterMatch`/ `ResourceClassificationRunner::CheckRewriteFilterMatch` / `ResourceClassificationRunner::CheckResponseFilterMatch`) and sends the decision back. -+ -+When validating and storing SiteKey, these are the forwarded pieces of data: -+ -+* Response URL -+* User-Agent header of the request -+* `X-Adblock-Key` response header -+ -+In this scenario, `SitekeyStorage` is the class that receives the data. -diff --git a/components/adblock/docs/ad-filtering/response-filter-matching-sequence.png b/components/adblock/docs/ad-filtering/response-filter-matching-sequence.png -new file mode 100644 -index 0000000000000000000000000000000000000000..249326043b38c9c667995eba05cf42aa4553a801 -GIT binary patch -literal 160751 -zcmeEuXH-<(wk04+0RmOz9Eyw}NukKWB8i{`CFdjp0+J*P6j0=xlaf(Ea*$BuLL`U+ -z5+vs&7FlAS`tH5m?e|8%``*tU-G3YoICaiu?Y-ujYp!`J;*q8b2@yRJ4h{~9nkrly -z2M2`0!NC^*-2i@4)fY&MgTsoW23OF1X}X<5(0J!AU44iidY$D{z>cwC6oP2kJVSKZaPUKZ?*564d-)rRAv=A^Fj_J=2(z3e>gJcm=IzW1!G -zO=diJv8KV5bo?ocu=hgndJ;YdD=wj`JRW)ctAGFB)A)A$NVC(*G5+d*fARmku{_7? -z6Qz6qmHYqeBkM+Zz+>t+_#6Klk9oC7H2Cd}<%4Yj)mMu&{!t{g&71@S&BuIQvDv(D4tq -z@Rc+(S;wZ8REt^fHKxa}2)WW(AHWt63$4eqTgDx|u(c0 -zHs0BhyNtY50kX?>Rc_p>Xzq*>Y@;nsw~Q3JIQWJ>GI`##(kZ$1o? -zBsw6}RZ0*re~ksC^H9bk-E+#m3y+u|oND;2ta76`D~!%AyXtvIwfC=qxPXI#B@?f) -z$9kcM<3@t#TV_{8bEo4Coe&kqkpRxt8g9ZLE?p!GU+$5SkjWf8s+IYAjZ7Mhjv4k_ -z6}1>qq9O5`^FLOdWbymeQ8^z>9D976`~C-*a&tAyId=CIm7cM%ap1*asKXDz9oX|9 -zjV=SiXoTC41h@3><)nqVtIOk;9zU$&WIC(R%tnnrO?m#l6Ekc;u%)v7BmX=$L2Ya< -zryC{VGoI4|Ef+`AxC+F6L+4p^!|Y8LRm>}WZ;}#o_}Y83bTavoA)j@lQwB26c43c -z&v#yQH2@Ruw{8Yxh%Bux9^WAPb~zJ8s?W -z0Wkvy^7#OFmRF7R5D>;nU^{xy2@g-bP?)-wK|(NQ<<&J((AiFd8Rf7Ig_e2cr?|lL -zr4ePFXVk)l&n(ca^=4zyQ&gyy^S#dtE&D@JyGb!(Q>AuV6eFv>49?M7!%s}TSF~27 -zFHe@1e6=v@!(w^Ylm39Uu8&+?*#umI;>mPs}qoJVtC-BZI%;#Cz~YNw -zpKR)(rgqZ>0}{jq;Sckp53D}_m#L^Ni8uI^!MrzgwYa&+vrm1k1E9GyTCF?-ds -zxD5!8WS9y1U!Bdxp*t)0FSzA?e=1cEIBuT14*hU`)PN!pY(3CE`4TQmDSO;B+|vSk -zR&08@pLfL=SEAr9KS~Pq1Ajb)r1?diuCP$7G&{iYsjo#q=~J3Q^81~ -za1h6tW-P)8q%tP3hL=IFJ&;hDmK#-h-_q>vRGqmx9^^+J+12UybTSV^myYX8>AKDXPg9IqZ#?jeymK23o$)`OVNV;U -zKou6))32!HaRooPUq1e(RO7Bs0Qc$PyAN&jb98zp9*~eH_GhXlCpK)MNv1{4T9bVpc723Zyv;Bj4fzZq~}a%hM}L_xi2lH(W8y7OPv6I -zFfa$6V|ZK)o|P3T=SJ6ng>Mj!5U6ajjy3{mY$*#nKw)B>qxpD)3Gr!R-2g7sS58<) -zDUICprf9XP85E1s`~)QxVe}fkW+#J}yIsYuM8Y8vT}A-K*#v5AjWQcvtmgy(a!%}) -zq6d<^@pug2& -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 -z_dG3025-MBnxs#agmm4Dz#LZktH~XohSbs -zj-$|?sQu(TxQ|;Yu7eA#!TO8Uf@WffEu2wgc7)#>FVpk7khiBwli`jT@|7kqADTe9 -zp0Xp}TPK}-9u02*W5Rh0iPqbE4uW1SkNzk{V* -z;Wro4uG1I`r6xRu19GZ5r7UZE^RG@~6%t7Ki_Z*Zz|nBwpXnfAVMOCNGK^3YCTN{{ -zNqO*C>I`lg)}4gfVcBHRG04UU7&7I*PV@pVu|oMb4IZ9rBSt<1UTjqYaWTKDr}IB=qqqaHZUy-Dpfvc&X4foRlEx=#_T#m -z-_k6y=GU@P-Y=U87`OoK{MEj`r*#0zYp&MtvNM1UEQ@nstqP#ORC>4Q@i*}>EEdqz -z-#EOu#s9~Vs#Sc&bj&CWr;H+C@e?AF2a|w9@d>!`aP#ktst?Ou`9)LT!$;cXdfV#a -zr}%uI1Lid&2H~Rg!kt*%IPUdA={3feus)YPTZ$ny1S2C=2naWURP!gc;gS?JqG~H_ -z@s>@Hp3sV;hzD_PS8{I>Y1trcwHXwke;t@mkp*LT-#s`9V;0Em>9yF24op;+Sk@k6 -zZ4MY^ehbNp6jLx_P1koQughPeRoT!|VY<5tG&ZEQyq0V*ZOz!2j2Hd9U5su65ccHx -zJGp>HrGgnC()&sV50{r$a8JKZtzy`Xmv59>pr{>&pTyyK<0bfw^NEzE<>M2 -zc*4!Y+Bb{ROd}O(@>gB>!BEoEd-v7OgV)37T>B}CKqK^)VyDEK`#vg~b!{fs-!5Cl{3z(h*TROH#vwi(2dKg6h`--FYgJWUElc_e) -zT#JvV;(0LbN-*(lWIjI}Ie`N$X7Ze{uzvr6JAk(z2@*nvy-Z{M2!GmFWsFtG87V3$ -zurQ*5Azo1^C7^)jVk(qgA|D|GK5J4PZXL5MWff!ZXLj$gf)M#*L+ -z;Rm|3T_|U$x6!{T>#&I^Rb#9RMXv -zPdX9%;hS8zJ%S;V72|Ik2*KrH>vtH(QZ*PQ8UcXsW}iWV;I(3Z)yG{YNM0Gpif%6rqm8)T$hn$ -z1m~jgOYGS5<@%fO#i~b)xINBUj@4?rPudgzu2#wy0TI)U{5J3h>;_qIR|1d{K6mlc -z%K}?lvEw}$z~{OE{jS3TZF#{d^aUGg_kF`AW=>!*ERMj|SYiiMVzp;_OStVZI!mjRm4Ozk;oBp@u{P`U6b$np!a$Y~n{qL;K1UuNimF|!H -zL)ZD+a{kcx$m8X?L4dyj|MC8xNAlmSjtE$tNyqlR|NOE)<07d5j4`=OGyfbn|C`n6 -zVOK|F%L4raj{g>{KcB<#g%HrUBo)S_IsfyU|FIn(6@k_137vue_XPUSpTioY34V0` -zmGJE`m_#Lvq(S%Xpu8DGu^3Ct~#kUtG? -z2Y4AHz(7(xpC9Hjb5;V9M5v(O0q*1b#WW&?tdzf!j*`1QIET2Z$J@IIs1Eh`E_W(l -zIwi`NfF1~S;OF#D`2j0oW)}i%E6leNdgyfVu(%kq*x&KYgi);aU6~wcg5azmhu_8( -zuYAbjB+NA6_ezvp`z`EIcK_??;ZfgcCb!TVQMU9S6~&C+>79$XubPKqFF3e?%dOEm -zLYfMQdpyd(c^1|*9mCduotk}3!UG0e8soF_p3{aF$=S1p9r_ewjCx>QnSUi}>hsrC -zil-i=1t*+&WqLp)XE?uX!p3L59qU{XlG-WmH);GL2idI(n;A -zfx-a_sx>b^Q7Tx!6T}gKX`QhDx+)k{#TM^z**GsL<|GvTa8Po#uKBVaXN~Y;GT^A= -z(otMpCR3hH%JZ@SJM?OmZ?7sfzO|0#MGrT>e9na@X%T4IQZ9a_K>wjL6ej{fzD?5?5V_T*|!DEFU -z3akYrf!8!37c4XIVusMR?KT);MQm%$azJ47qR%yjh -zK~7U5VAezc2>RIG9r3@?wKV=!QZbQ5CU8Q&2xFlsY&_Zlv_ATkgF|Qn-`Is|`)Fru -zD#ca*=vhu!o$(OGY0b(`$i?LIRG&LRBA(mlDh_}M=o9qHN~`I~mpGl+JTEmCiVABbj{e9~^Z5UHq4NEKp()JOTj^1bH -zf%REM%o?wwnWU~pG67xDlyzQTw}R?mDS2saW(*($+5!*V{$fQzM1(N=x8Py}V6T|i -zff{4=bg(fDy9J8>e+&M3O8@^B{6CNd2mOWifX|WyFh|%=1YTWO?Z}>v#D!s11HUDP -zxu^QmK$9-DUHeUQ0Vpunu! -zen7ir^3`mOen)SV!|h%-$>*!zaArOdOk;4&ld1Ra`{Ob0m(NqY>kQ*G&E|HR>}yVw -ztCpS*2W}2lNzFWIO}t7qdQNBRvr**odnHYrpM2M@?ba;NCC5_x0v;J+(P3#f*dmf$ -z@|023Ij0@a?`f%xT{?aNb4dL0ZeL!T@9XFo1{!Ju>x4?66Cl_wbKE@A*R5}9^$h%Q -z;`(+`dyDKXpV|HfDFD1P3)?#XKz0(mob)?0?%y;lu%ddBRkoUvwp@f67tq4+d{tA- -z(M8Q&9yM5G*rY}%hGUItlAp1nUBc#){8=}*EJTj_XvS+!XaPYHdtMKy9O2kDIXm$3 -zw}

g(r*iV? -zVF39PW7i)2Y^;Ma8}L%HD`i -zf@CTv;60Y8GxR2)`bkfrxp;QBq5NXs7r^%GFPpKf{Y-ZSa{S~clt7S7@!v%!kW -ze7;Y1q(D-6&p&Q-=11M8CzV)^huJZtE(dQ_*19{7)kJrR7*W7|0Ne4yZ~Yj7-jgx1 -z#k46en@k30KxVbTYH=Gm#!3!PRfV -zmy~xCkSdKl;s&>*jk*^_l;jJgf6@@yLiUA -zqg6Kt$Eb4-T5|o>=>fgVW6ezM-(rA8y0PXJ%_fQX525O$j|RU9493VD -zF?Z>T0p;gNyFJ;|y?|YWQ=#-bFbZ%BUlC&M#qPg=zZN@f!N1KZoBMWRErH{$!$eO+mH1h^vc!?zD;m=*X*{iw9>1p_Dfut)y6|lHGcYmhiHFXI1 -zp5qi>i>P7L>3n9ViGto{8-9ADx6JhA{_l_8PLr-eMo(Vy14`fmaP%YazF9>K$xBH@z&SI=LnODapaR;DvN&=&IY12_ox%x^x3U=fx>we9)# -z=Hr#DS<#*4wO|LS(UubaAOM)QYc}4&W_3;n>|`kbk6c3~;KhzRC*fvn9s&j{$s0yW -zv}4KvW&V01;#1cDa$Xitp -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 -zc&b%EPHN~_I -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 -zbL>iv6|)gRO{CFliQck>^B--p&vIjcsHOt -z{%kZ?2?)Y==(-yGt-0v4`;38TST69B@9Rx@b&g`=9fc3xzToH6i4ACN;FHs2pSIDt -ziAQ=Wyno?&26%;vkmV_KpZ9w=l$YQM0beUlIDZ>_4ck^01HME<+x=#RzozHKyAdhi -z6|ng2gh6lKet#!ZigRa6Sj7y%%e;TM8(*8?J=VwsL0KAyyGW4^62a3C)m7fBpvr_rvZ=^;c4K3v!Ik`JH@Qb(!~(%|;q0c7Ca64_ILC -zbgq8YQ}c=J@D4PIBjA&R+>lv`zpJ0N*$ME%OKoR@&S=V9nfT6d>~XD8fx$9OcIx)+ -zuCNH>_!-yH(%q`Ay!v8h-Z20rMl=Bp*w^?`H>CY1qjrIscY=?311!Hv&heFr<2gy4 -z?2cv@mjY=%&t^9VRf*`E^(uSEq{<0IyNPvzlI2ss+!&r41b3TB3~1H-DO~ki`vl>> -zubtT{J7@bjdIW9Oy`692O9}wPID*QoXx9kW>>11fohN9oV&PskPXcEyTgz7f=2Qb)j7ibN5!A*Cip1|}XKw6@D^ -zBW+7)cgZ?CMA~Z3qJ>jbAM@WB|EHv9!TqqtwSozw&P*{&2h(~OpjM(o)SfgH5s9wRyYOD0wk;rIN_`WAEBo#Ogu~D^ -zaL&wzWmzg|SGO%Gy@5@ci=24wEi&V`t!3MjgTHh-4bVW$1b!zyUcSLNTe^55d)WS} -zeK``4Z0D}vRPFXdr#Cz_9E6nCZY#AJMS&nM#pbGz5oL1wH?kWXN6jz$Bz36V2=_V` -z(Bc9PI`1Cz6lQ0m2(PvgMxzcmr{= -zg8fnqtq-spk;&bU$ce)itad;ae^zw_;E -zKdj$!vDUn2j5+3*V_f4J)@Hl>t^&i|*OYl#J-M&*-!^Tn#r}Mgpvke9vfm}PcS-lc -z=@Pj})n$*CrkI=bETLhxaQwa)f2k#l(~iybc}saiYFQC3rOV -zgU%?t3MJbO_a~A#{ix-5NQ?rjU(&<~H~9;Md@5)PozpN*IW?`)qmSP2O! -z1(7`6(eaC@6Vr)i5iXPz*B`H8cY~K^uRC_){#L4#cSAFUQ-P|8=zwti=A|?*LJbmL -z7eQBlY3Gh31oX{T#i{-c(WD{_;VNa2X~`uf@&=Wy@&*AD92ums06A8dW4I?Gx$UCl -z%Sk=d*;sILg*aAp>n!1<3yLA5o6Q|krR;h=cRvM+Uon%RLn(3bMkx64Fxn+$FJV!=6hLB3l-&30s0SiYxuv6Iu+v!&M*#(f5N`iOlN~Wj&kDPj -zS0}B8uf`wx=q&C@?biQT{cQmBw_Q*Sd0x9obiEbQX@C3Zxvk&rd3eQnL^)ykHQdjh -z@V;TxSk607vum#;JCfbjD?V#|U#;WR_FIk;hLPFlca|eKA;<=-C6fhpl*yHp+jeJI -zWlTP`9HBpdnq}AYBpYEv>0{mxpEz{QOav+CwFN%QnLRH@uGi5z|Jd}gjr{B1>KJ0y -z!`vJeSPfwO>hV=e(X8j?CDwZ%eh4zYjZ+%$cbhnH_5P1GnwoXpdx`)lZwIe) -z`%rUnkoEDq{(hNC798c=j(eujn`d0L-f+fUI0ZZ3UzAzm#?U;#0D>m?N0gtx=(HP{ -zFbxed)@7Q!CRV`mqvoXtq&%{2NbTQ`94E0DieJ5Yu -z@$uJXwKlMw?>wNVS5lN^tGR7EjQG!bkRYEIfZX{mF8u#)3_a -z4(il&pSCY$WkHRb#Sg7EB%&UxPU3Q|o5zkPl;Bm@aQWoHs-?_88W{Q>;p@>s!(oZV -z0}8H*;)ka8;*>cpw7iKe;hpaM(U60X|_Nxbahgqg@ -z624USR;949GRrU>nAFvZNcDVjcekoiAJD#eQ@896p*`v>fX=3>7s~fx_s&ZiIx2IK -zx#})&^JeO|o7t)x1|vRxTG^Tm&Gmd2ok_DS70kaC{to4PUn$VGbS=Q!iS#Be-0Qpf -zKz$sLF_Jk4%sq9kQeI_Q9s)JMOfV@(H_waUkyBpSRl|*0o{Zv^p8_BBP1@V+c@XPM -zpvGvl>@bft)41rnbLYd#kMCBHCYc+b{pK*02knpdLS&e(|p&xY|O=`nbKR+VVKU09@{2Z$kXmQ@RiPEY5R|0`j_`K_YPk{I09C -z;pbEa_gt8JqNO4A>4wfz(>z~od99EUW-XCd3>#hEZQCORCwK#mdP -ztbLBC=`;dH7n4*&)ji2tiGHTV8{ChND~vQQwwjh4vlx-qPlD_nZMP3K(nIt4dhTBMmEFOGhGZBbgKr#&l5avXRL45q(L1 -zNW$n0Ex7&&0q>Dq^a0?o(~ZsfsEpzrDhii{7tQ1Ht-w%t&9v|)hYuOGk;~X^K+A?o -zL6XMkm6nLK#euLCm+|03Hcdb-r&@fptP_>4Rk)r!W>q+GTm)dUxCckw+yI+fPapv? -z?9XC9NZc?D2v++*aGk)%bnb!K5FNreh;Yf&lK=~FuPp7>Ou1;F>AmywUYvvvS!$0R -z0K@6!@2eLSQ&0P7H@_(POz-z|eL|}%1o(vHUIVFFL#HzTNlt(~Tb(;OZje;aL%(ev -zbhIJwH!szZ@LHfyr9$A~w-I!`e$Vq?l%^ynd<>ljI1b5l5puxdB)yYJ@O+5lkaAd& -z^AY@6HNUTR1PH&|)+=N>V?nuF9CPWYg~yUplV -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 -zEP9khLS2t_+)+T0D~P`xRVqlMyUFSzn-ud7 -zVf5YaH3$1#t!RN5*3_uW=h4qt)BC=HhYu<66@HzbwY@uw)%CU-U~>+d(>^+ocSMmf -zXmI-RqohNomA8XqBmI45@5LT>gR6G9HUs26gt|3D}yWBRK#w#wf)(PaZ!xJ4?n1cjA(`4 -zL-^9MoAbKQBqnI@wlM+d!%|u13TjGI>PBxNfL6+ZtTnpo$b08pt>_&g>&G{k;0TUP -zuRYDtq|O>)$|Tzy;r4kU%O_vzn#Bjdj -z$8-wuA3CjO*+GtypIHxs$shoZv~wy2wuT%6h{>Suk9XuTsL47zW9w5sriZSeGed&o -zGz{4ymRIW)0~{FSg`|Q2FGO@)L;__>92&s>q#HT^%^n6lyuHQhR!f_pDq|&yhh`^a -zFkjHB0UK7%@WCuLfwT6m=Htsu{W6ZZ3H{h=Pet%*)-w5J&ti9&-`*NqQ^TQjZgEh- -zi}3WE&~Fr*L!AT^RB_;YSlbH}8h!~9$5b?Bpd#P3LqyYPW!@kveK$PCO2=O3SzX -z;=5#0pu-PBI?zF>(vz`>$Cx^9C)U1N<{80`auF<45){$c`{yj>t`qG&vQ73JzF>BcCHevY$MisOfe3guGGBX&%oy -zXOR-V#}Jz*drWT1d#R8lWRZF?v~7@cfB=Uk9XHcY#Q&feQAxg+c2GECjh1WDgLjb} -zj0x0%X{pSQ_aTzodp$bFC)IYPM$B6kHJ+~8JfB?x*i6i-=%EOmum)|@RDpm0EnIuK -z;4(*|q^wU$_!9Bvxqq!v#@R!Br8O@fP67X463AGm_|I9|72;3sc#PZ}D<6?s39@Ve -zoe;pTBhWAidLDzv`ZX|??vyCGnWofo9XTtvzTD0wkh?5|W`?q6^fVBwTFXZlQTca& -zWSS9+J|i-oqgP+2x?h!Z$BJ#7g-7mu4E{K7#M$)1AlV=WsXePmae&Rx1i96Ba1`@u -zM#!-;r5lljvjiL8)~8VQ%gE3iH)2SUnL?{H@_Bx!{J@hhRB)l=e$c8U-U&Z+Ad{zy -zSV=H|*L?+u>L{YMzj36Kj%u|55VyOjm_{N=f#KtW6NJqx3#FeK-)W-a$d2E>pNN&_yY9`@m|8lXU+)9@gPAc-r+aO^=#qADaGA-jq@l;vf%cjV6KOn6g4+8 -z=7qkQF<5Ii8hySv^W+3AF80;lJ0rQCYSkj&gLiH=pN(i`)jtX_$Q9mT`dQ*`K@Mn5 -zUK9?uG7s=A1*Wgx -z>XwjQ$e8Vye3%I^Em1i`w!5U!ohPzgn11YMk*@5&%PI}_x7@L}oupYfE_uxCW*l&O -z#0j-2|H_V51Ti=@msAd7@=xZ|FmOAEYP0y%LFB@`_)FiQB6txIcewIq@)ifLO3pj2 -z&x%8{{bYbl+6Z>({P05l)i|9Wfy_BHS&Vp7hTd^jRO<7EvJsoowx&wTXp-xA5nL=% -z@{rM?A?v~zZ69>y>N5t}NO7q(Y4M9zTpUjxiinWcFpG_DJwAHApE)EjBnz>>C{$MT}GEC`496)m9D!+T>KWtsNMVjElp>8hwaY_YIZeD`#Sd -zMuP#fGLe1aj$^(jSfeP#H*$iuIIcC>FiH1oG#bS!VAWBZtNc1Xdd}r9ZqD_yMRe;U -zY^hTyd!z)IGj`_Xi?a4BHRJjy0vQ~J{S`!ZGX -zpIqmhnOT3f$V*T}4qgM#$^@Ipxo~{W1)3G*4+N%E&2yPDUiA`CQt$gzvTwM>h_Gx^ -zQk6B}J;+z@{W>NPMoG6!j?;l~8()GwzUEG>?@9O&2{18SUW!K-A*S{GnH$^S>R^Sx -zQ>JOP3FGp6vPcogubnPwVeNPn))3ypCNhK6Iy+H~o6XoGk3Izqb120ienkETM*8TWPg;h -zNPuq(uZ;efWrEu{%?Ih1VaT0^@qx5fVAb6!P!t-s>!X!|xPl%^Hqt!VLtOO=lL<&k -z47rbzsc2X7*JPOw*dCG9LubjJ|+7EK}}M9_Ft=G#~hiHxYs -zIp%cy;Mp@Wa+1&L5AKFhk>3$HhkARy -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 -zq9;i4ODf=-%;LM9r50+)skXTLUYPhS<9&F>Ht -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 -zr63xZSFtv#I>NcE!8i{CN`TL>#MquTFy% -z`TdUQB_m6Yt6Q=?nv~2Mm^78PFKYe|HlXBnx3rMg-fJ))hL&=#hx0VM#D#Qz_N~8` -zRMaO#ijO%b?9Z-+FK?kSc5X4#_Xu#-Kgpk-FvR7&gnz%Yh*8svh<%nJHz~CKM&A8r -z64hCP*?qxc>%v7+kZ0o7@HY|G?TPkhIp4Y$T -zW^K2X`C^k3v)^IOd-W2a77b)i31i-C^sCM&=xA}bq<_#dn*E$#k+H1qcC}vto2NSK -z+D_7~S&vkfpVOYIOG9pv%RDIIa;B$&|8c!OT=g-yV-tRke|mVOgj?_toG<0}_xg&r -zsy7svs`5Jn`>IHLt9ke;tYn#1d9~89B(t(ens2ZZlg@b`SwX?X}h$(AGI(biA(%Ajkazvqf -zOdAx#NLlaZtxfZeEfiEjmKnpR5f_ed?|;AFl3i6*b!AI7PD$t~?02CIA|4`pk`n^8 -zn3N98Wh+o32R|+!O!%tgwZ!JS{>&wkp=+=XJn5WR`AwX -z^NUraRp(>U>RCi1)ubbHHCt?X6ax$xesQGGXIjz>Ie&Jgpm@DQIRM)rD{o&N($ -zON~UnYh9j}{A->Ww_^(Zx-i%BcE^)xt9#cObE;MBMI%KYyQ!A131@hg!Xe<&lC&+G -z?BQRxHmoB3x9$<2ui)Z~dHl2f`EOrIDlN_q8bc+}dHJQi+=Udk4-jpb=V6^~&^9#H -zs>u!Ulc3bW1UE^TXk!R53M&#}pM`3&4BWL03>I+CAP(uur1$v`qq%p}Ur;7$;1e5K -zOj__gn$9+9N|k(|qBG&*L*UOxiC!5*VrqFFT$B?9r@CIr@1PCh*t;QO)H&nWCfwH# -zMJKFvoa&d{^eH?wqU6Uo{Mo9qykl{=25~V}?|XOsjMoe>@85u(Y#4WZQD<-YRSkTIhfK3`%_cyZw-9*jE6W`jc^}t;cHf5j+#D3VQS9~{qCyjuA)+ZV@IdR>1-#L -zAGQllxue0KUKM`n?2)5WOFOYwuH*BrQc2$D1jcg3eNn@#sAI?B>a3i_)t+0FzSf`I -zI)7^QD;F^tdQDZQ%!+Xo;)hV~4jdD><@wEDcwt&Vf5+#Z9ObLU$(H~jd_ihuZ# -z#$_aSh6%0W&C@P2T9h)(TX?pttZXc}J`=w$(cV8`^{vm^%E~}NvFPNU9@&kn_zW1K -z&Ny|UL#NO*@$6@2!+~sOfuj&pJ(X(h;iQ_clPu(l^ZbO53-K%y;oMKcIUSE?_jk{a -zVdIT+r7rP*bMLE4@UN-#Gp@gI6Ax(y&iNn(G!?;GT3+D*VaXg_23v$kz5hi{)ubD6 -zEh~0f9PGh}wG)h2P)K9rULK=qQ)ZgXPxmW~(x}P1l4kt?!W-3E9O1E7MBigf+6fBt -zL{l&3TyKQ3=+p7>jFcK&@^Lhd$jZI) -zrjb5^kmh&r8z_bNa#RG|=r1;PhWwz)JeCak$il0J^@3Vtl1yYs9(!-U!CE4K>N@<5 -zD4(<%SpoZkCujV@zPynGW}OKCYE>4m5ns*jT`w|Pr -z*1wre5B+*#N8M -zUrEX{C4=RlV=CZHlo -z$-3uJPhczv=7jJJB7n?atQCA{imamZy0|9O`(AABrxEcdWmHvVIdM&93KI-L;NXms -z7hKG>z$K?RBy}Wp3>~0F1jnH3-SIKL+z;)jGk;CV}~!5y6{QNOh;gzsZc$?X-?-UWP5=Z -zlHy*nFx#>*Jod?v|6OcAM0ykYaY1c7Xq(crqsN5l{<36~ZD-F7q;@Vvd6cjP894b~ -zAV-BS68s8dt7eWWg1)ah%_LsH*@tm$IgD$-V80v=i0N+zWKtoIoHQsTOlk7a5kiE7 -zNN8;tw|4b7Ka6-q9?Lrs7AJW7NU0be1rmC#25vQvKMz- -z+v<)?zBhbvlD7RY42boYet4tJpd)n0$LIgzm_={-3qtJi4vd9jh>zm0K_ccG5D4|$ -ztQ^rRw;Qqh*4ERT^A%9tPHR`eU@%tT=q!7+W16TEXMS$aNK#U=B|ppk -z@QlBYm|~HYO-~?dL<13<$?-w~*mg)52br37IvA+Fbn;pFvn4FKWa!PM$bSp}bd%P+ -zyzyG~@p)lxfeC->GP@yti~jU5SA^LUEj0hpsEptZaAA0#s3pe7MN -zK-B;bX9$`LK9w@fyoz^M4hH_o2LHGumJc=fdv9SI9Zyk$fU(K1fHAD_T^;6@Wn~AQ -z+-2pfd95yyn10S`CqNWjvE-~?RguP6R5;Fce$D+Mb~ri|j7TP^O&!o-*3kPG!$#DB -z=nQ1-IL5J2ldq623`=lEV&NPdMqM?24;zUgL*%ywM>&D-)!?8$y~u5o7B>754<{$5 -zQYpcTUd6sku1I*H4Kd<{!C6e`X}T74I7SVVffDnq+Q}#uX|lukgr!fdby4-riY&)k -zZ|pO({@DVz2!5i -zO57|#IY%ukMU82moy(5Z3w4@K;)Eaf6~GI5IR%v-Pg!2;y!hvhX1IZ|CME6Ps$nt1VihE@~Jk>##GruDL~B>xSd1SM_q_4S>sZ>*&^+lVsU -zcUq;f4PSM4w}&wnw01(n5QX?T(Hg&sw4{3@$m52KvWCsP@BC(BVd=T -zgBb`rSWEJYQDNh)Vvxu&->$Z%W=xT?$uNq!-MiKopV($=c})A-wb0`*)~ND7apO%= -zv2~A|`Yy0l>Ps^%elKT`F6__7(X)(K#u6Yln3UHn#35D+;}GRZzI4w{Kq$6gFzl&3 -zIHz3@RlB)gA_~U;75EW?+CcLmuAO&=rdLpSr$GOTD7yg^qW4hMi2p5Y;sQ^qb$k*< -z{0d`!x7SY1_0Ae2yDOQrdmph6m;SH7t0*Su>Z(~vA_tBs;yqA~_atvQ3VZ&|3({8b -zdFuoCBGcSc)%E;XLYC!j5%-s^4wr(dZ}&y912Una%1 -zFPFplhR-{ii^AiDmUPwJkoX8lo8tH3(z{5c)!)GzU4=}ZyO-Wc9K}Orf8}o{PaLO! -zy%;zJZekt}l-XXhY69i{qR_DNeEy&cM5(Xcnj)dMfS%tOHtYortAW6WG#|epYY~SJ -z-;|$fMfotpgYa?{jbV%FgH)!+p87XC1CF~RaQ?2Uj7~&ec1{DCOX`@oU_WeNkqH5} -zTbh>!^1JKN$pt13)%I8OYn&1gSc8zRgEl5|ktK3^pV&jRy+m2-X -zKO%alKmAVM2FU1_h?AF)P&-Sc%qD0w3m;H+H!(@M01g=(9Hdk=H8qWu5*&V|Dl02{ -zXWHEMfIRWMZBl_z^&n*EbE+IFcztGs%jR+3%eeUXvQ(O_vZbwaG_HWDYKaR+41b<+BFT(W@xAprG=yCiqn}L|KLS4F3wBr2LaQn+t -zOrtFtB+1l2$bSv~{>LJB(uaUjq6~a6oclI7zpRLVYaRU%5sB`bP{QXg*d%5GJcyVN -zKUWgWQ@?uc+GBmm-)pjttjWbxvIR;buRSMvAq${BY#RQDDdUiy*O?BbIc;JLw=p8|9AI5r+3 -zn^ -za~SweA(QxqBNaKdO6`U!I1kk0=!#z5>T0TRUL1s#S^QE?N;L(sgO}uNa`&xB{Ef5J -zQucH6qc~L(O#sbs-r~~}^%>b2#31^L-xP7u`lP61aY;$ZYyP-(I8l%I?C<@&cI#Gd -z+{1^X0QEdesatH~bCgM<>I#163BFk5)NRG5KBIr*-Me>S7~i_{Jm87)7x^uY1m_2D -z{;X~&Xsee&k*AQ}UgRz|{}> -zY%HGKk!xzcSwR%M9LlfFhyW9XKz1@USQ)+1&k=p&=B~$pBe&%*4bA=!SA%~*4Wle@ -z^0f=B?!pPj`(!O^IW#5z(T9&87a?u&mO9rS4?BPsof8xJK%YJbefngGjxzww4&uaW -zdXwz0U`;$OkY9AGTm|djD;_w?uyHOqFBIEg%<1yZcV(+Fxr5u+gK&HlKy&Eu(VxA5T-B^t<7n$RR;ii%7Xm06i0 -zQv(uZPR8s?gJ>3+l6eSaC`6>0WF`txWXhB|>b>qwPwJfKoX+n(??3PF{L`m)d*A!J -z?_u3*t?OFX65*HQno0KYTa(|+4ZirmL@f4#&zi6)07<#c{0uzk9J7*zX3MsD-vLKJPhOs`f#WqRY(?=F7)C+bkFE3{Gw}41 -z^|DbHWtZXUH}b$3&LCFvGtWQuKd8t50NX~i;P{Nx@kn_UX>7g;e|&yh|LtD?=eHaB -z`)9JOgu5z9W@{>9mYGv73o*Uek5QenoRk+&C4Px3R}T|zb0_juhnfH7SAD#+)v_Va -zv$LICJLp4QNup8UU170pN@LhZkI5dRU506%zukE=5?Hvi+h2=V(T?I2_I!F0jAnA2+kH5&ZN2mioPs{jyE`=NgD1=?Wv3UbTydspnfnyGF1DU)rO~#8_ -z=_syjhz^rBJiW(Ld>06D&*+o%ZeBd)Jx1dY>(DLIs(HpKNt9<>U+h_K#C!Js0UX%a5Dy -ze`aNWdF`w$7IShe__rgE{|D0r%%rt`E5d+(ye$67Yg{zgsmxsKz}Ke^Ah&FdW2b(6 -zZ0ubW?QCDi6+S*}u`*@&%cngiCXqf1)ZZa5Q)j*kk$Ma7*|I!lrw42{4PBIK -z@F9e@DYv$sS!vxbuz)heC}8yPo$2c!b8uBIeBTF$mi~$MB)6i%JRCa#rI5+p_BrV^9 -z_29X8L!Rdn+-1fy(wgdQ@3MpcKb@g}V>>)#bFuMgrbll2-F95z -z>GrX5eT)(*v$-;()-g)oUaGn8+fZwNH9;?puiWO!n_)BQFAE+3jpzUi*t#I;d%ewu -zb8WLe1?-Py4CwY4m=f)CbUzh0^tO2i{95 -zK(gL~q}#q_UAD`hYR81C -zE3&gTSiUi?ecgc?Y?9QDg^u=`X4#nGDqorH0{P{utA@<@B`m8ouOs#)tl -z_xIalH7v!V>Ax=tQ_QkE$9nd0oK-*gF)lta8A4oNDx*{ueuX~Q5r#0yq4y^TKwuU5 -zlqWnk(w{=gy+}wvvYe3BD3>10D5{2VSn!+019`$1Wxl0<2A;fn2HgTy)3|z2tS##w -z)>2ao?$t@YIgl^_i}c8Y?+{JnN@wL?oW_Z>U^<2O;8Wzela+2DCJ -zOIt(ZgZuZ>3y+ZKnr^)`Y6O106PZw}>lZ5DI`_95i6Y6aZY`&CGZg_6@!~x_Jsq^e -z<9GIj!%DNc4?{P@5(3m`;xo|=Z2KGU1?%*i*pfi+JD!4vtYjC`Ke%|)(Nsuwy^?ZA -zM57?J?NI=sI0_ku&wV#zHs=B8;6=Mwj^zvAbnQJC4CRmkX#Kqz9sXi)X^XGX=~)Z* -z4b~q&9#=$AxxTFx8?xfqdNj7hWUr$Si_=13kffP;)4j` -zoqNxDBmcOFW7N9W{fMl--$OE?MqyR($4Y?>Vpb}d`fi{_R> -z&dU4Sd-8;>9u7yf=eS#p^LS-S-{KcFpI9AWg>WZ+R>YS((1NN4$=huc`X<*t2DkQj -zW__TT#r3fcGno_}L#L-gS>0P6fU^=Ddw)Y$))&=$m9GhL!nW@+9W3}p!^HI=qS86~ -zV$WBDUAr3NN>UHMt^DjXX5pkNGuBt`pdauUN_zXt-feR1>=@7}YORR(OIjBc+pAO* -zB1I)E69KFvA#tZ7MADu!#KvrSB{ld?~=JDH%}0(|@6? -zGNy)frTDj2wd2V+4^^5@+oB`_B~~>xHBnjC5eip3Hq{TS9ug3*bF6nm%)QpJbFTD| -z@mJ8P55x_8NE`W@DCWILytFdO$ZAtpmR+!u#gdvF2uluVd^%GXh+RHbtNPN4CS@ -z-HwSnBi&auqmO|3E^%=$co*4%HdaPQIJZki+}xgPBm~i2T=Z -z;}dz}oN5gxNvxl+-9kNW9^HYJY9SpbW@R$85ne;P`mxOv)&*^xA&@J4jv~s=Epr3H -z9qBGqE>D3-W(l1cY95;NSgB-JomZmkIb_<+6^7*73++FbKm)i|k2S8CGtFpi$rAo7 -z`}dR?QF+2~SzoXTs|kQVu=#6}R9V;RSGUHp!Ceeapi#4}bl8ChK0f+k=>Uw|)8J*| -zF9%5(p_aS-ii>*pEH1*?Yio|VbB<<5oOKw}nSq_33W^rICL|cvu##b&NbW*43sMeP -z8uYIbxCO~>aJ$L{2Yd)COuNZP}{y| -zuK=?6%hhC$Pv>WfpRyEd-nc6CP7+PgDeDjS@871UET7rZb$(=1eU{Ug+wk-i^qfCh -ztQegqE5Lq=S(gfE;v#V4TxQtvu~fexKfi;7gYgG%&8=E^ -zXoSg#n@!8uyecu+B__^*xBQmJsTwLtbH!MZndfxKLhA%dyJdcTZ#eamFTYOBdEtlt -zajuu86FJ;>PT8_~npP)K#BbM(+&O>i(s%Lm&SFiLghzR;i`&uYC={1_D~-QA{9)UM -z1l=#1$2M0;muu3=>oCMJ9C -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 -zR2;6`JN&KT<{s7+$3-amfNTlGN -zdl6aSwKwALxMEk-EtvBAJvyEQ;dA?E-T{Dh2!j%{prf@-Yk!~JJ)*~XaUl6KA*Zexzg3n3@GCJD9 -zGfq;uGF-*}xqwX)l9NlHc`qn?vvpVko?J#Q_=m0r*%95|iRN*MiAVXhyMgi@?P0rC -zjl_%oot1M`9))SBNmU?V+Xtx+wQ!{XDVKX}tgLThc7}7h3Cu;>ZiuRPK{SS7P<-ke -zcS9TM%}*Z20Z}{|C5vtM-F=>g{vs#;kuhIFPn5UZcCUf?KREpW>-Zc7tX{J>k2XFg -z=8m{svuMf;B137zs_r@8Am?LrnD>$|Af`pkhwz^(or$J-6wHix!F`bZ+HqB|v9s5p -z--$_;zMaokhkNUfcI@UDUCqy5P%S1Rf?^c4Nn%|Mv$qt1i*)FRT32*jOInTG{Q1~I -zC=dvfKYu$t{ms}i-mz>S_7Iq^KBgw0MTrtnO6xJGdqm8%tVw<8 -zsI|Z?yuxDB=Twc8Izo{~Pa~+e^to5r;9<@I8d-h}nJm6Fgrd1|MxOBH!?UMxU%E;R -zrR^Sn)}QydY$zKc4xRZM67J711b4n{ZZw#`zOiv$H{;e~P8_Pv#X&~XFxz=Jj<4*2 -zqZM8YE?QvX{)WibfRi;;&cWt>18Hx9C^|gcHWVrUPI3VyixlY$?s-AObWoi@yI@xQ -zNsRJ?Fu`=fH2zwCB9$G7oJj8GOlqKKKO9EES_@xq$~>_T9n!Q%rcNZ;R9JdCbZ52#6_xo^anl= -ze3@lnar2?3CWUY-7fcL3AA_HeU5AY;I?X4V>(X{I_`wu^J`7HEuo`|D{P!z|oszKU -zevMs`lo@0r7H#|GKEiuN6|f(#rXPbI=s#RnzYRtGr>qe6#LLK9W1&BL{}dV(_`Rje -zg_istBLB0|$!nLC@B?klH~1q2__Ig4O6KO=`5EK)6_F&Kq{o;csnR@NHszu6v!>=C -zb>#7ynh62fUy5ob%iec~qXhKKcoC&~0;D)yN8otes0k$xa)b@sO}c$f~7B#dcUj~s0;;hPy+vRs86 -z@-||m9OeFK_y12y@rN|+-`R)%&Cve`GxRUmw}gYd|CzVB?siG&_CsJd7u1;qlre7e -z;q -zagR>+&;P;o{wtS64_2Ad#>769wEU3~VWQw5PLfjl`;o`FC|rwhoEy?h`TuTs*??TM -zJDp8@CIZIXdH_$RaQfUOmNH|lYM+4%Tl5?w7<~fZ4tH>f?x!a`lOT?6;8)PL4vc*r -zwCZSZMG4T)C;3LA_~$=<;=W~f>|1xmCnS*6*fUcVfpG?F2n}456k_sGtlF3%t4$gy -z$Sv;%BBqNl;4tb{nSK0?9$UV-^C;7?t8?WTq{iN$|ERjzRMhdOK?O3`b8CoeuR{I# -zuMmGT*U`7}jQt*!%KKQDG~qhj`7UDspsr+&0JLCx_ZXK&soVpr4Km0;VNZv?$h3^Nw`0Mi{6S?3?^(Uz48P4?1@<&D| -z>&?oQNHebexR%i)qhPq0RvEe^ -z1a3v5{PUg1bL=h-ZsB5x(08cNqKbe9h*{`6EcE$cpb(KZnVl6^50977CQV7B<+ed0 -zrsx9g3yt=wMg8gUUEUVDz(Kl)YzxR-Uw9r2ya;@UM$J~FM=F3g@j1d(>Xn!cR&5D+ -zE;CHh$5+~Gm`T`#W61tqtS5Ft)_dZ#Il{I2*jcxRnrx-`j5(EEgTHYe?aLE} -z_~)fsWTu1;l1^C=_luNvz2-facnbqU8QWO>5Bc9EVR9!et)_aGBw;FyMV -zYhc_f{bLQD17O`1(3xxcM7fu3BrHtEfrt%&}?}WaVQ@mag;wQWNYN@Zt -znl6~6z3?}pxbSuVMtt3TXiCHxBUSMx*0=C>4XovTjqMj-0_UUn$`bby-z}N*%u!4& -zWPdAZ^^Vds3mxPKtf!fRnfFe2rNr(GTtiaPfwOcTaW~IRUF8A4I!!n -zsIzz{VRz666ob$2YzqWCoq7Lji-~*ouEN!o8F9kG*?m_Yyl%;zHg{BWNWN-}Dip-; -z4Wet~mbEBhvDVVjPS9(b{)*LorGVmd9XZBX-hbbsr~opf57)y?S2WM5&{=lB?u+vf%u*XlH1 -z%0F62=bjbyJdwn%xJm~1+-Q!exAN&OK-;>{&HQ`*I}^|b4i`O$;BJ=CbP}2pt_=xa -zSedTGC7BJU7w!t?_YU(Cf{BH!Z3?4MdM@O`0_rU;ax&($NUSVO$;y&+5Zkr?x&EUY -zAyO`x>ZB#B3F*!FZnWyQuEwWWv``Ih+|4Ey&4gD8G6e -zvBXOMTanrKB1d<9Q{(1>I+uqjgBDVA)g)2|%@X?cES?$H-F7&Blv8DN&<=N;DvHxo -zYan-%a@As=w@4+6>PBwHKDj%eutZACUT}*xl_IrTN3n;JcvO1G>JySZhWcOoZWOt$ -ze6T(?dR30xn@{NatI8Zg*y@@3XZ^S97qX7YJwSbKvc>_PQ+8mynl44=tgQ6pTxowE -z&GLi7pi8eGY+0uIxkLh;Ywxb3*W3djp{Cv9C~X@L*XXV~MaGC+b9d2tdw;uKlMpv2x=bKPP-@Pz}66kvQLpqI+a_=G^@x-rWVm$j)5tvK|2F8yoh -z>9TSMqGOeG_CQ*a3hCx8ftx*<#jx>!--30f2R2_}$72NC)mFI6@hA -z<=dXSkxG71C^(m{MZZ_wp(flv#?F|!WOX&Q$HJsDHz$~9>oJot@$Aw0Po#ZBwx4G? -z%#->B@8tS&mfeEMZkUM6?rer+yDCiBK0Q!iIM*1<7Uq;b2zl6jK4Eic4NabKg`rW( -zGIb72O|Q1zP^6@V6f+klXetdI=7n|EZON&ay^kxA`db*eg_gctA2Zy%(0Fm#_nY5` -zZ`!}$K67AIcGhTz(r5c45(tduYRc -zL}kxVck`nFzGCmL}6*Q*gQ`1w4%aHGRwQFl8lai_Sxnk -zThDQ}yMU;Et1Leu!)$$?|4t^uBXU8Q!|o`ZoH4ia>(vd@XlN_voo)Zxn3=xOTnPiq -z8nGQ~i;XTt4yxHjcP1n6=q{RMA+m4DVvIk_&%aO{7;kgBn@d&)GtJWZ`M-I>13%yM -z+oy`#X@WhCI8V+y(c0f6GG*dJvPxLy)V|*Ni++1M^=sQns~~AE8d^RcYc!~iqB8hX -zm`}vM*cUi`;sab}*gHX+ea9!~*9Q$%fW4aDJxu -z5-ACQJOV(E;-79a-AMfpV+YFJ{m)HhZU;-btmh=p_ht$Os_l`uk0Wr_EoIq~=5ZDC -z(YT-mX(o1`#9~#ms>QRxa`0IyBMr-*me1;aatdV;9prEQf;&@foAMXM^U1?f+0wBK -z?7jpn<$BHAw`CB?%>SHanmse8-13o%A9b}gb+K#n<1LQhdtZw<^7mSX!tJ?>0g#mP8w -zf)7NeZzGFhf%Exh9!7t$9lrh2?OODyUb%4%ej6)RyWc#HT^oh%ez%O9AtyCIf58mO -zK8)^VqvcB;9*6o1`>*FIPnd2RzZw}hVwb0pIS$`S~pF;{Fx#%0UYS;!#le{M$quN{P(XhEZglE~cNdLMF)T**^u7Oq?a%1SCk -z!9-E3J)8gIVVtCLedGLVt7D;uWDbNu%x{ewO+V{KI89_J%OE%3Hq*G0mQU!8bwi%$ -zk$35$$G?qw6_xj4lO+FspyMSg3~vT@D_sOO|&SG -z3`Uf59bBdg+x_cc%JCm};s`UT?8*wIQl#)6k*Ua?PVdbr#s>hO3yAFgX>rGJdLQzw -zIq>3=sQc(pd1Ym#PPFkmTKd~tjq9?U%%MbO4uSq4jE0;1Hw!h=Avuzl>uEHS$G2eF -z_#_nz;%FQZaXL$mr=Be4ijMse6Bg+dPV$QDCClkW1@SvYgE?mQ&BR-!?&_#o)zo{7cD=GO@};ra*;0$0K3-m^ -z{o*ZlW=uvdnk)sbuRHd^9w>K}eqP_qa*g*W)lwwAKsnJwQEKX|0NJN>?81gYsL#3q -zU1iOl8v#qE(rB?CYq@o%lnAg0^Y6XvX{0DyNjKTP{Eb$J8_Uh!; -zocSX2pSzU5ei`pJoU-{kTI7`dyP*}C;4n|sB$j<$i`-F@;BNK59ostZ&9 -zBUFKcy6LEafdPBK#Ork*S&)~RXppEpT!Sd5tl`YkIq*j~8T9Q-)J*@nNK039zC -zfiLesHf0KY)Y)`Mu3UkZ%S{Mw9op!ku~OZQh@?c4h7%|P)IOeUy2C@(~~0LlV}Apq1fny8RP#X3GpW#B(Pz)og9Xgwh@`z%npWLQWfnI5pp}N8}uZ!0`dQ -z^h$-$HmszU(cUld53o$Njvm7-F{g=})BZ>WxA0_6THmPWt$e=c -z^YHxl`M&&{BHtYh_mLPo7B96_W}_5@45~OSTsu&+zq|cI!hoyVpnklRQC~=%WlCWw -zDsBA9X8u-N+&5EG13NLG{g5_o8=T)=bzXmzBtapRUeGt|M|2$a{8LzRtTv(*N6O&Q -zH+#=?R0SOFX?(icJ)zopWK?_4VPE{$#k4j_utLm%8dhMZRSru -zJ(kh0vTRt|e#g`z?UPI9C%wbKxqb0)zIN57iLIk?n1`jd -zO7r}a^Z;ko68JC67$4eyYFk#dq{6k#-#wBR{Rd*3L>JIlAl`VxN=vUv4r7)U>z~9W -zyTwLJLw6g|g9g;e{$xaYwXktnz;&2z(~8#JrZReqASHGm28ur|F`%2_%GRqIIY9Zan#U}mcB16w>(y( -z4D7!$bozC+Va;4geb#o4vlbgrbrFCn`K)heq&Aal#(+Wj#@ZGAUG*}Ei$mriF8syU -z|6z?i89AcB(T{|rk1RHeFX?II -z>}e1^zRt4Dm>&61<*|gUx8hb#rE$P!i2V>3xQ_CD6++StOfmOhgVU<2I_S&g -z$!_)iPon4=WOd5j8K=agW00pDZ~ExV<;WnO{~P50wmAX5rBdK0tcI2&yy>f`VUtJh -z>6xgp5)_0|B2ZhW#|%z^*ohb9A76-~Le-hZ -zUyS1QSL&?rrc6%bIxz&BaRMk>2@?gSLlgp#qSo(o`P9;?KCS#e4P5`F$4N0nWY^li -zV$F{nCiN8mqY0(HLNO)jE#EJ2vHzKw{pC}#NjNoqBlG4@40y?pnn1v1TLzyhNJqhe -zYVl;bX^6C-Gr-bPWT&)uxXHsyR_CBR=*;BrZ?6&tMxp9(BiRY1JYKni_upLsxb{F4 -z&BTOo^QgZENm-eZLr9tQ<)kw+Q$&tK{yiiM6^SNNm&po&^vPa&KJ$DjXM4SSmbw7# -zwZ#{=c~YNFboFGTwUYlB^=8;&3;_spP(jRL34Q4Z0&wXll#Ez6-^`>P+aGfG9xeg_ -z69eG~9m)2sk$zEuRu9G7y8sqD2S_^EIwNXPYRySey&&^#h;?r@5g5o`e_B#Px;5fz -zDG?jWOFz5*XG%a@&Pi-0Q;W$D@7U~kwaR!Z8>*h0IoDh6K4fL3c5?dcw)nCZmJjUi -zttvd`;U5=}bva0~Z{v42Yd*}jI<|GB(=jIy<*n~6UxBSET&pMiZLI6K8dw$k -zGh3qqOP9EGTsCU>9>09_%BI*8w0ep#qLJ4h8{?68;jaqmD-BuP&1nkxCw`O#@-L~) -zne(al5T-+WuUd{3lQY8nXw#f-&b~inoQ< -z%Vndz0KuZ5k(%UqifC$-5$Bi?4;&@;_;xEf&aOP4N}0RJt*^xVs>*r=aCn|h*Vg;$ -zfGmgt+w$UFxLO3$7ERykA$t{nRml_ULBC~|gNEGW?tvb0{lu!C#*mF{j5IsPgA>?J -z!}P2R+<}j}Um*qdeOLUlmXn5c`O`=97=? -zagk>ua|m>e9M^vba4z{Epanpaxpw=sr9-moi|{S|t5xH4p#puhR<*qA(CZ$+iQFA@=%3q}QywT{sk-lh|;6~O2{fhnhCnnolx6<~ -z4>kP{sZH1SM4_3p5OjUbTQ}qks~p{>+Q;FR^GT{p0npm(6EZfnWw6TE!LG`;d{tt0 -zilYwv22(?3?Q>kpr%v#^TW8bwq$7hqSnkfMJQ-%`bM1a%mCy*dnfP#|?ZU7TIvlk^ -z4^Gu+D32rgBFTp`*4m4GLNjJQ#?ez|v>u#-BVW;0$S>Q~8W=Q5tm723DtZb+@j -zS?Ahn4y)r|-!=x+G6rpnaLTwl;vCkOSI -z3UOOQ#c)sQ*%^3yO`(J!O6hxLu{DIAoelFSuK=Q3?~6dtKG%>FWijH&K?FEv?3d12t3ffH_J;L2b+w?gr3aZi -ztHJguBZL(<={%X9yI}2!MD-VD>~(2RxE}?LSBLTop$fF8_o*~4zj3gqh&lD_Qo~t` -zB~r~#iVZ#`A~BwpjhZ1~bP2cftIqdw|1c6w@6Bp{sYs_WGal6!Yxi`e@8f%Tq1O^i -za41ebv>!K;o|TF>NC6?l6L@#Kdc$PrVbYny=`kRn7$+9v#r9C|?gDgNzh+>@s`Fls -z$rRDB?xh2TNXX0wgYI2N#P#&#mG1AC7Cmhh@hMg`Yw$~-vr^S^xC?ic>Q2t7KJ@9M -z1v#Ti&(|?uJ8NWPpcXVgxs7c{JbTT7t18JFyThJ#-DuQ{m^N2(&0_1^sk&!Zsm7m! -z(2g9_r?bm0>^>}D*Zk_Gs)jj=Po>)ID;rs_ejiEto-fT7oh&!BvY8|Lw%pkdh#^1I -z`<_xd_6;I@`U#pr>MV~!Fh=Mq)w0$&u`SD(-Oo9JBo%+pp>P;|(GMTZRMekVVSUyS -z=Xs8eidwk*=Zy-@IQ=$9-866$-51_xB_FdY@q(V%j=o28-PaIlMctvu9n@`ZWCBIU -zRy1Z-Ji1RU1Vx-`DrxkoAqYp>6V5R+a}~o3`Go|EeLam5DciaU^jJ?7R;%gy6A!A@ -zV#FrjGrq~M;pxpJS90)cSI+SoP>hl-9Ed_>A8x<354=`A -zOHUkt@SpS;Y*L}B$GJ<2wDTJIqC@muh!ZSOJWpqlL&Z0Aeq6W7X9dk>n+g?uVb&^v -zr!R$lGM;obG=99kaj{tiyXs!{20!`p>?U6>n9admDD?IGL!K5E9gT$Q%mBOeE^sne -zyQgzfLbsjAC9#8LzeOrk%uOsfzi^7|lWV)M*>T`0n~whFLNV3Hx@k`hy}m6Q-4(G} -z+UQ(+;=Jfu%ZJ0wy2gA1F8`g0y}SgPjH|aW$c&U+EQKsqXIh>xNy~a&m1yj{KyNH} -z-g<|=0gKKQF?7o6V%{Z=mbDU7GF?8GWabI`#M=>DOOm=r7Y@SpRjU75H<)~Nw==|r -z+PxZ=`_r3P;@$`z;P{?!zU{hU&3KHjg -z@n6og>s30qZD`rZd5cSR1NZKmtU4#x0>;x4k`l~6!MD!r^{YgQ8eD8tpGdktRxL7p -zAFpaktHMwuPzb8_XJYcXtB29U-LJzddZWV1JbEwz&!jkQ&tdL7>!BR<&=&5!*{x70 -z-;8+67Q;JgwSTt2>_EdU5PsK>LD=+F9iA8+bfQeEn$V|)J3gD-z+#CR%>f;XanDAp -zB!^Ah>@E!QykWIqO5;ozpLs{4S;&C|md!=y@s5un5Jr{RGe2~D0m3ni-xn)De@z>^e0ZvLokqgASur4H86kLsUyi%}Fo!pMa=aA+; -zjD!-!l{O!nM!ZH01g@gz6_a6%rzFFmWAPUJA*O1zwUbq6qOq9!Ljk#u1?ecHk -zQYZMBKc0KPML#eVGCza7qw~J_@H;j3`JrC*z}}nmU9%Rh7ht@NTS|0O!bfzwSF{;X -z(C1*K1p0qP`m;==D~4sINFu!|l~6?^zITDeTQnxHvkBfAbcKaGAco`C4Y;}4H>^@$ -ztGesf2Q3`izMD6Z*Xvuj!>ZBJ#V0#E&u{!pJg66mVKs;16OtXy^^YRFt6_FuUHZOU -zL#^xuh0_WeE%3`qxllFrlTKXJyby^qC3{M+}U5k1DZ$c>AW5csLlJq`#;03FN8tq-8=B -zGU@&|v=j{v=3{*njVt|Vc2PZuRJwN#b-#bq?p8Ss=|w3D{S)~+M=Hu9M(PB-{ijee -z8K0~^X~P-TW%9I(w!yE_r++;7Ay>*Ti3LGErd3T(%vad#QmfU|7wae93h4<_%MjRC -z{s}a0l1W7Fx;760ELlVL7kk@r5#{2);d>h9S{4k|M~$(#14)GO8rfq -z;owXPGQcAKJ{#&bKG17LvY+XM`8l?=^b=_b*>(~+cGk>a@HYd*_~ZiFc=p88_)rR+ -z@-=)Vb*HD|LM^j5T>k4X`|Gi2d*`+Ajh4m;(_ZyUm6&*$(gyRUX{Ay~1>@<;^=i`e6sg>v(>&&9SYVOZ|v`_@k|4r@vtusp$QBX3N -zef!j6?n#U{itS|Pf|lwT=kW+ug#mi;>jia_mluXeJ$T7Ljdm#1TrDZE_<>rUnmav -zlSo;Z8X6nl@q{&PKJw+OtM*t?Cq4@k|K(^MCO$WghS`0=c&2#zd~oHsipwl6PGE?_ -zqq`_+8|Y|)3Gx7h^glr!u%pHqQNYQ}gZk!G1(Euq#h_EgzGGj*O%qSXWcoiG^Y#Mw -zR=&PceFNIrYr_;2Zt!T;Q4NK6*8Zw-a0p>i+o4t2x5f81_~jp- -z%EEIe0H%2Me}*_>(gUoflqCh_lCwYX&aOxlF$l4o)s{KB8EsH|Lwg0F-HD1 -zW8^n``qSzVQ~R%Yp}&}6e2kYQD}EC1ejVtyH-7|9ePvyY?Z14}gsE@B3Hf#yVLgN1<2%H*D$cReEcnF{W=C{9kiUIGq% -zD;H&YrKmw7zQb%Yt9;rx;WaQrqA)sy+}Wm9Icp|U%x%)vuk_B|$Gc!>IYkeun|!Kf -zU_naJQ)|ui@vDK0aI^L2vk?PGnTL%z&J0Nd=_2;&+Q8)$Aw0O{9GVJp0}YG>ET&`< -zT=0JB|90=c-o5`Ll2P}`{};TtUmS}6k)|emya}Ie+*SJr($sl&K}9r*S0)_$NidIf -zk%3WQwGDS6C#!r*ixkr_p> -za%7I~j9eAl3g?8s8&NuQ^m6BWj%MA-ezBEb0>&zqrIrIJ&V32??iDHki;^S5gLc^?JFWc+N5D6`)4){qPTWcKx={|V@jstp+LxS@aucAU8#VdMNV$$aqT4%pqOi%~5w!trZJs?RG{` -z>o{vmtg=La-X1C2eOCV&xjYm>HQ~r1NR5U^D=Y=Z70@?TMGk%I&4o?CWQlVUclUzn -z=1_j*$6Qb~z(;*%uRnTbBvIsAno#InbV8`72yL}@eNovz;tPsRY`JZ~EJ_+iw`BMa -zd0asD;7f+igTopnGNwqvi$mfnr*rk-QmqG#QHwAHt=RsE!J|Dp=eF>FYcMp`6Lg>Q -zu(^MmK}iS_%Eqv?EI84!~V -zr5|5XGY5q^3j?GnhY;Us+8Gpj$<`9*a*xd)aqsAJhLCRs|$P7j;4QSC`kU@ZJOqK25srP|@=#%jnB>+XY0+y^78pPfx7Qld0TW -zSb3;u(7q_G=8Jq+>+Z+sT*{DR|0(dM8GYYooDb>rgQt}}s0-J0nBk9}@mbn-RJpl( -z8P$r9Jw-?JaSC7660Lu~1t#O``qpaHYOg7j*(~WWc%?}H`yGWv`>POuOVza%RaU?n -zgV&zhIA-F;J)cAhoMlPZCd2IS6$=Xv&@&Bvr$dSA%Z_V_iK+`ffaFMd$Tivau_J#( -z=AJnWDE(^KQxvWDO(HP|(KtgN^FRZ~U%kt)P+s(g|k|7d6hE&6{1K;L^@-eqwR$GKu2}980{FKu+VulllZzZPh6Ex5X6iK#xjx;+5QfBH5RVoRkWm# -z3YBFc+KKwrtDad9+Chsl$XRseklH59?b)27^t&jqP;`xaD&uQ#jc^VV@7IyX3U$J9 -zX#Si9o)$$5E_TO^zmhy|{rGoW&NI^;p>y#0riA4GAoi>>#GNy+X9{TGr;PX>=03sr#U2ZewG%XuyNP-Z)0VMDJ_Kd -zj?lKPwk9NR^skRXFWz#N+}krX^^JjvnWG=QIfN+KZpOBPz->YS4i$;MnGr}rM?h0X -z)8)4C4=#YVQ(U$*v`q|+Xr}$kXnw**9n{9=)fMlWs1Y7NzrXmkKP_8dCamS(lv?o` -zQvFo|C6@tBF>o~#tbDxwt>x85KX{k0)N77)zXz7Iso(P!=R^sSY^VutPCe7rv&MOw -zKaAeE4Y8qtPkY#TwC8yllwS7eui_)6!@1KL7XjO9?JW?le=ouC<0Ea$an7SF)=w&( -zo%kt~8U_NcwE{0!vT`m9ECrugi}4J}UIHw>JU%m54Y9?mTLJTk8C?9k8(z0SPQ{tcJ@)06Ku}TfmY9=RyT#hoiwLuAHVomR -z+u^!QEfz3TS4V2DED(kZjbC(E{{N`tehGc*mZ2s>YEE)(;xCH|JQ85=NYlOm#Gsc~ -zS4Cq(WbcnrlkAbZnCLTtHfbSfd~&I;c|(r)82(u8oUc2!k@*YD%^|s?c|^4G27wQv -zTskvW)e>(BW$3DeSYHFTtPEl$?V?cXut#}K6#8t5xP4eyzk@U_909G$D1T$%=ZG6_ -zB*7?Zl7EeLl|_)-K(&R#SVfY%m|Lga&Gw;V`cf$fzSeJ&>+&W0W?t$M`o1feAK^#s -z*QZeQVa9En^vzKE@$ZT$il<>F6{Lzsj$cHV0V+Co>BY|Ni+$0#rueg7HnaOM*j0f8 -zNPAL=F2b%2jIHR~xPRoebK2*`Jy86PeHH1 -zow}%IG@P$fnTK+AcZrtJlbLdD9UuKRayLVEN%wOwv5rJ=H7d?FVQiZf795?fgR-pJt`vrojuU| -z2twb_ZWc>YK%)t#8q86OPW_jVK`?5777JuIvL)@n!7TFDft<=~w?0CwBMn-r>lnWd -zSsn4tvFR;X1=*55Jigi2bpuwXP7rYSLc_D?5U5%{S~@12V(7MOC{kzGK{eDGTPyFJ -z1H7r3IHW7Mz8i7;K|DBv#_5!ibT?&g1hkG;GBOf;(g4uOYSoeSo@WbF)VG5Cam(kb -zs>Es#gJkj(N5nhRKa^6*F8nOq=c#a(=_);16GM>j28>tM9#4${AkM*R=Og<}7#$V+ -z2w~%aDzCfRT6lp1FPNjx5D6Pn7^3UHqB#qi5GI>13{C6y%kD6#UdMcd$PAYnfuzzg -zc1&o?@}2AQ)_}g;1sHUu5VPnA48V;L%#y&OWFm1jeO}eVr)lzcRZFTMKb%}tgm5hF -z{(VR1&pS^AG%ALI$+?X2u3X{&!`_?6W4X2c!?~g^G$2EgF=Z@DQe=n>AsLfdgDD}I -z=ZFd^6&W(mA!H~si8PRToXd+%fI#3x-t%fCB9czo%Jy<-}$q6Fn@WRSk5@m0T -z*r7yF?Kk*1bl4k)^LmgYv=AO9rNa^7lC)GCq} -z8c}6zZ077TA<`CTD?{#u0|=^Xed|2FKGQsamMTxQaS9ZChX`#IPuu_yrs{)oi=Il? -zW&vC_+=5*xosl(9l(l_dcwb46seR89bMiYQ*qp0oA -z4THb6!f5t6d!~tKYSdvF&Ni9r$%b%}#rqwX6Z2g2(Us8D*z?u{%YD}S=?SKGdHwl5 -z0CY*2$6nB^%>1r$x+B&bU~!HY!O5!AM@gsW-YwEyr@%Qb{i_tdR1bgjybyg8Sy}%+ -zkT`r*I&i4wHCT)yA`9pIBt$4q;%AnPK>YT92rKR(xRC2J*+SvMk(oDTj+)Q3T|Me@H761q<;R5<)rCh-Rpm;-82f|m`fc^(yh?ap9r1GAW -zk-=CQC-4K7Q`K>@q6}2eDtmEM%R{H+yUW^~OC6f(oSYfgVPt#M<4_%oxZ|wUi=%hn -zXTa=V`DEzR52@T$+6QhT3NYuzKPJlD770SL-{`kzf5TH9gidq2}l->t^-WqzcI~ueD)VSk5P!>?Q=1X53f&xs)4>D69N>|^Lf`?L82vZHs?d3w! -z$(p3fRD@6oEgR=Qb4HPM!%`}}{=$?W@7_@!outl2k!9TE7y_86!{ay`L%|L7?Upr{Z^xvjm<_bNZnf5&C+(TK!b06Fv|w0Y0vK+usoVc{cK -z-yoFx?TJkK9-E%bNR(EzSdiv>ktp|mqk<%e`;^f~?dWL^bJJ7l(iJ( -zz$Gd$(be7zE7s20KTGZ~43ir10Y-Tw*+80jos2;qQPk{uevN_*t6PLtxo07cZKfN6 -zt}I_3Q!lugwWo=80-!=8n|9Idd5*8WH2ki+S@s(xBWPdrGDQK{8>r@UrE -z2JFv$Le>NhsthHv2$F?Mq`u1behcfQ_3vzAAyKs&dDG>L1DiNxUT_o=y=mT{8iJvS -zzi5O%c!Y!{vTQ($G`IndiPfqEbnr#Tofa*-K1*K`=?)EQk;YAI@hf4XGN|!7$(mw -z{`x_0b8*piA6EI)vZC_%!Pnx;FQ-lt#?&Cq|ICkvp4aP^*Bg7uJmWlvQt9#-q{$a| -zp06WQ8>`IvoB-j&-ply5q?N91Aj;ARA#oet+XHvgpmWCL_U -ziWlMB+{p56>gO3Z1#WnF*WVNnc}Wc2b&h0zb<2c_tbFA?zz$_6I9mGxbizOpFBKFqyOZ2kVUh|wH?+$T8C=_ola6UalW~dH-Gven0O)60o=?()3`=6-w -zKU`rPcBh?|zAX|CCFpf3S1^DMjDo*-Bk3&^3%miu^-q#a_Mi|@yOr}1d^rnDoyF2) -z)Xc7NFeQWIrkDU>hR)cXS777X1TTac|5xQcI@sr?w|tI?oSv7xxg=lWrFS+c6mR -z9#BxRgj|8D`VR1`*9&2Ts8MEPQu*MoWkr#D04A%1FzDKd=H1B5QT~lqMixvTbqA*h -z8|a-)5fNMi;O*vcvqLl6wFKrHQM5|n`lS=ljNVDvLcSizehJustOML4i6QD4H)_*I -z|Kum2hH+L?`H#&{w-J}WL2u^?aWoVB8mlaPj9cAfBSP+jiCVAw`21ts_tuN+LV9Pk -zFxiJv)pepML=t)mu(nrM2LL>xV$z4YUWPUX6+z-&=b%ljTpdlatA|8%xBkM*-I-j! -z4_t9G-}CPkAHx1rwnOw>t8IGBa6dmH|15>&F#o#T0dBZ<&*7?{1f#>;;186!5jdaG -z#lKyL@@~>hc?Sb4Vuar1Cb6jKr*Txk#Wpjg%|0D0t~HNs!_;lm2%=kb)?B;XRWC<|<;&Ib8|b@kFE!{&v33?(r7b -zgix>gC=|^8NsMwg%qIKWeF_--MU)bSUpjP$x%yv#+z1c&@h6QPzf)*~Nk*q@=LfJx -z;2PAjJp0H%_%Asu5<^D#@g$|#W`x||i*rsRU9Ihx=%71y0mVj7y<<|CKbn5(Qt;#a -zGI+er>Xv_}#`nKkwNEV`|-Ei^Cm2%hwO!%Z?Db1DH6m{TQ0ub -zX_t^N{+Ze!s9vW8jDug$IWxyznmWjM!;s3# -zj8IYGb`W&^=#s{`)#>3K!q9fHWNK0lmP>d*-!Y{$zwgR6olPj8TG -zZuuJ3!HVm1frcovmU$UPhb{k_WYpS5+v}T7J{3D)j$g2?-spvSAcIP+Tn|J7@YQ8d -zFHFtW5;|codAWf&f{q+eINdA~(tL+cuvH0`SD2R_PB`+{ur5MRo^WM8mM`FP<$A%r -zXM0oE+UlHA$o+}*fCA3E9%N0faH1j%PUM%}P?-a1)f+*@``kTxXIsi(#{n6g;D#m8 -zhY$V&>c7+XgqIo7q09)5^5@VQ2RJaD4R6U@PN8m>3@A`ZA#vZ?9E4Dz`JC89Cw}Q5 -z=BxtG^^u=siE<<68cD|+pkOk91e4KAuZ;&nkR_DA44kwaMzUHwxzNG56`0v|z|t%m -zz8)(vBsbp&rx+;^>c5q{x;Rt^vgrN@U2#LmNB$km_AsYW*o{AYf@IW17L*K(7pw0H -z`9bY&%<4gJLi9sv1!LeJ>kI`)BQDL*-I94I{mabWxzD{L36J*A!lfJ*zg=xO|*5dK|1KKr=(rSrkZ*@!V^ACH((qu)U2^@YwLXG -zLA&p8XAq#wyy)josD~UO@AY)<4NYn6g%UWI;4+ypJ)U1Vz02v0>JzZT>b+ae-)P+b -z3&^#RMQqy3d10=9w25rmkx{B5Y@XhP4&a>lqRPlcnFIYXi~O+Y2_*i8T7inlU|Z9GygLwk{w(|+{-rCL0h+hY*-t(13PBvHY5!Em -zgTw*AC|a8@7f!z^;V6_s&DW=kEm -zXFsrGjZkx;gpTxsXK@u;ExX1?p)wh`=S+C^q7jliKt)t4ElY(()Fzs-0o+o|on8E_ -z0;fjctM&A0r@)<8&mNd~wGuQYegwBhLA-7n7&ga-94ga3C$7<1y_m*Uz#1aU3`f^-$A7#>Agg{;@qgclMHWMfL62pFU -zSkk*3bmWZSdT@UgeA}NK{MjE-E$RNriq+9kNiS!WULlX1=(9~Ktu8nYoFb#S4ml=19fVxT}%%} -zAAOZ{XY28Mj8%37+We61D#Vy7Q3mrxDaXPLO2&utGe{7c`2E>zCOON65~{-L3EL1q -zvUn?aHAmyJ?OEhZ)U4XgYb%Vkr-FxL0^XlHfKIyf3m+se4)Lc9d)8?)ai#+h3~VsY -zO0m4Bi7z{=p)=oyis7}@kzyHX)j*>b7?dPA=te=>>S^qBMacZvXIR|b(;!2@C!od> -zVhZ{ZocQFuIGrqA=}apR9pJ~&Un@k1e5n02sE0tg^x?^#`o3Jn-y#37~{lGP};0O(*YjIQLFG -zw8=j}Yz|DGv5zTro|IMPFWAhps)G-Qd2^UEE!SeTEO}e8_N8nb=KEwd1;{UZom^lp -z;T#zm89;jF*1_l8`I+3wSmH_>__$=o#DPuq-U)O13%~G~Q<^+l`;LESB?`6KOWj_!*08tbA -zW?=Ag44I^S3>!jK{qBOnu{S+MdZgQsL5ZpW?;*KRPZD`e&pm5-g+RKs0%$^q(fq;v -zYe0e46Z7T>fjSst6t=mvLDBryd3W8Ov(K?Jr47Q}bS7He0rQR_&19GTY=!fq!x76d -zz8|Uj-INV+-t#s(MIGOzkHR7hi)mBu&QcUW%3^Qj$^e7&KMff?jo!|HJ< -zjnRB1T(7oY856%f2)hmY%FsC4Ixq@8)rtdKKjetvG{%RfFc*XtuFbC8Y#4j$ii&>7yGwJ3h;239=Uh3JM11oKa^Tw@;qve(Y%Vb6Io*b4;6{IY0EgP9` -zkgLApw!Eu~qRZW@U{K+&<&jZ1{y7)e!#69zO(V3yRC9&AiK1-x+1_5L -zT94IfbWT1)DomWGGX`BWny8%BEK(E6eiBOXp0AP3*-TMaSjR -zJcm-Rtoe6DRb{s(v8x^-Eb=S3Gu`|0Z{ZW$vm0@hrjV;zw!mXYMm#C#JLQ5;qu;j|(0%!|!KT;<8jJ#<}s{(zjaQU0D48w%$Z -z)SRzVxwzBrcmk3RGH`jvi{Ti80=WR`Xf}wpWa!G_YxlTt<^G5o{uVOsO)0$mKvy1L -zr^lk3hTdCAxTiEAa4qr76NvIXmj2klQZ_F0V;3}K$XNp1dkuu9f~JwK4`@Lhb6TAW -zuPPBkSW1MQP+A_pf|>;HY0h|H(@H;b;_}PoIYGJio=I^C_cA;kZaWH0@DFl{gym2y -z|GsC4it)vzCJEv@Xg~TQ+e?&`K)Xh>ln^v7lfTE9gfSMDokb6RXmiHVe#34Op;ui| -zWgmHuc4EILFTQ!!dprzF%q!RbaW3h`N&^&z&At(0oDfi+D>dvD)EsbFZLZ(0za`N; -zN}<+YSZpc^3l-VJ=!%UmRgooqc)w04YK9qH#3gU{8S5;mTc@AH=fbs=QA5}Wa7=0i -zScOtvVe>3`cM~QC^Ra?CebrT@&)_ACZ+N6=c`_3*=OevNvVEVfy8wag>`O?hPYSFY -zOj-6MIfpOXV-_zLa`Gr~73TkbGv+{5ErLx_OGN+iVS(*i% -z7mFng*?DmT`L~o>7wT1>3)rL(FSAa6tJE<#Z=mLo8Yj(1Wc_hfjDAsGQni&uMXP4a -z#LZAWBV}|F_~`TgJ*V)WAe+^orT59CBD%=Mn39xX~+~O@sdS6O}@hQBuFDV7H -zt@0A5dmr#@NI2ny|Ku=y -zealk$FsaDbHRNVH$_#&e34Xj`tH*E+?ul`0-7gmI2k=Q)MW9lHcj>g3$XQHoqf~=3 -zLM#^$rYsk#7HanH&&o{Uq@AC2@s&na0y5*C&%?bbq_y;ct -zV@s9bN~h;M)WEFwGFSu=O|e5kk1O`ag!+cJ-+g~DJ-cMG{plIx?e{|hx%_R#!~X;> -zp|Sf1AApyf2iCTmYK%AceNtT9do3P+E_}${NVLMl8SLPSPu>_*famlq_%4%my!gco -zR^$WxTy$HU1Nti0VlZ06mpCVhh1x27B__?59KMFB`qXmI`KTlkC_$y)*mH-8BnW=^ -zA%Pk!kVTPuEt6?{%5QlYk%Pfq>=sTsaX9j0VSArARCfy9DQ8=01orG9coWC2Zxh|( -zN&;?f?)TeX82^^xE&(=%cX<+u7%d=1Pwe|u0gU34is5)hYYgyLb9-&91pNaYIPzcA -z&}i#5ellJR>ew$MBj18*_BDhmFYyaHfQdw2=l%g-NJSW!;FXh}S?jtCq2izIg~=Iw -z8^=FaInVOYKt_#6b{Z@t%_-(#Az#Nq59^>se}Hs1gD(+W_C>P}0_U~R^ -zMqJ#3_rwWGhPTn+0<4OcA{YgY+p0rT?2q78OlI?d*DpQ?jNhfIQG3{(LwOG%6I$ee -z_W{H1)85(nrE76VOVB8(2uvYqs%CIz8|f@)16;edy#dbjex6+FDx8Gx?ra2CKpK9_ -zgbM_8V#s3?y(jhpO{bX#WXo*Lc^QoRPTc$|LIX_okbpi1Q%#3&&%L(uFr{5B-=zT# -zRX*Tsa)3iW2%LH)TtfwHT3fS>7t|=QEsQes#US_W}R -zJ`~$NzMLkTuwiVbXspF~w&WBc<}7|_UBf;X>6hAX^Es*^yQ8U~4& -zOpet2+RpL2e5Fpgu2ql5WxmE({=679x>#hT{)@l6SPQr?Kj|ni=)*FL>y|&HZ7gzG -zi;?9UwRSY`_+1ZVj@VHkOzr0&~crQE%u_|NY2e|1y35K93e_DBW)qMl%emVqN0*h -zgs-Be7UUxyVB`-KaQv#YpZAgvlm6@Z0a7$jn~YX1x!|4cfvIAIX31+>Jwfa+EDDc3 -z@EVOsQ1CkfQ}DkL+qYmm;n$wrH@WfzOAZ@FtRcTPym6oj!nO4Od;7$HuJxbs(!oT> -zY|b728*}2PtM7WuWPOfSncll=x@ZO9yMo@0hM3qLD2aZA$~1Wi*7B#pEH*|J(hK{! -zXZ{--!bbGlSW4B9LwLMH0k2d8K9@gtujS{)kq?_xse?-(?YdL#p8}gJq+f*YkL6I> -zPz^hoyM#>n#z%aSlR0(swP!zr72>AM`SU(1%F36nUcE~8#{JZR+ajK*1Nka9mmkcN -z55J1H%@2VDZPrx{qF9+1#YHVG6DUnh1d;Qgn;|0K2v=&x#z`s{FH+85r{g*cW67v~ -z+I!>CUZF=D8o7K6_h_de&Y#=`Io2*1fj)TyS=jPW4j7m_?IW@PqweRjI}i_p^*aI) -z$5O0Fki>a{qs(GsSV$b;w_bHI9m0+7_>(Q&R1Y!mVKKPEse0-?c=#nKCyP7cbU@x2 -zeL>e&eR#jI#E>p`RGaN~*IAaH*uOEVrP}aY!*A{rGNCDu&kU0x8vfP?QBQG~)qAj- -z=SHd63z;sgpALL4=u4^Mn2Ip@k3Y3Kc -z9=$Qo#*79kY%$Mv+}(niAOXl!n9u%GwGt2Zl0G8zKk5Bz$Wy*zmcQTi8}W_S%#UD- -zb7N*^=A+II4hT*aL^nEe8z_%xeyB>v%E4UwuT0!sZxP2FT~@q5kfN1$1DbuH&aaPJ -zD~y1oM^TmxChucz9_+@_iBX32AkFto?{DL(FNbf#xHr4W;Y(@7P{BXcR_K(c1$Hzi -zY(_PAZHG&c2%u%$Ms~=3BSsLS3^JnfO5(;kM+*{{iQ@hs=kU!M_ZH;>@~in7s+Byb -zf|5J9*+Oh~M+ev&mlq}1cL>+xu+Yyw>v_CXru*4{QG*rM@gFh1FE -z?(rP@!p*huc_}v-Bj#Z;i?bWQt%Esy_;6!qrzxBbre;p>UVmQsBd*|j*gD#gX9DV1 -zeZiPsrrRRh2x|8_x1?RZ14o3+_%=?x!U2B&_SafZCJmXtEQL=VHE0`)6BAcAD -zeS}3ZS7_AE-o^-652slTHMW4;&emc76tt-r5?GJ68-dStpMe_wFto2k^syVacwa&n -z7>(%tKr&jh;{)sZK#{X;ittn|Te-yG35cj*LdC+V_zW<6uN4jXI^fiI`za$u!S_ux6 -zMX6Ma`ECqA+iOpycS4%-ZDa5R+Ixlvx0a*!@Yr)!)2g96b&eEUr?VXK!mqKSfGjF^XtQstd55<;` -z(9|Ufgn3lVIsioiA`&U%`Pita5-po={htn)n~x~q-Mb5wo1EK;LJbWK&<p_ot#`Co095^8M}!`TMxc)vM7@;J4s#IZI2);N -z0PO9Y=+ymY=WpKk2hsVm-XBL$jpDZ0yJ&2wCOlyU-~9nb`6zfiGCaFekU5FMKU%iI -zyWuyM5M5t2uD)pB+#x%Mo0_ihG=vYHJR}i`*y76Rp>vwjLl>;>bon|gpPFRSoxZ0tgQ?JsW5L*V)*{CHau%S?AlRNEPpCT{8PP&3{ujAq-~|l -zq^#<2w>6geBzX8&AT>FdTL}rqFQtwDt=IY^ViMai=dOq5h~t|hW~^0d66}Jm82n#D -zruuDsG@k1C7gM!OK;|G5Ilo>x2Mzl-ckga{e%$B@>RM75Um?Z%ZLYZF`kC4uhN&Tc -zadXM#2&|&tDY=L+NWpYFbG?>oBko)~4@2K*9WsMU4FA}NaDTg|2tPdZCR7~OlN+oj -zX2Xm?C_v$URw@TS8-DFh__y04$)=M#7G1?Rcp8rUxL;L6hxRO5gM#%7{ra6jkkdZ- -zXkt(UMI{EfRl-)zIH>1_rV|rfTLfXDKJ3=$^m6`_BZ{ME?>02K=Uwu~~ -zBg0Kn*vElNZzqc14hUIDixjm{OhkzI5f&4u-= -z-7kROYBnR?gG*^_&q4iM67QG#`DIl6RSGC1Wi5h_PLAR6z_ZmtO!G7S@n(AomvgM` -z`XqwkgE2Wct5Le1!)im;R-%9#z8_*eV2@q`5v^ZV7 -z6ekA3yr>vOWRSPA{&K3n)FoX=Kl^64XdZAKhM$Hn#~|jfkoMaH*cfyk&z%csVDzPc -zr>_|~4x2zSH6d=?;S7w9l*hO`aA#nAR66ID=7-Wb20#B37eT(eBre7N#K3J-%dC-U -zs6Z{DFNNO&Ma_fSOK)$$YdHxQEz6;pVu$}EOzcDN4=#N_7M}l-De+U>KK9`N90;#M -z_3V!WAA6;uSPPjBnhi$j1LH$?zPL -z`{iXy%9rcL#u-HvJ6!O7@orw0$7XT+`Iu2okCbu5ch}*%NruJPKTg-y*B9g0hS& -z_{SL1Ogs3WpD+mq8?R$qlk_|xR$cs4czE8Z_x_3&Ozl@YWicDCa%0M`uc*KIw}`#y -zRsHdf{wA&el1INc{^c@%b0 -zAdOXgurC$Yz=G9siAXl=bPG5MCck*W_H62^;(F+|-4qjf*0cJ9*gxXovua{4sA|U$ -zK`LK5I5?R6`t^wyGP<2uhH9Tpb!2licgZ+@jfAqp{7<-HhMsFa`2A;Hst_J~{z?xc -z8Yb7hXwcps;)W2Mrw!z;BQ%tBogm+AAS(86t%Cy`ZZV; -zHxJ=s!O}53KzJop2J?Pi1=P?zp&Z}@G?*qM^MdJD>Boz~+vmp8?1(i$5%TnGR=bSe -z%mb0z5ZKlsCe3MY@9v~|Hs5CoRV(1uOF2M;b=btrLF3vG!l4Xk-G(_xtmy%dk;qr1whi-2IPTJA;(ql4Ep94xyYb%<}L#2j*9h&w!!n8L9OZ)$h4FrpBR+U -z?u4lQ1wiR&iv(xRt{!sd$Hu06Uqgvy76dUq0^$1D61Ycb{+LvnT7F0?FpR*+oiob- -zQnrW;08GEI>4gUVdpeeJ0Jcz653}9?*uF -z;7!)H0coqYF_$$5RMBxPkl0N@=3KKh2XN*L#1EeZhcE;H9qcrKl;6^{EWgU}LVf?8 -zvb#E{(VOiw`^B}YyZtJ^uq3UOpVLxX*J~R9%F}W+tU3c?m}g~}IJcp`K0Evzv^vk& -zoOgjOe+%Axpe!8{dFBmmTX@9J|^7sABfs{4>! -zoELXtJ$<wDr>VgRoKd`Dkc~~hIbc5jM -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 -z97ak4ja;J7-^42-G&dkhg2KDe`Z*cm%Jd-AJS -zM>CK900veM$GJNvYO -zp&GDa#0Jm;two@=TrFUD{v)y{MnZ%_jaf$Fpq9H-s5t^>;Mm6kyc3pld23lz^|{SemPZiSlGH4W;P?uTX90Q7*G -ziL8EWU3-56bLRR1KcCy1>Pk& -z%eQK^dobq^Fs4-N>KE)h6s3B)yjWlZEyQi0H2-b`g&Rd}a|jRGf#S5R2~j%N0n~2C -z@$$#Ms5gZ(FGqtEq9t-%5|4SaXOif%+0#fnGj<0&AspD^dKPuVre}}Z;oHNPwfK(Pj_X24%Y0i*dd^uuIJVnd^tp}dc^S7wa#7vc!9|T0qXw;OvJSaD|L(w! -zTNcsyMBlXeVqDgs`r2?+kJ2m%-eJ-w&Ow -z4w7ic*RKjQ0W=EG8*;PH`z%2Gz~2maV%qBi3_XVHcq`gB0qPXjC0UDNbPUm-f@jQ; -z1Ef=jHn>rL>Gp|aViwaPv$7h<#|9tnc5yuY}E&a4t#|-jvNux_bOC -z8;#UcST4s$4jFGi1%B=wC_LwqUVoa1bbmv7u;BgGLYxe=pch&f7WOTXJT{D3@^-&I<9?x -z?P!wNz_c^(V*LJ-`S8bv9z-TDFYd3*&&O2}mD+#t%D|C|blZt5RQeN(pI7E&BhxSa -z^@+3KiIvT`yw@>R3>us8)}I?o)DWugwnz{D3Db5%NN!(uzq`j`Ix2heB%aERARxL7 -z$-7c{Xw=&iW&8+&LbZ)7^IX@R=d^;1zB^n&8t+o7cEJrWQ2rRmodHck5rR#?Li|>) -zP+%RO#?S9MFtyZSJG?yu-gOWD2J*V2zo^%H$WwI&z?ZQsjDx5^LlDNG~j&xGX>rsy7 -zMz*_Lc(jY-48wlc;NX2p2?=TM-@m`PEdpA>_p^`(N%DI0?5f~}LomQ)gg={SI@ZxB -z$2mvwE8*sl6}4v|Uw6RS8+)Gq#HmjA{+LRtT~gbn)0 -z7O84Ew_=y#MGxQEw)2yQhh)`-=O-!&E=|jvZ%~^a)AoPncBuFr>=oHC6^hRdkhq=& -z#PGR#>!WJ;L;&!w%jNl%G>*^)jt>I1UZL+ -zU`7?)fp*wa3RA|t*wYh2p&<*~M+=$#8HQW%;m1#T7%;EkxyX`6bdO>2fpT>4rXuPl -zA47e%U7`ODbj-@UoDQUp=R5`v2Y4Z-5Z>5jJ}hlj_&cO+u*?X?RdV9N0F!(KJP+or?S4Soq1c~m@^35S -zaQbwH<5Qalm9`;LFdIfH1t83n=IP%4*o^}){nmJtD~d`p6dr1m9!k)??r19D@K6pa -z%hSKlE%V{I_#BoQF*!u}9vU4W@r5tH%`mXdH4qK;zO?*f{XRM -zqSwLx?ARDlWIJB}mr!Hq*}Yn-ohYX_ON7*(f%x*X;x~7Jnp_NsODcM)({3VOXiK!U -zP}_%DgEWV@90Kz1w9Kjj=@M$2GKIp|x^FQ8P+D{dxs&)RS*NH|uwRR>UL`QL -zu&@*2MN3V5n3B@Cco%M^n?puJHb;BK8S9Q9>Y6(Ck`e8&vOg7UeE_lO3=9jQXpIg% -zto2h9j{M}Xruw3medZjE5{ -zSL(eSc6JB!wA;eT1-HwT%EL_{&Ij&L_KiY*UJuku5#sY(K@V$}w;YHMX%yIJ!Cd&D -zqW96J(pPohpbq!lmuUN{Wrc_J$@>&cG7d<_vLcELOumQ>aIT&AJIuaNcn@c74*e+# -zh{(uzBTNQZKyL>8=t8x^8o(yaJNepmp}joO&;nheGth-B>>pC2LrA%FCitCgLk&p* -zJ8AikbI9n20*K*2gzk8;>sAvG%gTn`hloJ=g`z(f;avPp3pFwfHCZ#XmS1Y+Jqy<( -z9X|E9oGMpG4C-KmYC&s>2k{76my|}pp+N~X!=H|@>xVCq2-y3n9{vSFq(>>=m)`d| -zpbZi$tGO!223_TbuYfeM2r~FI{>Rq4>2IZ|(Y8G6PW`7d@wH1|gABr6A?3@_CTKr* -zFxEi1P^rc2Z#H>!?)w*eeABgIG!fxwqUcV% -z8L5?#rr8MClF-c*f89G|XWTntmRC}`NE3Bz?mfkT0G~Lry!9@B4*aF1&w*GV0{7d8Xr<}=u(36|=K>jmA|99Y=e{#UWVBgE3mN${^sbi5f~v#qZIoGI%EGKNP{BOzx@t8cuvB-extu!1$U{x|04?R -zYkyQ>{^{~qe1yc;mCyZ22AL%M1J(V)@u}{$^#^mr{yZ5#C=m+#PKtY&6#VPguO|a3 -z5E7L$kPGhEjOxCVWbhFiqqB)_KcK_O0a+<9NYyglCnhE)!{HK&?MiuHPI5jNm)URa -zdTL`VL>SPpUXIy=`SIFF~h5QJbr3kSB~ -z$dECPchu-G)VOMa#6MRHKnCIi%Jv`7Z+RAOhwe-mvn2-63qSD%_A&Y*re%%E-*G-} -zzR#sUaU*cxogaXjjwx}311AiT2uF+7R_U5n5H12?Ko|Rw6-4jUY5Dx(o-*$d@BmIj -z5Xv%}CsC?@0h|VKw80KR>oELk^pq1fSv=^&$>qJ(giYrojMbBThYH#aK8)0EY$@V- -zhS=Q8VZ41CNp?BMO1prz&cI!qkPvzeM@jD+&-Xz~;|;hBT3Lf6TPrBhjC{Sb+nT;n -z`_{*U*588+D3fiV{niXNVI4!@Um}7U3&Sg8E=9mvt%HA6z3F0Z|8@fh8ruPG9zhf8MMaOUyUuLKjAM5IGSaXfhz)?UN8PO5B^?J)cryVi -z`YV2ZAZ%pO%5DQ%0h;n}pO@E@Vo{#C<-Vn>$OA~oN^LCyA&HoW!&=Yt#BbX{=ulQ_ -z`^NGlp>jQWS7#LrD04sUtrA|#CyrNEQ9*hUI7BIuxV2uHX=>98(7H$xS;B>Ae?ayF -z?Aoe9l4`0o{cM<3qnb5nG53!Ql~L(rdU<)dy=N<&e)grA6UVH- -z&h%8mg>a$A)s$q;fVo<|LX;s0ENAO_1xfK^!oqhzgd)0>2cAfz3TA};i5@}#fWu%^ -z=6!A@;Xbfj!uDStj79dp;|gD_j~@nZR63L-n!(>>mQ?;n9~f-DmZW#8?*w#N8=xX3 -zsSe5C?rYRA%R*#ukg;V~UFqcsUyv;~rf{Yfa?E}~=GCMuE|J=jWgL*_Cb0rdw-z@7 -zVjN5p2_n8hQi!tv8!jsZ4Zdccs?%2!pWIws=+7Ik#M{2Vx+Xw7-p=R?`jnRcKA%1< -z -z2#+5*UIQphf$xD=p}t=OWcTYHcXcu!f@;W8%_HY;q6gI9h%zJlvW`oTcbgm6(?9#s -z(v<(5_XsodJz$R8B&)6AF&zj>8vKNs+RlC24l%1)+dN3ZoMzQ*`n7lN@gG6Ky -zvQ0FtngazX(5kf_0!e0}uZ5{jg#gzbZnvl!`JqH%ooTRJXg*>|9S}HOdC*9kJ@tlP -z=~%-_)YlwAV)*5A1w8H01e;WN(J%3qftSEU`K{R7ben=9tyuDrwAa#GJIIhE)h -zlQrmg@#4i?$!Vd-El3-m>PT%)HGSaF5W-+}@DQ2Hh6{BRP^|$nH~acod-`wM1Pyie -zvu7LzT;?a{z9Sv#Y&%mxnUA!fc4)JQP9}?b9YUJU@mv9@X^M0;G;EtjmeS+Hw?fS( -zrPjCek#-$*gM-p&e8?>?=s_Fq4hL~Yr{)%yD7w|HIy!heO@J@56}F -zs8AzPwn|xwQWUagOO%OevJ*;F%AREynJ8IOvQ&0rgk;~PLMr>7C57xu*|I*@i@NXb -z<@5P|kLP&)d5+)jABSbU=RL3YYq_rTIDHZKF-(jShSHG|2OWnR1U>r*!ZTVjCdJ3SIBoo>e3k^5ot -z%x%FSGMu+$yLi7TS|h0Hb`!-t(BlkZfhGf)Lx{?{MsA&*CuzY`c;PJZUOsW)z2gVT -zS5Kuzam<3)x$XyX_uNGDLPqM?jcR8- -zzTwPN*JRx!kE5)5*GQ5P*DpDpb^(UF0}*lSA3Y@Q>2J56DH -zC>V3@4j!!vljov1f#7_03kXQ)Jy()rOyxiP4WwD6tv;z#_cNYCJZ5mdm^XA#^pTgq -z6fVG4U}vcb7cJM5RWjxw8v+q$tes_FWuchj7h%1e8rOuB`0;n3)%yjC{wdm)8b{?> -zbTbUY9+h%}e?k1m?PP9j!M%uPYzZ#s1YT~^Y2>CnJB9sz>W}q?vm1+nixb^r@UfR{ -zXk8uNha1eraXFn%sJu&~0&PzA?=%~?;@9AMxkeIc_DdXh;&N1Hxni1Nan#9>PcQm; -zAZ5m}@h -ze06QDuO=NR6`sp=0M-1^X$c|Vt?1c%+<;RMbqDWuT?b)X9ALHlAN&cseyB-cDh1fVC_A?+ -z_p>{jF>QP**X9PEetnWEAjo$uAeCP>dXN!Q&o>+sNs?y+!|z*QchGHm(6or3$C~P=7Hv^wL@_3N=H_>FAC0^6G3KN1Je&28Hf3_-7u -zGNDMg5(o75$?j<+$iJ8(b%y1h@vB>QYPz6I$Iq!Q8rkKw$s<}5yzWLepf=>_ny6Wma -zS9$i$g4}Yd?mUXu=t8upm8iL0KcyyRHef@*Ybr4b+asE6IG>8)w;&1@^O?t`M@Gs! -zTs+;y@VUK0uA4V`e>Ssq#ExHFfB}nS2v945=zO6LuVP@XQVl#%eH&5Yf+H)jmh)0| -zVISbe2B=-9=-GjgPKLRgVvCn?ncMK;IKIj;Ckd+uT+1%sHD#`EEaqF7dNStn)FhGE -z8SIpkKjT?yDDf0XFGKwVQZCeV@{^J$ziR|thgD6k``9@xHlqiW%EZc`xds+J*!X3J -zme<(|p~ftzHZk;n&0liUX&p>Ay7A6f(r5at`O#*xiE(; -zU_=YvfL5ETf)_vL)(-+fbReeWw)BH0uTEkZm{LVYQTfSBK6ylG+hOzmAi{64&@1;u -zz?6@zuC;0V@gNpkvlSfwR%4Jt8!KFn2u908(rPm)^{rV~9_34%Xgmj0XI>`Pl2JOo -zJRJuDpwvyzO8l0%353cD9Rj;+&n~X(gzVWXV04q~I+NdJhrimO#jE(z|1{(a9Qu4J -zk%9?%nBodm0G&YhRYU -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 -zE*^DzYk -zaW@S;#&x%afx9yG+GLX7xw<(4?m!E5yWRKY_YUg5q#qF`^V%->giSBOj&~LF=`k=P -zgpPt?b$PDabXiP*?{G%5ofLT;HUY<{U^$|VxMSD5_l=l!9M8)tWs$H}-ru+w?V4hA -zyHu_1oy@}c%^^d)S4Q(mON{L$e*y3K!nm5dqoQ=lVy&k4H+Ig!6wY@(l}ZRn%7|aW -zY608(Ueea+8NRzQuF+KLc{a_32?qk2bnNGj{Q%@ykL`qauHgQL%(6V<@sJc*bm$zn=zx*;y?@ -zH?O+Ec3+?XC-2;Fqe1hLTyha=yp0vtFHbX}-sl4CLt^&Xw_%(MNsJ@w=tMS)j>#35GJ<4+!P}E{+M0BSPeGS3L7NX( -zpUGm6bAM+^nT+ykraXv>1fO=N156*i1UeHo-`&@k(afTz@y4%(i0wJUrkI>Dujs76 -zqKrLUCp{fTql}qP*nEO==QynK&8fvpc^_jiUgnJ9#JPd1{!T@6#*b5?oC(GWFT_Sn -z7JF~R9&+gTiuvCEy(Ebp>)c_xvKLkg>BzWl9q?j6a(WD8^Uk~o{KN^9hEAk;((%>H -z!0_R?kF-OnN>1^(OCiD9Bk;T(tG}>)nGTH%1dHZC))QVQODe4inml^;dHX3!c -z*Zh*Bmk4~A{(<=b1<>Mx(KKn*yD3(yS<{W;Ph8;`lfLpuC8tODF -z>AC)x5fFK^Z)C^r&`9V(k`n{XExQPl8hYJp$JlnKGD^ANjpUm(pGN~gl@jLJ3|u}& -zhs!>kHG|FuiW~5@<6*=kgYv9n(2TRfRB|U#tWh7nui{H4DGh?zdQlGH=uhg=@s>$C -zULKna>JRRrT*c|s&tlCr9F_5VwOZxXsW+2NCO_$Z(TdxMXRhT6oTSp-zrDx|iEk7T -zY89KCaYry49Z77i>MVL#i<;~F7<;}00SSqL-ju|Ix9^>~xt4XNY=)AvB_d4KgGfRk -ztC%fz8#dO68Ck<1d997FEaBjF*7;s@P#uH2 -zo{m|5$mq%(D?F>GH$45sL#oZBGffO*d_#RG)xFuOc(HHV=F*H0!F6}&eZOLyOkR9* -z#tg78nwI8_9XROqEl|wDoYkH004^nS$+?jE+jsx -zo^P*hW+B!t%*~7yzihD83S!-H|6XU(BIn1=uG0b`vD`sT$Fc^N8Zr-Oo6dL&3|G8n -z*i@!#xop0$VDC`(agSgZfSoL>)@MxPaxgI%EA*gqekW><*!=GOZe|t07s($;vN@3R -zdpf&@a?s!aD*N1}3IJWT#6Zv_)mdsLeByW*CtK2aAHSml$pf>YDAb~poHQZ??a{K6 -z4N{yeacHFAQz*)p>n9FLa4L1PauA}|HKmnb11C*q46HT$rD5?HVVk$7daPZ!gNr0xr*)5@)`1 -z1}2?(2N@3jn*;|-oRK$C_KPUT%~+~x4u|$`&gX&(ks7$me0-b=O4j&_okjdw(O-hF -z$5fgikJZyu`PAt=ral*DYNL`FZ{USVFqi4C;Fp?gCe{?;sFa)ygRTPfhqrd6TW9;; -zzf@S)sxVVhMA=z2Y8%q1A|J>0)fqJBSr|G3O{Sl7+^e09InNaO9W9A@#(YA?AmU+i#>rKn1@oy6mNBBeSZJ0s}7ePk(NL&)T# -zqxrmMXQy7B_B#crxINAw;G9N=`u7Rwx- -zQHUtS#3stCEnGJSy2e7Bh3ebU_2gyj+Pbf4rPn9aXL6(uX%*gnh+L*})8j6#z*WO2 -zJxS>|N4?&+u;e -zF%ZH|cOx*{=Dh<71yuq6f#6(I6N2(#-#%5epBg0_3!v@K2qine50cN|1F(;fy%Mq$$h -z&2<|)%p?~Ahg){};;6#`d)RkrR*^@VE?O&1Db?qSjwJbEL$SbejWrSubjX@qX*CVd -zhey~t6?+824+JChr$`-AmW8L)vX0&pc|_zi2e^){r6ZY? -zunddFN{-yvb+0k&6WD!-{o8d}csVI&lXZmnqTD^^)U=rQOiSGc!c`IFBj33w3Kqlt -zpxFUZ#}tdVV9Bg+$AG_6gF9_4WHqb!rEzY6(OyFA>-a0iBlw=$Hp-kgVL*{YdxMg9 -zhqC0Tt~^?CPFawbp}GK0?UZkKOBK_`d}mH?C7!sbjxPj`hZssoS^H}AG6-5}irtJO -zuHoL>MIx;=|`hL@=|BzQR%(mIya0{B_p#zm3KEeDz- -zC^h{{XZRGOwvula?7FqN<24s?Hj3ejxo?I{=2Z+wP*aAVMBoV)!eIxpu>c`ucvRW< -zxU$nFHwwj9pwN7{Ej=%y$9gdF=|c#I=!h8atFc$@O2 -zfBv?#GycjYz^ZJQB=)FAu_WIU&`!J)C~bMaW6PlC_p{Gc=l5h?`oJjO!*s-rWZ~!ibyicS89!4`x(!{xp=sAy_fy;%2%E6RO+5@Pe1D7AlEl*J(qg -zo^hej>+zEMuHw`O{nXDc#G2gFT2#azd?=(pD4>Z@A0$Lvy5H&OGpbvqJDg;b+({lx -z6oaTfsd4-@B_4V@3#~Z0p#6hT8TJqZHETky9OInb>pL_%B%G9&pFONPZjOJdAuAWf -zDmB~qN*^ClJE7`yASHggE}py7X=p#8_59N(_SRo;-g0Z-hRt2BiIi*edAx5SdYAd( -zG0T+PGT)ejBFN^}O@mBu2m_X1A=c|-mJyy9a2BISJ) -zlb<}B#8$QPt-KOvD-Y?WohK*02=Ln~a8i?Aeke&m4I|7C(Y+eRolhltUWp_?#!tGE -zg-!uyY92t9Cz{#<#pZed7}_{%%CSu)SJR0fv25CxJbwt5yVWrW@H(#JF!TfAGhx%o -zevM};lzEP&!W^2MLY?>3jCB1FtIS-0Q^YB?RJcv%b{NggHWL)Aa3vQ@?=_8=iZ{AX -zQGBbGoZ&2A}7$#oMmM#nQaxoR!}yM9q;N -zE3gHl>Zo4+Ie~`kA#yv6;+-()O|uartfh5w>jEWV;+j6~3!cc52b8LWwg+JpLH@@H -z&RT{~dyzoHRo4LyR2|}WhUxp_;)&Cc#a)5Bt2J=D0M6*F(^UVAsr6Hkk)lNO@nYs; -z)3=+C3_^qm0l(W2VK4~#MNN6Ek8Kz`%7fR&g(xUv_#2|{2)y+>bVl(mpm_Pah=EYF -zpLni61Lb~=K6WBQ!kK!&R7WV~3H(^f9jQ6ezWpnUEc&*4I}mQrjRXLB#mxASA6v%@1P=V>Lw)P&|CnQ%tHHI%*}mfkbqN^EzPGEa>FC`KP}@$J6pAcBPY -zuB;}fA`1=H9Q#(WYs=_CUgs^Dk%iLc&JwMT-^@<$%`ad5ni2ecmpRqC7c)jXGR5xjqUUOni_|}Et@L@b -z;2(FjbP|VK=18b;<{n#mQ0D&c&G^5{$aR}PwZvqEI5PjYd;e8{LGzM{t`1Y|xaRcJ -z&F^95KYCtwl4);s$jYqvUwrwd8>!aKYDI}78_^f{%-k}XTphBW^c5=&$hrX9_~;V8 -z)?`I2&)Z%0`t>w0T&ja&3M;8qL=o)<4m4lN{j&w|lyg8qv36i~QlcW2U^Z|_G@yzQ -z%(9KcY}?p?8YJ|Iqoecb>4^7|-Jp8lE=$L-PbZ@SfPQ0_hrL7|5%?glRIwgB3xV^B -zU%-EO<#Ps{i-f{e78tF8LG3<;)!)+UMCxW=Me-Jpj$S_>S^oADd{|N>gKdA!OQ<$f}!V{{d*Lu>|lC3`dKiy-KIxp^)yivWnoEg -zfyFCs9H$9;HBwfg7n0Ca;s)Le!$<0W-L2nj9|fK74wzUSA5OuB=bU1|kO;6p&24L0Ve5Cbj^a>zc;N!-Ca2m^$U+Y^OZM`Nx9m*YWuH`P}TdR+J1!HRW!vqlY;!P9ZM3=5$4H@8JLY>1)`&Tl7++Uk~&pG(B-!GLQ -zWY_jW35gprAy+BpE-E#dZ}7XF#dK8k$b2n8_(1(}?w#?v4tyJ7SaB2CX5;aDW_eyFSg;LWGLH~=* -zWaT9<&>+5xwEd@8*Uww|ZSz7)=2HZE63_k^yFV)w|NfoqL%3tpZU6f=`vo|~f17td -zPt5=PaVn6+)4rQ^VD`Ln2U?Fypn>)>z`Ef7l6q%5EddTpwC!yqL3>FnbcG?;;!6c# -z!7DG{y`{gaxEPm~>uwnf4 -z!);&#X-)?|BqpGun~D4@LBiv>WHq-MG}|+sB^_KH3k_--kQMST{8lNLb^gsAOpeo4 -zp+p&l6cF~AhUD>#utxS({d&Wn1#$t^$ef&(vnhI);NFuGcp$QayT<;$S<4^f+5@qO -z*%q)i`4s@@WF7eiPE{2QByf~yeZ?qq8Gu_&gS)|Uh?H}i7IYa+cPaa2giAT=KyBA>y0Wy -zIc2b{1U?xg|M-^|7&f-cx;+tkTCL7xG~Tod62x#ML}&hoE(5DM|v -zYkJfx3yG4+932S1%b)o8kDb+&lQmM|2nf!cG}5i3rQZE@l95z?;Dth$O)v?xoVsK^25p5l7jp{5VW -zk{E1|p8fe`1k&}Bbwsy=@GM{7tKHxld6`Ba-Ej5j;{Uk)hA13oWOw4#)@Vfrhxg)0 -z{I!BWLPbhr<0#1`iG9VUa4PE0_>@6lX-5#biZ)s>uY_GX?sUc32Lmh9s0V5Ox#;XK -z0}h-Vg1~zkjW5&ppxt)VHWZ$K+3j+?==OGn%ho$RYu -zP(%OrFPzT9#u5@_7|$OFy;*L$%M9+g2ViTUwb5r|p&`N!H*qp-h+E?zY-a*xPm*AT -zt>)k98J&<||FNUdKY-DY-j?jeN)-S8{!lWg1=%-gU(6L58pw+DG{n+Fu}!^>!``4+ -z=yG>3!O*o)`bW=}q0U0d?%;yX&<)aq@CMOEYeHoH7_NbJqy?lC8ZlIsz^sA_5P4^i -zAFKy#jc -z-R%6F3iSZr`fSh)G5tnLNX7L}C8SYNRBlHEne`n7s=L|WMJxKKQ9nj*M4OI&a*u7s -zDI-)sHy>>yz)l!!LfBIlD5nSKWdZfU`imnF1KO7~G(a_?krI?dc>!X&c?T0{PC?S$ -z(Nzx3Xr)KPZ&q24wx&Oof)dO$*qnVlt9!)^wgT%fXX`_HWUjOwh8h24=(rMPH?|A7 -zH;`1=S;nazNBig_tD;tjN=+vkb!-=5@>P!(&KNi(>)W@!N$w3I7+k_rw5|s`f(zVb -z$XBi!TIx@6>pF863A^g!b^VThrbpzcMW~L(`7MGjyuHah46lBU1&CB!U3LsIv>kzc -zskTE#i}?1fGToD%pCC|UH4MoGsU`&^S3$cqlz*qj9r$I|x_>6dzXvh0k+d(EiQ>@B -zG7O4Y*NwePOXZOs#t2lBv%sd|6?qqUGtjI@-O`}CYcrY<0kl}8!af4~e&#w(#}e{- -z+s8g(rCUKXLB{%gP!hacIt&r9)bQd<&@S96IaXluM8eM6_#WW*wPY5oQwStp3>M~9 -za0aC%XLq(udKuEmPqO3h;qal*X3^CI9%GWI^(M%J2cykL`A%BGV<}*gi#+Znjg&q<+qln -z5k#HMa~e54`C~;ka$JQ5QS0a=wRp_(N3zl(Cr6#-YD2N(cjVg9$KgiP%Dl}58 -z2gJd`NZ;%UB=#*JGmyg^!%D;ju@owQsXKZy-;eSb6d0a+3sjmfz>_q5i#uWPoWHeA -z;D8NqLh?248^j36wFzS$n;W3kGal{_`ca!ls29KAVG(@%3M4;=<%KcWL`JfHhC>6k -z1sAFGG7=tpldC#4J3b^-+BFaPHE(Bo4G*zM0{ZLpfa&}!#!sR$`Gia6R=PY?bwvI2 -z+r8w~F{24>ccz^|)Kf1~IqDHAzw1{^s#Xr2{IWQhl-llyo#p4_QxTs=RB0e=7{mFV -zjW`@rNof^#D}{5HJR{v`7l7dYIrRtukeYF*(5xS86FEd%2L-%9zs;#~+-~sPddAug -zTGKMkuCU!l*8U!n)9`9^0-%M(F!L2ebCbjT-qtck;CoAxZbcUhhM-orQ2Cu0@r1<6 -z+od~+&~{1Dkd<##*1COME<`P?R0{LsHK%hd5nETY959sd7*X0df@5lK`AyWWImK}@ -zcI-y^VnXy=;L3F{*>~6xSl9l5Df;A|-WE~vKB(paG2=iCldnLqqw=`zSf^`Dwv+J& -z*VE;b2{(pyJOD;^8Z#iiUB*}}=XlPMBD -z;M%7Ub4PSy6Bs<%Mh_)A-3=?1lu$jfR?2Bo#XQ!;H)EZN?&VoC5XBJkj274jbvIjO -zaemXyT*Qe{M!JXC1?`+`Q6tb-o1zP5LK@0);ox0UF%3ou8&kec^}6E95AvJGh?&;#6 -zv(EKnAVO8Zaui5A9m1a+%Z0y=D6ky`RS|4W^~9vjW;}g7%1eI;L>gXhI5;z>olP9w -zaVvhXcTVw?)S8;=)|;S2LTkp$21+C-_K{|IaAaY#?hnNvN$mODiL&h-X^Im;hFapW -zNFt*>>XRop1j^Blv2h6--D9}b^2tXasoyOGO~Bf?K(b*0MTF84bt6&0Ti|KJ;5#@Q -zM?B;}-16*+Sw(7AJP+>sQY0T2$GzBq+fB;d>*zcS_6Xt)Ct#$*V=xCLlvK+{_*%~B -z04s@^4ASkD!f#Vz?9ON^a^j6~Y)V09*pgZqP=qkG9KsW8Yvm{}q9gtKSPXygh8_n* -zLr?Ys5Xr=V^3Qq-7ogg`bnTWX*AVq|Ec~4S^^=@p?-Kvr$-nso7seGk_CU`}H+{8Y)Oxyu -z1u>SkukQ(X!8sd9mc~}1!%=OFAi8ZaF7l3F2aZai>zxu5uJ(;c%q5Ha#sMKuXguo2 -zr40t|WoXuaM1eZTObnHeAKw`^m2S=t%8Rkq8gVRix>|}V%BsHG4wd13ae;cxXERww -zc9|2Lk#>k2+Y3ZT>&k>q`{JUw=)4=Wq?AF^ -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 -zvU4H3&+$Wz8`TIYv -zTtY#ETYFX!OKY?zqQv4 -zv6$liDO#hJBk+N2SFXYI*O~kKwnOi!6bW#67O<|Mqx5V0Tv{! -ztXe3qKRWh1L%y8$*0XWH#F+vWNl3?pp28OajSO(N^9+o6eT8py;gCxr`5 -zQ82v->VD6mdf2$_!mikFW!tUJfnkWcn-#SDUdbDXw|_~;np4sQEo(Me3O_ghpKrFA -zWcx}uOo+uu{zbpqnw5lM!V34-G~oA;O_uvG92IATdAoyO;6Wqqp3KthE3nWF-P{gb -z5^+mV8}kwoOuC*`QcCiJjfxSgyAX_Lo -z^ei1YOQxLWaBhHpzIgzEhe2oj8jlyqP}l31GaLh;b@w}<&R@JaG&I#YaR@49&sVg} -z0k3HL)&IGbVjmqZAOZ}v3#d+BLy18VT8>s`%`w$B3k#-SqC26sMCmLxh=_8r7; -z^X#-{<-58dyGw?$ZWBDJjb->YgBk`+Ur}ajXkOMgY$k&CP`kt!Kr1T>=e)sspQj&e -z07AZA9UdC^JMKxeDW~mkt(8lh)&*vsYE#qDX#;5Jo?W}x1=@61Q=hV@X0L^<+omYB -zXpXW70&}*`2a9fW2e037BSR(C)?lb$@pI=e{UsJ81FA -zLMF_dj-RNTt)SDRi#VUG>o($$tO%vEGbv+Gco@kWNPOsI+cI5r&=LMGK10Z4vCnex7wZ{3HewHZ!pw2~b5{Te9zYlY*8#Cb2LqO^Q(tKowcXKytd)Td -zZs0)uN#OZwqy#Ap4~m`%>c@>~7bDLxKA&21QR-PIbQ8C~GL*W@38Ma}F#h}1t|42b4RM=V(u -zD^h!Ia*W-JvqA8EG}T>?wFG^;pdk~;&PUmuj;2lw$rXAWmS|IA%x=8s;tZYWBdDu^ -z@e9i|ZaHiW_1GUc&Nw{9q$lSC8u=dz^^Lci531E!J3=8IZtOkxzhcwTrF#bTCjLl3nj>P3fhp+L^ -zZ_3kvvL(oAlGiq1+3If2EBUhLN`nY+GAEXAA@ZSxa4fs!i;WyyJvD9ScfZw1Sh$&U -z@fm&k!5&Oz@msAPR=Fk|Xn;!fNRu9E(+BmLC!;4#9ozF5SrWui9mtH6t_gY>fNlPt`<0xjP$F$}RsRm3I(&ZLCCHhamm!0-f_>eF6bD-1!8;7`569nu>fZp( -z6~IldH_yRp)~ir7KI%Gp=`NScIBEXa89w3r)sIbQ)*Jpb2PQ4A?=Sv3mu{>5s553+ -zxYlic&tGQPhN3I52`Wg|=JCdaj3Vx$f?-JG3`r}CyGB}9`21Rd4!0@%KB -zk2Q&9C)8wadT^6ezv#N9j3r2Z!(|COV4F3OJa}{+ -z8aWMDhiidr1pT=tKbHuXe(?#drXOg1y@ZO{@AZa_8Z#haN3rzaO-g>C!U<5g-a~dv -z-dBx(OFyTZ -z#w@#c?oCL`>R6G&QUHI63kjpS$}4IE6*Yqu_54`# -z+6^Z5imklkbG$R7`OE4wp20poKhFfICkj_cJz1f4*^Gzv9F+g++y5!~1m3A|;?6dy -zcx9C!r1j1G@H^CDj~HaStbB_wS7yv(U}CD6X#j|X8JVv>5d1{i6t5eUwz}&xD5|7q -z2KcaE$p*rT&ciIBx}$#V^CkwqI*-ZxWavj)T^M$>y|!WTOHOZMmzv_z)q5QFapz(| -zo(<>~#AL8OAJ2?5vT&`ZZ)s~2psdTZG)UI*ql9_aPg6=_&wF~~_~^kh6Dc6OVJj20 -zlMf4cq+~Ht7Vv&dW*ZTBmVA6Xsk%p;wbzwFZr;)0na)jBP_1Zf-R@zq2lQ+Wigk6) -zK@P|X1g>1rq0m4=RDe&)Eb`k@z9l+tA+mA4k?E5z?NGR>Ib09@Ip{+IpEXi -zTHdcEGkN*t=vyJ@_G5DZM#?E3Ho(Zq3#+P*~H9rDlZlP}N>8KIH -zPy5s))R{hNz#BB#%d>c4ii7_Q`WVHa(pHG_=Ujja -z3hzl{5>5YoHUP3*BnHM$0W6Gar%vwxuvl~BNIbQW6WBHbxML(cu?{`7m!r)@qx9)=3*FDMw;R^D65qCBmm -z^F=|{1k6r>gs^)^_1@g|%9u!TXQL$?tLea=OXn<^v+`>9~|D396XxoW8J{XI+;yl72+~2yDtuSx^1RoZSq`S$8_hBq -zE`g!S%1v%nxq6Y=kIW(U5#}i6h15?DgmwgI15jj%d*B<+BV?G$^eYFX+D|62uuP@~ -z9F-CtBXS;%!5atQ$A>CyoUgw7!H$A46DUU-Ot%%Bwh%qo8w)%(dWVp6SN&p?rnRkW -zxF9ZE7BQ&TWnjollO8C%Ye~!Eo{D{gj$7fl<_p2a$MlucK2u2W)oW)gxbkuqOr@zYr| -z>RA_p#lTH-&PAX$V)WJy2<^~nhCIj-f2PVD%kdiJ(NHNE-PBvy%o!zMw5R+wrzs&Y -z=^QcH3Tja{U;Q@Youpq;6*Ugb} -zUeCZ{cMH^BjG+xizaYdU4xdq5^a(iTox9{9rrOu>h(~IXw|cHwZf)WMLVTXvZLWBN -zp5<){<*xmKtfAw3*%neZ^e6G)H5@ZI+IGXx=0T20Ng;wALhSS1OEe|xh*UD~^JGQA -znvrMJ0j$Z{QV&-R1%`^dI~s>_}sNAWNyAu?ySKQ!8{GOP=PxXdg2DW@E!KKJgkouCwQ -zVQ{m`Obo<`uMR-hMw8PIIlC-WCAzIlrNGSgT$aDg#;24tY)>!TzJJVxZF=V&e^^;s -zhmIMHwhc1W&1B8iSxCpNKLPFjF;+@D-f%8UkiB+n-*?0KwTMYG9E`8YaK%BC^OxU$ -zDz+I;rzLt~Yeok8Q|05rM(>pjECyVLcQg6SeXbf4kg_;s5M-4n`*b#=sCqTF)k}Zj -zpkGF34Te2SQV-V1o7U7*bk0hTMl!I0S?w;K<6b)TjDCjt-;{0VMl%xnKym2XVqQ>W -zy9dMFGQcEj4y8d=pzLhW)M1hQ^sJMsX1ZV08azgiobc|6S7-3jHQ?)i(HFe>Sx-mx -z+;Pw6KP1yN<^!}7&WTG -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 -z;O0w`CwUO>$Hus -z_~=51aYZN7jVA#(Fg$$7vV3Cw_rC5gD<$*F0dy@-sbtH5k{c)VPa%@@s7~^-7rf<; -z`3Qx=)3-)F_SDD#4`C6CFxQogK|Jac^!HF?@>+pkAA8G7Bh5IkR%oXo+rx(xVi^SufuA`m_4 -zVPiG{r546Nm)XDNy$2?=F9oVo6;yXKK?4XnI$j5=Xo)NkSDph95=n_KOn&MC0;_p@ -zaPHG_)a&_rEF_Ui;y@)9i9?t6)Umlka#^Go@+jBAM=1n?b@}1!4-oUd1HDZiB$qw` -ze=k9uv-HzWYFhTxFflP9kN6VmswxEHzkV*27lPG_7I(xnNf(a#1%tT -zxd&y#nPpt^rQOB=(eVOSfH5S$KF5k{<)s}tw$iu^$~q-bKYRr#>GMc8y5FvW1!`~- -zFD^mvX404Gg*ub1u@MbEMI -zBi1c~QN|w2X^_0lb-N#QsSh-?uhreBISN(bj>8I4S5!1f0^#{TktzNrw|v&L(8`}V -z;Y|u~X!e1I4b^Af`yD{qU-C3a(ft+*O-ai6xsVo_<78hKw5>kLB&gdlt0N^fhL9%f -zs8;v>C(NUu(J~i{ya-_qDs3Hp9?<@rtpal8{f`u)r%&<3S%LYuNs=^B<^BmEvl_3$ -z9KNToJTtH8W^v`3254?6=hNb;0YVt0T^)NYO~RJD)6|&|pr+r%H;KlJ;x)h6PEs+H -zZY^V(R$^yEvD0h*!+;%BMPRDOI*X_^l>3wUEp-g61wsjDe>;!BZrd;i5fZVG?EP6O -z%9>sK#14&0GtBAaD+yKe`)6f5v#9E`fRPPd+QTOI*+5x0@q~d&=L`OjOpF@TfYdE| -z(lwbs!IIEqPyp(Z6kgRp* -z^N<9Yoc_vJptc^KmVi#z-bXWc&`F5Fw;6o+EB|0ekos)NvITf%;N4FDroK_JLkWv$N8>y%Z_@jT-q?j$=YFDM^$8MVl7 -zdh`Ti$;sRmGDnJe9+vZg{ -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 -z7Qgzvpe -zQ{?pXzts>x=JNd;w=dh-BCFVX=u3tf?-pot%+QjApdey&@`ktj(S)0Uv(me3icL4C -zLE^Ia^J}pO7g%Yc?~7_rl##2{+bGOB_vFaZ*aqjnh3rWJnKOqf%^H8L5KWy+>mAm4 -z%b|!fDx?zUMU-yy<$m>N*C3Qh)*dQ&0V-}wA3vG)Sq~jJ5kbyeVlpvYsvwxFP4t6} -zYF4`hzs`4N$Vsdfx%uXw&z)VU1g_u3y!!nds*8{FJh&!7zH8l!<$00w>YAJX>>BLE -zo@rpZ#CNe{WN145XHC~CYQik>N|_(;wS8}f_t}j8PM5G;M}Gn}RK56enyXD^V)(kZ -z$S3@v$zLjMU?eKk1RkmT{rjWN=13_B!pk2i*to -zW%&>3Qh@UQV^@w%WyGEGHsL -z-2_YsOUp~9q-XJmqN;gn>L}!B4#7!mTDVR>*;BLOxcFKk$8lHs6wU2`)+TJuv|M0c -z-BXnUXA_>CvXJV~0<266Ms$10=1Rimso4a{B=-HEbjf)Uylj2InY!aOSka3CU1qh65eJ=&JZlOxbbm?_&EeC&r -z2PQ_ZVak-)d2@LKR$iD?GLJtL#S$#cX6X*zgNk<5piNygXXDK+Vl+{1w%ju@{g&)J}$QwC0(6Z(3k*FCFt(R=xaStV~8N&~sGLwazfB|h)PMy<$fOGo@-alEL} -zpymOI0Jm#X_hNi4cMupm7Y6svCTMo`ms<5?WGWJ)R4n@*m`xe|g;PW~t`8-2KD^Zf -zd+;&z_Du5=UxFOY4LrThifTg@_B{9`S2jT47XJ16GW$kH!iIxWlX7e)&Cmm!2`FUk -zq9X}4wDq&ZH-$+iabg&0F#b9(u{lc&*s28A2I+xE<`uVEj(@k}@u>4>whK^(eaUl^ -zIIXz&W%n=S=5=omY%i(qVJ(Rd470@t#Fqx~qW$Zu6$H&+*34I+w~5E7x@A+YW}jn^ -zxnAr?mZ|lU?gbgUE)x(%vH&NxLS^k8eh$Xe|6~ -zJxzqPp83;h;X5NGUtNy5_WbRlmRWDtnr-T|*ZmDV|1(K*ZiUN}6LI7gCDl7j)_23q -z+}1Q(@47O(KyUQ8r1laRExc7Uxl;X7%0{kGKz0ttJe_6mc8DxUY|1y7Yemd?x(*V9 -z(#*RMb4rX^JwY~EM;m6xt-C7=@<UOT$omLWPJlx9v3A*id$soCYr*>u1i>tQV;VE7(NalP6Qq`2JkdL^X -zkLLKDO-Wr&A1P=`Z}i~^P3nEy#}+G1M(L8@&L=)En`u^NG7tCj#*06Z$&(GByA}k^ -zPbcca$bYLhkZkL-3$*4qOYeB4MADC^TQ+>AS!XN(01vQ6^o;OIk=+dH0F-*`!%-j) -z`^KjKQ?K<9bB|d)!%4gH7TiwO$cM~5yOaaixZ~8z}f= -z;Milm-(fM*5*MQ-Ipq61@7)#9Ywhu_=TppK#$OMI -z_l@t~Z(74N9)Q(0TcUS&PUI%EuY1ey>P-1h^BJywmgg1hSTsFCSTY{gbqU#dg1i1X -z4EnA`n-9@tq$>U3G$A!TFFcF$h*hKEiK#Q-1%-C?Xo%8295Izz;gZ`HeD?i`oh;#T -zI<#z=;KbV{s}sEIme=}lwIYY_p;(vdZXJcV-F|h#ao$|nE8v=eCiBI0@9)xsryvx% -z;RfDDcpUYS8v~USCMiH>=0joyy^dl6hR+&TFPoo0?ry2!CY#mL` -z4KR=dmysh0QDh7zJ+tp1QfIDl+^u02NcS^9?=0RVo}9`IX1;foQ?kwdJ&m{|1TjU_ -z>ZI-*E}cQ2U}?Rc2zUx*Fu|V>`|2FU!E?#m5FoStSx#~Sl{ -zEKdy*EA5rAfQ;OS%JYM73=Hi04U0c2&N|W!vf&euC>^6{KuMjA?5spbMg0;^@)Wm^ -zs@;`$Sk2|MVej3$R+Wb$PK3GQh-rvUMc81hj>VM7FlMHsT{wOTdaQ(N!{;6nhdD9I -zkcFdvdQA{hPxW=5mw#8C&7N5t0hHsT$dG2rr-|Z6AGQwC>**Jyqmh!uQ)?4sHDTCKxGF8G{_FbOj7U3PMq6**Agm$-SkPJ -z%lWcC`*$9=iFI1nowf}Imq$q{k~^0#pjB-={Nb{7td<>0PZ*6Ux!^B~xEpJ`0EiZ% -z6DRFQoa}TbaFY9=4W3Y7^-zS%K?PE~ITg|?wcJ{n{yH<|I>tm?t13Hqp$Od@;=LmYx7%eODrcS>VS)crxX-#@Dd0}^r -zgUMzkEZv5i4`vL_p3*0UmAdifUJj6FegWst=O<&VYh1ZT0W&9yotBJcm=6|Y+K`%X -zU^^=OTuI~bha$*r>*bKrM-lreTkVsrT)DVaaK~fJr4i7;dQD2}lE1f8xih{B_6LoF -zf0+Gw`Fa9Y)mIe+Km9kaG=(<9L{&6ixu -zS7X4We(T(~Wm1D+G99m6OedODc`&J~U7Z2s)O%UspF?)X_;H0$vvGxk;!U+-p6b~?T| -zk$kl{_s9NBhzPXmq}^4P0YZ0Ut7Osy=cj(PFC#JC#Q=%Kr#ELc;X{}bIzo?UL>y5+VjnkSg782k12wXLqq4O5J@lp8axs+p~xYOv3lXr2S -z_`T5d`6dpk)1p+@>uk8~lnusBvdaq{I0<6(%tH}LKXy-<|Eb8(c@nn7(X?Vn+rw5# -zgnN+%UTT7^avDAlzj&Ara0_IQo#dqr;8A0NGG=yeCf^f@kNb44dolyu4K}L5;p0I$v}^+YsH=vS@Y^ -zOh3~Z$g=c-Pud||G10!>vY>kVWj+-k@s64U1%8x5+od-DLs}z+%)(P`O)%J=j}-R@ -zLf0y?DG)q&0A-|fq=GA{R$%`3?B~Z<$BsnLs6%;`1I-3D@eEGtu4Mp&IMh6eAN(bO -z=1N(2uU?)owCCr6WCza%|6`7;A(ajo0 -zi-P#$A%d8=;te|XkQ$hxjW;KM)suEWl3k2kFqoLg`}@z?tzn78;vBVaV~rpbD6h?= -z`*lN8bi>fjJ%9fN@%isU5lhTLQI`6@-VD;c&5*Hu0x61WhnZJ9_bAk+Y>k8qu0D_{ -z1H_%v0994F{OX}9Bo_BmpyZ5AcFuD(zyjp7WeNIi!#R9IOrmJ#&0y56JAtJK=fK)u{d<45%=C8 -zw70a?kyAHnpPl$vU<2?(n>P>kt?W*=#*p6CJd1iBLd6KM*_yA9-b>!e=*4I9*@bkD -z>CE%1#{1>HJQfvopk~zK{StE$3L4vbskdeyy9VPrY3dqET(b_Qu!TM)uR>nuDSWP( -zQ|U+tsz*ai(l~4tEPyd{%WTe8dYB_Tzbszf=IuxwSD?MbuYyJu%=4(al@GeI4yxzu -zXXZ(D(we&EbdPFnAj73Kb(K_`J}lkwE@c0IFqqc>@$s?L1p60)-uw -zXejKKLj&FpR1f;BH|^rFRT4}rM#K5|g*=;>`uA}RpA!=KC~&Wsd@Mqw?0%Il=8Du%G_t)FQI -zEc()fmjLBy2NAY*XvEg>^=+7X3g`c1FEYm-(=F?V6ShvI$TfGexx5L-bzI#It*J%ZV9vF9G -zYe>S?V0Zss4gJeF!7WzFcO4}Zf^(V%Kvjdj*=I#9YAB#c>p43IDLiCt1{O-BwwBCs -zbTXxDC&VAU`Y{B6Z#rdek)m*sQx7;ue#Ziki>o7+mkr>oY=&z6-kX$mp*nvv2JD_X -zbfr*(GRE|+59MIMC^4WnsR^omT1PIu%dB;H4>jQvlywxmVceQ(=%Ajwdp65!vW?we -zMhm)sk^)ll38)??p#1;IU{-V7z8kI}7G-*Lj&y6Vbdalo`*?TDlzYNJUZsF1cTj=1 -zkPdP?sAB-Llx5gbNm0y$5G`&O)DP|*ideiVs|=k=(Hb)jUe-3brDykg-&WF{nl}(G -zY0Q6)6`9OdCHOtJVShB#fFT;-+GF5JoB4*}-r?mwR@f4x&C0*B-@*&HU`K5-S}xT^-C9l#yr -ztM3!vGMJZefNVYp4L{{3A>O|jpI!zbU*bFlvNEa=Tx%fy-RSo#K47CP&w^%w4b)4u -zfr6sGB;5n<jDFRx1m21#=r<{ -zKt+7a__aFk_E3Ciz3cDB0mYNzX`)2W@fEE`|kG -zi9_887r5r=3^qIQpIWZ}jqp7G_qf0NSMVsb3OYU#iJZ2sE9-flleQgzm5&#O_Cx1P -zdl0~nJZGT2u{%bS;4`8ro5*uflL6o=yfFPfz%}}s>V{!XZ5R;@Y)9?rE?2zX> -z_xx)>8P!BlKXPYxu8oBxSOP@T4cZtcc+K2lO9j|`Djc+jA#%6iQ=rOosTa80xruUl -zIitW*VbPbT3Am4~2S`QF&jED4L3_;*xt~!VQUT3UKDm(`47|%jsNLnYnxeFw(tt?3 -z=skZdVIV0KJ<#ZEu(!wRm{CnMXc@Q0H^?s*1gAMy4zuuWtUP~FmGix)X0asiV##{{ -zv{wYcgLtmmlx^Se${31B*qF~41x}n-IRv*w(I?l&EjOcC*FH9+e>^g~5aJ9P7^cgc -zOCoe0M-S -zzjtl7H+0}k-zoEjWEA05csllXg1pKd{QmaW5Z&W{iVM_cO=Wd3wK!Pt!T -zv{FK-dk;Q8K6aLo^sYK~`C{qUhw6!YaYN)a1i+qX&sL??IE_SErvp|&b`-$}7D>E| -zm%n&GAV10@zj6v>bIr;j%UpQ20Tn&~Cs1RRp~ht&DEZcW{btbc$>OWPP^#F7qI##4uw|tlz0FR -zsYy%Q#+m?`S)jBYj=Nb)VNCuF^5Lq%uJ0}sO4RdlqoZT1Y}0^X-hwl&S6+Z7HzR#H -zFoHXP1Fg600p-EQ+y+-v7eM`0=i7q4#3zCH(ksxm2?5OfJZwE))J-v_U#Z8;Uej%G -zZ6IO`u+s;yucD2Fg=f}j2t_yAsi&=J$DvKI2cJHw2SF&-%-uWPrhu`f&9>Tluh32D -zHXe}dS_n*6G@H_0urYA0K7e8LQLx8X?>NF7!nH{Eenq8i!+SZ;`}k3DVw96@MU}DT -ze)rPXh)iR%0y^7jSZ64`7HY9RW@PPt$mE$&F5ds_$q7Glyb?553dZkgN -zq0sG~=DaiLv0e21&C%OeZ=#3%nauL5JzAiwINpH}HS|2y#ZYWI?ge!cC>-@6c-aJf -z0;qiRu)x_n3ZM>m?Y(?j?opC<=bZwy2yS~%HVVq%Ns}YjpcR#y8gGuRZi -zb^IFs?j9(&%Xg=5<$cAd>MVD>(XOhuoF}?Htz}pbNgB4MVmbZMN}d}KiYw!m*&V?p -z_#mgz35nDMteI>PI0F-RepZ@}_ocjty3=nobNBO=%+hF$mM4IFWlVig?pZKnPleK% -zL$!=Fd&?jKX$OgoB6byDOmY>V%RbbK(d_aURgLwU1K3sR_bX{a0GJ5IX?bm}xyTC- -z*l^)s#pS5>*V(XunBDskR-r4@CnBI$F(1y+z1Qfo(o0S)bAReakWCCTzgBd;zg#eq -z*0aHeo#rk@p5!E#4-3u4z8|Si$eENIxwIzke^HI4{|TE_X3c^v=`neUQsY@f*L%^!Mty$9bt>e{y+5=^U@GSETfZS#pFIK!7@NC5=3~ -zZczE2vDt8E*0sTz=0HmVUT`mMNzhGrBU;=eghh-Q-YLKQ>fL$Z6&YL7EC)z5#9uLg -z-3g_^@dvH%^b%G(CqBGF7@s5K%O?I|j(5xDYbCDi$Hkuc3C(1mzzQs!FV2w7;UTn_ -zH}O_8CZ`v8<83)}=}=L*18@qlUHm?$XwoS+Gp$SO7gX#d3`!f`9;S)(kDdm4Xz%9> -z@0Qm>k+C!0Cb+St$T}CYNm)~T7l_2WfuW0N2owf>lm>P+H&|J?R>_$ -zD#75Dr5`#qA69key76e_Y7_PhRgF{bfJPRc21P -zkHMlRizUlAYB^_Ld67zU{GiDQQd$Gl=A(5BLcSuYCDf}0{r9YJ7Be3Vk~}zq4(fHS -ziXXDq;ec^^6KvO426#CZn!5XR>dEyK$L#j2#^%zV@V#ues_F5Ag(Pyr#Fvqyl$I?> -zJ6fF2{gBp+C|P$WMh=R5)^G$GU*=H2<}9{HREw_zgr*5|GN(h^ZHX=E33-fC$RQtw -z9Mms?0CnK_z{}TL%CM6ApU1j3Fga*%4YEdLpz@s~><`oibYX -zHk0>$$X{VQ818b+WS*Jlpz?B>^SI}vZpF_BCRIP;HiEJ9NA8yH_}r~PW)IUO_V(!&ido`8PKBmd!hBK9s# -z(qk}tmc}=^FjQ?EGSo&SagN=_y%Lf%8veEqIdo>jGi12jxiJ)W47$GT_|%8w&UL5Q -z3r2dXrW|F}AL^Ol9ik*BIen<@JhjaH=%!Bwve(%Hq=>$UVWEekb!N)BMq1iD-5H^5 -zPK0)!63wyZd`UYV*rAcUn;X!z^K}x2IgCAL>j|$p(Uf%_Qc$$x&?1$ySgvKdl<`36 -z;SGc~o-Zub;@3KCQ_^k2r5h+2mku@P+M{;oHGB&>^b4V-*gJfy;-2@paRJ?y%V}}z -zG}9`cDJSi+G_sWkXkbH8I+XH}+3t$Y -zGp-5K9wlXY8ZG}SCZ16x&^+v1x^~0;)B4ya$oovmT+sG2m}uDO11BeDvZ$L+dR^`- -z(;Cp#3Jz}tp#Q4A{e9|SGGB^P6uVFAYKUm<@(&XRvmaA@wYM;SoM~8bb9rVHAkq!% -zwPIBf^gJ)?9xKpr@8bnv(j62?SInh#p#)l-=v>YXPl2_xjoRIl57?#`0g*Iw2(!y> -zUBm1>GfzdG%&^h}$5l1@Dp-NV5jTR -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 -ziU5S0PTRCy4Wtc=3EB7_r~XuLC{| -zk{ZWeb^}ZFpUh^mJTh+KCM91UQHO~c;F0%a4D(ei!MuJ&isMGyfUDHlBWWmbl@npx -zGlmrx80jmqedtuqh(kA=0yv$Yz5T&#qWpa7PGQvIk^DTFHR^R^$#9qc)F}VHQHXZb -zc-`Ft_V6l8w&V#s)5dc}=b%Yp+!>Vg(DljCp|W75V-L>qx>FGk{QoKU5z+g -z8Brgk>{r%$dng466w* -z#vS+oWZcfk!n8=a61wLDKFn;y2pbVoFv+ePT%S%Whb_)0ON8Q|%N8SqRr+tb_(-2^ug?(x;#&|xH2!WH(0(49m{ACz-PCJtv}VXx0>zYuNYk)nYGnLb2$ -zB+*siM~bfA6dc56A8uE=FbIl8xdum>=o5RJoSN(lXCejOh=P7=B4Lze^%n#!93?RO -zzyO(q-TDs9QI#CuF*Fl&N$1^b2WdH~h-D(RlR)ouN0#&UKF61A6gsyiY!R^5X*+=# -zJ_lr&n@Lam5(55O?au&@%oFG+V@b{$fb6cgL6jW~S4$R_VsA91={Zw6(x`JcianMguN>`(`f -zVoxrPCK&wBpTCeMIo~1a++Rc5D-vtm&sqK AdblockURLLoaderFactory (Browser) : InProgressRequest::OnReceiveResponse(response_url, response_head) -+AdblockURLLoaderFactory (Browser) ->(2) ResourceClassificationRunner : CheckResponseFilterMatch(response_url, headers ...) -+ResourceClassificationRunner ->(2) ThreadPool : CheckResponseFilterMatchInternal() -+ThreadPool ->SubscriptionService: Matches(url, ...) -+SubscriptionService -->ThreadPool: -+ThreadPool ->SubscriptionService: MatchesHeaders(headers, ...) -+SubscriptionService -->ThreadPool: -+opt when filter match -+ThreadPool ->SubscriptionService: IsHeaderAllowlisted(url, ...) -+SubscriptionService -->ThreadPool: -+end -+ThreadPool -->(2) ResourceClassificationRunner : OnCheckResponseFilterMatchComplete() -+ResourceClassificationRunner --> AdblockURLLoaderFactory (Browser) : OnProcessHeadersResult(response.url, ..., result) -+AdblockURLLoaderFactory (Browser) --> [ : response->Resume() / response->CancelWithError() -+opt when response blocked -+note over AdblockURLLoaderFactory (Browser), ElementHider (Browser - UI thread): collapse whitespace left after blocked resource -+AdblockURLLoaderFactory (Browser) -> ElementHider (Browser - UI thread): HideBlockedElement() -+ElementHider (Browser - UI thread) -> ElementHider (Browser - UI thread): GenerateBlockedElemhideJavaScript() -+ElementHider (Browser - UI thread) -> RenderFrameHost: ExecuteJavaScriptInIsolatedWorld() -+end -diff --git a/components/adblock/docs/ad-filtering/snippets.md b/components/adblock/docs/ad-filtering/snippets.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/ad-filtering/snippets.md -@@ -0,0 +1,7 @@ -+# Snippets for advanced ad-filtering -+ -+Snippet filters allow injecting some preexisting snippets of JavaScript code into websites. -+ -+The browser comes with a repository of these eyeo-made snippets which can be referenced by filter authors. For additional safety, the browser only reads snippet filters from filter lists managed by eyeo. -+ -+The [snippet filters tutorial](https://help.eyeo.com/adblockplus/snippet-filters-tutorial) describes usage and capabilities. -diff --git a/components/adblock/docs/adr/README.md b/components/adblock/docs/adr/README.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/adr/README.md -@@ -0,0 +1,3 @@ -+This folder contains the Architectural Decision Records (ADRs) summarizing the most important software design choices made during the development of Eyeo Chromium SDK. -+ -+These decisions address functional and non-functional requirements that are architecturally significant, like certain features, technology choices, etc. -diff --git a/components/adblock/docs/adr/consuming-full-filter-lists.md b/components/adblock/docs/adr/consuming-full-filter-lists.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/adr/consuming-full-filter-lists.md -@@ -0,0 +1,8 @@ -+# Transitioning from minified to full filter lists -+ -+The main reason for using minified filter lists in previous implementations of our ad-filtering core was to reduce memory consumption for mobile users. On the other hand, more recent implementations achieved this reduction by: -+ -+* Adopting FlatBuffers as file format for filter lists -+* Moving away from depending on V8 to a native implementation -+ -+It is now possible to use full filter lists, which include more filter rules. This improves user experience as more intrusive ads will be blocked. This also increases revenue for publishers, who are part of the Acceptable Ads program, as more acceptable ads will be allowed. -diff --git a/components/adblock/docs/adr/moving-user-counting-to-dedicated-service.md b/components/adblock/docs/adr/moving-user-counting-to-dedicated-service.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/adr/moving-user-counting-to-dedicated-service.md -@@ -0,0 +1,5 @@ -+# Moving user counting to a dedicated service -+ -+Having the user counting service coupled with the filter lists service prevents partners from serving and monetizing filter lists from their own servers, while still allowing us to understand how the SDK usage is distributed across Chromium, OS and platform versions, and to monitor the Acceptable Ads opt-out rate. -+ -+By moving to a dedicated service, filter lists can be downloaded from any source, as the structure of the user counting "pings" and the URL they are directed to are completely independent from those for filter lists. -diff --git a/components/adblock/docs/adr/not-extending-subresource-filter.md b/components/adblock/docs/adr/not-extending-subresource-filter.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/adr/not-extending-subresource-filter.md -@@ -0,0 +1,13 @@ -+# Implementing ad-filtering from scratch rather than extending Chromium's Subresource Filter -+ -+Chromium already contains some ad-blocking functionality, named Subresource Filter, but it is limited, for instance: -+ -+* Doesn't support element hiding via CSS -+* Doesn't support element hiding emulation via JavaScript -+* Doesn't support snippets -+ -+These limitations could be patched, but that comes with significant trade-offs: -+ -+* Bigger cost of Chromium updates, with more potential conflicts to solve -+* Less flexibility to introduce optimizations and new features -+* Potentially irreconcilable differences between what Chrome authors aim for and what we aim for. Chromium authors may have different ad-related business incentives, and evolve Subresource Filtering in an undesired direction -diff --git a/components/adblock/docs/adr/storing-filter-lists-in-flatbuffers-format.md b/components/adblock/docs/adr/storing-filter-lists-in-flatbuffers-format.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/adr/storing-filter-lists-in-flatbuffers-format.md -@@ -0,0 +1,17 @@ -+# Storing filter lists in FlatBuffers format -+ -+Filter lists aren't consumed in plain text format right after download. Instead, they are converted from plain text into FlatBuffers format due to the following advantages: -+ -+* It greatly reduces memory consumption, down to approximately 15 MB -+* It greatly reduces startup time, from the order of seconds to milliseconds -+* Page load time isn't negatively affected except in certain sites with very long URLs -+* It provides facilities for multi-threading and synchronous pop-up blocking (which is a Chromium restriction) -+* It doesn't require deserialization, as it's a binary format that's ready to use. -+* It can be memory-mapped and accessed as memory directly from disk. Being allocated in a contiguous memory buffer also makes it a cache-friendly format. -+* Accessing data in a flatbuffer is as fast as dereferencing pointers to memory, there's very little additional "unwrapping" overhead. -+ -+A FlatBuffers file contains only one filter list, instead of combining all selected ones into one file, for the following reasons: -+ -+* Filter lists updated at different times can be downloaded independently -+* Less time consumed in conversion than if all selected filter lists had to be combined in one file -+* For potential long-term distribution of FlatBuffers files, having to provide a file containing all selected filter lists would cause an explosion of combinations -diff --git a/components/adblock/docs/data-collection/README.md b/components/adblock/docs/data-collection/README.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/data-collection/README.md -@@ -0,0 +1,11 @@ -+# Data collection -+ -+eyeo Chromium SDK collects some amount of user data, in order to increase our understanding of the usage across platforms, versions, etc, and help drive the development. -+ -+One of eyeo's main principles is to respect users' privacy. This is why none of the requests sent to data collection services contain any personally identifiable information. -+ -+The services in place are: -+ -+* [User counting](user-counting.md) -+ -+More information about the specific data gathered by a service can be found in the corresponding documentation. -diff --git a/components/adblock/docs/data-collection/user-counting.md b/components/adblock/docs/data-collection/user-counting.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/data-collection/user-counting.md -@@ -0,0 +1,38 @@ -+# User counting -+ -+Being able to count users with ad-blocking + Acceptable Ads enabled, and those only with ad-blocking enabled, is necessary for the following reasons: -+ -+1. Understanding how users are distributed over different Chromium and SDK versions helps us decide which SDK versions continue maintaining and which ones could be put into an "End of Life" status. -+2. Understanding how users are distributed over different operating systems and platforms (mobile, desktop, gaming console, smart TV, set top box, etc) helps to focus the investments on automation and quality assurance resources on the most popular OS versions and platforms. -+3. Understanding how users perceive Acceptable Ads, as reflected by the opt-out rate from it, helps to continue updating the Acceptable Ads standard, to make sure that the user experience is not negatively impacted by the ads displayed to them. -+4. For partners choosing to monetize their Acceptable Ads users, user counting is used as a basis for calculating partner payouts. -+ -+A ping request is sent automatically by the SDK to eyeo's dedicated service at least twice per day, if the browser is in use (including in the background) and ad-blocking is enabled. These pings don't contain any personally identifiable information, in order to respect the user's privacy. -+ -+ -+## Ping request payload -+ -+The table below outlines the parameters sent with the ping. -+ -+In order to track which clients are "active" while maintaining user privacy, the server combines the values of ping timestamps and a randomly-generated tag. Dates are truncated to further protect privacy. -+ -+| Parameter | Type | Description | -+|---------------------|--------|-----------------------| -+| nonce | string | Number only used once, one of the algorithms that might be used to generate nonce is UUID4. This helps to anonymously distinguish between several ping requests with the same values for the rest of the parameters. | -+| addon_name | string | Identifier of the SDK integration. Example: 'eyeo-chromium-sdk' | -+| addon_version | string | Version corresponding to addon_name. Example: '2.0.0' | -+| application | string | Identifier of either hosting (partner's) application or product as sent by the client. Example: 'chrome' | -+| application_version | string | Version corresponding to application. Example: '86.0.4240.183' | -+| first_ping | string | Timestamp of the first ping sent by the client and acknowledged by the server (as measured by the server), extracted from the first 2xx response body. Example: 2022-02-28T19:50:00Z | -+| last_ping | string | Timestamp of the last ping sent by the client and acknowledged by the server (as measured by the server), extracted from 2xx response body. Example: 2022-02-28T19:50:00Z | -+| last_ping_tag | string | Number generated by the client using UUID4 when it receives a response with a last_ping. Empty in the first message. If a ping is retried, the same last_ping_tag number should be used; this helps to anonymously distinguish between several ping requests with the same value of last_ping. | -+| previous_last_ping | string | Timestamp of the previous to last ping sent by the client and acknowledged by the server (as measured by the server), copied from the last request. Example: 2022-02-28T19:50:00Z | -+| aa_active | bool | Whether the client opted into Acceptable Ads. | -+| platform | string | Client operating system. Example: 'android' | -+| platform_version | string | Respective version corresponding to the OS. Example: '10' | -+ -+### Ping timestamps and tag fields -+ -+All the `*_ping` fields are left unparsed by the client, and consumed as the server sends them. This avoids subtle parsing errors, for instance, if the conversion method is locale-dependent and the format is changed, no longer matching the schema. -+ -+Additionally, the `fist_ping`, `last_ping` and `last_ping_tag` are not included in the first message since there are no values to provide from. They are sent once the client has received a ping response. For the same reason the `previous_last_ping` is being sent starting from the third message - to provide a meaningful value it requires at least two previous messages. In order to adhere to the schema, the fields with empty values must be completely absent. -diff --git a/components/adblock/docs/design-overview.md b/components/adblock/docs/design-overview.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/design-overview.md -@@ -0,0 +1,32 @@ -+# Design overview -+ -+This section describes the general design of eyeo Chromium SDK, and how it fits into Chromium's design (classes, services, processes, etc). -+ -+## Services -+ -+The SDK is composed of the following `KeyedService`s: -+ -+* `AdblockController`: Allows to control ad-filtering settings. -+* `AdblockJNI`: Provides JNI bindings for Android components. -+* `AdblockTelemetryService`: Reports anonymous usage statistics to eyeo. -+* `ContentSecurityPolicyInjector`: Injecting a Content Security Policy header into a HTTP response. -+* `ElementHider`: Applies element hiding scripts and stylesheets on web pages. -+* `ResourceClassificationRunner`: Decides whether to block or allow network requests. -+* `SessionStats`: Stores statistics about blocked and allowed URLs in current session. -+* `SitekeyStorage`: Extracts SiteKeys from response headers, validates and stores them. -+* `SubscriptionPersistentMetadata`: Stores persistent subscription metadata in PrefService. -+* `SubscriptionService`: Maintains a state of available `Subscription`s and synchronizes it with persistent storage. -+* `SubscriptionUpdater`: Periodically updates installed subscriptions. -+ -+Following classes are also important to understand workflow: -+ -+* `AdblockURLLoaderFactory`: Processing network requests and responses. -+* `AdblockWebContentObserver`: Listens to page load events to trigger frame-wide element hiding. -+ -+## Highlighted Chromium classes -+ -+The Chromium classes that are particularly important for our implementation are: -+ -+* `RenderFrameHost`: To find the [frame hierarchy](ad-filtering/README.md#frame-heirarchy), and to execute CSS and JavaScript. -+* `WebContentsObserver`: To receive page load events, as well as injecting element hiding. Parent of `AdblockWebContentsObserver`. -+* `ChromeContentBrowserClient`: For setup of URL loader factories for processing network requests, handle web sockets, popups, etc. Parent of `AdblockContentBrowserClient`. -diff --git a/components/adblock/docs/developer-notes.md b/components/adblock/docs/developer-notes.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/developer-notes.md -@@ -0,0 +1,117 @@ -+# Developer notes -+ -+This document includes general information that can be relevant for developers and doesn't fit in any other section. -+ -+ -+## Command-line options to enable/disable ad-blocking and Acceptable Ads -+ -+In order to test scenarios where ad-blocking or Acceptable Ads need to be disabled by default, you can add certain options to the default Chromium command line: -+ -+* Disable ad-blocking: `--disable-adblock` -+* Disable Acceptable Ads: `--disable-aa` -+ -+These options can be used when testing a debug build. -+ -+For more information about using these options on Android, please check the section below *Setting command-line options on Android*. -+ -+ -+## Logging -+ -+Logging in Chromium is generally organized in modules, so you can enable verbose logs for some components and not others. The logging principles aren't described in the Chromium documentation, but a general description of the levels can be found [here](https://chromium.googlesource.com/chromium/src/base/+/refs/heads/main/logging.h). -+ -+As in many *nix tools, the higher the verbosity level, the more detailed the logs: usually 0 is quiet, 1 is detailed, 2 is very detailed, and 3+ (if used at all) is very low-level debugging output. -+ -+### Examples -+ -+To enable very verbose logging for all modules: -+ -+```sh -+--v=2 -+``` -+ -+To enable verbose logging for modules related to subscriptions, ad-blocking and converters: -+ -+```sh -+--vmodule="*subscription*=1,*adblock*=1,*converter*=1" -+``` -+ -+To enable very verbose Telemetry logs related to user counting: -+ -+```sh -+--vmodule=*telemetry*=2,*activeping*=2 -+``` -+ -+For more information about using these options on Android, please check the section below *Setting command-line options on Android*. -+ -+### eyeo Chromium SDK logging policy -+ -+For easy filtering, eyeo Chromium SDK uses the `[eyeo]` prefix for every log output, as in: -+ -+``` -+DLOG(INFO) << "[eyeo] Annoying ad filtered successfully"; -+``` -+ -+The eyeo Chromium SDK policies when it comes to different logging options (release, debug, verbose) are described in detail below. In general, they are designed considering that SDK users prefer not to see a lot of data in their logs regarding content filtering. -+ -+#### Release (LOG) -+ -+For notifications that are necessary to understand the basics of what is happening in the application, for instance: startup, subscriptions management and user counting. This can be informational, warnings, errors, etc, and should be relevant to partners and QA engineers. -+ -+Example: -+ -+``` -+LOG(INFO) << "[eyeo] I want to help with partner integration testing"; -+``` -+ -+#### Release verbose (VLOG) -+ -+For additional steps in the process. It is more of a step-by-step description that can help a manual QA follow what is happening or where does the application get stuck. Not so much intended for low-level debugging. -+ -+:warning: **Note**: These messages are only visible when specifying one of the command line arguments `--v` (level) or `--vmodule` (specific modules). :warning: -+ -+#### Debug (DLOG) -+ -+For debugging purposes, for instance when an important method starts executing. -+ -+#### Debug verbose (DVLOG) -+ -+For extra information during debugging, like low-level tracing. This could be operations that support but don't belong to the main flow of the application, like interactions with the filesystem, or very low-level checks during resource filtering. -+ -+Example: -+ -+``` -+DVLOG(3) << "[ABP] It is crucial for me to broadcast to everyone"; -+``` -+ -+:warning: **Note**: These messages are only visible when specifying one of the command line arguments `--v` (level) or `--vmodule` (specific modules). :warning: -+ -+ -+## Setting command-line options on Android -+ -+Given that the command line isn't a common interface to interact with an Android device, `adb` is required to add these options to the Chromium command line. -+ -+For instance, to disable ad-blocking: -+ -+```sh -+adb shell 'echo _ --disable-adblock > /data/local/tmp/chrome-command-line' -+``` -+ -+To verify which command line options are currently set: -+ -+```sh -+adb shell 'cat /data/local/tmp/chrome-command-line' -+``` -+ -+If the options aren't correctly set after running these commands, follow these additional steps before trying to enable them: -+ -+1. Install the APK file -+2. Open it and go to `chrome://flags` -+3. Enable the flag *enable commandline on non-rooted devices* -+4. Relaunch the application -+5. Kill the application -+ -+In order to disable the custom options, just remove the file: -+ -+```sh -+adb shell "rm /data/local/tmp/chrome-command-line" -+``` -diff --git a/components/adblock/docs/initialization-sequence.png b/components/adblock/docs/initialization-sequence.png -new file mode 100644 -index 0000000000000000000000000000000000000000..7d164ac06fafb0f4a9e03de02f081f12cc51bf4a -GIT binary patch -literal 129976 -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 -zu^{KB5N49cMh($<-!z1kn;mYguqP3VcNQTS&S -zDrbu27(X}L1E<$QxPD{Xg3S8g%)WumLa=(1=|m0!q-Kl6Shg4`=d}_@tke4ya5O3N -z8D7|e8AweBA2e8Y@{&=WP;-$(#YazNayGVA-oy>6+DS@cn|7TGKAEM#Ly|4I0t~u^ -zi`^{{^jQ{Y5G?E}LI4CUI5C3g{;-rV!6fTM*Xk-Y_rmZ|>iYn*c)5nQ?ET*NFZ^S% -zR%BFYSz5UBToYAK3v$ycb?R_1D9OwnQKkU---Sh|I@F1WgJ1xZ19&PgS23h1*~%ql -z-wM_MV7T(YAUc13jCM`*-6|-4ha~#|R9nDj&IE$I8&-w|24!CP--wWT_~W|J8cGgr -zD^R1?3VzG;PCghMbYwXlad&m}^TFCpJU+7otS6`m6fDRi5~V_)l{DCh%Vfko{Nbfc -zHiI2mHZhh1eUC0@Cz}EZBJLZo^gejX@bI+*kT43r^ZYgud|?tOB*|lb00t7UdztsI -z24VyZ44#3l4*j-crHI}jq`gn63!2Z;lV&J)I(#jvgl8_7}FnmGbt2PZocQs -z1j>o_VKNnsqx|>LzOxn(#?boNJ)z -zX>(zI#%+j@e~yCV_oVu*mDlusL9jqu$BR5R+&9ysuI0qh7HLhnX*u>^`If5q!d0|0 -zaGXv!a9z@8*%-nwm^yXb$~DNJ>M+;~^RjeAOK#D`{FsA~MJw^-|EBZ>D#kE{Ix;1) -zPxEX{r!Xnspy8NUEClI%2D-Z2rAbbFpV8>V55z(aA9&pU%g-i+3d5}!X?Xxqpn1#+ -zO4I-(ThiMD&9N_1AFn#Mr2u$tev)cW6|;y&oy@c0d<(3k&p@s^vfL9z89y!6U78Vp -z82KHF*G;8w7Ql7JmhRYuk5OH^{KmHJJ6{!k7Kkf%0ULz&Tm&DpU?TPhQeIh&O*9wY>OkC+t4zOs`y>&Nip9@99#&!lQ@AZa=ult9F_Q6;D$V52U7@7NYJc -z=Pw;iI%FQ+h7zKvUl}=f&@3Tg%&m#^jkTP2o-6712~QS4ed(w`ROa#N-S#!-x9fW&ARQ0wc@MKOlyG*Z~BlU#c=14n!B4@J1d9@~BA&6N$aHpJDn1aJ5eD{8I2*H!i6U8;ay^*~#f?^JfPW{$ -z^A_zR6X#}`n)SfWE@!r2iC0h!f_SH5F-b`-g%w@+?F2DF+a>WE2?rPhy2*P6%XB)7h@5!`?~_?=H$PUdN`4VoEoaipZZn0Q*!!zROG -z{9@e7yyC}0LYHnq6-s5N;rps%{*c#4p`b+zg9h8$kC>yRTt4Opz>~Et{Nv -z8>rf?r2;XmWqKcEd(5IXId>VMU?^mE`dzY#gw*C!EkZ~gneFUHFeX8C&mw5)^lw+9 -z=Age0XuxO}?d<~^0sKZzt+xJRCjN-%1A~xm5f@uf>Fw&mpI8K=b5jO!< -z&9EvgP#*(z4y@}^C{F@&C8CF -zB%9b6*lwEdDdLF+V{j|(xE+z0Qy&GsAi!;L9OkUN8dT$tG6E4(ZSu~Ge`QH|TUV%- -z?YQGUA$=KA*>ydb`9KwubsBp;nCcN1x;0*s&is-2nT`RhsTL~Wd3`>^#H6@LZpVLY -zHo0mTdCQ6ge{>KBq7YFM=mqlJsV|p>#vEw3D5Ff!v^j7V-Q6b!dY>kC&3INelLt=7 -z$jCT6)d#l|&5fnc-2cr2_E%~Hdb|Ts4*5j@*>_A^_}ca~go4D{Yu9#vp71%~#=q>A -zyI#~`@hcw{bbiS`&f7Jqb;(?*a?6cd4^38fFlGjWXF -zDsCnTSh#g|D=yc1-?y%bQwXblYt}y5}zfYQ34;|=(R>O(-(GeEM)T0tej3!t$M6d1a&yIc45dy}GA!!?2-GAUgn|QL!{Ubu -zK=}vR+_07iv$O@>9>#}%#gV5`5S(DAFUBr6p6GIFXQ0l3m50F;TCy#$P!fuIkc_WBzYr1yNV(UOCUh -zRR)N7z;ni^EE}ycD=eYNs4=Ow55O3qMu>a_4J(8H3M>D85!BF+PwzI2*zNMgVlV}o -zlc86>rd+?L$2ltgFveOWI49ZC1^nkcxhxKtUybYAE*ty{E!=1c)1pw%2AKkohqxB1T8u**Jb>gOXRF$XlZiP6(0$l+L;MdC{9oT^Mg_VL6C&j4;8p!+CCYW{1L=Qg -z0sd%R{@00pfI0epqRzTM*u)?0jDOgq8xz=(C-UsCmHvc;|M^A#)As+*cQ6MS$jJdy -zxB_t5UrzF;MgQw({O9^YY4P*o`6-!jba*PGD*_Yeh@ZvQW4|8qmj{DGh_ -zxDS7V_(%BvuTS`Y8A$)ji~fHGPlZB!pU;*H8gA0Dl>{QlX-EQqdbYlZ`X^8#&MhWr -zZLKr%ze3w7po1CReii4f9J3e?c2n;mg} -zAFGz%%q{Wzd77S%@E!dA5rI|CO`-df>LDgM4F%?zL0c~C-4HCWz(cIHFKgO_@Nybp?fPT| -zsd_-&|KVAsz!1Cvzt?r}a2)MI91(z_JKj5}-3frR1Mc|1cG_9(ZHB+5sUhH&{p=X_ -zs>vcguh6?P2kP$=qVKI!0$f5)77K?eo~ZP6RI9D#A!d%cKmR)tshx(DRxye+-)#mf -zj%AJCo`b`7Kayuq88*0G8?-c1jdn;PO=cxhn#Xs?m!}(DHqwFBxl(ux=P=$f9cWvX -z{9Zd`%$LcJf!|GpVc*&9c>D>yOdR(6Qxxl?dV^17?fWlYW}?>vXXzIydL9WA`o27q -z+fb3V02F8)C!;0o=nxd(#k8lYv)^KPc}|@<-c5#8pjR> -z=NQ&6t7A5Eyw#u;bDH4<0^uzK5dNDdo?pMC{#Z=(PJRXB&hwh -z{sgtDcr~*Fd=Y!iL!JPI_<0I9duPuv|8(P*Wng6)cm-l*308*BAidB=-U;>^5Vm>E8-^l=o+Xh+Gnb^^mR>aiUe-;d8XgWeH0PpQM -z`BBJ@me&br|7}g}rNdfa8yCs{GR7kLmql_KA54u@?OIhJS`OAn=~d0f0OK}uY@MTx -zTMi7Eav5Z|(Lf?#{MYW}Mbz{$5CZADE&KLez*m?9;+X&1jySHG*XSS&|H~Mh1f{`A -z4ZLjq<>Eg+K!9{_V3lf&PZZ!UW`!5)6a1IIaDi7rc`@i!_Ok@!c>=gbVt!t*3%vL7 -z#o91AdJj{$|F&k~-kAxojn4Rg8N)fu&>25c=C^OM1HOEAXiLH_U5lHOr||u?J3~s-E7911Z(1UvsL(C;1{mnC?SN5Es{>m~q4bwAD6q$yRLx$I -z*-WIZ>Cyvf_hV_#XRHu8xl|#n?{(-q0e_(ce0@j-y~XgIrFz~k^{bcc_tc4P9p{ww>a!v25sC;z{Q`o -z_f?}gg*x?LyR*u=?b03y_-09gG9j8dn1TQHO)!B22Vjga*1wDq7HDVAF3^SdJG7#K -z^!IT5i5{jRGk^@3k4@2c0{(*b_0^oe!uah|6KN`i_#rJ>=^CK@K>B6MS@`_37y%rz|ZApquc&WLVK*m3+vlO|v8rZzTjr#ZGz48!ep)*N^fcDLMVkS* -zj5L)qzUMngPAL{ZkL8DtcOx1yj}n;i-X|_!^#z6n?Ow9y<9#7t>JC4B+Dmb9c^+B| -z$gG+r)<05oL9bxzMfJX}Q+XP_^XHDm#qi+%kn;KPJ0VN24Een02u%xms90~&OAi~W -zRz+v0mA>%N7)J>f`b|zwQvp-I86;Yj^2y{Lk3zg~pOi4VVA8fS5Aw?jrV97VVd@cV -zr~lL`9)9oo=u15@nw&HPZPTASp|Te&M=on;Q4ZdgX0yKCmW)FNl}_11%d5x6ob(_y -zzN*J3t6F}y&rMaAB+>!tK{Fus?EWoA8gDXYQx1AhormMQi476*;P(%3>}F28&}`3`g+SotXQ0 -z#Hv0=r$~pQnk_&G;*{ZkV;}HSean(3IY9qu*gTYhimz%heg~L|iiSJs>6-@^ckxxL -zSDd#6TATRa%`!D#sD=%3gSjVKolVg5EQqN?>igwH`%Lj)mvr{!2ueC8Km)T&mTc?&9SI2`-jA4&-0{ULP=4~K&Czf^YadcC$z(-%0 -z>mb2jreE5cUF`=kT`*7io^Lv(S8bGbDVvjuN#5(p-4FBR_L694^s;;YV!=&!K`0tA -z(zM~zPg<>RT=G1DvoqwIOjhJ -z`D+}<9w%sIYnf$uF0YcnS`$*%%X;r@C5_e=KW8fLA{zNrM#H4PU5x9wU3(moFMGLa -zzLca#f)?c&-5ia=HeR;p$k3ghoL8J@x;|-_E^9iw;n&VZP}z0Pw^SkNd*i6^1)yfx -ziFU7HJDdEbx+LzI?E4Bq7N2;$xLYDfy~t*D6g-*dL>6Dn)asq=6ANe(>j#!gL!B4n -zR6+o;wItn;`c~M1Wwmy|+R+Of&xa(r=11G_FBW?-@(f`QmU0^I!RkMr*}OiLb_rN) -zU#*Z?85g&w+MQCb95vZ|*SS5i#Aq^9^U_9voS@T8-bx_LLhyJth%^MPYfI8}^!2(} -zV44{l9b5#|#ODT)DTpLzrQ5AuD!Q=JRw_dzr|fCR?)q+`vH>jKFed8O&xOEJGD|bd -zh9@i~0H9QOx=yLjiGwGe%NF)57F=YAG|KCg079~sx@Kfb2WvI~&f$A#hG4m|x50$g -zIBu<21$AeeSwo<8pY$TzQlop6|NhOgLF6 -z*SVc)nLMoYS9U~O>pof)%Uuq1M)fFn`=^UJ7dXJHR-u4dv{VMzBLc?-@-6^r-kTR* -z4@Cu)8`>5RfU@TK5B5nkGc`nnCRh%LzC*s$EZs#w -z4uQt@UGHtL89=^u@QqLVb1c~1SHxI>eEJ#?mr94nkL&P;>&^xIN)ek8bK?egSUpTy -z0f0#6GsSaY!~uz6jVc9d+Wjx@1yir~&|<*w5~j;vDqr4T{btJ}Rxg2hTqKFkvNu|P -zTDBZl28Ri~cUkdm+XRN#2QU2$s|D;>Iv``eAznHE0}70X<)QzH0q+;m)eF*2fKNbH48~Qs+?b7UK($ -zNPvCW=hctSJd7g7Wbi?g+nU~X1pAMVqIV5;$R+@Ve5~=u*N)xC+NMi^>iL-k1dsL< -zkB90f6Zxy>RjFUFfLBLV1_1SpWQZ^GJM^YI@u$Uv?@H!DXP9zpHQSA{jkJWNix}U; -z=3Z2gDC7RvL5o7}m5fha0t0j=^oU%)(3@RVk)?Z29+!bqKe1%Y@9={uLp_g@dgCZH -zdX!>2>06Xz@A#@_+h;psfCTld>3sF$YQzQ)3%)4{+1~4#`;0_fraR=DK?R`2^j0B@ -z7<6w_8yIpoHnj2Hyg-K?mn*YsR~I18Pr0TqAyl2ums;Z9{qTK$^uU+^o&qiLG*YLx -z4l2V}MxR{JVR+!+T}VJGVXP`Je@B_J)^3mwIC8Q370EG;7KCT#;ED>23eg~xadv?X -zJ~mHJt8_xEC2xW4gZx%)Qe9a*-!cex*d!=xz#d$Yzu51&7gOUH68pf=QqG#SJ2~ut -zg^IBFG4tX}D&ieq!1L*_Zx7!Z?H7A$2~JzPc-3Sx13XudQfl-wAkyAl_nweR8_JFU -z=FwwoLb2`e-sN-QGk&S$9_ce`<=Tzm-1vy1m02ivZqAwqfypUQVX++BZK={KU&viKY-SfDZLy<3}`k*YiQ%BuF+vj`h+W~ -zOrWw%rUBKnN)}T#!AT?5WGa^L!FcWQw4{&{R#_ts9a41ENSJRvf@fgLyX;r~<%1^^ -zX5*Ey4t64}tQj4m>W^c9AsUTnc+IPrCv)50pPG`}JMuVF1BByzss~8*N~y=!}D0PGYH#2sFcxAhLdaUAV0qT|YTnx}Z+4Z#Kb -zpBJbenU)`sKILv_Y)7&TY$4yeZNf1V=9Q>pA -zIp4(Dt!#PRZx4lgK1s_?7d+->-2NUIB_${`s`Ml4qqnE#xq6`*3%sZb`UpuL73Y+= -zi+ZWK58Bhd=hw`5{3g`Vp;tdi@`^sc7g#!2(4D^CX1_P}*wO_F*0{{ty3|GROily?XDxxrWOrqJl7I_Mmn3`$h6)r09r#&Pca;Xv%T%aU6MN#!HQX_YWA% -z`%Z7%@M=_k?35X*Vd87~Nik8f%~%G-#TX@5@33~gbSS;vF+bogZrk|H>Gc&thCsqS -zZL1=r%Zk`^#0kRlHMB~sGDeUB!|^AigFd351>!R(!#G;{%7VC8D!EGjhhAehe~LrN -zXmC%yX2Fc>fpNm8!emP}6OE|lKq|7*dyt{C7T|U}H$u(AnkC*A`{V$y1u{qC~1lp(B -zk7_q;)1_B}xJTAEUJv_F_8;Dkx}@}G756$T*N{OyW};Qz*lqmsH9xa#Mb|3m!%=1U -zs5w84T9?XZUP)T5GLoaa=a=?{v{LxzN2wh3y4qgN?M;mQ3v}mj@9&C~WXo|%bG}1$ -zYCm!Nps9@xO(s6Q8Opt)x(iSA0G31%PkG6U7B!{i4{*rt1du}gnwZby%BzK%>9@qs -z`NeG?PS1$Mbc{2ZIS}W|+JDdsBom`#f~j^Vla;)?JS^uNv}NcythICOy)x7FF;5L| -zkAL?oXoFNhFW0n`17>?>vj6FQj{TdFvyQf6Ke=j)YZ6JLazXJ+uYNZ%&o+{hP(yXc -zMn1CI_nrLdWu_UHMe9cjE}I^D3h{gA@pqf7b*}V%9vtrgEg)eIu(@z@>ood&9H%uz -zP5de#ic*0@c-8#vGOr7cClyNM*==!(c1oc%VHoYK!7qzQn20NV6_6|GJEVe$oid{2 -zf;*@*5geigI_pTVRA$tTNbV33?o2@$dKG!b*EdQUdltFwzO0JIBQ(9*OKt{6ueUh) -zAw;yrTz=89$S8%hdv?#Vc{B^v@|pTJ+{%fYKpgM92l{fwg1gk_b_Gc6KF)SOxiMEi -z{?UHE2Zu0~6BRKV5D-M`rkcu(;G}sJDY;?8raq;AvEce-frXvJAIdhDA*NZ6xE0?( -z87jFsN9^FCXas#Vd0X=-l#s>^sWJQmePrl#j@isHFTLrtZHkAGQ>*@9W(qQaMyCKb -zu@}Xjav?Bom4Hn~#)^Fg; -znWqWJG$qQtiXxx8GIg3yJKy$V#S=rRKoIu1))po~+Aw0S3BH@8e%1ppS#u$UXTV7m -z9(9>bsw^zAu)U=nbwWC^$+48jZ2lUeVtvJuC_ZvhF?!H9$Ga3bcGOldv51%E4>}g{ -z4Ud6?g*=sXXTIM;yv^HFY|Pcn!}O!E-2pju!ZvUm?GyoSTE1gf=(YFCju7)5(2NkN -zberHvkmHaa<{lM%&U|}LJhyx&e=DVdBQ;uD{v?PZW-pdT(J{|uQ3Dw8M>W4zWVBf* -zhue|*kcMhJKkqDYhzyG)<89!Ud=Vuz2j(Gw+w_Sa>Qy%D&P|%Nu!cEvV9;3IekT~b -zH$P_Wf)`2{R@njhX-m{+KHZnn6e5DKb>yJB_ulJvK{(UjgjOrMvw2!*M3B);5^}}P -zE;itp+pm{0Sv;p${kZo5RlESHMRO)T#6?6*P5(aoq_zocWo+BzQs%H83^v~i{k3pd -zOXAh}5ggBQvd3!%astAKa(0q-ps`yl>0_>CnBa -zU0(56*1YXd7vHN(N`m -z;E%?IUFGn;M@Ucc%D`avs=mFtyhGmOd66YctmT!(b)cw$8&L?FaudKEp_Y2DT`X-^ -zo4aF_t+tFrpIdh6mx+vz0pHhKQv>9Mxw_wZ0pGr4G(|2WWk%oy3+l;c=S9sk(kvTl -z$7iBF*eXBx&^@!#d&WxOxq@3*o!#2SE^cv>F2GWh^zX1SnqMk2ls?xym-?8jAH?Qp -zwW-APgemNlk~{xeFN$>7DJeGh>0NzMzU1TVGunKwPjk)&6o{}24qMYRfqe>y5UG_+ -z!Qt8zC{^?Fl|7*g##+>FlJuRC&yl1xbk(VrwrRFf&c;#{E>ZAu -z?lGdHJdoEu3+Z|GMU*u8ks59V_bOY?;{A@FrnmDnvSxJg;bUDEKXD1$hU7Op)=iGT -z0n#~X&ZDW4C_Jp8P-`Vz*3rWWicgcc-6if}K0}NKx6T)DSdp@SYW}dHJ8CO35~AUC -z2C=2hIYGr;;v!`u9X5(sTGYJG#JxFvuB$Xpwpj)>-()=3j<3573kyDmu#s4a?Z2Q5A&67_JUpr(p|H=gl@t-evb9lk>B8}gI -zrD;b3gSJ#27}t?>rYxf#iYnS3MPUV{-VrvF^?^s&yL@& -zZw#-5UvYLunnqR$u$JsSVs@{WYB$k4y*k@PbSK6>CjSfeS;1LO^RaEK?YWUrQexWU -z9%`nCmWjX2M2v8T6YrOrdk-PRfT~D;F=ub#r_v!Oi5@HEj+G)H3k&9lS+>`f94CmFg@fj4P%5!XTC%rp$7~`Scut%~oWrnHL_MF$bb@saj^9p3mq8;( -z-WJdSbgmMeJ@ho}XbT*jcd_Z{Zm2IiS!&tu+qDv3oD1mjAh!s$Is7su_d|k7Q -zM(?!DRxWw2pXMFen5k-v9olE#BHYbMt4Mb-!(KMh8cGR;tuW^{exBRN)ex-G?Dg4d -z#t8K10M1r*+@t&4D%`R9h4+2&U5NJm@%=3eSAUCCvjwE>{gZe{97}#$Q++&2K+LZX -z?{!=wOl@_uJE9H->%O>I-BdrE>^!0O!=>{m3SSssP)&6Vv0?8#cC77ZC25otI{f5B -zfoa!DHAf_`A^EBWc`s^Ie3K{CQ`VB;2uD)wtIk=|+rr=THQm!-?m*!U4Aac4aO)|5 -z_i!XsGFxlbUjrWpqS>WD?!l3dH@a6Lg(!&^c=t87w`4~AQl_wFwHn1kTNdHjFy2-Z -z7d2qQUHd^Q&s+}#Vz>57uwP`TO$%A_4NC};YUYljMFqb$EYY{v8UuqnR!Iwsl2^u; -zT=Xf}mgkKOHOsN-r|?o~&t29Eo+Sw7Pq}$2cdMrF1QVy$g4^ya3w4Z}>W&UGSYoCk -zxrw!5n0H1nR=ebuhwM0VJz+mCK4p5ss=8ZCc-*@+``$hu*D<>q;HW^4vc9BQcg93n -zPHC>IkC;K3;3QsRhvQODH2s-_T9jFPpKLrvz?yCc$)vXEbPHC6oC>bLa?b2A=yN7^ -z;cZ*sX;dVSH*pwGmsUQSIuuDF{-=vZA -z-}v15^mUgJ#f0c#lbt4TLSnj+gKwQ?;?_Fuh|vc2J5uhi9KbmbhQ<)^0QWkx9qwU= -z45amn`aadt;ljm3f%>oVZ(( -z?B2tu1Suv%l(e;dhS`t#QmidO2kczFP)`>n!`fR@LX`KB)z*srwqc5Z> -z3H2>?9d!@KT~Y+Lird3zaO6c4ZjNYlhib3AjCyEA(dA`=cZW=?|HTk)=7u1fe;`1z -zgL+-o08V|n20&<9YgX5Z#(aGT^=5>p!p{kje3=hp%&<_cJ@=)J>cmT(k3CM%vO06q -zSH5z<(dnl|5}f%&#-|3#x-d-BHSCh7XZ|01d>=b)#dXzAJ?uo&=aDo|qW!mN(mo^k -z8)XRV6HqZHRQa)qa^gwiZv_2iA_lSEE_}|QPvz&xu>Sl4a)Jxn2xV1oqi%a6qX&yG -z8_{ynKVsKjtUf5QC>7MWq@xr6C6Rimw?XUnT82s?ws)HS)J(%DV-Hw@7X|DGMo@1?Dn6?noU%pOS@jn{IMn}vtjl_@p# -zLJSa@MmJHj=;B*TkR(X3;J??Q-{bxS^oR$>jVP0g5j{xe;J!Aq*>V*IV|6=kZAKkO -zaRqV?V^_vBA~blK?ne0SH9Wa6JLq*5~U&b*=k3Y -z=ovpmZxF!_=+CN_RI}>!k?&e^W5Y1Ac{kucuvqS1tWBs6jQf{n)C`(9y%{|EEtXVF -zF48tnYZe|KaE5&b7rfQD-<$nCS6?j6kjoD~O!!`h>Mi#`y<>d;7yXmCdP2UhW@{dN -zpA}o=P1P0RefKo3v=d1)l*Y%0TPt_4C9=uu=06i?^d63xQTf3kO;7laIR({$AYQ&f -z0rdc7AvIJW@3>UC4;T}koMHR;<1;M!HBar1slInPbRN(1zwny1;DcVbjpgI%L9IV4oI>+^kfuy3?K6!5*(C&^I{ -zaiX~g9ASa*adEnox_VO|3s3L1%K8Yli#clmM_#H-OYytQXtl!eqq~hAT}AQX&S#20 -zr6k9d){-@f6USU1Pd6xIyJ&4$Z*`>kYiQdEU2?XI>DfP6PORqZTa0rTCJ}sc -zZ$QcRYEmk&3ET^>2cR-Ah+T^nF_*GVhwI{onSXzrevs%JVqr0&b1aP?kad|rEEziwJbx4zLi73 -zQe8LOXZC>O8dAOLrPSB~v?w~fetb>0we$kM;9T*?`TB&TVRipQ*n??3vq;!VX~GIm -zb*v;cy>2ynUfwnB$_<{AK5$|-?mi~+pjITVWOTEH|u#ys9~oc&TfucCXmVQ -zu@IeVp3AlRe8{$!hxM3%_Z8Q2(wyxzbWJ8F5`n|A9@a&_tP>!kDbUnFyw~4xpch%h -zmLF2u%koFt@&Hk)a!+2dvc+}a>1|tz329&KW#x~SlVcQT)J-Bsq9s4Zo@b;_7>v`7 -z+iH~qtp`rOrwqf7O+RdSmTGjHI!(;yK9)`PG09FUrha&j>cH~6>xwR8ygZ*2n1k{A -z<~I418y_)_yL6|oB-%UvoPN0b%|wBG`SHejUJE1TI*ZJKg}u%fv7SP$jMjFqF^{7= -zWVprK4Y;>I4#C9F`JQfSPUiEL`&`%oE(@b?*6_L=)Z5Wv`F9V(Ckb_D=O%l-Y3k+o -z`6;$!uc0fo-Z!GwjLp6ty&1l1q};56m%K{W?RCtW%u59DHzNb+mIIo17OTP(WO*5d -z;o>P;rMM_9;lD7G%wnus75^HO=?(|o&~W{!e{PQi9TTZn-Z1-Nu(4u)vdo)1eHhM1 -zT`rq$N!vxw7>%vltyN+3HD~@RIzs?tLcWb7EgKX)1jfRa_Fb+ -z_AEf-U(K_7XElDgrd_V411xSZx5iV=w=f|)7OeC+fDrKnM*{#(Zl)lG`ap;9X#|l^ -zdS@Cv-Vz&aJkYXG#_4)oOt#gpnL=4CU}+xC9NH+z%|28Xw%5o(l(_nB`OmU%}s60&58#2)6#jgMEJMV$^#zR#!*FJa`K=tD( -zN#A3Yx#ZqmFlj-d_y{frmL{C4%}xevt*Tq~el7S)sXm*y_-SMA*9}Mrn~xxxLAvu# -z1~x?Y$EXAUT?g3iekqw67OjFmYb+O|=R?YPfftVSRK121(y8B3Gyszuz8Jf&v#MVD -z`mrDeyR+OgE%D7e?R6!q0pCMod%553-WA`N95&UJ&VLC#fyrv&bN(oZqgoJns6 -z2^BLpKSykHThMy;NKb@di*IJB!kXx#>h$kDxn<8bdO#kZxT4a%N;BGJox}=ms^y!uUkJQ=ZODQ`2)yXYqp>)i=ladh;1j#w6C`b+}L4v^T6W8AD+;i4GZ{Pdlwb$A^f2vhM)tqCF(MKOW -zeP0&~Bje+lkhP8wQ=k%imN-&TXizGzB%G^ue}hO~eA~yx`E337zf*noe^ehEb>|E^ -z*7$ugNqD|7!>Js*8spM=KW?JLpg2qwYc++pg4!6dQgK(NDtdPKifW6Ko3@{}zr@82 -z;uXr1j52{8i$|5TkY*SrLO(6StC3Yv9WYNivrcZX)pX7}DKNC(hGz6-{$XVC={b4} -zl(qgGF+VMM*#s*V!CVF&CdEa0ofbEblgiiIvnFV^qvFX$WbZTuR}nj~%6w-U`iHJF -z45E%PlaJpw8w_^6X}d~f%@J3=ovqapQ-xz&v=W;2Ze%rRWc9?{Y{>&w{pu310^WUU -zt*7s&bZt<=%3~&(7(1Lgpx=%!ZF<-0abBhBEO{p=x1!5`DL2ed@x-M1`^nb~c_p@j -zRB59KTq~6H=M}|9uDcl}w{0i6zo>lN&KW98Bro93S>)|7Hz_gNT)1SJh&a?d_y;xDUy -z_c=yp$nPFt=3JL;L=-Sd2XB5hlJpi*VW!DjQVrDIwEd5=*dcCmqsi*z&1x_Q6Wyw> -zTjtjAd{WXXp+p@UebMN^z#et`ZsRVJ-al@$A<-1t4; -z(Hbhv^AMdSJmIjnC&bHz{;Ka7{!_?Lkm1x$cHhEpR^PxAESX`L1aPU}5Pr2${*BRH -zQ>yM1pZ%`9+F{6TvydGV?xcFsJmszO$-)715fe_)KG7%$(otkl;N=u|8;G?A%l1HZ -z!nvmgzIr{BI5eyTTw4JOtS6Ehr`udw?|PxJdusC|>!%`4B$++FmNYvg@k_sop6;E} -zuiNLk@U@92lk*AQU>(J|&9jV0&n#Hzsw4_4lbLGgtgwvwh?bn8^!M}dZ)iy=#gb1> -z4KGtWIry`A$T~Byv0V~5xi(85WjEc2SfMn0|%bDU0zBpMr$Nym2E49h`)B$wLN#Gzxe%{1k@ -zsL03$o_TP>3l9rX9}jfQhx3i+ocV~)$N84kBs)}U?A5wUMpWN13w!$I!>OM!o#zD{ -z=4-c8{upWV-vFpSG2x#SmhaK##{xUS=wZyN0+f;pCk{F(VSd(?;srJiL`4wxG9Hz( -z^Pg2Qm(Acqr{jhL?7iGDOP -zAJpy$i~@U~ZEh7r0GwBtD2~D*#f3QUEj@=iOZ0#x1%%4xGOx8<6X7>+36sx|Hzkx< -zE&$YM_?@H~6?S(=N6mre@hNf3ap_nOA?7XUAgc#4J?lz;*Nc -zrNm2aWbY_Zm2bbECCg3FG1_PfR8SSSyshqzA|T}w66<6XIXfZVO+oqHfL1d(NpgPN -z$(Km;<^<=`gHF}Y^36UPY2;CU^s@Sbh=VS -z!Sih1?Z`<0F=uM~*8jNl<;^wHtfL6a&`AGHl@)p%Mi#>k{@A4$GIZmO+Z7n$A9;y1 -zcpL#1H*5yh;wf&E=U!alrPf`Fz$IT|rzEs^`E%=Zy@VB)dtUO4&^<~eX4S{hM4Tb) -zblC{0LSv#02^V=>`RjWMoy{LEl?CwMA))*(LTO6vPc{|P2{b}IqPZ2Dcvrw-ZDqRI -ziHdssEc#g(Hm*!y(H>$iUBnf1yWchXzVgBoZLvK-%)gXhF6sFM;Iv9T@j4x5z5G2p -z&AQIx>`F$wSS**`Y$&_I=y9d+t0ejix-toEn!qgUYn38&)J53#ywwbKjZih9bSX-q -z?zulI6mN{pMs!qkJ>3{mE1yE&mwnbaXdIKVq@N{Ae0I7$JSvVrM5c^L^)6Xbr_#Nz -zOpOa^>Znc?PesyzCw@oY3OHHRgsv^O6_(FL$aFP~J#JI-69)_Naje_zin!>nHZ5x+ -zbNxNNY~W47^3 -z7Lls5F1ApAM`;qOE}0IC -z^Ue)xzq!`WqwlY9U!299UDxQs{5{U|8>ek>nE5sh#ID9sb3D&nKl^Ery#prRXSMm3 -zh=qbzC`G&k&f{namzv(WLW?rdF2zN&uHJFSxA@%Kt^7A^*t?{!liX=46CInQ1BYLa4-veJMiu-JjyC8zP_Y -zgmMiqRQqc)sLuDU(^IyCHVD!=%kf=77v(Ss#gZ}4&B^qAR|rse{vUzT4}>W(lK=H2n_vn%9%nm;~#w?DP-9+Mo=IL>^TqWeaI -zpIx|%u(jTVxGR6C%4`*smfujhI6j@-kORzX=qwz9x$Um*vKRL?OkTaxqr=P=Zsq8y -zVkXyXPVV>0uf47QyQ{CX6x)HoVI=ZI?#u -zH!te;>`8@@4058rjB6a&iIohi+)%}FV@Y?kO0p6ny*}07w`(`Rp+L2k`Q|K|(~mZB -z#_dPIOe`4--6pV@jHR_jmWwE}$Y)w$Cx%nMr^Sdw-SZW7mrrGqjlTA?N0}glfmDk5 -zNS!W3N9|Y}`x89Fo3p`uZ&J@7pvBC7pWC%bI)}FK(LC%pVX{cW+BF+ia^>Oc3W-QA -z4w2WVfad9gDZxy{&sQ3dM?njaXi_9ik)i=#$=CC<c+>d^gl8dRlBz(Jfu`V`-U}m%pUOBJkRT -z8pX?%$_Jc2Rmey!hD++SrTb|rySMy{+W13^>7ZrASQ3&`*slq>YeU&ZN%_X7yi7dX -zn2uT}j_=ZHJwtc4nyPL!*|$vFF_E@2{fBX+8%{Em&I_IhL(eL6w0#@#r*?Bu<|Og6 -zAu4KQH$0slQzQnb@okkkegyB=iNrhmF!F2=kCyRMxii!A#eht@x=`gRNBhARmw&OYxU -zRtfcIwKyAix_RW08>#8(IS-VAqpp}AwUBnMLua>L{h;L -zqTuh7U7WPZKKm=3tn6{#QpNA0`_dmSjA6}bZ!*rkuJ?5tY%7=`aG7hcQJe8P71lCU -z^I)o{tV~0Js&j0JeGiz}M$XLI=x>Sg%o3v?&G}?n(JQ}M{6IhvcxhpIlkv&VJLJ}c -zBN}L>fRD3B8+aF)KNNOf%jwq&QNU!ov+W6I&}<9G?N6s=9hHAl&-;96a?kqFs1KA{ -zo0-6gyFtL36GDl8bbUi+#b-0=BbzW27hqt4GPqrpW_vgDev$(#fQ@Q#sWlKa;$cEqH -z>0D7DV?4R29a`nGbA$4SkDi -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 -z4aB?T~iaeFedjUTANF{ga -zuYue@@zHl(p<7zbw4(84PTJ%f8_=z)1|Ej3Q^*MqeQudQGUK~{%X!K8cNVLQ8!-1x -z#UcTk7j+oWyzg__C5?GrtMOh^%^1A08ILq2mIzp$WG{L-USUxuXjO@S2y_GE(1>?! -zM8(OeisUN4T#O4&1Y5428;zq*shWF`BBv@Yj^A5k_zD-OZunTouQ|s1SsD_>mzdmlcm&9~P`n-f$2%{p5kC -zzwSA$!oDi#?fDhya=ad#&^13qkLr-Z<2KA~o+oCI)*4;p6UvM}OJg%`79l;=!9X0x -z$C6%fpC<0Tfg@8u3vca!5;m#-{k>o7c8IO_$sqc}%~&U&&=%Yfy3>zxqVLkwWF!gIcO6CK%{Aldzn|_b}aM$Uyc@z4@=pU&xa;(ENtH9ITQ&tHZDbA6W03DPA{eOCOwDhS36pCw>TaeH;rrw@ubd7 -zBc#oBw}UhlwXboBdn;o5xV^zYs4ad9XS4N6n(xVT$#dyqpP$T-LDR<%D`3tsQL -zacuD~UhU&&-wX92Ma0o^>U9~(i-mQdM64>hQPnBwv@}~B-@kZj;-R6oLXN7Xs4=fM -zt~{ozz}B&)4IH9HO^-hOrnW4MpnjUYmFN*Tj1D>Sd~y%Ls;iM%Mn$C|4%lPfa6WOW -zq-lGwKPJF~x(5zWYy))BTnnb5#WSUm>dzCD{GupEJhafs>i>8)e;IwIc7f|6KHigC -zPZv))7X|y!3GCJz7GC?3s!g``I@AR$uG2>qu)6ZJOWoCX7VHO-ot309Y -zt|a|d)Ab7Twc7XxA8W_I1h78<=4)?TaP8Jw!wJ})G$)T_=oy~3m}v`haOlg{V7|z^ -zw+tjQ1!Rh#@)94^cHlYO%@uq~quNiz2CaaCNT#QtVPciCQ&EGP16x<#ME~C4eH3vU -zpE^~{3P;dL{D_}|9Fy-?z;!8@8mI#+z;WtmoAlGohh_#% -zcT1LU_!L~DA?$aOVfWZcU%YW@>(XewwHxtdu5pn{GF672&QLgxI5AV+PGLi>CTe%3 -zqPOsJ2;KH#PO8T;O(ZOJO3tqSm>O?aZp2b_%jE>S)I+J=+2|e}Y$|Cz?B~FN`=)4c -zT7cO<+{((zh(WMoVLx}CFN^vG9CK$xS(&i^ -z@K$aDL@Ud-jZqkf@GeQIL`Q-g7B=oz;pRHYLkA`D0~qMe&C9xaqeTmMNs{;E<>9~O -zc;?*+--;g&xH(~~ -zid;_3;@zS%#55FilUobDYTBIv#jvtJL^NwU;_IfhbWP|IkugqVf -z;XIPVBU$jXGrsq!%&eW54bK=9A};1M3~ziRvAUlUg`Nx*y@d-?iR%r!<@`5hKB6!E -z=e^;E$9Mf#FQ;H{=_(i{C?l5>h-5_(wK3wV;ghr3KT!^XDuk76wUTW77(Ww!sV7@Ko@(RP -zfHW?JYGO0Eo%e<7zx#}qCSI^MKiJ!<0IuN9r&~+kwipEvYl<*N<@OsNJTdDLA!ad~ -zYc6c>^$M<^7L$511YygUgE%*B9*WY>Lv$X -zUa{>Rmm?SF^cORgR^ -zL946B0xJ$04qnVAqq!TQpCBCCMp`!%vAjE}Z#$I#oC{@xukPVKG0d^+iIy_;L7h!C -zMqW5-1LV~39^s*VusR|+6$|ZW+Lk*gQ0PU4J7dU`f&i4Ia!CftWx#DvPoFZf;G}~U -zOq3jhPbrtyf;*N}SW1*x2h*&E{b@KaX}k4g0tdP)=Q+Ia2Ci{N>Uwu^ppCIHUI83v -zHG1N=V5N=01%eJ}`sHz{PQy6+2R}?x5E_FOw~%!nCWDrzdG4;i2E)h8Sl;@?--|WB -zRq{UW!$q6QR3R_&j^y4k#C)pf%-Tqu{WXfIRm-n~l5k8Hu>m8b0pxy6i;`7|qaRKk -zD(ME&liqzWbU#f10dr{I!LiZ2I_B4J41)I(jwgLhS5pFwNYtF(6?mam`bD9d*F06@ -zPy~zkxweIeoj@KFbB5*_D5Wx(U8;Tl3|~Uj4H#=i&Wksk6t!@>J~Xnf0_t$`5hse& -zhG603(M%gBciz`pNh89vx>+GFJ_~F(ginYlP(MsFs6d9aAH&SvJRqv~MHAM3z^v{5riK^Xe%LtQsld#7r*sOD`xSWB -zPR*VZlyV%3a6EqidyE(aTQ`u-3H}rW56xqLm5~~U5TyZfHei1K;|$o;8@s%J$r;Bq -z7|E?6L2AfO9f#?PNlqf*@{)UoB~SJI1P&n$mt15P#pxyZ6BU@)>R$`b@xTlwzzk+} -zTh_LNG|p{k{WXJf%~!lQg6OGZkv&=tYt4U2f*f`TDLk^oce-jvCKs_a(RqnuWTlwm;W0@_mBTt!K0Ug+xw5t|NXlp -z7#SU+V*Vd=-?3)&g5#}4;orP6#Y5!r2>-n+W8nbV4!Vn8A(}^{Fgv^L>8~}`jTA(; -z%5gNwrOLP$=I3{|MX-~@`or>u;__%FS(>$lmfidC{9Uf0BOWL_uD%Kjt>uZTf-u%g -zVy6|&lL$Hbu)OaNW{-PepRn0l8YZ*!k3df8Siv+;XT$;Tju*{&L?GWw35)DU0=skv -zH^=4)vO&N6wM(&a>p|x~IjaLgOu<(N>r@`SA%{!xo&sj#5&TR6{!No{8QF^&Sc%c- -zB!f0TSVn5tJe7w2qikyY$eVBovVY^Y#8W>Phcyy<4+=VkczUsk8 -z$u;Abo*?bY-qD(p9hx$`v*%=@RPMp%x}yPW`hO9RIH&saMbqzhPd69tWGaXKTsb}b -zPhpqE_BVJTZ1Mm)hzj4Kk}by!ajEK3U1hqAg{=j9I7M3@tO{OGTK*sq_vvBRtqOYm -zfb-=PxZK5l*E1p-L44g_a^=$paCFoH?TPGCqJK*X#r4X{W8$@F0O*PH -zaHMo`KvWcL=k9Ch+IbOLhis|$n%+KCkA3PG9k;3a@He;nfWs`V8 -zr7z*-7ZenMHdy<7i62oB?l@PT3zdN{GZ9G1y;RV-&JEWIqUmd@J8fJ$gipp4^Zv(K -zK4d&Zh@KS*3FSog^o{p|AQbQZVcou@dQXe$uON($QA>IZYT{pL;P0Rqzs|>W-oQoc -zO01HC93azEPB=?~S@`0I?mUMl1=jdKh4X)Z=beNs0fP&ox5fYZPMSUrM5Q*>TTm}C -zIiOtr)t#5cwAn0|DZVf)H>MUngdHcV$l`LFY2 -z@|dedH^%$Y6_M2F={mcYG7mdr_#Z=u#7XFtaJ`Qr_`pb>9R2X#h}9yGGqkdBw%!9b -zsjfTm@&{x@Cd%)9D7^CG%u9EQB4p(xaOut%S!yJTT{x&X -z1KOS_?2%2}ePFke8uRy=hDhNIm2+bIH&GsphSKVrO8vD&fM`{*^_*gzZ -zR?zU-&mGefobNn}<1_=L%@3y8FIdG$gSWbk#20VB5c3TnWi%zmi^+^JEjr08)|IW9 -zYz5i1>|Ydz2kV}bkaoEzYTi`}d|w}~^9BBZTDd^mr*RCqeA$D`-YCF}J_D2obvNIBCc@E+2f@W3))R14*jLioV*LRaOSJO+Ski -zQ#m@gBcx0bWcTXE#ZOPnCZTKQ$C5DSi#?kaocCWQSqrW>E6o`V4Q~U>v~`5cUhd+& -zi8hUt=jI82qu$XA2TpSN> -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 -z`nB!>R8dANST_l`L2X -z2y60=rqT~Er5WYTiU=a3PEQjD&vXuh7T55ZjqVCpsHAQ9%=u!)+cQn045u*$PND0Z -z3tGQru(AUxuho!^={?v`ttN*U;;C`A&VRMabrAT6A?fDlSw9<+t~REC`K#`tskt(b -zw4;>+<9!uOiq$@wW=T=6TMG2vp{E5)#s^IIDT8Q9+h9=Xz?_prL{%$ZJP4fF8vAto -zrKy+Bkm(I^XMrcbwQeA8FSK&oIsAND1u;u9#Yfg-R3DVo>BVwIN8Hr)$ogo*BchG- -zif^QUvm9gfBx}=4wv*9#>*oq)8EDoApfEBpJ#dLg**W=f>G^A9IrQ*7?4!aCj4J&n -zB}=#UGEF7jhOD}wA1gns`iV@0uXF&fvlluy_@vtEQ_1ovr@y>4dVgJAo#gJabD_fl -zePjXuyvQ$`=LcZvaSM`V{?se9UhcLgxs(LUmT-yEx0TTZK~`yfzd)Rjg>%45iR=vT -zNJs(#uvHMs+sW1S?|%F$75uAcNmk7-G1&Y6YgcC0>>5#&Z1lb|7GDp8UH}edMXpuj{+W%gu -z_OnQ2CFZE<7G3fs%0#j0_q1(*%5XK7?^Ryh7#n`=Yo#?0`anU*CD|>{ufj>b1}jy- -zcJou2MMT@_^ -z5Xt%^FhkYV7;yRU1|sO1_^+z#|38w*|L{bcfNI!aVtgze8k1H1LX#S&0oE;-HayP- -zA<a$*yyB#qF -z1&xNe`}X3H>P@7a$$Dm`!Am8bx^#I~?Nq$wbcJjbR4w_5zFfynZ5|0LP4*KHA;O*F -zqX<2qMv27glLrvCO5BAc_STf3>?F;l|HZG^0_sW-!pQsC1gOWKpCM#chQc^Rn3+Y^Lz}>$djad_uV^_NL -z;%Db1g<(!reW-YndD?u2MKW*hOgGBguQ_pKwupCpakAtPgpK{0jWY_*LW5Kwrk^nY -z&RHKSd+s5Xil-1nSd%Kn-sy6m6>N&S -z-~61>Pbu=g8;dIj5uWK7IN26|)BP9F9o~A8%-`dz9WPtI$8E4G)7Q>&p`&_u)^!jDIOFl1wb;|a_4-a_Mr$&`K;DXP&^o(DRmu!PZ -z)ZdM9af%VC{(>IwpI3j0e$P+j(}=#@*=@j=#}m+gBTD&pLpYY*?X9sutAz+hayJo? -z7nCC}57zrRcGldFQYEz*{kJEIqC7`CB|SzRn^1kydrn8=8bnu*(ARQ}OU~)Li$v+nq}{nUr@r7eoIoaWPb_kKc14uZ&J8C=`x -z+l%tUk8tO>8+Dcam@r+m(f%9v4Qz^>wEaEDVB(T&`;mx%SXbVx@;wZVFUP!f+_LWZ -zovKT?Jo;d_v)1=4q=u~zs7 -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 -zf5}r)EU-fPj@IyE;MsynK1(6Qx){w`67Q% -z75tz-eS$FB$7ZM=B=b#$%&H>^3lLF>4Vuq6Iw?SV_autnRd$layvsHp=b7HC_htd|tP%^4pGm?s#`GsXEy$33ntbt&pJGJJ -z#0kO#gXCQAlF=zw-u(|)bIv+^1g6Ws`{9#7XvonS;zo+t& -zZEsUv9t?4m>KHB&Y+OSB6^?J&4^HzK0TSeCo;yivGaVCn|5M@U>ouxsc!1tX#n7px -z!v)}3$~KY8t(pj4HOt>uD=&Wj=KiVl!=L*0?lJ2@Am8cJ{`g{(KbJ@vY%_$mn{|+< -zf0IbA;7ej+j%mO$D15jVc#6Kd`sv}mmcpLOw{X7jme;5A7#+Iy-Cjjsy=E3>gSR{r -z9fyEM_NrYE^2L}*y{cf|&!N>pF8m$1E(Vq@5t0>*Gv=E?z?@P1;^}S-Y|QJQgb3U( -z=C7FCl+GzaK0wpoE3pKNXU?53vKIh+a1JBbJ;A-b*|463FdE{*-oyufk|`xOlEvw>FT&E<1b`82gL!2@<#!+G -zu#BRv7jP~AIR2%-i7;okN9!_s(3&U@KzWQVXEqOqk1y*yLx95Rpwd=#&S}ApvX0e# -zs9HJq(1)Gfu!D*$q`f5jOfd8E9YxLC)AY?3LJ9R;+-g1vkzh_H|xkd1F48-9+;_pZ?q)TZW}mEq{T8&$c4`O1$eMYpK_jUR+ZG^kQA -zwC_O$a$ag?QKNHm+Gh=CB?5a-$5b -zWcQ7#DHo)m=&M7*pOlcAdJJ!yw}jbWgLVVP>%9QEQmhV26Q$4ZZ^K(Y5y`=o+OYVS -zzxf@uA)=8Lh;j@zvA@qHB^OI6eRP(;7eIQ(muf$k!1^Zaq5GdEZOsE=+rF?49?H93jy!SM -zSDUQWClOL1*;Gz^9-83N+q`D2Fa*``tv*30t&cFBZ$L%EqD^ZGh)Cnt&-?x-d1>q= -zCGIbV7rQx~aW(N`7kj%Yh7_iItAAtJyQSC0noTNR1aEt12!^e(xiFJQGVSZ^cYeFM -z-rs0khHAYKC2)c3KlM*yeow|T;Peo__P~AuAizQGUL7f)CyO~3#+UVsH28e9X^4bL -z+8WxfC*}UcRlP5o0~k@mb+`9d4>6(Q-RSX!6*l!68{CvuVmHnB**D}(qtYlDyK}nV -znc=5$M#Q!a{(VB&>n;s$%=8BfuN`UhT(<64`?ZU`VH=pRw3v+KBlIBUk@Wof`D(;v -z2*9ui8JUPe@j<|XUF37+MclL5>7g37Y$^IYhW%WU71==2e#+u4#Tao#sIC}B8% -z+JZ{Ry+7ATAK|VSZ6mHFU2-cUrNHYuVvhD;3FeIqzH1^T2VkfTmR8j~WMm?Ca3*IY -za2EU~V4$p^W8v?ztOiQ>bhX6;?Eg5Ad^c0-^rsQ)2XFZeBxyQm+Of@^B;W@{nv%ek -zo(`Xme1{n`;2hh<%Y;g1gmP!^6jKII-HAIR!D^zbd{uF_w}5}|L7&;SZ3GnyEx7a+ -zr~a#OhkTB{6Fqlhl@~hc+E(rQ*z;U8asWdF^IcRBzZ*|z!|U{Us3Rx}=!H%7RnoDd -zPdEZT85PH2z=4pRgI*kEnB1~Pa0n?}l0>xaWjwZXwT0Ep+RM(1CbSvBiBVMxLSP)3 -zjyWO9{L7L?pyH3cv?G8kWc0AbsY2l1C~@&L#V;&kHTS* -zNdI8K;BM0cC%Rj_^Z%iB-k)2a!H5qKb=XlE^!~H$X}H$Sph}GSq{P;hKrJhmtusb0T@5(H0J@n@jIFA6gsMb=s_Dl_Ay1sIXwj`rez=*;&! -z&o8(#5keo{pV{9s{-6y$auXw!06us#eS|d0G;D%%J->_gWTKx;yqrBYew78LV*(}G -z_qro0(UII;zWO+EM=G!XJ=6SMBo{*|y=s1MU$ngiUw901kNY|MTL -z;C>j#1vw+zd0Ip_UyhDR_XzMu)zGD*9U`EN^n~NwKp7o*cMT`g6l{%}QhR5Rh@|eF -zYP&H$fe%Mlxv3es<_oZ3g~+PZzL*P{EIX-=BJNi>5jg|7fOff*o47tz3jB>RuV$te -z+`YW%#H<`fnZqBmv6VUGsK@Cs9Nk3OfE4YuK@G4N-OE4-&PyE_f=g1(QbbXc!Kk5X -z_CG&*mQi3Fy0~ME|<>o-%8>Zd~GfZC3`Qz^L6I}9#&1Zsx33V6V79bFx<=Pq@ -z`b~aAQXm9$cpp+|-y9pi|I7DRQ38Ki$V@>c_Rbv~f85)Ct%m+xZW5$SVoPq_cB9uv -z|8QG?on9kE3cJ&FVzT^UzxH8)%evX&*ZzvNz?6TZSm0TR+xI>ZeIL2 -zhc>`b>89K!)N{yFmit_iIc!*;VM11)`5M}KJZ`g}t@4-`E!{-ml!2f0K0ZV^B~Q7G -zrMOFam1C+P9;wo`^ZhU>E3>Q!K_cmhZ=-6d_L%Gfn-B>l6wg#*A#cL`lF$vR(3zT>HnUSIzFw!9JK|z01EB8@->GjAP`56-I_< -z7Rh{-Q&a~Go&K}36en%qkSciuj)z^Bq*Yan<0dgTvQi(AmAXO7G2ILgpCRPKZ{ylu -zVuF+~EaPCQ%m8B0^}7SbpZ!%(A5;I1h5KJ#@VCEEA=O?MPekG;GyOli;BWtp1*<|^ -z%)nJn?C{5ZHZO~AtpVG3bN1W4Wb&V+TUqouZq5GY2E)Al3wNeUOrCVAG=1PCUyo7S -zV^ZL_*Gk8I5x$C`zS--$zR$Av>e$q!Y`YWPj;fjpZvUeBQw%OTF+e3lZt+tLmTkK*85-nc -z_YvMqH>@9qmdZnE)}vjRDvZuY3u -z+5zo{-k073UN&-{@65JN?T13c8BOyVPzGzTgX61b73LX -z#`eC%^byAk-7TL+w*1NzC9flJ(f+ayF~CKdfQwiJWbSMt_SL07?5qFH==4xZiAt3e -zw~?L)`-?{QkdmTw2xR)oF2e1)-dR?>$DY{sW%!jY&ubAo@xt(j7Qi`T=7QBF*EjL8 -z93XWu1ZJ>-+>W@%5izC>lqFAchfjssb~Khl6Z^&JB#`j6!zS(Nc`vTx#P$+>QOvO# -z>|3k1sfG!hAv)7Z=_LA5JhSO0{)ohk?UGxU(E7HZO>fp12=#)HJEyUMSXdj9Eo8?3 -zd<)49_7L0=WP$nVSYPB0EovTd+azAQ&|T!Br`?Sri|L9e?F}~RN1MchcLE`OXaGvM -zk66mtxO{kXUsOLhwYOZdXtYa&02$UYjt(ktbDUJg5-Rgm;PZG2ScT$H+>Ky1@_?Ywhr>Yprf>)>pZ6%Hc?kJA=BB+(5 -zjrCLFewqCEH?G`N)5m;pxiDunna6RCbn1ZgSN|~AmTL;Z!C=_OYC_YnkUfF;_5YiIcl8Jr08$pw2$Sdo -zfF<4b+Wom7C!QD1TbG(LIx(?Wo>S8&HlJrLpKgLYO-zyD`~%Gr)&TBhF|M?)n`4g0 -z*t4$mlk#hin!lWH`T@nbWWiO(k9$mAs>{AG__4P)OJJ<`*YW7#;qr-f<*(B`p2!}~ -z%=%;_w`hZ~{$Z0#(G?mE2kKMwGGEj?i&n>5VCF7^j6H+^XS}vV#}yu>04JG6sgoL{ -z^1x+;UH=Nd#^k#&H^`L=e2XR;XGsGGUZ?oNNFoh>+rcYSa~Zs!wyv&4-vY^fp*_tv -zo(_e$cV1g7aOqIoxr-Mc&r#-mz5UuwX&!`(6&oHSehN>O)ff;Xsaqck)SYi{F0TUV -z=67qG;0os8V|Sm#U?{U7V~$qnBqxOTI3f6L4RXW@VF8l+(vvGJ*N$*epBFf#8atFO -z9e@F;34k`OoO>$6yY@=#bAV8-6f|R^R{UlMXr0R7bRbah_+&*Om3xYikkIqWc=+R9 -zx2_W&9OUBS+M^eY#YC&zc%kA9D -zwg4VuYuGA3u|#iTh2q%S2`G{fmzw8P&E7Y9z>pM)N6}VsK%H>9xa_=$y0`o^Mq{R9 -zLSj}`l!FpaP!(P-A$j1%awx~=j;&uX=9vL+ppU&xNedP4$%75OfI`^> -zTeWEghg&LiT$xkFf18c|00p+w -z`B7*)t8u{#*yJAroM$>$(nT`^-ty~Byo$r)pO<kY4{8UnVM3E81@TPZ?hA|TZZE>%FX;;2_GD;Eigh-4WCAEymP#V*#A -z`_dzxde~}mZm7#o^yH%TO}C3>?XyZZa;NTSXBRbl+s?pTJlK7S8?6E0QV$(Rarq9M -zgFlziEXn5|z%Sk76Zm2AFCVkxwr5SD%Ym@fyHy!o4zi*+w@|(y-qz8h^kLIi)ZSc( -zi(7IfEv@`hqPQ2Cr~HTrdrBUtldl(pA(+;_=PEB$6{V}}a$7j4rQ3tP%#klw}`zDRf`(+t*iccVf7QV^=C0n2xQqi=ogQyc3Y`@41dLIrO*id>)F?IVM>Au$1k? -z0^%-y72P>(nU%6`LL&6$P#9D2I#bGJ$bGis_R~Tq$ABIcw^Ta^Ms%$phyM+;m`aRd -zE>mdgQ-hDOD+C{sTbYJaQ3Uw -zxPX*=q8iz{r{mAElo|%q-V(LJF6sS>6n45&g<$kCdxRrQJCOQwC9|k?^N4_L5RS@4o!L8DnKXGR}LJe!O7ocOx$D}WbG89Abkf)%cfVmP~ -zJB|@S;1-VunV5yX!_tI7_FL*$OqujmgIk4z7X93Fb=M4WIcZZNm96j -ze~%T}nqLT?y8xMyxg`wy-{YJ6RmM0H?bc@y5hVgD1pxH}aPt;qYL#rqb@!=N-USFG -zLUtfQGLQaeJJ9I}wv$Bt(Qx6CFSJp5bncODT9e@%zAn9%|kbC>fAItHC^fy)$fc#&+?%Quq5;w7A -zz0l#m=~Hk4Gi-UR)o%j)pWiq6YKo|B_iN?aUlrqX?!tvK9H)4rg`ScxqRon03r!?> -zl>7JZe-kULql%$}5Rup;MOiN)av2&{`#Bx;nbAXiEk>k4)y1V*C`Nv?(G0G$$Zn|T -z&!0CKx|@#Ammg>8h;D(azTocNyGII{_5h`1QZ&YZm$33?wUcG@ik`;1l8t=02nKZf -z76}3(&=H1aozye{yU^~jFG4q*_hhA=nrjQ9#vgMiVCOai#BD{=r>9n!yMLIZ5##^8 -z)_=$Ae>EqzJ2sj+)omL9hg*j7(x5CQsEt(7txWj9t>FtqSue$tbUEQ026XS?!1?t?lKLM5;X@(iEDn4*rE%gV_{kStnU7;d>l6-$&ibD0ABL)MZZ>~Vnj -z{J1zyW4-$nBQ|{B1qm(u5E%JQy>T(L -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 -z*LBLz2_StILuHPPoC@>-6bthi -zpoWL$oHPn5d3HSBXTEnQ%B!L&{nnpsN@nz8=o^fDYk~nT_RPSMPZoDwB5?Z9 -z4=uol1?T!mHWH1G{7g?QFPQi0Ey -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 -zgZc2T+>PC7kF4|qO2 -zIg~?I*S{BIem`cWH|N&Ho05_Q*UrR?MD)34)`c#s2%t%-=n7L|zY8zs2CBG;qp=?1h7}MPWVXYmRN|8;!29O4 -z*mJ6@2;a99PBW5s{1k%}r%|L$;q_OsL85#BMKlpp?h^q6DS|_dS&Yd~Q7x@gd|Wb@a9l;o><58g`ut<3CdLrKLYyGvK(o#H(vN@3Vk^Jk`10BuMeJ -zN_1f8*M&l-Haxo+@Yfh|!7tvmFueF}tCDPlEf^4H$KtK9kXX!D#Tix89f4?id=blmd!pWmEnZ@;+X0=#y#iIHe0Iz2M)r#f@d -zxTw`$)ru}(KBHOj_WOf9S(V{YD_OoZL+3 -zj^X;-<2hZqq|xTG>|mzguOIVnpGS#|8mvigW%gRc6&~z(vIM572SCyuHvlhMky1p6 -z9gbtwcK)Vej5KS*_&)kLxuK)yOhI~-dXRqKAut6CHQ6PRAHV*z73Ccc&=;X~NlWP* -zXn)*}ZV}>z_X(Vg(|@_f3TP}dt;In~yT0)gfHmRpT8Zvo&ZF88BE7Eu)8QaP^qv}{ -zFngvYTA`&`E3?jYgh9I&2y=-;4&cVZS|r&oE{csSTx}z`$x9u&0*hqNEss%fpgu*> -zMYMXCjiWN#Ej<^`>ed58)+oE|2ox$+V4CSgv<=%7f@3$GC?icexa(62!#2S77kURU -zE2|6LsTn8)QXtJbp^cWXc?2f@btEz3D7Ob3uq->ad+~-tS0NjJJj0TQTHUf!f^!h{ -zP>%?&*r#60zs=`ySy{QgB6qXF%|q8Bu}-(CuQ@l66+IR}@k@HSP=e5(lh1#E_~VY( -z2XXy*#szB*W@3XxbX3&~m50`a>na38aQ=v>2@W1peWOZGBldDfW`pLA@#_md>)ttw -z$Bn)m<(4Z5H_Yq#`Skz{i~bLLZyrx&+qMm7NMt55kIR%F83#@)f0yVh+yM9Pul&@a1J150><>H4v2U(#VkD1u=y -ze`;<&3Rk<>y%OZT`Q7K@jCK{ZA)nX^3yWOm|ezuuT@fudO$1g&>J&jHQg -zDvT2--iNjItmpuqjguN<^-mi6L{>50=0ChBhC62Ur -z8tPN&6u+|xE(vzv3&p>qw=O7%egao3O^kr=o>OOlnpDd&egD)Mp9!?R%wam@aTlaI -zK#_n9KR$ke^2Q&z{TJwbxIa43{-05E-E>sqBb^4y3djysIK6z%_H^-*^13PqsAPMF -zg~2ULs^^sBQVa7iv${GNAlO$y|B^Z^hQ=hkK*tosR+>W`+CfjCOF7ZPf_O%`3Za06 -zza~D8Mm+dJ0--sXJBINV?~#b)FxU@)6lJ-O-udYQZYBzfE!so11uzT7z80~oT~=j2s72%!)jCuy+&-`c=gG2^51a&jcE-qKJV7)l+M%>^sLkpBC;*Oq}QS*s}7t -z*$-t3*yLz4+<@^76H^@C828VQxRd^X);y39V6tg|GCL@{$5WQxX?4bL>>TVME;fP< -z**LGY!4DrkxbfHD-mYV0c2Cvak%LJMU-vGXqtmUMaAWn`e$FL)yez37)fM2SukfO5 -z|Lej@Wk=75?MaUGiY8#H0~Eez#PV?mKMR(BjpWczs+2a*-72gK-0&+O)8d&~0K!?i -zg{E{|Zw^JJ|jy>%92hI%%!ijW^ -z^<3psVkyg@jq^Rcc1yig(4c0iM!3{K(U|}Hwag))_}q-OQl(>`1}d10_M)SS<^|#& -zcCEXdN0BS}3JmVBpD5%`LsA{kG$PfHF-%OK^gYyf>!$A3Ia75xzfW)KRythA!Qoxn -zhJNF5x@s6nD~w(1xbJMD5yo$(K>36XW_a*Rc)`#bdJNf*M>m))f|Q*Eaq33sJ^>L3 -zWK-VdV(t6(Ll%}+wbOeqv=MJ3l8Xm|BrjEZA(KQq1ap%sp3~eBf`-W0>0@`1HtEzm -zCum%F)O~}2?y-5(?wDtR`YK`KENZ>HcG61?5h}I2p@C^7+*|{FhQol-j2)Yux_=cg -z8kxV}z4zcO9Lz}wx&bpEc3S(uIfDRm@he^^6> -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 -zFtlK5?#4i-fPSfoH -z?skKCZww1Q$a#4Z3>)vI&w=LCWe{d8AZT_Lepic0ZJZ7l^LPAHWGCEmJ?|m6u1ifvl -zRhF!4=0K}j{KFphlNAS8Ot3RpH0=ZnCJB0#8e8SS3qipg -zO6_dg`3g6V)xGnbWbc^|-+Y3*e!vBZgY0FQD9>Z!5*BRpPTjjV=-7Q4$OOj^tk!iw -zmRkNb>dTv)u5=Oyui5J9%Ortbd-pteeZ%I}aUyQi_?tQ>Z0U_29V>OTYb>Br)}-48@L1=K$}fFm(7(YWvc -zOf(iX5NsB~^LJsGklQE1<;I4iV&f$=2 -zLqMrBWhCzxpvMENC)p9Je&KaD^+vUDRHWQeC4L-sQItDC^VfGY3uFcG02N0jhn~`A -z>Z%wv*F`!bFNBOzVai>CMS=-zF3e$?%T4MV*ZX*Ay(__xx1*Dnr5iwDFa6c`uRX!? -zXhK;=Ft?e^$yzJEv!`1R>R3pFDssQw3oNp#d-rDQB3Sz71b_%KVRK{c`XFX-?R4Js -zOY5j&Kk5lBsU^~AfJ -z$)4^rJ3)&Vox112QO{5bGg`6e|oAI#F6m -z@FIa{^z83HqX0_0k14MUwMfFgmf-6r3110a_y#oMFK0h7qmZZzkKPKuGzRcF-QK%y -z!SZ{+kS^$CHaLF#$@@l<|A(sj8AVqZwkt$dLUKlt1TZjxpb^JPZwpyHjqqQcqe{(W -zW1wx;PMjiUamJg*XDa6sV(YBU<|mroAG&HlNW5n;&kQc*eDAQ$l3XTKRg>pv{I=-= -zEwCET8wU){q{1aN?{4t=4^Z&0zgh={puQi<#sgI^EWB31RdcS2%S=hKny-kfsBkNv -z!QNQRJKT;M0^7(oRsb;l8#Hy>oce#pjdRF>*{Kb_x0r3kOaN4V$G?gS3SwF1dQ^0v -z1zG@lnVkDcWdF!uutB)#W5O%{a>URM!rI;7Pr2Xa4T!an$ei-_RDr@ZY3IK7eO?~W -zcwRebb?_gZugnoM>4;1!SlIpJ2mSmjF@K5cd!+$rs2-m}4gK8NZyy|Q)ax{<{PpF3 -zZHD{{RDV&|8e&WtI2+vSCIcnY370hJTKxHO0YCMj;hS$Me>wr~_!B+<(+N=WH-ET2 -zRZ<_cM@(A4mJuKZKI~5>s+7P)BWC;2eFu-Tj-#}E -z05im$`1`H9MNzEn;{$PhrB`#=2}$SO@eXJsxmm%AUz4$o{02 -z7Gwa+6gn5+P6WdW#RUL14UjdO+^ThUC>-psb6GG8M-1cokQRwTq0&e48WBj+fw9q7uF+Ml=x#u7pSa?uz -zR4Ai18ov>+H9<#)UCV<|4m!Z1A-zmX+NfaoNM1vY6!^qMcx>z|bj@c%(1*$X?NkA2 -z4H*L80;{kUGJ=EyZh$#5qG~qH4H{+nfU|-8kVVG{+)kEb>_|w7Mc_dMKV?SNtt~1L -zJiyEELm>nY2i>nh^t^xfG=tS;S+RJ_RTZc=7|aG(V+Va@mtJukjH -z-uR^_e4=;hA!$BHRWQ`MAgcV6uG#}^QBY6)6KsCLvHfVw!pNn+(CYtcQ249w3EHfW -zN8^znFXq8H!i1pe$5;OH|O5Arq~ -z7?3qsVDc11|7?XeeDamro=F8?}V{<$-$MmOSkBVmM@@%Ndc -zQZEb3rq-yJ_OJ2t&q#{?^qmmI{;rz3f5P7*(C{L5=)nFZe%jkJv>gq+h`LjMg?Yc-;&|&3OG}QU1Svq;vDy6>$pe`u%N6)H -zU!K269u(6gUutlf5mqUg-9mv2l=_Z2D>W<|!dL;~u?;-t>cluhAkqK98FH;Hw#$ -z0cdAbJXv&?f)o#)FHCX-o`WSJ|41HeeNIWw9tKss`R!g1a1e_^8sq5x9Hk9N@Bc== -z2X2N3?=~@H>9Wc(c;*@p$_j1vN`(MNsgIauwf3+%IdpMkJ+(oH{i-x}5s2QFW)IJ#v -z>ok;sv~9E%UjB!h?|2rhzV&;|`?cVvG$@))0z3OE$QDYqZvG(+g*l)IkF6LTvr|$u -zYNv(af=DIv#XzN^4;}h`e;eX~8!B!FoTm_TQZRl|1-^-^@q0pPOo0NB;WlIbj$raAOw=74B)(Gr*sUkA?bjC2+#s_>~`PAd$3bUEsyGZXC}Q1VguijP*uJ -zAuxlaV9b5bmU)F^V)E6i1y3&h!AHW7{lDTPK{AC@{@`I7vXlRCh9D4G%j|oHaa2mn -z*1#`4Q8<^wjbffk30z<3H~Weq)N?9w?Rx^Q%;^bKP^}Wccc9u6B7|b544i?ZrT_Xl -z?g$|wM4z}0$^Q)Ff>2?-gWhdN%>Ju(~xD51xH(Tm`V~F?PUV7g(5UM*F?*q8uWlN -zzZE(S=#WAl2`@o&ZWP300?alIRmf2G+-+f@gD>dA_@4!H6vIPy>;$3;A1E};UPpn! -zUnp6;xzA_>Xgl(DI5ZgkK0fM^je*a;|K^>R-lU_93T&VCgU4a0B$=C6ittNaK(r~> -z@3ZaCQ|@2=$r|dmrcR*>Z;oUxRPdPxa&#z}}Gp74&0PM`$gVGMa$-B&e57|9|yJm*BJma?!?g -zawfV#x*@PWa8Q2%r$^|gL;44izZ_6<`%6HC6;=8trHAp<&JI|!|SXNcDZMc%(v;{t!{;gSd& -z0dXyOx?tjwX7-;__J*6ZcOgoM)%|XYTPqc8PJEOXN*Tcm0cr9pHu-7Mq4sXu-W<-XvshQ8`o!8 -zxw)1$eTc#F9}@R}^;Z)(!TzNqx`w(n^fk)hlyww|JpS`nX|n6VO^+Ac3dr3w{tVDG -zr0Q^TGJU>+TI>J#AvvUo>d_0HmhvBykE$_e!74;)b<2qmL?DO4XB@_0^XTF4YuX`a -z%FskIviTjx#Lz0}XokH&hhBP6U -zFYg8(D6*m0Q2zdoNbmAKmzuhX3?f@g4FXTCXKY4I;g_o?g2@;dEL1HZJ!(v+pJ4E0 -zG*aHfJ5jRlvTf&O`RTc!FI)Fk(%*ea`2cos)U -zm!P79rp4K&o=!1sXdoBQSd3dVPl>{SZS4u)gB5$puqx<9VKgSt{tuJF%^tXZ_KX$G -znRZGrF@`I|o@}}Jd+;lI0!{ptxe1WYae^NQEgY#=7t#(MLT~bn?Ciq< -z`y42YY#=qT#c%%_1#KnV&_$X-9lg;RdJ_58r1~@;54!USnN2*z$khRmP?f2{c9(juZ2HXHWt5NaRk%4 -zOd=1Lz{LO;cPzCAi@wimGC}+?_9`MB6+(I9Jvc=wx`gcu`mV`Eivyl89U8NK8o$pq -z>uKvgq6>298x(PkJg#QwuHh?(-;jDNv$fR(|F2a&Y(Pkd)l^BuFz}0`uVr|i@G|DO -z_LpGz94Z1$y7;i|tH)+vq-|lqrB+Gd&j08du{-apkdD4_AI-eQFw2>DNyp*WOo!99 -zZd~KRJ(q3ygi|5BLn?^RZy47^S9kZYN@D)!7Is5Ix)$zi><9PlD<&YT5GP#WAuB(@ -zf9D+sOGY7wvo^YjZZpE#dna`}*UGSPWigNO`vu_9xF)&#Ji(0g(_+J2iF|k$_{3*Yohyhl61NF9QmQS4i>nX -zg|paq0sqrmAjpp|*?k({HdpYpwQavQG5RzYKDe_*#7~rU;m(#-u;ZSihi&_bs}Rn{sCe>_2^;a>nF)kjAY$cz4Lt`c -zxCJj%dz8x5;1&og7};@L#xTFiu*2tw`p6F&( -z_@?L<){<6W@Fgs8v9ovJVoM)5po{&;0~c$KnH3;M7h6K!(XN_^8Dkw^f(twb|8&G= -zkq5o*xIi}c$oX+9K?1UI8beI$7xz>N8#Sb~WCWHKt1&c5UeQ}XlBo_&@J660UwwP*BUk@P8aH!T16UsdJFWeivokpVNB68WPpg9uFx`q>S;g$rpmLAV?VF@gUZ -zb1IN1@)9Ji>w{nYNSGiTN_GYP8{2pCEM7r0ct{cVzRZwOiS`-mDQ?RaFo@#AJ=u?(xBs -zFzYpte8_{_jkV+IRoQo^b-8$nKO13u!M3vz^t^0fyTHER -zvt4OzW)x&$qzA*`Yy_5lGR(|pwF9a0f -zkRuv&OoBT}Y{>H3NDUtfP<$ZMgM)n4e3$5GhYi -zUdUw=MxyYn5@@4`*KMxeV;k1*Z5FzfK^G@Kk(wHDs!iyY$lw-eUg+WueShNylkpeUaR8Wbje%>kdtruL+45EgXkfF(H({WlD3{E*Y_Y;jNn-;{He-OXf%i(f)yN|t -z_kE_v4?1<<8n!g#VYoY9aS_{&?*+5>5>Ti#%kMd}QsA<@wNj8`wr|L1EOr!xD_~4L -zuLd=Xd;`D=4VM11uU}~v&y+$3G!p0}XYpUeD@Q_0;F6jSb%};}g86L(2>18ZKnHhW -zjuLcf)D9W$T2o+_^|W@y_8e!sL)FI=;Y!p}S8Rvlb`%+RJQP~Xv%LhXLxC?CD+SU_yo2&|VR_{Jy3OkGwHliv -zgy7wJZr#*s#v!w2I}l`lCk{B#A4r{{x&qN5IlkZ!+TOpyBnoQij|uLCSNGsRTA5z* -z@~}b#jBMkz=M(CC_|9*79yH^`O-Y)JrkIqc1E6L;wQ`+^N{;JY$^l-oAB)_{GJPhz -zG`IbMv(Zqy_JtMPxL>^yC;}J04{Ui_L4w2%=D*b?z?8cL -z24F&H072&=p@Agvs68*R_@H6%FXDI>r@7Zc8dTDF4JGKLFtivQ;A3Lh)I|0KYDbTanb4)NgtlkSg?-EG(E>f%^ge= -zwB18ej4?tL40f8x?LSXLmG`9+$nkBxW2K8yu~N28QNhGX70(z?inQ%RVA=W+XHERN -zJa;IUKeD6_p~FV3iWz?e)G&`ejzb+f1vpID2(a+NueQTAEuuhJ} -z6&=$3^B(-MP9zCmlR75Waz+!Sh0LJwe&lw-suLj -zCZP3Zq#inTGtK@o&oiJDaKcQ|q??7^fpr99GVXFT#!mMW$V-oA -zMPcq0mg!_mQx?hakbTWHu#1NX$J>7B$51Io9|#P#pRz!!d&~NLga#hgw@BF&*x(HZ -zrwd<7cfmMEr4%|IbzCXj?8lmx5^XLsM4pBir?;My!|))-=x%@S8h*|<+xF+T^@SWS -z`I^IkzrtuZeFOX+>ooCOv=Ayz5Q60IVk(S*RRgz&x6^bPXi6GkohMw8w -zBTeWie^$2Ct9uD>U@12LnNt1!*|Ha6qi{TCX@Hf39ptQ%!eq?4j3(A0Tc7L9dB{wa -z;PfhYgJ~@KQd-5tU>^UdsaLsfeG$$&a>`F(-;R3l7h$E&yt-Y|%748{guhVvkZBcBywj_OHj)=WUVKDNNs`X2U3?5&rQ@b&yKhA~->zqKUF -zEL@OndWAW8;z{w#^C(0*c0u5LG&$_~Wd#x#sX+dlU}vL3`2ot0(*ZY8ew(1;P6!*_ -zbX`-TF7?57SXJ>RMfNeHz{7}9-Ty0j&u-HBBX#%UBQgV`u)Rv}TZnFrJTW44x)oe< -z$RS`s9Widtn@>aRX9r>Ws0p1tu+y&;IHJ_(9UOHMkYM<9kg%Smxp~8TQ1IxyfUP%= -z+&E$0eUyI;0kuf7u@&||Te-jCAb%@1G@&7*WpQC`uCCdgR!(Vic`S8RwN6$~qtp#HDx}o1CqHT&g)9+A -za3|%OApRA;_7udVkFZ~lh~d9Os)Eg~fk)H!;^jMdlD(v_@~~#*1t -zx`xW6$Yzm&^30PT?~F9kK!c(Nwvwl!uMhPeAY~+8q&Z$`UvSlUH!@dD7T*29BN{_0 -zrZ8J^)Cc0xDWNP(BR0^K#g5)*9aR2Coq*07wl{^=C-U>QUOi#SEU16d;*wOY~UC`D%EQ1IL?1yIRwlz+Rby}kfw3!`frz6LMTns>desN7DmJDv&kyH6p;y(jo3mc@Hi!RYmTNg_!1W=5;igNoScC(Kd;bxyveG)Fo -zfYS;7SODwa9zNgB+c^VD(e4rWDa-nf$^f!gsqjg$vh}0C!p?ChI*%x|kO;hJ?#7bKT~*Z+Axa%q`lH|@KX}y9b#65GW_s0$0)Iq>lvP%%yG(_%P1wx -zudLp%@_@uR5?EW8kotK;{AL#5CYt+1qz -zr<@E>_Oeg2{=)f!mO)ohIMH8)`jMU5hfm(|LYI##q@50mubDnMN!UYA;UP*d6S%F= -z-IfgT+k-<}*dF4V1RP(@*`Fa{8`emxpF8(>mJ$5CZb^Wkr7v|ezi|$NoKa>aj8W!a -z(aMY7E}Os=K)({c!TtxzA2ap(H1MadSsXy32=_FY@N+s9nCOi#ZkUPhCb+94`>)LA -z)`14)0Fn;CJYGvum~DM>yBdVlmX>cj34Eo7v6%r24&|kpf&Ef;L8;9!MX(XF0}lIi -zr<-n|u!=ObPvfhdZhL+CN!H@z(c%RkYQSMq^oL6`Q6|Oa{l*3*WJ3KCl&|Aq-8_6d -zVz**4n1pta4(+^!g;3e|ljQJBA7ooLq+Z7G)h`}_8s!(Faaa$|0yp?6W#A6;TH-T$ -z*_oR7PdBfN6+Rq1V*`S9{bUB1(oj#!ly&Crox9$IfD-jffcWc#OHCnlvSy;))17-M -zZe{wNflOAp0mjYKLbGW^P;XpG-Sp!s9J&$jgSMWWb@01p;Y}8WS^M(X^$iprjc`8f -zg%^go!F=8bNbnEtwt7H=TU_Dw;d(v{KV%qAx8=bVkmzgRA;K%WiNVc#D-p0f51P2W -z#Nkmg{pRII)UQ2xUs$sSQS#A)e{TbR6TwrkNDF3ov6Tj2mTJ_~I1Yq&Lu(wtC*Rq!#1 -z1o4qjO8PQbvo%8S<LX` -zvTh{Vj24!6?qkXbg2SW{v@~7ySta9s#b}23DOm%>1r3qO1rYh~B<885%0h)xASc1d -z?^;u2tLIm(xp!C`a~%zY8wF}F1t`;e6^Vq4D{U)bNRe%jdL%{kupmcn`W|kh5)7gqG8^`NDFHe*XBV6@6}&!(y|=9_1`DfYM1 -z{D@ndgu!P%uDV^j3CTVX2YrFj@LLhnFz_p8=&L5)V}^2$yOW}eqEL{%k>l)x*T(J~ -zRRwl(;)=}?gB<=dk~pX96RGoTo`G^nid~9UH7{r!9Xnui>%Pq&uzIJ7a=DlO5e{Jv -z-$4Zsm*(aejcxG151fz%=cPj((l47XbOFPgrtY~y?8W!CF{0@b5>}bonF?m<3;3@~ -zAiWXZPPhha&1-8v-@SL>+i>rS_|liR2u^4f$TD}!y1SwY$6(I^Ia8&S?--P79D#K3 -z#O1<`zEC)OL~mb{xXFVfE2%jK$fF3H0qy5v>ETdj$$+1RaqhS#9>PL@+aZzo(}$c( -z9J_&D5l3*O%ym2lLoCr+Spt)go~i5$(FskqJ2k=?+6@ol@OCnyl32KlIDyQP=KxH= -zQl>e^pZ#{|);JjtkAb0wZLCaR6x|M6qEl!pQfBCc&RdCh@a`-qW1Ihc@BVW<16FDm -zo103_wJO?WzWKx5Um@hgZHQHLt4W3t##(;{Y@Bpb08QHR>5=t19Q -zIsz;8IIfc2`$N|Ov4|(6;uAOr3tz%#enil(UW)8yL&`-H$Zbz4w+MkzpC$tCURJhn -zF2yF7woAQOKjD^HN+!iSUw>r*7Fd;F7}tg(BzuWMW93?*Df{Kj3wye}Fg9k%8&aR%*Sk*s?Yc1Dtut*J)Np$z -zTi-3L=MSu+t^mm&H!J7`NN?*XW^)KN+Fw!yTIuX -zC+P}hqTuAb-r^VMFB{3qS<-7SEQfq2Zy3F?zMl~0qm|}~H|4X!tbm4c>@k5**krsz -zB;Wx~PKup7nSFeUVcc7;;OT|paN#bd28;BsRx`ubNH2D9+}<4oX8cH_|5G- -z0O()kajtuSax3!BaYDb!*`N8*&&pPx^q5q(jQfYCn<6|>QY{qgJhWuoRv2z)0##fY -zGiHFT7WxdS@}a?kxg-R~!wJh+X-qWDR1T=xk0i|#TdWYTLkv7#;zh3ND`mz8Wwkp8 -zj(2)T$U9VR1m2NP48KAj#g0=Cw!T5EO)Ef}%WIGj!R6FKO3DUmMBKX4)04H&1=h(J -z_d45A=46@kSM_H?F)PVR%A<8o*-nM~`VM9+-g;k+5G`Jsm`W-aiSG16+@-E^W!ERA -zaApXU#4vq@zRA|LrG{^Ip{k7BV`{zDq4S=bE0fJLqQs4BOP}_WZB0-qJv|f7u92@+ -z*c*k(5Dlje?Vj&)CF7OqL1=;g`-vHMV=~)YE>Sqvv#=1e!{&R?8i~QGF;eusNYfO_ -zPW;5z%9Uh4cb!#XwmiWYGrBUa$F0}%=7I)=4?dBmWBx*{$Yt*7e*xp_rxLN^?`xsA3<_JNjB{)388H`Q1^X0dmy}Rg?^-;(2L~GXL -zs~L|j{(;4WXaR!WJ0(#6?T>%@{(o<>u^Dz!paTUgo? -zQqukPoNetBR!%PyVjD&J{+yfj`94T8xKhn-Fuw13*K@i~|>q&gB}a -z6NWN0P&Mr4^C9hat?1D*vTN;nj$cwup)Xc73?cZzRgqAdlA?vl38yXyfuUaup`N-- -zPav}_l|It@MtD;mGOn0&^rm!zL~2{fpH&%0M0kq0K37O@sj+*$BkN4!yEIAU9(|T6 -z<7k^u!SLF|^TTT>8oW6vRfnO(U4|<>WAlFT0Npz-<`v2y}{)?;DqNz!s$_QP=77A%JQE?D*Ks@8dbd%SCS -zpXgH?ROI~J#Ojc#JGHo6=s_9%AOE~v{XNGRwgwy5VnYkNfe_mbDVd -z4w*;Xc(+-|shPr-z>Q2>%z60>wIfg3)*@2u -zYt$x%XmM{&wCyaA-|Bd>mVQX@__94;&=+HyRVwa3cQ~ry8_zu<|5XQXFOi?iLYgB) -zhooXrlWq#n7t96aBT=4Pf*nT4crem>U;j5qCeBfvK53t76PemW9AXRhw^s^|ByfsV -zjpI$EB2@x%QXUQw9U&js0THVhSFLK|bQr_^1}A?7^{y_9KQj~J!3A<6d^Xu#un~#Q -znnXB>KPKUMR{CZ4>TST8mM5p~%cet=$Q-$WGrLpZLK9ANGT|W2>0W6KBEnwc+9W#W -zj$v)L>leAo$xcU%v}Yzn^E(k2wI(M-VVrc|j=wk{8jZo~5%)rD8J7OeaKfDSMb#d< -z3zdRFn-P6xSPZj*_t|7Kx%W}*PT!U4=7vfWX?v4>d6H=SeE}$wKCdk#*o2?Zz^AlT -z=3g{sx^{8GKe+b)ey8tA-PMcPiPP=Oj&RnHI1f -zsSwbWggr8|@oKR5z{-)6EQ#S&U}r3BFM=zJnHL*1&95=(l`F?ed}PG+W&{C_?rMEM -z@z0GZ7y%DRsG`RBC#vv!7jJJ@Z~g2+9{r$o^hTAbjRnrSMS&?*If&f2jr -zYh8e7SJt>TN6rnH5Ft56O3;rXYCu3TBD^t`U`otI%6I-aYIaG4G2k=1QLxC~AzkJ~ -zKxLaGTPjtB*rjL_zJ(fmzzWXL>GLiwd?#)Io -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 -zScgK<6oJ%OO|0`H{eU8J4-Rl>djl -z_l$~i+qOjk3neN!iWX2LMKVavsR#;~h$2uDBuEa5B*{rA5Jiv-CPW290Tl#AKu|;_ -zOO_xYNr@tFOjg|c?z`7IXTRIpyX~DnHdd{wufF=`oMVpJNAG3pc- -z%=ScJ(5&_NWC%+-8wCr)4_w9$K0At|45J#*!E56OURB145Qz=ZuZTjt*tF82l!?kZ -zl0r*CL*v`JVX0v*sxlak4WdsNiTDMcV@4I>*_4DlU5?LsD<6VjW-U>d+Tv)_Nk}6O -zH@x@MV$h4H!)h1c_3Yef&9*nbP#a8Z;n9^M91==<9!9cnZ-vc9eD>?apA;fT^a}Zs -zF(vUa1u4CzyPjKLE$HU`3V1em&Y{Sp0(v9u$GI-}_EUA&=%`js>hs`Ds&2F`s`JO~ -z*yEq$gr-o{j_z&Ux(DjW?&-y^o;vu`aJKUYa}*!#na3K*Y~n|gCcPR;#Qc?s -z)4N+Pt-p`PQF#3*9$Fog!*iUjm(9*}?T>=#I%CYy(IWK7!0hWNSkN_JjfrYdxJ$Pq -zOMq~8DP>jc738QTLSBaOc+*&d`jg(>GSXk{7xcGduQ8p>u0EZ4W~_R6TxwG#^pyQC -zWFr@bIjiff7d(#N<%JT2yjwrt@3`dexU{MQ!lYT)r75&ccp`ITC8P_FQ?M)wd6U)Q -zjbv75G2A?q?9zhl7YQ0gWMNB{2_b_((KR9JsvD)#kW&a%nhDTZCix5An1=$l_A0M6 -z9XGaFu?H~Jm(#!`AlG7OKK=w;Q;nbG5~xVOLu8PUao?q8&NeQi*GngMNO{J3+L0kJ -z-vvXu+!v`EwxYIrUF!Ucr>!x#)%7?{&Dw)a615vKx?%n`pL}~C3KFpybYk||u@7y% -zM7@>F47xJ<7<4K-leuQ$Ol+9F8D5l!fCx`qSU3pHoY~ -ztmy2~g>kp^BFBS&DmswEVcuW(_mTbs^K@h*yG-L~EFW6BJ&@{v6W%G-1mqEnh_ADb+NpF^ij*d5Gmld$y;CNvcPN7bHIquMLue -zC%<|LY7Srb1LE|W360?XWJ~usY^uVvl8J=3iG`-%@OIND>2t~P>#@8f{IcYNVcTRJ$TR0-_b?P%=&Z@m(0cm%lNv^~;;x@ejKG{ukBL_US%g6Tc|P*(C!pD{xnX%{V7F*3IdI95NT8uo26j7FVY?$E!&Ye92nry5UvmQTdLu@T`)w$gP){=_L_yX -z?+c{LQk7(j2*-TPTO8H(ME>aDmb96nznxi%pl&9vRqQf^hT?0-R{Pmu!8z*%;#>0=7*yW -zI2%{YMAK48pN7~~rQFP&i+K-^_C&U;wPfV1Au);MF}lZxmx@6WuQh&hf;37c-JMQA -z&_=U}s=421qK&)wK?68MY&)G!M?YA%r~YcY48R^;yl<#U{ok-Mya(PgL*q_0vrKiD -zp}wB+I8UAyP8+hX@oM&Q>IpP^2zCKqZr!Lur<$@*KpgkuP*Qp}L08=ut9LJnJM!r$ -zjmJ;9E2*|$fJ^;+wNxP0p1YpzE&n(sG{NCwv3jakn7FgraYT@{li4j1g;juud_U}n -zXO`fji!V`d^Nm>Nyzu;PqMSgDXx5dH -zLQX};M -zUMG)skiwUn`L^i{YlLUusvS$}81?|*Xnz`mBkDZXIGYQB%^Pq$r-Wnr2^#s7r#L5& -z`W5b_Xc+*>WT*)-&4wHKy&6welCrwucQyOO&g-&}W*j2fh<<;7kwk -zX$7WBO6o|{1(`05H{Tw1p5%PXMAmluP6>w`Y(iU;t8TY!HK9M(WoKbpQYPxVp8Zg> -zsyR53zOb<+6r(xD70%x5$Q_8KaONlYMb~)tx2wa -zzbDlGz5y!yW(klUJDDw(@YmDN@|H@^#~&$*R8nipmA;ZZ3c{TD?i>Er3(tlt_2Hme -z*BOx5bugS`R7F#}zjFH6wPo#}>HC(0Pr!~-IesSix9|n!kWB!yaU(ZE4=c^FCfH^Z -z8&|$v&qAb4-4JX0h1V0fh!WGS{Rqp6q0pXx?Wnx!ic%FLS2D~#N`D@%qh;)ExA|RP -z0W-Qsoy3E5J|tfLi74kSW(EZgr;YFB$AiD7ojpjO;=dPrt^1cNWdO^|!OJ@H*c5Dc -z<%t#_zw%fKIk}|B-#W(G6^h=InoRQW(JhZw0iLZq8JEOGR^T?CQrU>xs^NU(SDj=j -z-D7k-_gZVnacL)oG`JzEP^4t}xid)upL&-2BE>9T;=7dj5!|h8uH|vJ)g@w0b#g$Y -zi^hMt>R@dE;;}5Hu@V6s-;Oj%FfDUjAYgvt|NNwBUf4hc^-vzF8 -z29S8m*>kzzvH57{K+l$;7=f&4K10C)b+Zh_#82CFDlgJCQBf$-HF@?_>ObPO!7a>h -zmwoaJ9U+GbcA(}R-TxV<_M{dOoo>KANpwq!0MuSe*oum&COt2l+XO))4Yai1h4$KG -zW35=Q?bYal)m{&L>OHR%XP+$ALH&74aAQAyA2FyackY(G@8X3jsUuwonSZYA{QQPX -zoU@kB>iPGvB2PfMyT$7LtU&S~9<28f@0Wm1k++!+ffYzI-mJF<$WOws=Cd3Vkz@iW -zv4?;TNeM2z?k8 -zqq*`c>ij+UyRlO;uC*Yp^+4;>h!Us6fS-4Eot!;_bGoN6{)7)^G4S7e|LytkZ?MN1 -z@z8tkJEEiDZ+-=4g?QO3ly;OwC$-v^Ht|z~Sj}D*j@%kh{}}mx@5zFm>6u%Fb>MZx -z1zMsoQx*pR7hC42URymU*e2*8a(iI5YhLs8oXcaWDwBAsL) -zbc3)#Y>JVyTL84!J*bUG!ud>OtE76~0IXK(k!b8*lc=kt=V;^oR+-*#unvuYW*@^f -zE4b7C(1JHO0(uq8FG2gU@vF)9Q0;K#SO)6mPDBO=;@ytAKcndl51}#C?TPn|fu`>P -zj^;pIV6m4>M=(p7O0Ss{z=mQE^i+*UX_(ks#l>)>2uzzucPdK1w&pf@P3rt2bgu}% -zxKmzGaiNhn*#k_y>!IYbTW(90!-VuT&%A8@qpFF~Ssr{?3RXE!8+6jAvq`wTR&cw9)q=9mOg@mrgQaSQVI -zt%qS^TY^1S@FoI6=a(F^FUX*b&3IH*QEj&@SWo0J)$T?BOCAsYTIDBT(0g1!iS$+Q -ze9j*XYk{aB8jfI|H>5EmAX1JS)&@z>NZ%pF`4@tGKVX8)bKq!RaB9+x(i3)#$$=IV -z)u6NkLeoTv_kA^~M!6*Fv3WrFl(U)cdXOyS3e%AaZp%Ma1YxRk0P}|H?7bkBsPmc` -zzakd^!xo7Yi!Q*TonK$gqxFh77(8xc2qg_?CKVtH6@kadifMmzr0~Uwx<1ogJoJIb -z&7BHav>28sW1_!%*mu=K03 -zyntP1J0ckl?x_8&pVNTh(88&)u|Y-6AIemD1XW(fmk6(yOf(`BpvNaMK@IPUy`3KI&U -z!M#Dbu>^@;molwnq}_9=3hZ@#MEeLR(x4c#q_YMG-6rH6XlyDAn#JL#KyY-nwuxY_ -z1E`oy)&j4=k`9`*y!%8;FDQK+A{OHO`r(8h9~$n7DoBtwRZkMt0h!(a(MTPzZ~o$?t@!FQpVyIoTjr(R>#vEPy`` -zXaH|DQnWXVAF!(Ldp}L9ZUhqE;h-Lr9Tb2I1u@d3%9-X+(a7jAl+B#MgAC`uq=IiH -zQfp;6rmr8t2}v4NL#lEHOc)CIySRWvo}?NFTY$OdC^`H|PAL_utL~mH%#ix9$(1mI -z>ZEooahf$&I>n@XqDUh7u8@WvW#n%-tGUu(FB__BHuL01R^>5FU)7 -z)q_M-vqVxv8>ppGrlzX>TmcG`ol|~6^5^h%z%nMXO?2?QBeXyWIRg6fyMhHw&Y220 -zVp@sWsZes08QO6m04>F(yZJ{R>~YM$5AQK7EunfQGi?rAcGT8B>GYrNlLe#u@3lf>(f*96>A7g-uJ~W~Ah^Y4i`9HZEpn -z1PV#QG$+yz3Rj1TNkBrcRe8}sxMd1tg$WBg!nRKx=5X$T0M&ydVsHP7TfInutyn3o -zsvG(><(t0G)Ky<)`PvP2Q(N||zT>($wL0!a9l(chO@Sr`IPUipg+AzE?N!EW;3`4; -zW!u1rVSm#CdnzYDvS#iU?=4e&&$5NY?~m7UXTwLuOBPF?Al{mvdI_?-P#SPE5gM@n -zcf!CFYpGC -z;_+Eb1l;>a%I((ab|zM}aGUumPZJciIbX*jEv#_}_SE5S76}4!fJM4+z8D-RoJ>uT -zB*=+TZv^Y|7Fc-PfCg-*{)+adZG6^(0d=DCAXIK}hn=FKh2<_t6Ih)EwlyKJ0u|wr -z^uisPNhn`flRB-JYd~#hH_t3k3!lFmxXx)7;G1yc`;g$uo89(Dz5!jNRQ6Ix^G{!lJ-W$+k*8hs+W -z)xNhQ4hC%ad+TXju63!ek;snji{dD$Xv8#aXL)JAGBoHDbER?zg$;QPOuLWb=1hl2W9o5rjC=syO -zVe%3*WqTxzvA63mx|9Cide;%d`vdmZWm!Qy%)VDMR)Zwh9H{Cx$pSe2d^zh01HV|$# -z6Et=yN%g^TtQQQeWV!lFpsZDhU~64)0mh5=i*d* -zQ|ET`D*o1fbYw~^4}iScZjwjhk-INlp$mFuRP;B$y|udaCz-&}NSM^8ANSn(7n7PD -zcS?2t#fFMnKsjb?;A0vnu -zuQ@Rxqz86s9H5Bjklu#`&^8bHJc>lPOTzv;OePLgmI>U^s}Am!nk-%T;3#TqS71is0Ydw@^L3{J7yJ!3iL3U{KBAPxI3{H-5zW)Ug$f$*S -zD2ucAk@tP*R{Th;nrx}HarDFUfSPzn{C_6Z^!mN-vS*#feTL|a7Rc@ -zbpn9VU@BY`czsKr3TVS+kV=;hmzQxMo2U8h437VM5JVAW<(UW(jTgxT -zURe3H0!9gHwCU*x$zN&7M`K~6RvS6~Ll)To5v}z9ADM#x^C16*dLJ5cW(@(20GTI= -z4vC=i2`=dX84>|cfEn=w6niT8$6V0zk&+Ds>c~MF&~hNcCGDZQ&k*w-V%b1KMwh -z56wD|7Jy>>$P8rgAHYi~^j}2KV?1I&(sl>d=5Z&fsNXaJBxE4ur4Rq({r}c)21U>R -z)pZ9{>In72S*`!U9Y?pd6+HgM+qW(<6AjB=p-1=zSU>!$YyPWO_u=6pGg*z5zv0G%j9dvBg^AeI=B^Z}E)5KJs%mrGfz`i(=^>tMrtfmF1rWb8b@ -zhem^|V%!>Kp8Ftt<Wj!ClUfLW&&EAgu>rGgOght-Y*cwZzuMVLWaB>?t9M; -z*e{sikHWSEbNF))C0l0kD=QyExZYD_3~*MVr)$a>uJ`kQt_P!+^FPn;|HApAd@Jzq -zZ(M+XIk&Uj>nLpy<`O4T<=EixWy=n$_#|#JMB4`Zz?)6% -zlxn2?=c9lYX>%hE(EY(M>PVjsvm3@~)AF#uWayA9!xj~K@cG5wwzx;gV9T)vvr^p) -zTJ3s3E&GF5hg#5*{vZbx51(hxX)6@LO{LfU%Km>9p8j<}iiM-@yvyGWap)SFXz6cx -zZ2lI-{^HZ2U>Ex0;{PT%|I0h2gN=gl1DpB(ttT6QcICh0Ld$P(H7VCjju`@gF-0+i -z^BSoHX{=)qjL#ndhF -z9SpwslYEhzWh#V!!+C|r@aE7w2>*(^A-Eqv`#7?b%GrQVI0c^{i~qwSEg!v9&UJ70 -z`##u2f|>uWovjfS{1Ow#0M~qaWazV1p8})7V^{QR!0czs&~$kq*n0l^1z>ZdbIi~? -zoxQXG;GfHA=moT>4w)PP=kjAw(6#E-KdVK^#&fPEFx(1W?;Y4mPQYEQYb+PaNkfm{ -zLPIPUFKgGCL9o?{e#_|G=l$Lg`HgmTh&;81^+pGiowoEHZ7J9n#h)$zFF)p1s!(L2 -zSX}kxHhEz<)FhPW(9yPUY_dWdmkvn2U2RZp2&wCynszRrm75*Z{!#&_j7>J_IAiuU5I~tc-Yb -zMT%opA;JW*d8~m~v)+hTaz1UQOe&DVW+XbOBpv_i61wgmGjV-F! -z3$QwEzyxQ1taNK1W(XB(sW!Z`82e0-ZmIMt_dA@6x0jSpk2-rXA -zk)~u0km6UuY^l*h7j*1S5@t@_OIdjv~I6BDlJvbBlfON&1LD&UQ4 -zCgU5r(+jI#hpjCdnRzAHP{nOGYX| -zK1_L9n}Ur|b`MCiY4$Px5cxpi_DE_Or`wuI(E_E~1&^N~$&Z_|iHE-R^1vA1Yg_;@ -zkdAFF`%a+!sEtEYnip~==Ln#GGuAxy6<^8JfvP(V&l~_#X1l3fEDOdE^#BubU3tz1 -zaJz4+4xI-bZIZymrSp_oH%`|*;|{>OU=O0LI{6cnf-H~XY=YlHr3(2BYddXE4<}LZ -z$^rHuHt7Crw4j)3o|^jv97|~$j@VLGIwI?wzEut$s__^}GKH~(u8VCU;o-ZtU9mkM -zC3r;l7qb_3dzTlN6J&Y>e=2yA-8I!F1|}1r`kXkvtuk!!9LQ^;psFbF=K7+B-J4A0 -z=*sWDfoh}6nMzuLsvTP%ui(SPyu3FidOXBa`sL_NR85GLfhXk2r0e*SZSH&88=kj2 -z)}Qy!2{SC>-wdKE9N5oYy8j)H(OU1qSDDXun7==9%`3B*{`tj9;b$wu^IYD@QnRdX -z)^Byw??o2xB}ECUL;9jmy)*55p5~=?A_e|S(Vy478cy;g;4W~}&Od|Z;HFHM{@iDs^5?g2{|=K_LxAI;ow1f{6gp9S{9*j7xo`N3W0{z -z7^J>LO4eOzrXVvAxF`l#b%Oy<1V`vX+E^CmR=;OA4PI; -zO^c+~Z!j|u(W#)EYS_n#*~c-UGtI3)=`8xeycZ4rv?RM7UAd!m;Jg--hwPoo_@>y4 -zirSHvO_pLEb|&Ke;3WelU2(!V7MmbeCru$u!7LTF2AQjKsvt2fdTVeg)0OF@1xJYc2eDK}3wGe! -zLf-jGncE6`0=fMfUmf&wEWUU=R=3WKH^rd6i?J)&HQa@XFzFA3j}cjdjoKjh)V3LD -z5)HkT7hqIaGaVj0-Oo>SXK7?fDc5Y#T~bca4Ksbwf8PNg!?C6zcIt4GqPhkyw~sbY -zqn+-yHs}#w|7c4xmF+Cfh!+;)w>$ixBU6_c9P~a@Ybi`pJEpK8ncD%((c%_#d9L6b -z(8iPa`7+hRj-|_Ey9L%WGq}R#lDNoYgp>7GI2>rx2rELT@5!dXCNPK7A6Zq?)PF%Q -zky^`t!cwTpEJ%Zl9S61P=Y}47cfqN${fzg=O5@~$Uz!-)L>6Yv3lIGeL~|EBINQxV -z`=KR3>P1MO=%x5p4v7W8 -zrte(k3(SbvLIpZJMKtor6ED+s(`d*krzZ6ddq8-Ob&u~;m5D@kV(-azzO?uud2{Y$ -z4C%I95~1AGH2bgkq-zq2Euv11Gz7ui0=);ozo7Hi3BpqAw-d37r@fNSHm15m_-rr{ -zB)A}2i>(urL}G1{whXO57JQ3LI0@n46(MDSm@w-$J1Y~EP8HkoPF|+ -z>T2!u#^b?-qTMWf8C{PGJeCAEH025_rkOXmPbHs@TxR_2D!Aja#qpbi{Ht1&1mw9I -zdGOV(%Lb5o5(xeO_O$AlRSRWkR(tT47Th;#|8`JB+2e2#T{wxkN$D_h(Fi@tVJ4ll -zo$dfd;E`vjZCNOZavkXmGAKCh(Aca>!Zjaaz=m-^$`XHCPYTkrrBpalzn|wJpUc2` -zVK!34LtJ)IA1F8aGGi#Y3^wR`R$?lI7{asFZI6n|X-S<*-D>%q+%@@i$=2cxW#~pz -z3XIPX0EBRb99;~r*Neqsr*X8Qr-5kebR5@YwSSykJS=D9kira;exSP>GR7==O=M7B -z<-zB+%QSMoERA>nDVU+jo&o}b1-@7qr*T$1pHr%^TBe>}Z&LR#1aoSNu*!roEWTAL2(l*!XuL -z1>sg}0ldI9#cSfaPhF%XOW&TPzv2ZN=eTb1#vJuDm(p!m4kLyrnw`kn23T|l9&0#_ofOtJ -z?4Xd~M2dCe$FMDFv?Qv@B%=hAPq%W@{yZ@0Yq4Vm!ND&PDOnN~^ -z2@rONLnbO`e>A7ukcSw*M@haml!i6_urGyPtXLeC8oAm0n7k3|~+paI4+SX`vXW@+I6GYB4VP42+eXAe{AR`C)QKn*k%(afpRR -zLVp_Y`(z~pN9bCo{uqd7c^~QnlLBNf0(YqMfq>Yc`6&HQTs;lD#h`{W)O%xxe_#V^ -zDMBM0h#o^nkyNBWF79qCkBOt(-I@KZa&F+(miAPyqcMP&kxqi$lW{y^S~c~cUjKEU -zm67Ls$ojic5h7^I*UedMcWWrCkB`pQAO53i@Lur(H -zXK>y8Ny1k-+4+);lBf}Ttjmw#$=iV_DKAl$f+A4@!wWXOJGj##Tar_0EEQ-9`YtMJ -zRM3y1Q|GH`-e{&D3bY<%m6*vePi|!V$miy%-|NnG+G%2Zi@U8M_&O{z1-w;KA}P~t -zl2iq+OQ4Puv3|(ALl|>5Z8e%fmFo95poNYywLQ13{W@nC`itMMUPfZ$g8v9$bHQ@^f0u89Z)i_uP|O -zav>_UxTizK?q|1_^q6qP@t=ZHk1D=>Y?pJ3({1m0+H7S9UiTx)^DXYj8%@Fq5t$Ke -zzpCB7pn9vam(IT#PN+T63o>EdME!df`+BKImL%S)+v=Q8uU;%ZK-vuCpTn*W!xIcw -zQ%OEI(EUsrrIU4KaKu5-NGR_u0SlIYo*us=i4r2Wlz#xL|)#g6Sq!sNZZzrs2NQjim)^DMm{tcQ -zw}UY!ep?~?$|h?D=1a2b`809(y;JkTA3Y5r02U1VY$)p6StB{thrEGpAU!%89OvHP -za~Qft5hM83DLwJfK&8fP`zvlR9_40@~9?irY1$ip5^h!)Cxi -zxwKN$uiY$JSzf}4?ervG0^(Mfokj$61g>)UQ|J*0R- -zf$t&+B-?|zpiWG99M~z0hJ_!)547w#fI$j5IMWGWHAMquQ^3{s*`7>A-M!cc{wn}k -zE!b%OR&_HK$=&1kQ5wRinLwL~6X#gAMgJK1c2M!^Bp$^ztu@Hk{L)0~_9rcvadxwf -zfZ_hRosFOX1Yghga#6U?qi`#Q42$O|rDWj6hW_9a;A|fby}^XgOfb4Y23pX~dr!}k -zmfgO+sq)kC2?^2iNuDf2?7=!s<3`GD`|7pD52P=Ih5Bh}&29JJrv6^))y;AV?=}qA -zhf*P8Gv^V~c`nK#GdP%+ro@ -z7g#N9f-UzesFng5r0V&6Nn(xmH&c0N!dO_AFxrH{^Vp1XQ(%qk;g!=OB%gSpKVAUz -zeZ4P5r<#)qjsBW%9Uh>79yMyj<~!`X+9aWlLq~yn8q+Z*KtI9IF6buv;?SWe=*P*Y -zniT#SzSIW3H2huO0Wu1lG4#~L7w_|e_N)Wsr5ADOBuzkICpZ?OeB44t46v<(b(?~7E`gRd+opEKYj -z#q^-QleVZy;4rIE(kO7wycCjL_Nfxt*=N73IBQ-iWW?&4$oJ}I86@EtwD*LOke!A$ -z!zN>7ZA#F6Fi9r_OQ{bH8JnT4XaT665>!7!m(W{Ju8G@1=hEYF_wUXvNTC~vu3h;2 -z+7*1&o2s|r*B$0ccIN42=)t`Y2NVL*ei{<7N8B2b{>mnisF~s@>s1-oYu~?q>V=XA -z$uDimb;zh$MatLK&Sj_sKhw##6@(jL3B{-IcZ1B-6+Abb(j^u(NkVxrX~JfJ4QLrbRqjs~?h-0E-mZ~xJ) -zg7Ggnadj27ME$LY;=lQe6dpDvJP$R`{u>`i@`mxk-xFIzQ|fQr4jD9*q@*}jVjssU -zS^>^ZrVzcGr7>$-@bOp{(+9Q@V4Bm7ynphtUvAsFU>>9CBe&}h%gd2ZgDIJN{fn$hW9owZv8Snp4#dvT(M%g0a~n>z2z2RIJH7Ym`=*j*LJ>A -zKAx5o(+(|YajhR6zEY$@TWtKQb1enkO&mP+%SR71#FCH|(hZIfgpLOA!+_Dz0HTC_JL~mWqZ5X9LDSh#h>e1bnH$yv -zPxA{by=qQ4Szx9xx5y=Jc+NZwgH~MhxJ&cu&?lMobfXGI7%q!7dWbtj6$@7kz3Lba -z5A=ly+@r6jJj`WbcvTpB^iuG`OYtnOu)Jg7dIq+~(qU2H3`6{U+V@@ix(dJV$xjSk -zQIxJ;gCtZHO=s7H`it8V0vI~o -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 -zi3Cti2J{$fV0&G++N;fa2 -zhbV|lZm+Jxvb+QlEcncGUG&&o;JVP4l1}gj$~nKhKS*|20HC3TGY!$Oc+3I%)1GRA -zXGs!j0tF6{h<$H`ub_7}?U=rT#j6Tl;|>Ywgj;?hvg;xs#Y`}z@g5-TCfkFaTMh*c -zDHxldlL>03~<4;@XJv=Fy)h}=1LM%Q=%@L!81swZZlrS;N=;H$Kh?1aK(MO -zdhsxo+9CGg5{^NUWrY`_V79p!!yn5XGBY87oXzyCve3y;lNaDg4xOywDENgcUy}To -z{xWU(9|-&Z7=#@RxGe&ZrUq(i#(7TzmK~xRgkT*rU}m6hdSY9V${%;%d(z}QFuz?u -zw7VxEHW%J!j`=b*b@%q|X2>Oy(}W}s!wgkF_3`$xic`E{DBUCJO2n8k0RmO -zem{nI$lK|3A48-LXO%tuo>!Mx8YXG=d&vEBJVMLxAN~h68)i+%_J0h;{}|%_6sSWW -z+SaN6^ELkIlE42aDrK2c{6moc-(H!741z+;IJM3E@Jks;N~R!V{^R*E?>}xj{^q(X -z*hqu`B2TAU%Ny!5)nTTuABPZvd}DDYcmi>?rKevP16GI{EtDIi2f#|9|8oKE_=_Wt -znc?F%EkmW9JGy|5;|fRB%L~!@Aab|DV{Ny*FqWvgib`rV+v7<u1tl(jrwH=6mH(3 -zaMO`;1C`~DdY*cjc2~sWFXs9dbK|H)AtdOEz%L#w=r-p@MjS#}I -zFVBDm?5gxo@rPk}!4Yl|%L7M8dbI^MQIs_qNB+2os?Yyd7L%}d)rik7toHy7XBxqi -z95b7n<)TCw^J>WIiVF*0CMquIRzt_^88{AEt}zthwcMo<~1s%ga;K}QX>K=^JZ;%_N@LD5$S--Q|ADk -zzYFClnG0*)oGL+2s0mL%v4j}&r4+0)5?JDZJBQ6R1`G&II*RG*{SEh{#D@5+(_ -zQB!US_&ALS2S%Bg`lk6uHz7#;9w2YafY}RhL~#Kb2hGN`XCIw?vD(<4m%AY$$ZeE_ -z^+81YUeiarX_&MuFcF<@dCQOCAxDw_%kpUIe%%EyCj2Dt^M!y8Zj(wYsE9SLyezlg -zbU4TsdNh@B4*gmxDFe0DhyK7t!>Lczj}o{PI%ZNm0Ji9Q8?;oGa3%mc*mpzkks;sK -zs|tQ4LVY<_LST(!3!;ywAhJ2>Iz!!^JPHkUJYPNivGl0MX9R+JDWDLQ%1GJ9DQ+A7 -z6jY=gTh)9xY419C{L_so@Dca03jqHHgW!&&^iAlk8khk6qq> -zqRL9<+1kf@J-`}s0BuCVdZq8+3VanPC4Do{nIQ|T{5ho@lRkEKX4FxDcqmN?G;&{M -zuL0M!+ibk*TNxk)lC$@cp~^B1xJ<_Gbt0dB-#b}w0DSB-+5YnT-3Lg@$eFNFJ+M)d -zQ4h!1iLpCeE(;%PHb^!BW|VzBJq0Q|Kd(B7rco>Ej@}D)*?nv6yP1x!0=z!?Mf`L# -zlSAfg^M`Tp1}+8lP{YZf)zf~N?NA(=SZlRhZCJY${G%SaOY@uut0@D821iSP!OKMh -zho~!VRbClBt&>#1bX$=Tj!1Yo%KS=7aAJm%a7VL%S3fEV@Bgcku&UfeTn#*8(Eo{e -z?OhxQ80UhmKsxtmRTx>)Ms^&tq;Y%1N -z)!rZTmx8?$q}QppuaqRm4T6 -z52(~a{oEi^_T96Um!efh$z)Pg`p8o>qd2(af%Ar7xdeEDRP4QDZ*C2d=MMGY#Jz`8 -zF&$BLGN@33dTWW?obRmp`EesVObHOqFaghAn5%|1SAcTe`O%=JrX4DPP=zZp%&BZi(L_QJ{j@NiiwWEtw41YzJNR`yh+qFcs -zpHKoT|2;Xbyf>=kx3b71MxB!V)=32yEXa3!o4+tr7xA^ersTdF!^}|+!1#K(`^Ta4(N$Xdp&s1cM@C579zyI&CWw5-+d^qrO?V4+GV-F-mQ|K6Xx64wgL&q -z__YJ3_iH1>KipH_UG>c?;y4qH9JC4ds?ui1$pL(7e(rM8zOYhdLHyGf3F#2 -z4%9@)X{ttK{o*=G3In?}+f0T$WvGV9!)aEtc9PtR;uCS9wNCDAIQ(-``Hh&5b -z$A|E&`w^k;;8(!<2~3gi+{7wNT6Uj+Vi=il6_fotk$vVPW%jL}!G{6(xBFPa*F&x~ -zlnt!a;+c6JE}zuir#cGr6Fj=al&l(HEKq2QKt9lj55U4^Mpp@GC;WYi9pxhQD!QG( -zqVf$6DcmHB{iUW!x}kRSC$+)GbMarQUn*qs6{>sRezQL4g;HS;YQ&m~5N0YC{9MTO -z|Fn1I@l>XJT;?P(3Z1N-h*Lq!NT!`Xzud?$a0Rb9+Qo9zFWf(E&N%%m6ZxOHzJcEn_4W;9Y -z=Oxa5g+9E8#|G2Y+&C#gT+#Ee=_lfz+E}mg>Nq@b)JW#jc`kR)=UktqTnkH$JBOt` -zBN=5n(@3miv}a|zk$7C6+W2sUoat+AHM0#G8plAaU(RP5EkcL6o7(rIYkf=6~+jP*afJ`$Kv}@8+Xc$9j?zROA_B*$m%XCFCkO_Haf&f(h8~n^qZ)LCKpbc5b;# -z`$xXd9|3n=AfuaPBi^hW<|M%%ymG~T-uCP_c{R0IKjQafok$K}*(fXDd~apR3obLm -zyz3#d-~}Sg`$pkn-e337ng3YF*i-MPjvmwX23_=v-#E!k%;qj?gW8Ru`qtX;4ZGyG -z6OEq#Gvt)M+1y>6;_uZ@lL`8}L_RTz0!e~bclGG-%hsN#nb8D`cjxDvAn020IC0Us -zk|Ktbcy(-}tu%P^JMc`m*Hif#>iD<1<^+=!@2T$G{(VW(u94jZUEzDCanm?s`WoFA -z2(^MdE@8ThE3xY6)Jke58WejxQrpC|QdkhVi -zmYE$iuy<&}eRtb&y_SZpf!tc1Rf=8$Oq(LkQM|&fsUi#%4of7GLO>%B4 -z&Bv|kYxwC=hH0;gj2Cdzq>?Sceg!?c-bn%^AALV`3GFAUtvDV^cPA+vEu^xIwLMgI -zRN5qUFLm|f%|dr%<^-7*!|7d>8Tv$2?@i8P$rSFG$AuZv$iNoOxHkJAfG05mx=BgQ -zCNSTpRn=(<2UoOxy)D2McMDz`S8}o@m2Ds5MrKYINax_DYxz;1>*~<N525Ioy+)Z2g{ZF+4cm&8$vT -zuOz4MINz`u=zX^#r@vgt5WSW5__T{JaQSZK7VWnjS4mf*0dP(=`=P1s?>so0vCa>G -zv2~)JMm!p*R*$Xe*KJhQ^@NAJ*jhjDp#CeSTK_|+y3$w9x6}A!a7jcS6((-{>APg` -zv!`n)YcE4EPQm4OiQbRvtIl1 -z%L;6nu&ZS`CWzUD=G%P?5$3+r`+Ed`Fp>a0xA#Vt>@l%O?Ql_94D>N -zuv1ngxvODtPnb?|-lN68gwwGmf_^SYP$H4K7`i7nYzXS5TufTNGO*MaC8#32kcWBa -ze0kj@yF(<;hGX5I(}Og`W-zt*3u!y@4qZZD*P93FEm8K^n=illqGKy!b>CfP;B?Nm -zI!TgT#v5hfGK?};?Tw9M+%b8stE14XD;#{(*~O*(DpC@k7t86N+)0VLD8{r{J(Uq= -zKdQkIfL}{*(2eXy*0x8d<&hH~?S^NA#*D&~?@+pjq-$b=iRIR?gLH$JWEK1%dAZg6Hj6(s3^OLAFgIa^$^GpJV8cvat`-s9il -z(<*G9?BxBo!*4Au3c=3_6_?Yo30<%f9QqNX=hl -zZA#iQj{pnxZmT63W6Rh&4K8cxP3JFQPg$jxlHr$_M&e28i3mqSOYA;o4kk$%w7Z2allwFl{*`(Vduw|w$rMoj5a9_>Sz#1C_t -z0#$eDLoE>n=h!!22`7A4LkjtgXZ_!HnmER~#5=r$u>B}}vQ|z*Lqm#wZ+-mODuE=5 -zDzL^ZGV~Owxe+FO?8It06Uxtg?0@w!ZtF7j<8YB`O15TD*E44+w{S^x%&us{&`;H9eKnm3b23v|sH0q>$rq%hNN9aX# -zX|Et`IePN!)QO%|=|s*p7iTk-uDOD<>5;Q-H0)W6!dU_E^5^xtWlTkgryFG&us*fq -zOcF*#d;1*1!ooIf-D(g?Z?8GjEFS3$;3=(WbUiq##e_HvrXIFs12DxKlHn7E?6CgS -zDZuN*EW+F}Kw_OJ0iH~p2bMsajY+1T7wG5FJ*r*;a#b9=W#rw8k%%v+$AP@deO&K~ -zY+LytJcsF_*uON8{vcaW9ES~ng3i72c8~yq6j0DK%}~Yf#Kb!%hKyW+(iKC>0Kj-~7QJeGrgB0@Ic9---Bw -zhx?g%Mu3BrJw>r5oBmZf{eMV8?>7ai{ZGHf|Ds>xGdI}y`7IY+v9-|M -zQa`_XoobjhSOP{_Q4d}`wdO#}8I$Dg@AR3v+j|>RUtwd7ri#!xYlP;m(_jh6`$!Kh -zCS&Gs3fW0e*;(^*u8?>`wDzOesd8j2{^}1nV0k)ck$nkEr^S?A6=WUxr$DBzinZ%J -z*ObB$q7eAypx|HqqF?#)P9#|uxThAprcq9U8mi|n|8JPTikE@jlX`}#mr*NV)KnKO -zErUKAez$KnSy;>li8Ov^dc~rUf{^ -zBfJ}0YDZ>FOoFDwGRL}kzi6bTrYe6d=?}{>L;cP2@7Xlx^x$y -z%n!9?Z77y1Sx2t6CTSfxpOGO|$+4b>ANPDK910ynwqEzKN(8+;d{yDSpHyEIg(~HY -z3MhN-5Tf;?gs7k}<=_L;4?(5a2cDmJVpWZD=aD65oFET2;@SdM_>bM^a`WiArhE(~ -zEtY@~B;A@{S9g)6I0Prtq};N@JP?!Zggh_<5NLEuqnrnY`}JlJZA+$On#%mZ$-KQ6 -z!ox=Vm2GF2$iPz{oM42(?7%h-!diDz$jLVe!nmQ+uiu|T4ptRaOrJn>ZQV0jCtZ+E*U{? -zCoxjC!efl&B9I^+6;+1pDVuN(XM_3*9`#N;GJVv4@ASR)vrRSa(B|>6Wx=1GmXT)G -I+UBrowserContextDependencyManager (UI thread): CreateBrowserContextServices() -+note over BrowserContextDependencyManager (UI thread): Service created due to ServiceIsCreatedWithBrowserContext() = true -+BrowserContextDependencyManager (UI thread)->AdblockControllerFactory (UI thread): BuildServiceInstanceFor() -+AdblockControllerFactory (UI thread)->AdblockController (UI thread): create -+AdblockController (UI thread)->SubscriptionService (UI thread): Initialize() -+ -+SubscriptionService (UI thread)->(2)SubscriptionPersistentStorage (ThreadPool): Load subscriptions -+SubscriptionPersistentStorage (ThreadPool)-->SubscriptionService (UI thread): StorageInitialized -+ -+SubscriptionService (UI thread)->(2)SubscriptionPersistentStorage (ThreadPool): Remove duplicate subscriptions -+note over SubscriptionService (UI thread):Set as initialized. \nNow other services such as ResourceClassificationRunner\nand AdblockController can start using SubscriptionService \nfor their adblocking needs -+SubscriptionService (UI thread)-> PreloadedSubscriptionProvider (UI Thread): Update preloaded subscriptions from memory. -+ -+AdblockController (UI thread)->SubscriptionService (UI thread): Install subscriptions from pref -+ -+AdblockController (UI thread)->SubscriptionService (UI thread): Install custom filters from pref -diff --git a/components/adblock/docs/integration-how-to.md b/components/adblock/docs/integration-how-to.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/integration-how-to.md -@@ -0,0 +1,216 @@ -+# Integration how-to guides -+ -+This section provides guidance (including code snippets) on setting up ad-filtering in your browser. -+ -+ -+## How to set the application identifier? -+ -+We recommend overriding the values returned from [version_info::GetProductName()](components/version_info/version_info.cc) and [version_info::GetVersionNumber()](components/version_info/version_info.cc) by setting the values of `PRODUCT_NAME` and `PRODUCT_VERSION` constants: -+ -+* `PRODUCT_NAME` is derived from [chrome/app/theme/chromium/BRANDING](chrome/app/theme/chromium/BRANDING) file. -+* `PRODUCT_VERSION` is derived from [chrome/VERSION](chrome/VERSION) file. -+ -+You can either modify those files or have the functions return different strings. -+ -+If this is not convenient (changes of the above affect the whole application) this still can be overridden with the gn `eyeo_application_name` and `eyeo_application_version` variables: -+ -+```sh -+gn gen --args='eyeo_application_name="My great browser" eyeo_application_version="99.99.0.1"...' ... -+``` -+ -+## How to set build arguments for user counting? -+ -+These `gn` arguments are required for the SDK to correctly count active users and attribute usage to the product. -+ -+* `eyeo_telemetry_client_id` -+* `eyeo_telemetry_activeping_auth_token` -+ -+The values of these arguments shall be provided by eyeo. To set them, generate the project build files like this: -+ -+```sh -+gn gen --args='eyeo_telemetry_client_id="mycompany" eyeo_telemetry_activeping_auth_token="peyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ...' ... -+``` -+ -+If these arguments are not set, eyeo may not be able to correctly attribute users to your product. -+ -+Always keep the auth token secret, do not embed it in open-source repositories or documents. If the auth token leaks out, contact eyeo and you will receive a new token. -+ -+User counting does not transfer any user-identifiable data to eyeo, nor does it allow tracking or profiling users by eyeo. -+ -+## How to disable ad-blocking on a specific domain? -+ -+Call [AdblockController](chrome/android/java/src/org/chromium/chrome/browser/adblock/AdblockController.java)'s `addAllowedDomain()` method. -+ -+The following example makes eyeo Chromium SDK cease blocking ads on the specified domain: -+ -+```java -+import org.chromium.components.adblock.AdblockController; -+ -+String domain = "example.com"; -+AdblockController.getInstance().addAllowedDomain(domain); -+``` -+ -+To re-enable blocking ads on the specified domain: -+ -+```java -+import org.chromium.components.adblock.AdblockController; -+ -+String domain = "example.com"; -+AdblockController.getInstance().removeAllowedDomain(domain); -+``` -+ -+Note: Pass a *domain* (`example.com`) as an argument, not a URL (`http://www.example.com/page.html`). -+ -+ -+## How to receive notifications about blocked or allowed network requests? -+ -+Implement the `AdblockController.AdBlockedObserver` interface. -+ -+Add your observer by calling `AdblockController.getInstance().addOnAdBlockedObserver()` and remove it by -+calling `AdblockController.getInstance().removeOnAdBlockedObserver()`. Do not add an `AdBlockedObserver` if you are not going to consume these notifications, as it has a small performance penalty. -+ -+ -+## How to add and remove subscription(s) to my filter lists? -+ -+Call [AdblockController](chrome/android/java/src/org/chromium/chrome/browser/adblock/AdblockController.java)'s `installSubscription()` method. -+ -+The following example makes eyeo Chromium SDK install (download and parse) the filter list: -+ -+```java -+import org.chromium.components.adblock.AdblockController; -+ -+URL myFilterList = new URL("http://example.com/my_list.txt"); -+AdblockController.getInstance().installSubscription(myFilterList); -+``` -+ -+To remove a custom filter list: -+ -+```java -+import org.chromium.components.adblock.AdblockController; -+ -+URL myFilterList = new URL("http://example.com/my_list.txt"); -+AdblockController.getInstance().uninstallSubscription(myFilterList); -+``` -+ -+ -+## How to change the default filter lists? -+ -+By default, the following filter lists are installed: -+ -+* ABP Anti-circumvention filter list -+* EasyList + filters specific for the currently set device language (e.g. EasyList Italy) -+* Acceptable Ads -+ -+To install different filter lists by default, modify `config::GetKnownSubscriptions` in `components/adblock/core/subscription_config.cc`. Here is a sample structure to add: -+ -+```cpp -+static std::vector recommendations = { -+ ... -+ {"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 snippets and header filters or not -+ }, -+``` -+ -+Consider adding filter lists also as bundled filter lists (see below). -+ -+ -+## How to bundle filter lists? -+ -+Bundling filter lists allows the browser to use them without previously downloading them from a server. This is especially useful when the network is unreliable or slow. -+ -+We provide the following bundled filter lists: -+ -+* `components/resources/adblocking/anticv.txt` -+* `components/resources/adblocking/easylist.txt` -+* `components/resources/adblocking/exceptionrules.txt` -+ -+In order to bundle more filter lists: -+1. Download the filter list and place it in `components/resources/adblocking`, e.g. `components/resources/adblocking/my_list.txt`. -+2. Add a target that converts the text-format list into a FlatBuffer in `components/resources/adblocking/BUILD.gn`: -+ ``` -+ make_preloaded_subscription("make_my_list") { -+ input = "//components/resources/adblocking/my_list.txt" -+ url = "https://my.server.com/my_list.txt" -+ output = "${target_gen_dir}/my_list.fb" -+ } -+ -+ group("make_all_preloaded_subscriptions") { -+ deps = [ -+ ":make_anticv", -+ ":make_easylist", -+ ":make_exceptionrules", -+ ":make_my_list", -+ ] -+ } -+ -+ ``` -+3. Add the generated FlatBuffer file into the ResourceBundle via `components/resources/adblock_resources.grdp`: -+ ``` -+ -+ ``` -+4. Teach `PreloadedSubscriptionProviderImpl` at `components/adblock/core/subscription/preloaded_subscription_provider_impl.h` when to provide the bundled list: -+ ```cpp -+ class PreloadedSubscriptionProviderImpl final -+ : public PreloadedSubscriptionProvider { -+ -+ ... -+ scoped_refptr preloaded_anticv_; -+ scoped_refptr preloaded_my_list_; -+ }; -+ ``` -+ And in `components/adblock/core/preloaded_subscription_provider_impl.cc` -+ ```cpp -+ std::vector> -+ PreloadedSubscriptionProviderImpl::GetCurrentPreloadedSubscriptions() const { -+ std::vector> result; -+ if (preloaded_easylist_) -+ result.push_back(preloaded_easylist_); -+ if (preloaded_exceptionrules_) -+ result.push_back(preloaded_exceptionrules_); -+ if (preloaded_anticv_) -+ result.push_back(preloaded_anticv_); -+ if (preloaded_my_list_) -+ result.push_back(preloaded_my_list_); -+ return result; -+ } -+ -+ void PreloadedSubscriptionProviderImpl::UpdateSubscriptionsInternal() { -+ UpdatePreloadedSubscription(preloaded_easylist_, "*easylist.txt", -+ installed_subscriptions_, pending_subscriptions_, -+ IDR_ADBLOCK_FLATBUFFER_EASYLIST); -+ UpdatePreloadedSubscription(preloaded_exceptionrules_, "*exceptionrules.txt", -+ installed_subscriptions_, pending_subscriptions_, -+ IDR_ADBLOCK_FLATBUFFER_EXCEPTIONRULES); -+ UpdatePreloadedSubscription(preloaded_anticv_, "*abp-filters-anti-cv.txt", -+ installed_subscriptions_, pending_subscriptions_, -+ IDR_ADBLOCK_FLATBUFFER_ANTICV); -+ UpdatePreloadedSubscription(preloaded_my_list_, "*my_list.txt", -+ installed_subscriptions_, pending_subscriptions_, -+ IDR_ADBLOCK_FLATBUFFER_MY_LIST); -+ } -+ ``` -+ -+These steps have added a version of `https://my.server.com/my_list.txt` into the ResourceBundle. This bundled filter list will be used whenever: -+- `https://my.server.com/my_list.txt` has been requested to install but has yet to finish downloading -+- A website is loading and requires ad-filtering functions immediately -+ -+The bundled filter list will be replaced by a version of the filter list downloaded from the Internet as soon as it becomes available. -+ -+ -+## How to test filters? -+ -+Eyeo provides testing pages for eyeo Chromium SDK's ad-blocking features on https://abptestpages.org. You need to subscribe to the accompanying [test filter list](https://abptestpages.org/en/abp-testcase-subscription.txt) before loading them. To learn how to subscribe to a specific filter list, consult "How to add and remove subscription(s) to my filter lists" in this page. -+ -+ -+## How to 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. -+ -+To get a combined patch, execute the script to obtain the changes introduced by eyeo Chromium SDK compared to vanilla Chromium in the old branch, and the same for the new new branch. You can obtain the necessary hashes for the revision ranges from release announcements. To get help on how to use the script, run `./generate_interdiffs.sh --help`. -diff --git a/components/adblock/docs/settings/README.md b/components/adblock/docs/settings/README.md -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/settings/README.md -@@ -0,0 +1,46 @@ -+# Settings -+ -+eyeo Chromium SDK can be configured to provide the experience most adequate for the end-user. -+ -+## Default settings -+By default eyeo Chromium SDK creates and uses a filtering configuration named "adblock" and populates it with -+the recommended subscriptions defined in `components/adblock/core/subscription/subscription_config.cc`. -+ -+At the moment, the following settings are supported for this default configuration: -+ -+| Setting | Type | Description | -+| ----------------------- | ------- | ----------- | -+| Ad-blocking | Boolean | Whether ad-blocking is enabled or disabled. | -+| Acceptable Ads | Boolean | Whether Acceptable Ads are displayed or hidden. | -+| Subscriptions | List | Subscriptions to filter lists. | -+| Allowed domains | List | Domains on which no ads will be blocked, even when ad-blocking is enabled. | -+| Custom filters | List | Additional filters implemented via [filter language](https://help.eyeo.com/adblockplus/how-to-write-filters) | -+ -+### Implementation details -+ -+Default settings can be configured via the C++ class `AdblockController` or its Java counterpart, `org.chromium.components.adblock.AdblockController`. -+ -+The Browser Extension API defined and documented in `chrome/common/extensions/api/adblock_private.idl` uses the C++ implementation. Android UI fragments consume the Java one. -+ -+The [user setting sequence diagram](user-setting-sequence.png) describes the flow of updating a setting, and how it varies depending on the API being used. -+ -+## Advanced settings -+eyeo Chromium SDK allows to dynamically create multiple independent filtering configurations which can be managed separately. -+During resource filtering, each filtering configuration is queried for filters and the logic is as follows: -+- If `every` configuration contains a `$document` allowlisting filter then the whole page is allowlisted -+- If `any` configuration returns a blocking decision for the request then the request is blocked. -+ -+At the moment, the following settings are supported for each filtering configuration: -+ -+| Setting | Type | Description | -+| ----------------------- | ------- | ----------- | -+| Enabled | Boolean | Whether filtering configuration is enabled or disabled. Disabled configurations don't participate in resource filtering. | -+| Subscriptions | List | Subscriptions to filter lists. | -+| Allowed domains | List | Domains on which no requests will be blocked, even when filtering configuration is enabled. | -+| Custom filters | List | Additional filters implemented via [filter language](https://help.eyeo.com/adblockplus/how-to-write-filters) | -+ -+### Implementation details -+ -+Settings for each filtering configuration can be configured via the C++ instance of `FilteringConfiguration` class or its Java counterpart, `org.chromium.components.adblock.FilteringConfiguration`. -+ -+The Browser Extension API defined and documented in `chrome/common/extensions/api/eyeo_filtering_private.idl` uses the C++ implementation. -diff --git a/components/adblock/docs/settings/user-setting-sequence.png b/components/adblock/docs/settings/user-setting-sequence.png -new file mode 100644 -index 0000000000000000000000000000000000000000..259b06aa417b385ee262db42eabc6f01bf5ed6d8 -GIT binary patch -literal 61104 -zcmeFZby!sG+cqkKgyhhn#L$A2N;g9{C?VY-F?2{v&Co+ghop*1$4CkYLklY13@HuL -z@hzUm=Xv+>{oZ#U`_H}i@%=S(%-n0OyRPfJuIoJS1=rG0B)m&|_tvdjgvv?~om;o= -zxZk>kF^r21{L<|u(+7NDc<3m~-ug6nZ~fLS##_n|nWw&{n>qL=HoBJ$RPJ^HXi_Et -zcfw@Ow7UauVYKQXut|6iYw}}do(OQND$f&bo*9mw1Qg2;asgc)B;<__5>mpJYJ4@+ -z7K5zx9`&c>09hPVH>%(WGPZuj(<=HRNipKTkRfBL<$5^lj3_ -z5Ul_GYkmzDr4IUaolAfEfBg5)6`@Mf2uHd9=HfpMwA^n;PbcXoP9*>5{r@z{N7WMk -z4?O@{aEM^YyYz6OHkJQ*>!jnkVOT*YuP| -zPL&n=yG6ly&kuo#j! -z4luocP^haR!g}xJ+M9*I9e=ZvUj3Gl6yn9W0O#i4qx{h9#%GfO2Lm11A|CCvW2SU< -zGu~4mVtMiHmhPkfT+w{_m&Q&rz}RWsV$ptJRgZhHT4-~nRy{5LD3r)BhG -z$I|}ZJx_1ea*@Jm@6ViiG4A$18VXMcKr?HPN&pjJ(Vxz5u@om1FT0Zvd|{Om!E!}_ -zi1bu`_W`&RuN{fYkwyW}2xw?{L` -zJ*YAXCI1QRq`T+?fsn%0X3Fa(Utq{I07HHlQj~*|*sSPiUzI#YN?RZN)Hl2T+QfUJ -zPN|H;UC~I&+2V=F{8!!}w-0>^mGOryrz=?<(BSi_;p{rz)(jC3TVp3il@lDPlP?#J -zw1_FH3scD7_NQe87|Wzy^PPW&8`XBx%5^2fE)rNfo^0<)2mUj}7O9TxK4|8+kl~W! -zL(AEQ_SwVc!;!u67!stW9h&nI?a)N?rTK#$RsQNzqXnS|jd!@;+To -zi>hzE+~Uv7v>zjn$b#?;cMV`MwA1g!kQ;jr%Xbh5pLE=@cHu&t?!1~ -z-+*KLP|#6z$kRRrsS+JCpBXRhB&I;yflP|)bbFM?pa^@m|2A>~SWGW+x2BgDb@j~; -z)$$~p-`!iffa!+Lgd*0`~(ChY=VBdHRETbP)Q%9 -z8yI?y(LKq`OHPfw*PT}p -z5hZR*U0)@FiAB(fr&}opiNS*j{TlG|ln89nv4w_|@w;Jb-e-5?AN_+7S|)%YZH#_o -z{ewE((zo0qcOg|(#GK{z*T2lJqm2j|&q%xg_hWiNC`(z{msO~$rA!`ay&8Cei)~ry=Ufk|x -z^-x#9R>M-fv!mOXi4P%_S%6!Lq0)vPo?B$YK(FXk9UlY9eXquKH2 -zF?nPL>GXIMr3fK!ydAMA3+cOLcc$F>$rokR6WzJ_n>(L}73jW7=p)o|pQ1@glVFxu -zqy-rg5ISZIz}{hwbrktQeqXPecLJf(1=?(rxD4KBNA<1uZN$I~pvX@7TeE(vIofKq -zDyS>gG-H7az$3t4Q9EP^EmI(kt-h-Z5!TOm8-2)Q+023)TMPe=d|axrFr~bqfChgN -zny^kDUvbb?5+)t){lU9+i!<%W)3y@RFn-=j(-53`eOM}WN3!875(X)0*{}O&72juSQ(~)IoF3QoIrjJK@U)7Gz`_6v5hB>cUNH}iRI5{%CT -zDLXUa#z)_mC0FS!E5kiJ%m5E;Q<>vZJs_z4l-cJPSjBM{2h3@^iGb=ETzqS@>$9 -z09zmRlT1?5I~Eb+G6B~0O7?oixOo7=aVz`=qeazW0-gMRij+*=N|5QQG@e({N~qe= -z=Ui8QNjXaF1$Ky!oL6m!F)|fbtU%G3IrCC -zUkA^Eo^gUn5IK@7^5`Wvd#qZE6t^4&Ic7JO+Ve)+RwW@cY$mJTZWWMrOHojm2D)8_@!IFhS(N) -z;mkq=w_*@@Pgk~t>>ZridV#67TW_o884UmoIfuN)42bi1pQa^0Bd#K){jP_PVZKmu -zvU{&k|xZp2j~-Eo`Z_sjhr -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 -z(rs7X6{)*3J3&I?XKRIF1L68rSX*VUt-k( -zAzH;=7u_n)xQWjU#Z9O--R+-`#iGS?dtil&IeHrS`H6AB-q+;^p-6=ES3D>|^Qk%0u|*>nyztVzpwCta -zF`HiU9>7IBVRM~wuJ_`07aBJYqa~|eBc(RI4Z!|$;kEp*T>V?}+qU}O6wQBteE^iiA2aqC -zSo^e54)`%+EZ^GgY>z>_=xO7I&!Qag=|_XY%=cW|Fidj#7yvKUp*Zi6cW!)wQj{bm -zM~0O*en17DEJSrgbA1v%QOm%@J4|njrFqm2)SsxzN8(U{Yj2|3Rk%L>SekzM5|~2* -ztQ#`I6sX~T{ou*$9(=LH5anDyQ%4{#Kn%`qkRHaj0sLGL -zFt}YF*Od>b98s?qd7n0HfZkS-2%n;pDUhu1Vigc&DG!vtrpM)yp6sW!8mFU7{FYPw -zGxarj0Jj&`c0&NPLwG`80?AhXG?wY^D=H56J`?#BjoVBd?&o87$&r^N$Gzr4A4Z2@q#NqX`X -zNb65ep5X$)yH4_Jx!b3}w*$9*4m^Sspcla>6#xGmM}b{wvM -z?}!1|I9DKriMrv4EFviM)zWAQ#d%q}>8&~sKHRq0}=B@-K -zOKB3C_fyL^pXp`h`T%L0#SKp*zJ!yZ)9Uu+4pryruM|$*c16j3#HZ?~PY_w95?+K` -z*LjQ*P`klw8hiGf5`lhv(E*R@rI;h-Yk*|=i>I6g!sJLmuTTNwUXjGnh(-2pPE>nb -zmr&sZZ8@67xB&i*{w`1}!#|$&hc;|h^~vrN&KOsdgQ*;3O1=B}yT!}7%RW@82m{%} -zqLk;=Qdb45!Zmd}<^>xqLliZbq~6!i9w5n)tO% -z+1#0nj{$mX@3{>b61M@9NSN^g7!lDNPzY`OL@MtxhU<}+PX#_slq16X3ro&7t2+Rv -zJtoL&Ly43EhF{n=KgXT>O|f+^4oYxC<8@>@ewwIr@*}!&&+RwF2ZL{+dNVolcrG{? -zSRnQrnS|ivrq}Pro5w6rl>5q?x53*si&_K68fPi4&c<3J%RYU6@$_;_zhysFOAw(N -zd=89MES_w;>0qz|m^|;V`iLot7TMrwE<~U86(IG<;k7fec{yJEk*bxtJBhjYaUHKJLrQpmJs?n#Jtg;W- -z`?Qj0!wHc9ty7gK`C+E%;gEbZCGz8Tv-qilkDAyX_OAh|K(4WdP4e=le%uZa -zrP*cKTa&tSL){yec(XJ* -zF=XJbVT~sE?G~UAF#(uFE=!|sT@&D6{u92e8?A{jJra;qSTPK*T~u}7EQQNdApsU5*eXSpS4Xeya+x<6&#}DOyMG5*>zSX+1JW8-vh -ze51b!^=EQ731`!RPr^@SEL*k}hK0igv?>3NU5G -zi*CHF*mz&oCFC5P&RmmOzZ}Rz$?E?7H}*q!xU>Y217eQgZ|5A+eT2Bo1{IParc~{| -zjYIi-Y(be{PhE5jAv{AQv!)Y3y`hlG{L}7y7;#sKgf-{yxi#m^)J8L~(9UK0fK{WP -ziqLNq6j4iM>T5A?7%~n)?{9Y1x)Kiyd1fl;4r-JmBywK{o%Wg02kpg}bt;_{KzNo< -zfT?|atrM&G`uU4@u4n6|L)X=SVn-rU7{j -z)UCMG&{9^h2E#q{r#@JLrXded!tBXtbh03D#K)yB7Kpob8QJ`7Qt4ocyFkZ<7_ICd -zt35DKmhRnsP7`MK;1#Y#0a3*-Sf)IsAq%kgn4JKkeJ`6>>cTbP=T(%{g~f%KiwYYt -zu9k}iS|BN0yfRvq+2-d9K!`#F(8Er`H&sMG`?nXD`+dBv(r$Ee3yKfsrjZ^U0G!?P -zO>bGczmpzJBpMEgT7)lFgj(UmWMJWjJrtdd*i!5xaJ?;j%!*Aj_ukib0N3slbE=>P -zy)!@Pm5kASpDX@I@s^63`L%Dzjflq0l32As{p4CRXg_%`RjGIGy@s%l7*R?Z-5%g5 -z;pKwgDGdUP4p?PlJJDfBKR$-&qvK0{-J5{|zGD>#u`ixEgm2%7bsA{XbQ--oo0X?l -z=+CEDC8-Ps2BU&Z*(r|4?n0d-96CYG>w#6i1Zdp^_UuyQ~O -zaNwpj(@=X`JL}@|FX@N=yaC}XKnE-~r>IX`)p!+fquBk5<_a{e8Y#}=H3JsO_Uc)- -zDbCV_nidEb%FHfbNHZpBZmXU-&ekw)yb7g9Jc6KHP0yZU^Q$E`u(tpzmTn5iGsu~n -z-usN~;qE$8r1*y(;#CFL8@(OBCO0|4z}KQN`UWH1ShItx-tAF5DA?xYy=0$iObiBB -zte3A#Stv4OlKL@6p8`rLId105(VHPqFZAa}!R)SD48g6uFNb3m_tt-xYn)9rk%?fx -z8#puC?`#Y8z8ruCJpy=YDLk%E4VD2VY!f4~W{p>`tLhiOZA&tl)ey5SaTjUP21hVe2otY14*D7B)?_mvzk$yczt?t?$gk}ZbnleiQgNk2YE$woY3voF8k+>5fS_T|b4(Zfs)VcX@DNzfPIs!SM;n -zN|OWK=K_e!QlJdoVlLG4VGT1l6x3Y9fA;7sxK~p`YiwZU^@YUo<5PLugF4V9-q -zKeH2B?fSd3Nua3TFv*f_U02fItzEQ^o%zXKmFlI(0n73{i$q^~!y?~e(u!b0Wiyc= -zq_({^4CF#Y%RUl@A@V8-&Yk>%ZFxHUaS{uWp-sVoqAcII-B~f2ZTWvh6_%R;JrSKcvz@7q;)jX?Y$@CK^3HP1c>Q^ou -zg3r3SYlptn-|4TZF>rb#2;-0R0Jj78>JqwSnY-o0+6gmF*l$oNP*4dG@M8o&3eiy2@VtA+_O|UDf`@F%jEw{JVYt8@U{v~ao=yGB3a&9R@;q{6s+9E#WP5(y-53$dg -z4htwIKuNn-;VDsBmpIP6{Pmz09o1kJm2Mshcl -zxD&4R+@v&sK+rdG_wM5%oWbu-J4VSQ`T^EA5;>+EeI;nDYHD -z@k6}Bz#516)gAjR0PBO91JXYT4CY^(@8$VF;&P`UtJ^4TNT5k35pNStt?PS5N``lp -zGc%Rt9})x)Vipq_>}$$E70;=~SO7L?CWe^$K)eA#p|`>~^tljIJCZOvCNkSeqDy*# -zM7KFW6TNb3bx?4YFYs50z@6x-DRBHkhz%U -zL4_O$RcR_Kfv%QV=Jk#}jFje#M{O82w7hj$9;@p@=AnEgQVzAXYK<)|+~Blx{C6tn -z3YH0nO})(SMiP!0WZ$IrnygzojH7<>&#p8wF-sV|JMTIN@g`Crw1vz~tzm?nY=toZeJaUE`dtSi29usq?50 -z_n~n$VYw{qz8EN*Jc+0x>&F&7@*~(iqoVp{n|kWeEh|0{r}| -z7;?8U++P#1bGiGCGjS@3@2=O_ql;L0)|7nhNeC71lsF0)N-KQ(!d|IjwFGeU@8s!y -zl>`g`^-CQW!>b5vEHV~>stk=+c(-%tbHF*$(VvPDUrHugAn4Se3}ABy2&WgA5{Ob^ -zQ8jLozuPdeDidi%otd^evPw0cyXC95#eX5nVS_J$tRW372-PU*TpjeW{SFzxsP}kX -zH3G7j`@+Ohro1viq!YcE5l10Kec$7$?=hNuzf^(4MimzY40`UWVvA4ry_=gi(0fNC -zexS9mvlcTl8+9R3){u?kkjO^3{$?PIcWb>bnwu+yfP9j*x2y7<)Y8cztw*$0Nh@Ok -z%btSeuGSKAL|9rS=7~eF=V14Ih-A)^uIxNmH)&IeA=L3=oaUI5)E$V`vaXE=74xzv -zw@%cn1FcVGyxY1kQ$EYIvuXErkau0=3V>sLHCnd4%46`|xkvYm3-YYzl=KF^=Nca} -z|In}dU^wf#EE2v;qaK{4UrIE -zQZ#I3nC)gdD6Sg|cTB#@*th`pk?lwA}A5#BkEEc`R&CRUit`# -z3U?$(xzo}`&$i}QsX>t(Ip2zEpvCha7Nme!iOjPr^`gq$;EJ)^is+(qri+0~=pO -zZ0cEYQa3 -zCav-X(p?hEC~{UN%4D%8)u*D=1G%iSe?)nWK*?F%X;k?rHQ{ay2lAB3St4QHOJBV` -zwwo@otz6Hjx_ArK0a!=pt+MUM$ltoaGh!UkvuEa91|dJ@)=m$d++D#~C#1NzYqh60 -zq({j>yVKp3kW=gU3vkLZjN*CeM#UT&MI8I-b`sjTTW7@6lpc87ij#Lv7mm3JN)z7d -zDy#^^s_Uo(Qq8q18NIPN5A`PE;Bq(Y50yH2K#^xPzkxcQVFPORY#q@~3Iev84jGLogjQVE6RVT~M+`!1>j!imoIa8(_JeHyQJ^tqOARI%U)gV?hyq9S -z4x2M7qb3l0Req(SAPeF`x<8`;1G2~U7148N1emHPp@#KUR3~N#j&L9$@tXJ+EL41p -zLL`f{Dnv5$U>G~Og5~|Imgv;hO~-m}NF}vg4d=1=VufLAPwiTidE5MxkM}%G3WnAg -z*3F)?ViRAyKF2`<8ou`^^@6drG$awJ2H#@vk0HS4p2&Pcg{O -zG$;c{ENLzksuM60aj?6%TWqMb#MYq)Dk*@F;^$Qjh5XXNdxTl;RUHM+23Ru~pCMd8 -zdv*p=%i%8e>yf{oCjy+tPXN{26E7xa137=O^n0oBNW&b&IHg!(RRvCKNl{j-3$~Kh -z2v_{e<{I)cd_gp$=_8#p>t_dLJmWLZllE*-l -z;nFO*c>Q1La%4y+1}2sN2FooWTw8PcDfMvE3*VrxFp+lgOjq&l_f$!x*^#HFlmRt3G!ZKE22e%3&*lh#)2$y@tN -zN+-)oGSn4q+$4bl9CtW5wSkb9py&Vp&@sKt|;QI$WKgvk^^nH60Qe34a-zrUl -z??3`hbCW%qdOU%&B}GmkZO-1>F>Ah}OV)kyWOYgIIJU6LO7!-Hs~oT?MtS<5Rn1{h -z4~%~z2d8PI5P`H4)s-M^!wa`d_VB&MVSXIdy<-iAeD|?HuAaSdboV`Y=$}F)ieS&J -zS#mIl`3v~gwD1@-;T)nmdgu|qM<^D4RN-D}U@)-2sb=&I)8Z4nn09@WCkv4-5SPY% -zHxukb6n#r1484HS1%L}KpT!)J9!C#!@>fYJsyM*Vbeh8xY^a^wt7;dcWDz3s-~m|3 -zh+zQ2vlXI`rL?tCbU%v4o=%>ptp7t7pq=uA3E!m9{3cZ|A!ok}^j7kk~lPpL|_O -ze5D;!U&#q19)@sC9;^3}-k!qROo-RXhoyE_Yy)EcW?hPeU$+L02`=NL}FL-PJ2 -zu=>Y#7&WjM%#>Hu+!elx=Q5sruD~&op1Z9JT<>w6>uStXGdmgc&K7?19Zgz?CSKH% -zB5NX?9-FgYI-9~rG|;x^-)+HZUr1yhYX(onV_Js4&G5c_xVmsk7%abSel}E4{=5jK -zz9azlt-?2)PJmeP3){J>U#C$Z#arP`{n5s3g^;D%oi+VQ__UMMS8G@cSZqOc -zRgFxwi{ua~u1msQcu7LGY$?!ll7@%3Wc%vg$klq21}Z&E83&(zeITx@Qs`LUilH+- -z25uwR#*`Yy5Bgxx1YX_>6Rt}|ZP_t&Qs)K1R_(oYV$v5+bK_7C3y2@YpgvAY$W7U3 -zhx^w~SbEcTvmnGq45I6UZwxM;YyU5U!@}_tEqC(XdGb0`gwLwCrqt#UqAx(?9GlHb -ztVHTwOJcdt7+Q|7j&GLR@p=yJwLdSF2+aLl%5m&$(F_p_+d;RNO(P -z&g7bZ#Nh5}w~;)zK!r0VG^ivZ&9>Fy$l@E~9!cv^v~ib2L#0!j+Gp%jd#^3eor(ta -z$AX*#6zQ5JZC^cG$SV`DY)pR$Z`(hqbE;KzDa3SQbFcPLedTIEq7<4DH|8v~+QA?& -zHV|!mraI}Vhu*g(9x7CU8S82Kz6D=Z`BSpWAWYY(mCi+G^0b0hea(PZm<(~Ql -zem-Qc@BalXQE|!z@PtZDpgVLpKKAV0hFQ@5$$( -zZ(Zma!ax?;fVmb6qG -zM=H{MJAi`QX>l^AiBsUjo*)wyv-S-uy^=k~(!BXxPFb`7oXMR#i;6PY7qvg>Stj95 -z!y#~MF)ziy^G(2yXY7Cj@hP}H(iwH2tv8qq;lZRmliBaw5UhfQ%4VX96S#J#Qg#p! -zo-mBwbc*ww!+Jkf6+o|Sa$`XI%`<^X8z7Odj6x4vyk{}P$B!K7CMZsZv%BX!HS?7a -zea+o;3b0-$OHia_YCZcndzKJhl@S-q1dzJaJUW_GgUd6$!dbJc7AQ24lK&xf$uy=h -zga;whc7;7FdWi2ijj8rNu{e%F%0Yp~qVG1DyXYNm5j1ug^IOJ?}A -zZ6FDLvbdQ6izea>9plD4M6g}l$pvNT+=(GY&K%tcG@}!1fC^Niw75q#xE?0?mK$9E -z;+VjSuFIrfe|(28D^cy@{xJ@eqLx;*a@C^rpw4zF+S{ra82Kd`l+;t8yy(LK*u5Gb -z><8gFw)URlG(EQE>B|-w8oj%zn=u=`Q8Y&#j*2h@_`yaIpV=VHO8H%!sggLsR8|0> -zIo-LNPm>-JZ@pe+HR|HmTVcrMWLe>IaLi=6$?+yeW9w38r6JMUX0gpQX$gl;@)tLO -zLhZ4{F3v|7~9O9Pk?|nIh$~5{fi_r -z%@*v#g45|$1r}=lnw?_@@`NA1`B>X1=(jkLK1**dF5R*gRi(XWEHvO{xV!8VMvAN` -zzE8TDq4P2Z!S==4zsc*#ME%X0vGvmRHd;Bu?a~M#zqddWzfMj@U;195vq6i`ISHwk1Zb% -z55r+HyR~#ABwJrF6C%HcP)nj&1MwvMmuk5fPMIv@;=?Zq;I~z?ESU!u+V0+!rL$;z -zVz31qdmSE5&uq5s{>21?f}es!At_qH1f=Hi^J@MqK)6^}Mn*|F>1M72S?zIDR{K0J -zZDi9J%i=!K^6KT0mEDY|?&YO32%Hv0K_+f(azs({loZ=r?JR?@#!=BB!B|gfoy?Ft -zU&{q1hc{GHHZB1}g -zRaS!1#I`y#IrM9i{d(O2$I#+J#F#7Ux$aaoD6|+%%T)-ZyWE@C76iK{`lqndA+jVR -zmFX9H=iDSIaS=GmgKcpw2|?LGVUl^cx3J%!g#sUDd~~4>-;Xt~_@4e2b0hYkG}P!X -z)C0~AIFHR$eft(K*LWW8NdEf`le|mt`Lv!smkL@SNT%IQ68%(gxV$3u7MW^YKGTcH -zmhhIWP$4Tw7ZCXLwG27PM4qQg;x4G%Iq2ZCK;al`prT`C_Xy+_xZswSF+GGWGN9FRMKELL*8 -zUk!+n$*Wd&+&ZQDAH~*%uvy)w{zkt;I@h-&-Gk%7DPM`UpLCVQ?olg}g|v@ntzsNg -z4e7lig>|8Gty}GjvaPgd!t|1yqzFN^oMD&G$_i+2x$^2cN+$R3y^fhDLMtH> -zE)HT4bvNSr7mA0W;!W@umNIz2kw`St7wKYc^hnz9l`rA_;cmpVx$l!Q20#h&cidyGX^#@T$^y7 -zrRu}?>}0J!d`6D>XxMlUF+Y+HG4y^p$!vjNYcK@dS4Imk&1ZbEwg#e39R^72P<7}k -zooq7vNlZBRt36vC6`(Q`&kOWt3WsUbuzv}uUM_s`)B?0HsXH{3X@=JG -zk_9||x`}7jEolYklX1mB^vc9bEAP@+sRNW}KS>CY3Cly!U^|j${a>bj2gY5J#fWeN -zhfvvjrl15sA(rl@WO{I6KL-6aw*P^~;a63Y=Fch!I>~Gj$HNlxaLIZFhkMtJp@=wl -zFPjk+X2;2}vbd9I)6-q!KT0Z%s1sQ7>se$4Asi~bi|ib$-L903xMF{d(yekHQYoX| -zgQjo&9z2p4FEiYoKTU60{{HFOT%*kxucs&d^-@L|BUQ#~5%I_)Q;Q4fkb6*Pfotj4 -zq)+8uj1upzGK=H`#Sr0@oF8}xGOw;Vh;_ -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 -zTLjCSPO-bUGrP;TAi^_)#N -zE5FA!pg=oZSdq#1qr0nLYKk_W)))toyRaR^C`74qhb5I=yLP`ZGz8bL4bqV0q5t==2YNjJ~=&(N!evIBtLZ_~vEf59DN -za0$dMkbFDFAiL7D=lB)~!!0xrw}30bsU!I*^tx5(qP{8zJ|w-02Ie>yOepGeV@`?? -z(#XU;bA#!?U=n!Gf_CK=+~-)*Yae=L*PpWwi`l_VwSI~9R{+|L4`R+>@iOd?p@8?& -zx`)k@sowfA1QBhYW97#Ulg6L^P_*}4P$ZrS) -z)>7+D5}%y_&&Qx7%$lRB?D|cM+D}D!QQ@2aH(n|~ZjXjH2>J0)j6c|*aJ;b@mL2%l -zpMdu2GkpmsG9WXVWN60^KD+%!-;TGQ2G@z?<8?=2WxG!QgjWIoQosGCIgjcU64k98 -znbrP%prQGbbVap*2X**3U93VbO}xe4$B23>h}($S8K=9<=!Kt6rv{hx14ig}`CCH5 -z1^B#K0|oogKES@ALkMjbToCH -z=rW}Ole!^`-D~TJFf1ZQ -zG7mZiwjkoU^kec5@pML;dE4_CPfHn&ej{krNwWN?d!e4CLg0bE*5X;Ua6=3cFCC*^BY; -z_fH;S*9(vbsld^VzQv2q8V)ZRhX{U{~Sa{>@SjtOcHZFs_;v}eCQ+x-c?8Xa(< -z{Y-)S_BzM*8WJsK?)pqb)K+b`RtJ`T);1~l!t?^>Y#5mg;(at1Uf!j@HuZ+~^UGLK -za%MJ;atv06w^H{92v_kyYcKVqPpuF5BP;at6}$2loXF4rjy!n|Bcp2jhXYAc`76Lh -z+@iyts0YMS1*|zTC+%TQ$|}u8j=;@^ahMa9@b5Of&6F|a7Q~hd)RW6rN{GhWjDRYH -z248=E{v(|fK>Xi50(;|4SSDq!-nezaM|B$CCG?ak9f@bF&5)pa>Hl;0w5mYGLJsCO -z<~0yzR(%m4on58-HYr&2f23JqKBwz=VRNFU_5vcIRPuSZJ?si7G(Q5rk!yy -zANb;U_BkRtgK)UQ5tZTxaz0OfJ$FU?3kV@Iwt{r)29ln}ZkUh#(u*IVmkkyS*@h)s -zE3S6_F=CyIpS~%j+JHP~gz?qnlW=r>fLAa6F2cq8{!*5!hcVtCDnBYi{b@+1dn4C5 -zInD&=RRND@9%cBUD28`GY|!y-rR57nG`2?}K>4I;vte+3&f@GF_WB#;AusKGJ$MmsUDvZmRhhFxk1i>v{RmK-I)3$#b--K*%iH=x9jA~ -zVVB%Ss4926w-CkH4_r4bCa*&MN8dpnXn5LevWNusjf)Fj0tRG|`rD>QEOUAF=<+WW -zGQ)x3Qc67rqV<}QsRzPaq_>Dt#7*W}J3%H@vzU?0zzE3aBD(42mg7b2v17_p1@c=n*WV!I%<-?`5}`RD6@9YmEAXFG@8~J_XLoZFv}^ -z1SeHm(P{B{1(_Zih|yE}T)ega6{F02#f>{E(z@xV`JYzZwR{SdwKmWoJ``~_TQ1P= -zIz$SebFPgUGv`G}q4{E^rqbMiG#m;Olut$fQ4($ukfH>g7Cv{Bhg+I8KI&>QB0vBJ|;;xeu6sdlcM^d -ze_{awL1`k^)Zy>qAFKXJYNZkN*KV9?@TtQ-s&@JoLQ6y?vZCapl -z6YY5c!rQTRGJP$e0j{BN!=1nT4a6HIS9U4URyiC5KPAVq69t3ZJ?od6M;nNTC@2#y -zqS~u?z6YJE$aA@V=yJyKAJz~%sq)vuxww1Db&P8MRd2IoBwm#w9QEzw* -zl0(%L8Bouo3Z;Q~|G?pr%`k=c -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 -zRIzC>qGkF=-}2wmoFt^U7{p`n?)Ep5d -zJPxZK27_D -z(*IL7HJ1vCkl4adboI{uXh<16=&>EtvP75rvNb6mprJ8X7&O1Lw^Sc8b9q^<^6UV( -zlWe<6GGRodW+FW<8NXkt64j!pPVDrCf -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 -zLn>x%_@+bFuuWaMPLU`=WwP_a#>* -zqXj;#PKNkN`fsWa6fRrfo6XDnO9Oe_7dccO$qLR8y0sf+vuFj!4E;6pEJ+{t|9d_D -z^*IzqX}LT;Kiz%GGWZeNBu{&J0ajwYwAbuo0R#0j_$tT32|TH+Q)$_UM8NMs=_vO5 -z<79pg1I)3P<4lTUp$7hx0i}kA5z6rP`5E`%t!0K=nycg93&RaBH?R#xR)qq(SJkd- -zlobgps4Kfpq@$F}2&vdB_^oMr{1*b+%)>agxL5-p`_jKx3!k2lg=&y?*12OC1NfA& -zjuP>rg%AOSX6}a4m-APfzNxIM5)yPIV#^Yl*mA(H%R~}1rnSO3yG$07sjsTdI_9WN -zYuwQw6B^&oMG{_JaUU4(fzrP$sb4`!3zLdHRC}hmo8?Pn)B6RPuH@q$?-x#X+`o>O -z@(0*UgXyWjSaZX|eO5|8@t4ZhN;i(qJd;}3z4SeSC_Z+i#)c$gdP;n(CrD4+D=$=c -z^mDq>wPpa6iX0pENq9xLIgq1CcI1ep?k?JT-Wo<&FK5;sKaJ3DnT=%OOCL^faA+w!R>S8}>%Dq* -zO(l=0U0)z(o9|n@PV(-!8ejZ#CFzF1(qY8oOlT;$1g=~v@%t?X&gUabPs^T_(@Af= -zzh-;y59-6wP|#dVyz#c^onh9!gC>{64o@ -zds?&>w`QZisk5YepyA$p<;8f5gmp}mCH^O2pHTqR>qz7}eCIXQJ0o>~4WM?DYZi}nUB>da19QhBmXRgl{6PxIOek{XMD)U#P-wH3? -ztM(|%t6g^J=a`=*QEHJ=k#e0frOGJFJ4WuMQL1=F|JzwJqORqXW~VG}&O%7z?ZSj1 -z!Rq;G;2bH;(c8uIyimXr-y^_?dXDlZc*8HJhdiTTHFp_@NEe!b^*W -zcybDSu@Rq6v_zt8ijarqa`wge;xk+cE7Fg(s8f7fEw`e@%x)1pyi7-E`7+NFZuCEW -zXK#?Bt8Tj>zGc=ZTXw6#=Ix!LNd#q-~DcGHqnd<0dHP?&IqP-FZn!Daly3hJhLIRnhWpys(? -zLR-1#ht~dw8s@bIOuIP(^7Z@9N_%SG)LS0#_q3t5n28hYw32NUtrYxpd??+qQ1}*^ -z3>nhcb1TQ{lb+LhCipmk^`J=04uig@;8a-uyYO|| -zx@b9f>!J0FVZ(%Wzj@w7*|GqFY$T>)vGmsLN`5r?D}ebXnquir=w}Dayk{rmlY$OkdT=LLT1^v5>ZF^DEhCD&xI(=fV;O?3s{iDR-ZF>(N>65=XUKsh+q@{AA_8Zj-GL>`$Z8Le+N7Q%1EyouiPU -z-c-#wG)4-xSy54 -zWL&O~=be34gk&1wjK^G27aMgvOt#!K#4xXoE)}V+lX!1ZP{`9f9C<&pc%R@~E31#M -zAh^7z*CqQ6%s>g+3O`bCBx2MP=gUFK6tNp7A$!%EBM)$pp65GP9i;i8v@3p&@nuEV -zO(MO;3#JC<*Mr+OZz1}3`S>iP%^)b?f$>@$ -z47hR|leSSn5Ea}_8N{qsUl83GJFGcPN5A^fxP07`$Bg`lrSbph=7(N~?4r`p%smlr -zk=CP3PDznm0)L-}R1?C4s=_6i70566M@m>J@4?aK7yOPU+#WnB`sRW-q5DGAluT-x -zTvpso-sxe2By(ZuTqtavCjh;i4tDJX{=H;imc}^>|+Rs-Avk -z>{J>so)&25`@t+|i531)Pl{|}Gt`U5D9m?)MSZ>#9%Qu)=pGDTnq9(i+&tqoYb4%|Z_-Wf#DnXm4O|?z~44Ft2V5j-< -zo$4|1+Vq`GDPP|RQ{vL13i>(8hR{Yzp%n1wjNi_C-lAr#gH1C>fCcAs{;%@KQ~f)M`A2U1jT^>Mx6F(m$#B(BUjdCFkuY;f{~tN;zkmHzo-)F+ -zj&dg@4o$y-bA?1h!s$NKtTOZ2|ICx&9Ii;HLkUy-`|LmSXypGt!kAI$>bbXluBe_s -zefEPtxl&2a3%MS)Z)Umy3mX985u|dPoVK@voJ=&d6bv$S>UlIQlv0@7wzshJi_pl0 -zSXqzjCa!p-^mQqVB4?>3;HL*fHtAhyC)=R5G4Xv~iyFDtFDhxDMdxHelR7Bqgaw~0 -zK@xfeTH4cB$48gHe=oIZ3BqQE)Suf$*?>6_xZ&wETvJqPTGR68YX -zTd1?w^_N;upH^&rwHLninZlG$nGvI>Lc2@3cZKLdbs!wAO+^cF84?q}zHyI}QV#6j -z#{Q%kE+z_?RWz7NDoq7)$}aP`53uAtyd&r-nP@2CQcP6}`{AL?;O8k`dmqbFMXAE1 -zK5K1BA0i3YeZl=YqnsIvdQCjA;`CB>Q_%d-0ox&Cz#9rRGBsGCtq++!O2D*U$O<~h -z7?K2o@@@1`#Aw7M2b{JueV;+nYWX4l6gMnLMR*z}-k7)x+HmUJO7(Rxh(o*gz*)>A -zFz+L`ri|X+cg0n6B5HVBEbJ!KkL2^}{gzP=-fh -zQ=fvF-KquGotkI%XGfB=G_U^sA$hbt=G@sbp;3STnI1v}74zi<+(l^V??2RW#NH2H -zM$XzXOJB2gp$B{(YJv3#MXX1tIuF?t-t2|+mJlZl$r@sl{NZ^?Z>mIS6rPE~-&)j8 -zF~TrihL=5IA3OLN8R3^D)t6}wXy54H_idEh2zjQXXPhgPqYs*a8!rv`x%|&n4Ho2e -zig2~Z6k;VbbaPG0t&NKeuulN; -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 -z)6=&JD=dO959(UM$eP0 -zETp*kWdV;>e|+(5r5}E;1Vn`RNR1PI6NQqjfeA7RYT50iUybGDViR$~OA=O+1^P#K -zYu9k`#p49Wm|SkN=~WwihYPgW(h_%l58rq)%jtMDS9J^TRXqEd%&q_E%)yEKoQ(h;L4h;d9j$c#{$cuJD%XQL#@8NVfcIAcZ@ -z;3HJQPBit$`kaheyJuMe$k5!?YPg8d4R1DQjq;fEnyxPkRMj3My;WQ`OP=1a!+BUX -zKVq>EeE7NaY2$`aRK{WvcYJa4qbSE1BeaQ)T%i?iSx>o^kS503u#uq1`Y!vlPIhU^^Gf-Xv;`R -z*}J8xE7-?H^ -zcxg`iqD{7KmJ$bzFIB$ONN#e}$|#I>V@DB>+B9nJ;Op(Thz+-IQ5LPJ%>?NKX&kJivA5->ukanzdRuSmT3n29NVYbnnUkof~>2=j->-XnR^8zwaMNhcj5N&Z6;}1pOce -z4$nv9RRo6QxWtcr+`0+AgoNa02En>Z?>spAF(&dW#=e9hTxLRHd%6^0-U@&gTXXe7qR2_ -z{QO0#GuYplN9`)CZPIC)69Q&r4Gj&?;#0zb!P*hK0s-63OOZx+RzYZJST>uWttJdlS(Vaz9={hPs&73SWm%3grFO)c -z*vwg@zHBI-lMP@^{oP_`GPkR?uP3OF({LQ~HhP$m(?*@96u)Wd!cp^~MmLFiRiZ!c -z`T!dns+!PlJYLJU>Z>QWa=6#tB)KoDIB095EWzYE=8HlTM1^zL_&p3e3U>cAI$S0Y -zIH`|cL8SiYL+`4|ETrYsVO9rf#{IRGSZRCEp=aT(jWgsxV(@^+V0nKs9QFm%phbdP1@S3+;!~I^)#`~z8 -zrioi}AXiEjs~u<<-(+g+;3n4otB%a|0||t>nVF&9^RGXGI8G5xL@`wxDw==YCJ(BJ -zM@s`5Z^=Wa_;SnV^M;h)r@F4)HgV^|$iP&MK!t#IzrcMN|21bHhjE0K -zjon|ehcX_Ut5oXs@d9)U*{8kP{)q(`Jy*C>D3?A|YugqM(1H;P9u;^dX3e$b$=737 -zMy0n*`9lA7D-IcNPhl!R^&RU$o-AU22t=+!12(}~@BI2aY`;x(c*9=vrY%8Rr~DY% -zpn7~s^E?!6`fIKjT2$JFO-@cGyPOz>#t#h@1#1ED9fr*7T{$AI)1W2brVXF|M|*D` -z7xUh~k82E-B`qqXOw&fGtVM}wC25hSy^y_CqfLp%k`_ygB9ZKwYFcO+$sSWesI(!K -zr4>;j<$Jw4oa5a0Ip@AVkH>HQKEC%q_Z-dgp7*?7&)4&LJ+JF}9=ljXV{HjPDub8G -zE74+gcMNXRh2g2;Wy(zg#i2Jq+{zSUow&~P14Zi)ro3cz{2(h$qKS$FAd69qnjs%! -zh)0ueid19*P+uB8KJh -zHmqg0zXXoDc1?CUpK#-p6Q)xsSMlnv;j?Tc?tS7UZM&X2bj<5aU!J_oN -z3>#s^=mrQxfV=93`5ObD)oz=Jp(;}s;{87)8NZZ`XqdWo=IXJ31^NyXeQ)Hg!;a>U -zW#kG8P?Ui7ste3{PGLs?(=}OeW6$()e+QIP7z{c{TH#%^**(|IwKrzQ91zViq}h{& -zStj_19Ab!@t$OX8?0KKL=+$jf&rUNpjOP{((4G;$usX5dDR -zRF$O;XESi~M$*U5A4iEz-qKQAEKk=i+LlC?<Yd^A`Rn?7hdNH;^Jfn?XBF*3X&y -z#WYb7^3g4&@1~0Jwo9lte*v;|Y?${rbSzSEPv1|YGw^|ec>4UXa&=mx0@_jsXC#fH -zh>@nx5_&XmjFIQ -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 -zbiy1uM_cbFO#Z^sWw>Wa?IM4dA_Im -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 -zR4TD=Vlpo&MLEBdGZuC+^3I=iw-8=xeoknU)j -zpXeAW&5ATH*HKgI4H|b)t#R1z)_@f=!iXa9$2(gr|1q&KJTk}N!1&+#r_ucVHeVga -zP`4ol^o&^ZR$0F#Tw|TF;J&}iqu3ZpRDlyvS{IwfaDtw^Arv;m{h|NqwZVi(29>Or -z#PKB5&+a!~{KVpe(WF|$MVaVc+mk8scilOIB@B9CX`E6%z|Qa)!3N=s&%y|9cSz>E -zPx>*}MHeD2I)q?u&O+?Biaa) -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 -z7=v)b)+F^FiZ*Gha)_?u;i -z6Z&{=KfII9S)T9)0GNkHp*`}@BL*tAH~`G3`895%NqaXBz8z+PtLkXjzawX=osAHV -zbs80P1$BA^v5}kpxSWohcMg_?r6OFKD5W_e7PjdaMpM -z0J*W-qJ+@)=931q2yM$~q0fN-+7*_a+sQB@YdMbZrZGI$Y1u0MYm;MTK8+DWCS>)) -zN~wzo2-tXcAqUz>p|vQ;lIN>&cQc$xxQS~ue;WB8Ht8>Zqs%zj=Ra`+Ft_E}M20Un=G5J%8-%-SPO*j%`(N|CjCl`zEIS&^y5> -zg;=mxgH=f!)Nk9~CEN$0Ln+r`sN#7~z2Fk+iK0*OGl!FH6eE5F4QRqChVJ8d3^y!W -zUR0B!QyGjhl9;XcZ5czfA=fJ}tjPQeWfh81cP-Sl@S->Uev&^tJcNHMHbOqy-kLVYc<6U)F|2H&;Dh -zjo)&}aS2Fy=aDrBa!A{B$Z=`c#Lc@;8MK&8O)9Y;MKH;mo2SvW$2ibU5b=JYIqx(; -zAUw28RFFCgUBLVMRx~m7LV7O|aVaLX2@qMp6N?y&%5xzu+7pnw>Vc_20E2!s_B>Hm -zUnGzKCeN-x$X_O7czN`kHvmz*K*zBNyx@}sE0`NtpZgAdByD@*Er&tPeH|cVUPAHY -zMQ7ckyS|W;+ZhSL#}aEP1EZGp0`3kLP)(bCZIHoPNsJvQ|F@@cYF>H<;! -zs@h)hy*wdS=Dg?k7mfrH>j#+82{a@JELaEdZhnbxpA0W;rycrDJ{qCM206GE3Q9N7 -z#rLUEs>iae#OyEoT_7}l5b5q(-@9(5er5NKg_s4P74&|UinWs8qR)-P36ylhlh~b+ -zDG!)`tX>Zeb0-Etv!152Y%?Vv7pF|FhWmX6s)ix-8*R9v(t8*Nmkoy)g6}lEC?bYQ -z#O?=?;uW_aHJ=xBA9>%uy;+oxf}$QXY&tmU+}!zFUe{m*PwV#eU}c{{2@)9a*i@w! -zTuCn&aB;u(q4B6y`uN9~g5x&KkX>X_RqQr64y>yFuIpNUeRssW@Y^m!{Vpe_kyaihDc6| -zyUWxpGL{}YAfx?2*b0s^St=h_%;jFC^;KLN?M_Z~b<xs8Q%eJ56_X{`cNv7{f}KOzD8&~whfSNa%-BRgkfO#H=3 -zu-u!WXOI;;-9%{vv90g_^5%rgy3BBD7jc*(6evQ!hl5BP(av@GGg~;6&-j!7wH*+v -z9Q&3q$71eMjHF%K@*I4M0BrQj0n*PuL#~j49ncHAn4(2?SEhu160>|bqGz~?^E#wI -zPOqoYhW*8v`i(}LueOP-*P>Rx#zevqo}^|l@m%k@?Q>UDU!t2%w3`EK6&`;+R|Cwom#;13YS -z)0m#%mbOs$`Lr8fG4{QNFety?UK+NZ&XR_7=XMV;j&`@$2}4tF0HJCqVjJ4_P^h}Qjib4sc -zo(aMv4H-t&QcArg@oAS|xBmFeM8zPgLWz?kLh_ri>xFVNjC%5RJut0?HQ)DDQY+mV -zUc#R?Za-y}fSDZWs&=r~sFPj*Db@&Z#C8#DM((ql5IWhnv|oTf2avC|>4SPH^Ar#C -z92GhDn7#4qe4B2Yz1udW*<}uD^coQaTzmeNrJ3V&{N6Tlm{o7Ia~5;E&R=`TWOroR -z2^L`WHE$z-^^yjxgaae=LdAGVp*M^CTAM@;#Bbbi -zn^>c`@m#NNMf<4cfC+f-Y7TMkB467>N*(F?s>B`>r^XXBw1XIE} -z)ynZvidexcOgsa4maaYDHIG;E(sRg!&&{A+Qj@vVYSeJfk?NeRuzbe-wF?HB55h+t6eVatw2Esv$Pi>P=uO9vN`-*eR -zd=31F1@lo`^)Xi#UN$avwV&gjl{&l_s>5c*^fQ?$lobhFN70Q-u3RVpj_%>|Dzhuo -zwU9{RK(MWab6_*aQ^})Bt_fu1o778HH0^RZN9_YTigmEx6rO%+J -zo$%kjOAs9z@-`f{Jo}5QK8QxlW`jLfkJ!IX-6{VPu*aX=5MLAVqC8^U3&k>_nGdcE -z=^N+RQ*}q+oBZI|X*X;}YdkhR`PNNwY7{Nfa8vYc{oOXcRz{-EM&(Usnp$W&5lN#g -zF3(%yQK~9*G&S8x`ed|{fWPP-);qHiD-BNZRQd)V%s)}ym-6D{wyWpIUvu>l5?!K^ -z$~U)YV^FBG##^cMtl09~=mJ->C0dpRW#r -zLe-!RmtJtSK9#RNeT%X8YPRFvVeA&nB5XM$qR5~bVx(jE$%#C7ft8t7{@)U_>>E4l -z3p%r`H%b=Vx^>GJ=69b-4oko4c=AE?NxVs)ZEO*OPf%}%L;t<(0Z|$w?D`uDl%dgV -zHoMn{-D4OwjO{prG|LCa)<|o*W+YsHPR$iRa{Tyy6x+nUQ)1VyT@LrX?J3l&uBkmQ -zz7n&79qTvw>*5%RScwDlA{^WtBz6pb*|6`xf$amsuP8X-I`bb*JT^>#LKPyP`SU^Q -zcs6?2y2wAo0=pVENf&N-n6m%Y-f@R>Q}o0!F;9Y~v-9V3e}7Tfbru!vyQ;xYojXok -z*Mr!8GE{-{7jLzEi92(1c)r}feoPo+%LuuJ+iEm?oknzh{v5PLaIV9{5#xD!2d@wN -z^9B9*5PN4vYr;-l|7yOjheExczT0Q#Mi@8DoH0YR -zz~_YtxtZ=G;K(VxS41`{T3XZAW0fyuzHs@22X|8RLYlyWm^p9Wktc80D=}8>p+XoS -zb5Kq%>d~V|lEFLIBu`D{d%3B~mRG8>_J)U_>RI&l<;#~M7Tvb^q(jqY%$)g1gLVGg -zxiaSpTk_Eqwyk{SSCOlwrKOrdd51-56&~!Qq@)QGCq{nxveno->(={il(AxO+uNgC -zTU#f{$awIL?!3h)<^q{Amu=zng6JAK<1j`dzTfmUv*!p*H; -z6Gu&D%^stEgCEsEjWE|Sh^;>}r`#ZnA%Q2DFDqF_74map#MXQHK2)ek!W++C^iJ98 -z6dv5RIsDu-w&>Dw+bpbq$-`)myk+Cx1>ee~^7f2ym!(kSMo(qM$LqulV~@eLO$=Y(!omMD -z@A_<|L^%gG#^e37?~!d~)hEsf+KxNZrEL4eYv&!yO%ZiQ`jVQ?X3eg4o+uRfC-5 -z8yE^n-m5$*{=(ltB=SYXF5LQY{inXZr+HVrghlPG8i5%30zHHM>J*`&3CUAE)5U+; -ztv~lb%z0ep!-i$%KmXy64IRcX#%~dIGhg-J_8|80RxF>7<@YQ9YR}nV7^eF4j=py< -z?g9#^yH?l)osatOUkxp@yI^LX?vbe{Q7hG!jfr|CiRw;npnK$r*CaD+DWH}U!v%!{ -za(a_c375JD+!Ci{&!Pmrd%hpyk2~NV*}WF9@H&GZEH-EQjx-*RH(}DGsM^}v3376- -zgZj@+v0)8HC@U+wty)DpckbM%XBCdPSbN9Rp2H5!&CN6C&*y!LF5Wqeoie(-y!3HhF+liAO$<+DsIoVi?inoP+GC* -zTc;m4N|nJsc~1=)$tH6{F$UV*5i6`T{Mc!}*Ame?_^vs4!X1+}Gbq%{STddT{KaBR -zXgE;9-U?CnkR>tt)99zmTei#FXlSm0%Iwg5?5+fSvR!wISr{W8Z)?8v)^uv3lvRs{ -zSBoUooeU~X{QSL_*&>yQvCG+vOv5k+3oDytEhtWlIf>u*R>eN&JiJHSXLZX}`uNjv -zY0N3M??Qy7Ey^nXG=U$%={}!{t)G72MK@InXKnS?nHbdiiu>Q~^b@#Mdt65T=a3Ud -ze=2L$k{B$waNB25%ajF?V7uLc;rWWTqxcSC*KXBm5G|_jM;;?#C+PyEK+W -zOXnb6tI1tw)|Gzb1VfVI^ZZKWn};Xv0@q#x0t~am)09G$k`DUz*|(!6M*&%T7>6!! -zf}i>S!d|RC+(b1teY{G?2j;mT%1f${D$)TN0_;^-J!kp -z$@ykl%yduV9rOg%l> -zHEY(WeH?aP%Ni#V-#&5CA$s7K0BmuFEEcK`D_==~5{;$uOq8+}o9JWNy-U=~^4MSD -ztdA)t`ChKdvgT*_oe>X{g+)Ls1Qt -zr}P$D#@06vTaYr0eFs-0GbL64J9sDV&1k-yK8?O - -literal 0 -HcmV?d00001 - -diff --git a/components/adblock/docs/settings/user-setting-sequence.txt b/components/adblock/docs/settings/user-setting-sequence.txt -new file mode 100644 ---- /dev/null -+++ b/components/adblock/docs/settings/user-setting-sequence.txt -@@ -0,0 +1,14 @@ -+# If changed, go to https://sequencediagram.org/ to regenerate diagram -+ -+title eyeo Chromium SDK User Setting flow -+participant UIFragment.java (UI Thread) -+ -+ref over AdblockController.java (UI Thread): Extension API and unit tests start from here. -+ -+UIFragment.java (UI Thread) -> AdblockController.java (UI Thread): User event trigger(ex: onPreferenceChange) -+ -+AdblockController.java (UI Thread)->Adblock_JNI(UI Thread):ex:JNI_AdblockController_AddCustomFilter -+Adblock_JNI(UI Thread) ->AdblockControllerImpl (UI Thread): ex: AddCustomFilter(filter) \n (jni binding) -+note over AdblockControllerImpl (UI Thread): Set User Pref -+AdblockControllerImpl (UI Thread) ->> SubscriptionService(UI Thread): Add Custom Filter -+end -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,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 . -+ -+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 "adblock.test.data" domain will be intercepted -+ # in order to allow installing/removing/listing filter lists via navigating to -+ # special URLs. This is used for internal 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 -+} -diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn ---- a/components/resources/BUILD.gn -+++ b/components/resources/BUILD.gn -@@ -97,6 +97,11 @@ grit("components_resources") { - if (is_android && safe_browsing_mode == 2) { - deps += [ "//components/safe_browsing/content/resources/real_time_url_checks_allowlist:make_real_time_url_allowlist_protobuf" ] - } -+ -+ deps += [ -+ "//components/resources/adblocking:copy_snippets_lib", -+ "//components/resources/adblocking:make_all_preloaded_subscriptions", -+ ] - } - - grit("dev_ui_components_resources") { -diff --git a/components/resources/adblock_resources.grdp b/components/resources/adblock_resources.grdp -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblock_resources.grdp -@@ -0,0 +1,27 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/components/resources/adblocking/.gitignore b/components/resources/adblocking/.gitignore -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/.gitignore -@@ -0,0 +1 @@ -+snippets -diff --git a/components/resources/adblocking/BUILD.gn b/components/resources/adblocking/BUILD.gn -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/BUILD.gn -@@ -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 . -+ -+import("//build/compiled_action.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/resources/adblocking/easylist.txt.gz" -+ url = "https://easylist-downloads.adblockplus.org/easylist.txt" -+ output = "${target_gen_dir}/easylist.fb" -+} -+ -+make_preloaded_subscription("make_exceptionrules") { -+ input = "//components/resources/adblocking/exceptionrules.txt.gz" -+ url = "https://easylist-downloads.adblockplus.org/exceptionrules.txt" -+ output = "${target_gen_dir}/exceptionrules.fb" -+} -+ -+make_preloaded_subscription("make_anticv") { -+ input = "//components/resources/adblocking/anticv.txt.gz" -+ url = "https://easylist-downloads.adblockplus.org/abp-filters-anti-cv.txt" -+ output = "${target_gen_dir}/anticv.fb" -+} -+ -+group("make_all_preloaded_subscriptions") { -+ deps = [ -+ ":make_anticv", -+ ":make_easylist", -+ ":make_exceptionrules", -+ ] -+} -+ -+action("prepare_snippets_deps") { -+ script = "//tools/eyeo/snippets_deps.py" -+ inputs = ["//components/resources/adblocking/snippets/dist"] -+ outputs = ["${target_gen_dir}/snippets-xpath3-dep.jst"] -+ -+ args = rebase_path(inputs, root_build_dir) + rebase_path(outputs, root_build_dir) -+} -+ -+copy("copy_snippets_lib") { -+ deps = [ -+ ":prepare_snippets_deps", -+ ] -+ -+ if (is_debug) { -+ sources = [ "//components/resources/adblocking/snippets/dist/isolated-first-xpath3.source.jst" ] -+ } else { -+ sources = -+ [ "//components/resources/adblocking/snippets/dist/isolated-first-xpath3.jst" ] -+ } -+ outputs = [ "${target_gen_dir}/snippets.jst" ] -+} -diff --git a/components/resources/adblocking/LICENSE b/components/resources/adblocking/LICENSE -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/LICENSE -@@ -0,0 +1,14 @@ -+// This file is part of eyeo Chromium SDK, -+// Copyright (C) 2006-present eyeo GmbH -+// -+// eyeo Chromium SDK is free software: you can redistribute it and/or modify -+// it under the terms of the GNU General Public License version 3 as -+// published by the Free Software Foundation. -+// -+// eyeo Chromium SDK is distributed in the hope that it will be useful, -+// but WITHOUT ANY WARRANTY; without even the implied warranty of -+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+// GNU General Public License for more details. -+// -+// You should have received a copy of the GNU General Public License -+// along with eyeo Chromium SDK. If not, see . -diff --git a/components/resources/adblocking/anticv.txt.gz b/components/resources/adblocking/anticv.txt.gz -new file mode 100644 -index 0000000000000000000000000000000000000000..5c91292b46617894f6ec7977d9ce78a0d66c9216 -GIT binary patch -literal 66744 -zcmV(wK>gzrrRgqeDgOreDnwSL((I?bD=C_7ivi()xpa60smwMS5sc^6tCfssE>E!QcNJ -z1*5$y_2=l1A$0kTNySBWXdmnyJlWraKL=0tp#4492|KipU!U4To@nR=jcmYQ)*k@E52npdIABGY;$g<9 -ztpy+)M%oUz42pEbV1USMLIDk)1xZ197WQqP*$NP_1EQGf+d5)g+FFtzhR=^}l2HIN -zwgbsE4@hEjr3$9(p^)|rr3+<91K6A<3}D%kK`4=0v2q$beTXyPmNSqB|Kw}0uJUN9s*ux1Z@sK!$if`P?ZIPsa%2)Zc)4b!&9v$m{9H<3n5pl|QwH|Ohbo>@qE#TZ4% -zopRb%Qr4C;rrXNRvn*%8ENQ=h_*O)&NNf-&OCShRXp#|k#3}oXEYu7B`nlyZP&JWd -zEYNPD`hk!_Xcr_kRJqV*>9NK&nxcxtFcViLMFLdUYn<7aO0$oq^&??dAV^Fi1yeCn -zPYXBUBi2t%5jw|05Zc$)}1fd)57L_yn{Xo{4zop=^E<<3~(%bGH>MBZS%E-F*MRw|-6X(~Rao$_^4 -z4kAYKwkAZWNQ6xAS&eRm?)P}TEF>7n8!y;=dsoxnTS*M!< -zo72`b8Fwl?S8WYtps5(;X=_zCQ$bnn)Cw)SUwsBGlWv2^Xs6n&s9F-3)ovXL+E$w- -znp#A->{NNs?TYf;)>0C)BJVWhJm_Kxj;!jgLiydV6B0zaY~WBYjF{>ohW`R!aOuCy -z)KN&TxNg}#AxT0U*c)NP)Pj=9x-L##Xq+6}oQ;n&{ptLdzjJdU!QH9dg5|)@H8^ge -zJCq_liHJM_!(G8K9@Aj8-`7rY^9*LYoB?Uvq%r*xZw{CsC+92yKSRjlB(Y&*d`Cha -zSRHFd($yvv6qbVJfNfg=1c*i|C+7LHr(kKXNQ%ag!~zRwQ%%Wu66Hrx7sgeg{f7HM -zSpp9z1gEoQZ75*Vy3w~@LjM~ad%s9RB00%AzJ2YAg$bjCLzKe7l-|aTSv6@8!6?P% -zrhE=l@_h8Q>l023v)VCeN>p?juCA_TYx`{#umg=@4j($;Kx)D>W&huZiwaxEtfWY_6a$wBlX3>D)oh0Xt?J+TfvLD%X*r2Qcj78~F -z+z80?0`w8`6oUgZbYzP*Hp;Wx6iw`0ndwlxy_!*Dy6e{r6xJMWKyrdrYmb -zza5WVbd5E*_#<~bxyDj1WCnH^potNa5@lg_L0oyTPhQzWk|?C&Zy1PR%E#gp5F!=v -zZ%b)jG5*iLzjL9ttF*~d87O+>lWcMoF+jW~y#lKYhLGtXatsBW96e};Stvq1kj^0w -zJx;BRj|6>&o}no{y>WI5;VDl}5t4$?1eoLH37IsjsV#X#F>1hl<=#yAH4CEoJ>%Ik -zFyPXpRj!SxR_r>%E$+IXfp%r_1dFQ3F&e+_lZaGdH&oa-2bIK9%roJOF%~0;{#7h; -z90!NkXmsnwZw<$(NDfQJpSyt1<*Nvw9&oMxu5dUQTMgP%z`&`%g9@w#;DKv;Kgii-^X -zcxmxDfD2Rt8f{PEuYYi3Hkp37AkOvWKR)a4@Bh>Gpp~g!&s9Wu0mFPw-*)q{Ln!=W -zV~V;Ddpi7hAP!LELkUWzluy9Rx654{d!niq>auUcR(8SZhodcQ-KrYQMh|Ui1=D;e -z2NP5QkHFfGwh|x!lfEF5!unEPq|W~Go~Dm4v=&ttgBXAZ4<8Z3Ra}?In{}{|m?2e1 -zgU>J_-6c3C6$x=1KH)Ar2bDh}>Z_L}X(O}l$1^;E1t9w!uzH{q-A^s}5i#&#enW8>!l^SeU#Bx-+7c5G -zjksZ1w>PfTe5MZ4c?qOl!`Rt5$VDXC5WWlyb9@PrGnn}~L^y>WAX-ktE&$lDp@ory -z_R*tKdS)m>SZnRMYKM)4vLffFI2uzSw89 -zfAD-Yh!XLLBtty8A@uW0nBmZ8yI1D5kH2803uC%dlEQ{ywj*F!!o~~6y|Gi2l-m~D -ze`btTHZ2NqvFZc!%#?Ygdk!qHxiNODF(Ryw7d#-`n=+MLx!Q8EesI;Y#Fc^ATu67g+JmoceCMiPE4MkFInm9-pT -zI8x&TA`p_)sXg8$+2c0zCmH2U)Bc}DDrxhcnB_pO4Bc?NqnAVJ#R*aZdjIbURn1xii^PsST{||NtAJu@k -zi>bxUqBfldG;{I(wL@}V3dBP}EDKyjj*1)#*Y1Zo!qpQkcKxv1W^x}CwFf0K>cg`9 -z@k5YNlW49IPsEQNk|eeW&fsl5 -z;zN0~H*cbrFKxQn6}0CapyiJerRNF!u(6)DNAT13+SO|yoGHI2J`u!4jG -z-bVp(^1^p+JiJ_C$2O}rRs$Wp*)VZ3qOB4T9v)3p<0N-{=Q5;pICuiO$VlfCxVSt> -zSob`RmA8BybKITF)AAkzc+u^s#@C)7foJ#u1Jy?EW01d -z1lKSTM89Q2{e7D2$$3r!W}X~k)7<>CCk6GYVYX$#LDL(rSn#adYsqCmXT%kU*&Ng$ -zdw|zYj-B5x2lmcxb>s7b)61Rm+9#B%xI=-0U0M}$JJ$_REnvnvP^xeIEF6M1S9^Lq -z4a=Lp+0IqZZ3TQh86LC -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 -z(Bj6O_zXASTSe -z2F;aN?{xLb2>^=Dq|QFaLeZK5$&_{#obCOGQJh`C2R1VX_`*rSKQ)#!h-EZgUR^yU -zN}6&!WY_tL*4VHtt1G?jlm^_584OFQgbK^fO;jLN6mT>vdOK}o`B(nd>zM{w^+=ib -zC4q%kQ_|pCThQ9JcTpqU;3V8~#sH?chby`zku4P^#(LG>H{Lv&PW-CVB86WUny}3# -z`npiOmH-GGLE3A#?%urh+O3<*MfKA`DWPfK=H~Y}kuTubA~;!S1*Ag*fToK&R0BXF -zXQl#CRT#QnAG=09ZS$4XRN#PfgsEg=_8Xtz9YYRK&D6`sc}z;;rUKNkV#B#(0J@L>Ht6NJv5=7M -z+(Si;$X`t0?hD(|AXrTBzMV5v$Fdd^Iz%4eZb6~@WAw7QutRGnu@|?F=|+UF(<~%} -z5xL>TnIBDkv*(=Qqrt29XdK1qB!CO#xer~mvaG_7XthVbbN1WCfn7YWr!Vc^e0i5K -zn%%y+^ZOxneX__{G+Z5?oNx_lW_3f>JRBq}mgl+FRB%t4phYJPX4w|E;i@Hkf+)wEO14kBl -zf$x&w^2WMHNw%1K8ZU@*FHUwT+lk_hg#^(Ks2i{Kqme@wR0g@$iFWEQ#i=_-1Tk(H -zli_Ta=I)-u)|e -z*|n#nnb~ZPwT$$3Zb!n*i~IjUE$w6`pVmVV`tGUnC9_v0ozZ+cw13FZ$QpfMHEJrx -zk)C;Jt`7D#*O$;|2GD_iVwq8rI-X8Imm~aF@#ZQ~ieN|z$Z4Q1`N -zTKeicaXEnx;OZIL$0~RY%3fV3qgSUF>d2{@sC}z8t*@tOz2umxHUUEkEqvUn0MTCo -zp40UCn(DM}e4^<>19?j4Y6pnl(nH**zM7DRKZWtTO^iDRoJ)AcaDdj|tNaj}U)qhG -zexv$P!gb77&HzPKLsloxGT@FOwpMi&v$NbIZjWXM@`d8%iF=Q8fWWw#INjaVmC4E{ -z3gI#{4TgPw{ddLzue$B5yzD$fAAr|6)UD{bx(cMjO!z5Oae^4Q2D-(; -zg=VtQ*-2rd89~v8#}7#b&|z_}H+BgHTnTChED3+ccvooN-RTKop+6saXIHc;ay6F4 -zqdhslUmu%7*oc~D#)V|{mFyTkF6~cTVNbzsMYrc(7FJz&yOx@)mCfKsOS%wU-JjM8 -zTa4wj&juY%xtXuzAXbMkpKYI}XZ&^%DtMWq|K;*3fd=he7$6I(aA~wWrKd=)e9EKO -zBkIM0D`=_e!o58{JQu6-$!ldZZ{RS< -zv3i6g?fH=v&~CO}v5J&CbpB9Fr0=23enT?tX<(l(rcD@RB(kQc&~R}?dEacBemCoO -z{aO_Bhc|ZGg#*qK7auFgpV*pTXnY}X;`qp?R!T#h!nGvtgb2XAe1uyxPUM>6Jq{i9 -z)uK2~Ud|&2wk~&W@7-8Bj(ItDbo%8;Eeui}xFlfcm82{N8N*Wt7P4}Nb_-gNDjr-T -zM!L`#Q}+V`S%qE5R%@IbaD+t|PDIm7S<g_r -zuSg)-j8g8&eWM*!k0arTXZ;bv` -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 -z{2q2nvIiM6B~F>-DQfV0Jw!Ecrf4b -z|FQRPO_C(Zc_8{8zap8{vNc^ykF31Zt$S;xt1kmI*thNKArH4vlVag+5#br`ZeDKg -zaoZ$>AV}^K;GA6^AsC1)+)r{6L1aYJ#NxhUy&{PrIqm9inVj#>)<;|1e<&q>GX3DmP -zWLIGpUJL>e7I?t8$tIRWa;40gL}<6C4;od7fEq}S%2hFu3lA=6T&S)Ghr(|C4A}+N -zE~lohFPoI41d{$rCz8dGR~=uCX_81V*DYXFC(?ijis5Y?ql8<%(p73oWS;iV%;ca} -zv%-QT?zEp^ga+!JjpSR7C?f!t%i#r?pGD!{Q=-EOwD%acgRBDgNyBci0%Mhza^!FeWk)S7h!_SV -z^Tn{Rq%rCa-2NNjXlQkan+N;@XHxC75$m48{OM$+n3RaIF+@9a3{;|Mh|8u1Bv#8O -zo*v@ab*z^6AKFU;SdEt1fr+AT+h{r`W@@!epU1ds_jyxv`!%4(avVKn7CTB5{iM# -znpY6)`0^;$#Ek71(85%iP2g$Ry+v`COwq7!TR+bf5ohHfl2gr&ZD0}tAmr-+Ri`o- -zv85#ajrcZy3IHSa;>8I9*<-K81h;}oB8IKvn20o6SW`%VM3H#CdjQ%GSdJ-Kk`mRFEA*>l~v7 -zxbj!EFqAvY0s|u&b_~64e|G{d1GUK0to;owyRCWUV#ZOTyaWlu>{wWWXCO7Nq@o^A -z1lT^6N#Q7J=m70dtD+DQv$&{Hx&1IirATpgBQ~x(2@VIsVIk3!HbOs?25dV6gQ%u1 -zUH2gQUsL=8nRW -zQBvrIH_0#pW(sFQV0{wp7_8Q<;0l4?+C1ctK8H3MN!2|Uqdg<8-Dk^uAd^KumsBeK -zQ3^6c&c=5B2>{2ye$*eN%OjE!_#R=UW=2_;1u2Do)0Ty*a!>c%aS|=6j%5s6l)P-@ -zNa+z)lkFIOcQ36-{O)UGOC|B<>y)R;G$N6I9Ik; -zq_%S8bsR3^R4lXv$~kRVnwZUsy;ja;pC+09j)s9z$^KRnr6<{<@)N#t`X!eGK7+tP -z^-u^)D?=>W1@#(7fL<{$SZ -zH6mB~{@V4TJ#fv`_^M#TBg||}$_%7SS0xnHhoo6Md#FLg{fzn$ -zn?Euq4ijA}L)Zp%#u^>qxIL^_1GYp&FfE3b%NXr98Vo|n@;C#rIe?`6JizI3<+k+U -z@KV_7IN1+Ow!?Io`e{mAof8BP>cV_c5t=;#3IMI)5kvr@C++HzaKy7fC2o62>P0MQ -zNe(MU8Z+d=RjCPp1T7Z2m513E0S2^q1}uzd=4-Y9V*jfYtu9UivxRHs((dX2k{!FB!L4ZSglv!))ia+;E`*oihZW>CA~@Ov>Ty5`B*-p;&vvJ% -z!&;TL9TFiTl3yJwY+xHAC6c{jb$|=jE5T!tR*TkTVo+6Uw_}8=+XEwOPbbR7HtbOl -zw#+m!fdBWuwcpyXN*Ho@V6p~RYrP%@oE=48`~iY`vQou~oWmx|#z_bT>)!M$%jjo| -zA(5?%a8(v=Z*Gb-A6fKsD_2peCC8?!3=J_eZt8)`#>)D-6Z3COPewR&Eb5Ns56ikyHtsgb}fOyup45G`sZrv5FKCzUe* -zEHSn{GMtE}j>=9fu`Qd!JwKvx)wly`+M#46vXxz61am+!_gerWw2)lN7Edat#IZHK -z&HOedXF_#gEE#zsL=CjvckE -z_ll>j4B}`7hmv50j#%%EM#@5>PIy{W!p962YeX+9WF+9q3{n)8RbZkZHBin{7XDCP -zDyImKluDvtgbAd-q+iwaHC)Tc^!8Uivaw_m9DR>e_Z$Y>K3iC5sIeOuF~Dg2p4A#8 -zd?8j)7-h8r`(ZMY20g%5lMu)JLSS?}ywb2mdBe{dHmuABowhlAxz}brYV~WhI|OCo -zGbm!Zt50^;6wzJXp6s~R2w8XzP=UJhrVbzy;7Uhkm1;_!>z1}1%8y%9H6bT#78-ZZ -zQtDY@-#HdzTJuUC@lF+WECh_Q^XI?C?`ZA$38U&6ZPgHL#^%J`?nxGE2KCs&r2p%9 -zoHeAm{#vJSmom+S4-=Nmw=+&LwMq0 -zP*${sl$ixu0m;C1Sat>h11_zQJT_-o>~dhr&TQ%3{Eo~aXXM9~LC7{DfCm-Msg1B~ -zFqjWBCJeHXDS%xBkhTfyhv6W<$+tKnm?Qw*uQ;G=G0IScft$?kMFxhyXBEJ;@trfk -z47Rc0$Aw!4vQmiQ;-`*7WW^%4+av2L=FMPjPIWO}Aq&eXNw+61+Rwz%Smlyrl0-%! -zJu7o0y^6C&Y3?LI-bPyrw#VY;=c*VqPJsoq7^Lh|Z3Z2Pp(vK^&+Ouv>lVR|iY*N( -zwx&eE+iIvkBn^3(myJ~|2y`UP!$2U;ZD4t`WgKGossxW)kT_^u`C5Rn`3Ls-K -z9=!7sx=2=jFfEwjs38}3_hyUi@OH*km8v}Bw&TK^cmQ`s#w$8pUV5KdW_%roPar!V -zhIa7*VGZkAVb_a+S$cE70gHDDU$R7T8*X{5b -zZY0o)2^A`WcBJRDtAw9p^gQqAH-2unj$j;~hb-If9?wB;ew96MH3YzEmo0bt>%obC|9YK>Iq=Ug1^L+ -zUB^c`Fne_cFotF;ERhs+v;TXZNbqCmHxon!%!Vd{3&Ob{O -zmZav`i=bM}Q};^^TS}q=5ZhH+7+_YomvEV1;&vnUsI04W#Y|017xw8o;GafLWgf;v -z78z>lxD1&(&PP2!0kkf+0T#sBXr$T2m6?SxuG`;73nIUzl~N`!|ED-V7-p(+ceesW -zDvx;2aNS>%Ia9614UI{rGFLX|E9RpM1}|Snw+zF;UTHM@g&0_G -zKn7_*oP2x~I#iVq$>az#Gd#~MR9_0M@Hc9ff? -zD-NyEn~Ega4_ujqKR7*^FPo0jaq54u=QdPK-5g9O>BLt6p9hnMQ#66O%#?qEbU62j -z3qi+c-vpORWv*JqTE1lOvcS(GbSRpe=5t?HMC@S01U -z8D8S3-k4}Ga_MN4Nq=+=@0rC};>ic#FcW3)>&RXWG(c;Zzic$ky4Owtn(e7wI1^As -z?!RyXUQYrce7kf*N|JFKB+<$K?3eIF!sTh)M1JR=kc3gS7D57XYZfbck_yi-CaH11 -zumG~-S@y{+Sq|uj5c%~IvO6~=Jwpk4cIf-dar_g+#qIh;7+14L99&AtPq3Ka_co= -z`(Pm~AuoIar)Z*jk=5 -zjco#>QBRMuMzbG>d0V5$w1N38fM6rrxnJvrCv%3hvn6KD++NFaI|^aE-azFkk(HTd -zQff%w@Fr(?MPzDJ;rPxX(%{G{7sMoJ?X{~J)(#^UI!j)TeQV*$d8(cFqeZBAOpKBz -zfJy)4;n2OCAnf-Ue;|XFL*h;V!xahDcKZdaN)p+UMF5*g3jo)YGAo7T5_o7$8tq!C -z==;s(<;t3%!d7F2+uqk~E` -zWln?lh}B1+<#bVeFT^W87KigJ67Vy*f@}>z0S@iu(qgn1BP^V{P1)U3smH851UUNq -z);i-+Uj4|$-1%~u3UlwV!9UAZfz4^OBlfb6+*P1ljs)|c_8bN%w)pswAOyU+;i~Kr -zMjmmLjsrA%#mggDr^6X#;!!xcQ6BPV@p+&GQSU0jltL?2wG|NGwFTOduu!|%vZD{4KU(m_EhaUjR)}IUr7g+=c2K_I}V& -z`*xLIN-N7#IbU?2%om-L0IURdt@B>2z^E7z0X&Fk{Vh;Ay$4X&LYRlaVRKciyo$Ye -z%}vg;BzVZ!Im}iFsH0`)svb)i)#(evnP(N}h(Qu9S)t -zE4%R6D`zC_1;HG*{A_QVczyrB`5fe(#AW_89lr^0E;CNV0}zFFHibh_>!SXA#IR0qt` -zpsyNJ9C>ZTfY5HnJ>J22_FT}0h|0K!M!Jwh=cZuBCohnesNuD-)Xp9N8~F)zF) -zPrg{SC*_M^tb-Xgi&r>!NxU|7mkkQOBw>p*=KfrQPuv(qJaZg=$@l3KCXoD+z1w>ltDR -z7kml{a2;2ILNp}KF4#`lUy7qbin?>E0R(SCiIouwDU;AsqhK+4?#o#^Z*9-CaEYsD -z2&Qg^q@SbK49nK_RnF`gU}ygzbl1-cJb*OKsRJyxzyMxAp})DkL$xQ1_T7tG+iIo? -zabj!c)gHkXQE%DtoCL2(4Py11P-R -z0@%r4g&xju<$_iOimm4eI7Y;me_Nr2aV&t_vuPlV%xs7aAm3l}MsZ)wJ7oX}&HaEw -zG121`ySlfCQdUFSJX-09?{(07J56=zIzad}=x~0|1!kHey{@POU?2k^Hg`a}h%yTh -zQU?H2gNL+fsq3BKG2U2Cm{jL2+h({nbI=kHBUkEJ71IFiY*o-NUE>Q<-Y{3ja;L_P -z%#1d#CXw?PQ^r1_S{s7OL?(Qb96>z_4uS|_Bz|WZGL>+w?2PITH-jX^;*x!k5zg3W -zVA;ZjESmyR`@9*!=uA=96mO8N7brvti=$Vy9XF0~UB5zu1ZamoCJ#p78c63m7h`s)^Pn{-csr!xYeJ@|lyfz* -zRIijDSrsrFRnkJ>jg>_^B!Sbp*S-maH-P`l8B|F#e+W&Z&)Z`LxF3@y2}z_x#)8(t -zDg;SOWzb7yfEuC#u@JVIg0>6lEAH}G46I((>kL2%<;S{05)31w`w=%h&F7Y_CSPKl -z-7S+6i>dXXV{e`amx3&q(EH&JQ#xfhrXr08byG`!pPB|O)e*5Pf2Eu;gJ#jx4GbWr -zQoyc=IdtIF3N16TSa&j~TjZpsapctuc-ktWfRGgj{c5p4iQUAb-dRro<~6wBy1ygk}-!ugcd2J!WoN(|9>a!9mNk -z5DU8-ahZ)ckSrX^U#XySVCZWh@GL;g)*Yh^MOf7&M=KI)3lb64i=&SGnFZC$O3hcF -z(JYf1aP4=4U@%Y~95(HpN4rEunAP^JGIFa6JhH143wGi)QC76}wpR -zlPZJl$!t2Mf>FMGhICv=v+!-{&oshx!FkVCt%}IHkl4zE;FHDn_l_Hp!H|%Ba`(g| -z+QLY&t_~JJ;9XZZdFYrBptHMoefi$Kd%k@C|5ZmSVKW?Xw{Z7fcOF>mvdo{qzsS; -z0>QK!>ZCMfLpnQ;Ueg@bsUQ=q-x`2$q;Q+Rq)bErbW;@&D$7E6AW7Qy#3w! -zeh14~O*v1tDKAw^)1@fe_mnNysKj89w$&=tHg7V#N!*2%0dgL7xQtG-mIlBHwMeGU -z+U9XtGxdl%SUDX^1YmCDImleua;*bX)j{=iW7%BQw^8DNm>Lf{E@H&YF^sRzxXUi+ -zFWt8o@6qE?wQ4DXuZY|I!~jnCXT;bY*#erkKfo~HuccW;pgse$kYenAUO4j}fTbrh -z+9XFRm=}U^pVfF}dR(41xx4noUL+i=GD`sv3&wVgLT&E_hBRHNqP>k);?jk6Zezn; -zh+wss5sS>xYJ;1>j>>RoV$?G25iCrSM8M0IV59f7-ve1Nkp0--O4Xk50oy|jf5>5w -zP7h{?Pv1k3)(4L=L$&jqE1SA=6joX_gjN;lAc;RFDvi{PK0kjB^DkqcH?&ICFgPLv -zrZP@O9>i(i!h*OP>DPf-kJ?$&DiOn>CM7wx=^$uoaC~fWX|V`eDAR;UgKstyP~+mP -zpUFzhR&IR~Mh%CwVuPD$2zeG65647C;p6nfU@&uBh`}V7DIo0iNe_=&KW2bwy>P)p -zJkH((3#*v`e8G$I;O8W8S#zkw3&j;1$9QU~Sg2W8jYTClg%)<_O(uZqndAEeB036f -z|8P(%Q&NhQwPMi%BciS#-38sE&rq5Va+UkGQV`kVN}h9RFsF9z1fZa24M#mNn-A& -zF`>?tfZ{Y1f^J -z56P63k4QbO73oP57b3>W;z$+{fy06(^|)O(25PTQY8(_p%|?Jjf@W<Em_8A32)-H; -zpYAU$0o%|z^XE}oK8G4^)HXiW3=bqZh@Xvj*ow}YvlS1l7-_e8V^aWtvtRF?N(fQA -z9?fa3S>va+{vly#IpDl$*}n>=u+~J+h{ZJa0RQGjZ=M>>#xqp7*NQ5*?T8>v#Hd6) -zc0Q*Zw#i<+L)q!)C{JBU(I_OUii6>ut5{l}Z~$y-A??v4NE}1N2M1&4up>U_DYJMk -zlO}JCWYK;110ZL_w_wqM+`V)Q&gYo}8&`k^Occ->$G|)yV+kWsks%H}D%)SX7vC7Q -zP#go*)8u^NqXX)2s@$bSoO%IFQs>nP$lM5Z_F7<^Uvi6R22nUw3ph&!CTTWUw+7S} -zY)wFngDJ-(9d@YTuqfz9WkTP=|_za8wMKFm}f4o -zp&meMkz?tfal&{_QBBuy$c -zi>-1&U{Ne=Y&AVu_y|WjD-lbqC?~? -z;Fz7<58!aIp&Dl1K}+MlO?&1?V6c%k#eyX1KFKUcEFukBYHXk?$<|!1XYfo~jRMJ7 -z6AZd#Zosj*lJ^8a_DCXJmq3Sd#w;zZ3@~4~>+812-?o(Vi7e&BL#+=x`&dVTM8*gn -zSQ7qm5v=f0oj81w4ccIYV_LqK^bJHH&ifAC#zg>25cMD(7#!rp&hZgr#)%gmeFS%R -z5E>vJtsqWMRuFy}0d?}wTD)wWTCXf}m)B$0GAg*qX99{3>=j=?w6fYx8gllacQx=B -zwnw{n(ca}GHGthS2xnYx_Xl^f8oAqP{_J(8ec45uaIp6@3lXAI#q9NA9m^+ZwJ^^? -z^f^5ahr~pFHgW3O6MgE)t*LOqUK?rnu+A=HG3CDLBFq?e)G+Y)!V$rJT -z47>JJ>wu0cg7b?H$!l&su3$|AY)+Gd$;g^f!*~7av9S7cE%OplT@pV?yl82@u*hRa05P;c9wmA!ps^+xcz5sNw#R5uK~F80g}fX92B;^ -zv`*8$NRMjv&~A52Y)AuZdaU!%sk|*WAIu*n<1J>JhiA?B?2)U -zOJxxSTuIv}RN+)t0?MvmBl1424!tTlLxvZlSuwJHEfJ5Wt!Eu#GGWbGgu#@Z*p^L5 -zE!(MbV2|2Rs$_ChdxNQ|P>C^{s4akd7Cfn3ydR)5+_GX$A+a#!E_d6>Oljj)E3-u%(%(`<^NtOIQ9LCZj?(8EfjLmvM2B5~oW2FNr(m -zU?6OFm`!~-3~-DB?_luWGm!zaVfY~W<0IF5gm$449L7ci?ckhs$icLMTB(fnC)u`H -z1;zGc*{mr(4s}+W@{B(qR$THn -zeikGvnF~IqR}2QXhZycSD{*jn&i9wO3y=+abcq=WiJqo!WELyYO9LZZTn+1ibQ#Je`=!GIjMSO&klL`en;f}2mYR~}>zQ%n0>QHYKgC+>C1(1U5^q9^M=c!GM*TZczr -zrtnD}1iy~pe1Uk4?eA_qdw?A(Nd9Dm@!2`Cn>C|c%y(|E3A(!&q(%Um$pN&}#GaQB -z2v6=2&i>u+RT?<*&14-=fqBH-k~btlkan>GXJb*@=|oY(GuIE$7>+j6PMKT7ahf?4 -zQnXQfTL9xj9^eDI(wd&7xc!$6?GM?i_b^bMsQW;4-Cu#}FeqY%=`3Z+oux}IR&9|; -zbrwjuEbKXIo2glNjf_Cvj9TS_Fg^eZmH9?X=hpzky)`Eh?5PGF+rk0UpLN5_&&|2c -z0}HsxLI|Dt>A5A4IZH|Y6XxioSwC|VMBWw`tOcO3wCU{Tv9Jj><}$Y8#2k?ytx_=O -zutDt}#CPa|{ztB+V -ztY8Wp{G_}9?-1cb?OAMaM~>%@z=iwaEHPJ$+AbMfp~JVGu>-9KMUuFpO{-vLh`=WE -zJ&279lYOPE{5yxD4)5$)255Cok8|$2;YW?x-`7Ddyet)=X@?d0Mp8iC&PCURv%mr+ -z6dS6;S2|B@k7%7o061#1Kbk9%W8>)Hc6~45`wia$d@k%BWxnRdOvebE;p*8gf_5C> -z$I#tVvV6pyWLR+IjI9=CSC;@TT>tCGdJ4;gNxQ)w6Iy^7Usf7 -zz)%S@lisGckfd?8tYnc6X=V9jIg8F+fQ1TVQerTqzxo`V1UgD-Wf?X*MDE)R)LJ3T -zT^Y#jJirXf$}{^A$)Cjz72Yr26Gw~bw(RXS4(dLE?RGQ_5Ur`jqe$jLxO>01AU9|x -z7GqiY?_++JJE)2{m|NsCKLEFSA`hGkapYj8x{#^qkeRa({u -ziHaLOhD}nEq3N~nAhG#DzIZ3wnk-d(@aERv-sJ2+Kc_!Z<<>gy%->6@KD&l(twVa_ -z+qP>TA;D>^w)f|qxq6Q`kNq|J#CQLD(&=tcb8r7XQ18s?TS+DV#=B<^lgngs`S#mC -zr&IOl{;uW~y+G~mE)r(URkpiQsmiqOb*_u=N<-W{9n&V;Ye{BDW?oemy4SfnbW&?V{0)sb_0c=NGm?|5SJ;u- -z)!PD@59e%o?8QEA)`}b-Odx>dJ%*kWT(FZIQ6s -zHVpT(exs=$AODxpk6DR`_!(|%9miT9bEpZDvZ{WLEmZ4ou8e2*T@?;Rs%cdhT- -z>@BgNec|+YbM5eJoD3zK?x0=L|7IpDHTCanFVapr8b$uv(4Lj~%0W3USDv^e)m@Voscl$Us)TW1S{ObmG>`_RuJ2=?j -zXNecTolbk<<=JF=I&s6D-z5G2Z9;t@h~IBdSs#mHDs+&to?TRr4olkANd7_2`ZiNN -zC+S|Txr}fmJ*S#TV*k2hjQ1Y(AWY8LI8Wov3CSZo4Nl%$oaWZGbo-ZgZgetG~*t{hiQQi@FG&fWZX8@6@kjskg -z?-A-C)cD@VL^;@7XMzlrGKoaO$ld*Rl`DmzrLK6I*!;o$iJm0jJ{x|66!xiN_51N5 -zf1rFDBHCA8eSLFdX+HAxEj{;#l9u>IOzVkskgwpR^wljr`57Max9mW^<@)^=j;CmO -zu03*m_S!3OlChGc+Z)#EdDe2)V;wFxcc5tVqL&WREE_0MB^!P#o8gc~eZwz0OTj)b -z^0L|RYpycdE-&_wwViu?!Jjs7NYHJI4Phu&>62@~bcSE;I3+hv=ifY6+r!K{<$`IxQAluA>NRhLm7Ha|d<1Mx7c(5!<1^y4rrX -zl^6OXe%kk{+Y{Tzn@>(KEJSm_9zJ_@^?iGq>wal4@tt-LXdYHx6t>%~8-D%ja|in# -zbY0o-;I1A7U*=jM0)V@#*>nrVIof3X_z(tMCKB_^>a^!H4H_{7R~s|cJ#oEw+iRrp -z60Pj2zmMEsqMet0mNxneQ4o2OKtikjf>pzn<^K}RuuW{0i>y{+J*OsKXLgP4brE(f -zWmZnIe?mL#ljKboPm#-|4II@#ChVk-hB1d?&C)Tl@G+CPMvoCwOb-=%+OW2(&CfkwF7F28+qYJU-rqrRjAn~BnW;& -z@)e$X$!6ZF#B%X<&Z$N!Y_;BsW#EEjQof2v`&3is7h1tZf>!wYh^t>ul*Ke-YgzsM2GsNMhwod; -zLDbTP3o*YkXnwe!eOk%v@nOMfZq@9lP{tDQY<;#~_#^4_lGKDixWVJ^t4ba)@&6q; -z*f7uG3^Q5{-LeQP9@i6#8BV(j3)G^%Wh>LgnQ6{EhSpv8yc0>mtQ*|#r^QrpLnAOw9L8LRm$!R8vi}K -zF~O|R*q!#7Z9~2K4WHJ08IHty9FlH-^Q)WZdBbZ>rsZH^Y@*9e%-}7R(a@xV#>jB? -zlLvQu0%-=2+$7y+_xVK5>$f?j!}f!lNRm(8 -ziT51bd?YJAaUstAo+?J42tD}(fyzgu>7O)r)pL>blk~aCZ2!>XZ -zebJa%PoCZOa=o)zq#ItI2|s>qvdn?Al+3jYwkasl%q#^y12WeUN#x#4+qbecL(Lp -z*PbqPqq57(#qhbI0bQdPBZffEc?rEIXF)Ae$jqjRBfkjQQ%87E_vwlnvUI5<#BzhpOW+p#DDqL`CnR*jzoHyh!e!u630!ySpD`&2 -z&eNvJ^F@t+gs5tp6$8PPixCbrk!pdn+li`DGaD6@_72ElqON^%Jf#wt{$0Wr&aBSK -z+lfoSuoMkd?1ns@$f&8@6l~|2qq}!UnXJXAHxbyT<0ejh%Cm(|M|opIZI)@F7L|-~ -z%Pu_k-g6dDvz8zmx-62nU_~P8Jsm$h8I}EdBFly4!giU?^K4w#CH5vn3%0DbG*^|u -z#j6K$_+0ht%VD0p*ptr(O6x~`Nqjq#<~r@oA6C=Tvb@{hUEi*mtIo2+)5Iz#v->ak -z^m6jBxTpF`JinZY@_Ax{Fl^~M)t{yK4>OL9*u)^{|c;zN~O8;=KH3*AW4!NR&;D(F=hi4_uaQxyagb -z8I)rV%Nj`J-+X)sTO^yCX%#bjy0BKu74hx(X^ErqD%F4dn^&&9L{^onpTYgAg4kVs -z^=JR!5B}_z{@;K0OTY7H|KNvz_Dlc%$|y9cbWf<6C>B>AiMmo)lZiTh^(Xl~ckK84 -zb^eyn);mr|W6o#7CuM)_zNUq%hInhq_w#Ch>UY38#=-4^?fwUl?Qi$^E@UldVo^3O -zFcdVBaV3Y34{t#O*-?XatWa>E`d3%0vPA2>u+~s7+-$dtv&YAnrZ>{$*{8+<;MsB4 -zDO6x%Zj|rr8s`~YGho^%exj>KK7jdO@ -zFP}_fmu(ynWpyHi2($7wbgVx%hM|!qytk>&E+lQzGTw2%y -zk`Ui(&$K=?a@WelOO1~ASiIM}*-|)q -zA<}mAJaYa|MRRl7-br+cY*i -z+*C_d_Fm`-F&yG_xp$f*8Q7EQf>Z;^`7e+aOqAx%4p*6ucFYC8-g;BqJL!ppV0@>@ -z7f2g#)EQAW6)hj*S~zQ+N9_#$99{@I(|t8~h=O4M*Zz!HGofyAkQ2 -znXm~$&KtzX%#{} -zYN5M7q&Cc?gBLcgx@%tB;j_)-f|!Y|4S*)7u-9lTOq#PDSRk0clxdYIqn~r!^{Pqb -zKos%i3@4ggh@lRxj9w1$Vi4B+*P>%TnTIAMq)kE4Ts0^$U02#P8&2)ImB8EX3{K7 -z1XaV5(&kP+Vr&vQo6E+*F>Gn$` -z{PRbaH=W&o*qfxe%Vb&aX!ZjZ(Vrxqfj`_VhVKv?p$I(R)G;T%?x`>!p?$FZ=cS0n -zWc$w(wQQaT^6DAyyj7DeF|H3>eCh`Ybv9e@4P?TLL2Sj8*tjVpoS4}Kh#{NHiT>`` -zkysXT2HT!KHyd>@&Q7~LnnNq|+#qLylh5wk90(33K{kNEvvrN2s7Zt4zg5Ynd2Z>nE#P>iru{d?q$p|!2$LLXa0Szja{ -zA;OXIX{CKl4k0bS{+HaTMg49;oL^)g{gw9aQuE9;g*>=@x4&zo)_yhMlSCh!?0j(T -z*Y?5Foe!@4+CI4Jv8@l?Xvnu!YMa^cGD5?=_jTG2;r#rvleIcII?WrT-Hwb=wN$aS -zrw*E$M5+FwE^(r+CutcO6ubb~i`|=Ej?EQm>Lz;jwLAY!OyWlM_`Uz;mCXk_D_y_J -zyU=Ra`A+D*SC&KZ_|9M6`xf;DHtUN)rP!2N|0RAay_@%NS^WjP-!RaJ#F@nimxze1 -zH{wXhkrWJ!D#)Y6sTZZ1k(}8oHKcDZ)pQXzlM0y(I4ly;T2As&jPO&_CWGv1jR(>{ -zi8EeQ)6Nyi;#eddk)WH{sw|e`Xgp4nROE^_4V~G5K?y==4@O7Hf|Flb{g%xEVvx^I -zY_X1T{A+;x(!PjyCYg(oa1JP(R$VpKOeQ9osZEPZx*U%2H4U=np~x3g&QOlfS_L#n -zLGP{?@v>n36e%!7DM(_#k+nu*8L9}gs>}*u#Xn5~gUWF5Kd~Ls>0}}c9jgpxkrd&4 -zY5#+)N3W!JB#4HW;DDRM4rv6xLSi(dObUeLNR}xqDY~CcInE-@WE;+uXbCdr6z=?t -zS<*ySq-!hFf$cl)jAImDFozI>OkTj}7x!j?jzFFxj^H2FVj{6C-N8ND)u+}ka|g6` -z71G+(W?H-I(%RK7TDw}IwW|%ZcD0GtuC~zHm8G?-HCnrBY3(YcwJT3+SL?KPwUO4Y -z)@bc2ptY-YTD#gvYgb)byIP~Qt5sUN3TW+Wjn=L@w05;dYga3@cC|)pS1YvU8P20# -zTp{`7g0$FdgXBOT-@wcZM^ljy@nKHl^N?kD<+JUe;k -z>3i?;dF+iF2u?9Omub95{PcYCky_&)K~s#9vKlCgw@@Y-t|T%aK?pp%bdRT@)Zx051x0K3Q0 -zAsdj*CE1H~#l>XIK{j5#zc16-pc3)WqeGzH;BiM#>@<1#uhEk6pJK0k1JMcixY0!` -zIehg%6v=`A@lr>GXJ?xKIzGR3F*05J%NvLNWZQMAI<^N8$L5Nmz2$SkLqUVmT~F?7 -zUTj@)w|K|YoN&rJP|4_|y4RTphzq>-H{B+9=^-2Up>bimBBoY{1z-Ym8$DN-<=gpLwl}4>Uexcel8GNPuS%=g+D#hxwZ+JGq;hQW` -zo}QfHE~@5WeK$~M)&@(*Q5lp>J6BiVA>>q3EantkKeMiKZz~n}jyMxF#_Q}k9 -zsW}NNlQsh1VTX|lvzSP4XJh&LYz55>M3hr^ -zGJ092pyPMg>BiA(AF(0AwtWS6Eu$|UEDnzScph7`erw;j6YvYf*}+ZiPLp9s5;%e_ -zaNCeLlhGGApTcMn(C2|2V(kOl+`oU;8%P+l(ptNNLiQ?4O!)fBCAf>(%Y4dSBEuB6 -zD{oyk;ot`@M_XDVC!0U)Cb?W>gG?-)uQLA#k8t=(xaL{b!Zi6#kgib6f^B2Lhd%R> -zY*2!M(&B+Jv3)X?`O(3pcU+!rA2vI^9o{^V3+xtrdV9Fx`Asiv(Lj94{J9r?^sSDE -zKNgO;sD9Xantm!%gcw@;^jo+3OC0Q&Q{`R>^C|WV_p1*8hB%B?iNP_xiz>wm%$j>N#_S`H}spJ<~kcs+1ld -z^SrL?fY{DgWWAmHo{iGCM%{n0gW>r3`N@4g21NKgXFA+x|2lp`OyD-C)(0^&;vh)H -zy&a5`Hu8hpOvo%%xlm2dn7I9xa(?f;HxpUzM7LY(t(uIQCK@`CTQs6kAzIq^6mhyR -znA%!(4ull!iu)S$Tuvm1?OLNB-u>jA_aonsl_3JV@rF0kQ%~P{^6^_gd(-Fk&Pu?c -zZ(C`hogm!TK~%ik9GU~;?}z{4!(PeCpM+zKIBEe=2S7Xy0X}33;k*mMd9Xz!G63M) -z>49Mt0BQKXvfeNAay&xPiXfS3Lv-( -zMH0$^st(qwJSmnk9c}xS??uJ9SDUCURqU-QlGt{&F%!ksceJ;xw$*4zuZh!yRFy3? -zIv2e*s1zF};&#h_9^UK4(-%>3NE!__;4w{OR4+f`2DoRXl& -zPet6&hOX>6Uf=@&@WM|G$ygvsE9`p>4M?ldojUnkyAKhXSLNow6}-iR#5;VMScfkY=kVpUzA<|Y -z?q -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 -zzJ@n_QF&8dqO}Z8;_N3PVkzLnew#?Idj$Q -zk~Y#Y!SoUCr&5_4PAFFb!IW)VbvkV9FHIUR&Jwq|UpAMmmQMgFUQIL+wE5dtH>`sb -zg|?#01jIqBizEhkw!;5+k_t~<6Qt`)Eg8}_Hu2)?gtXK4ZIcWUq&Tc>X0Tj_0~#Vm -zxs4Bq%H%HMvKGZm1g+pKVrk-+PH=8w3?U}!bU@Qt61&_g2rhx>Mi%a-I -z$L)5LlZY9QC#j97i|$8=r%y6b!X9zJK!Rpp7y}IL$vvRNZ94|CjF*8)s#*YH9s-E= -z>CEhIzC8^j4K;@!H&zReT}qn_aqP$_4dzJ_ -zw$AR}^<@qj4zkZPr6xwCu^lHqHf)~Tavty?g7U>Gj0fB3m&EQ%=N^?Svq@+hYM&!0 -zo0!iA;LVA~x;&5C5kR6o8}+Y(NW;h`h9qjRBrUhK8$DIaG$$gPNq;*sUl6rg!dXDE -zrJqFzIs+TQ7~ClW3y<0jC0{`7ay9-sdTl{w@fJ_B+!n!Cw)3@M6FSzYftP}G%b{iG -zYzp!=f0j8JWhS+S4PEz%u`Ub(x5NVDY=P5=4&02!^-t$n^| -z&7_1G2Lf$@3$++nKV7s^i&Pq#DTuI&F)<1;#0ZS7Rf^E6opnG)^A;X<@?oKeGnmn8 -zbp*L3;^T|f@W?o@yy)biM;P0&7l%APanct6EDeM%_lPNpRRXsKTVNmqAU3wOE~3l= -z1a0~)faTCanp?u%JhOo)v&X3dES`vtG+;iVGbdM30G2>$zi{sFe&HXyb2zX#!E-Ff -z{4x5s+Yb#6(CN8-$RQd%o`?V!8Z2xE6vGP$SRi72>vKp5r|t!ag%8roh%van8QK*m -zMK#MfTM|1?lh}rEiAbB#Zc8PamUg+b;ue*%$}K2OKVy@ELqJXQ#~jd%b%dadJ_i&q -zgJ#jx4K_?uDRAG$EzUJ}ZXhN#Ei@KV2*!m<{@qK?X{$VMe-mv}<0L3PWRE&uc3&}0(zczk91-hj;Ysk0Ng7Y` -zwhx(CCDjqPCbExojA;UegywI{>|qNP{`U9O}{^}?Vr -zQ8~jtOM6629w0@@SX?cr+D@CB_@w)%!MkC5>(H7`vvz#yU=C}0*6OxyBcd{2y@V+n -zB|i3W2Pr^w-2{PINHO+5FWX_55@R8S$Uqg1_YB!evee|Gu!aNHZp$VUE>{czBxSJK -z%6R&+Txca>F9kCa`j35CQ+Jev(z0>-hQ&xF{$RzjBx_lC{v5X=OtF!&A{E?G$U{#X}i_rQmG|s;Q=TxeF3W1{&<6FUSCA`ABc14KvrE&YD1F#oBlbR@V -z1Q6Ki2+b;@)zGyd?Z1;ghc;DG!5b=;nXgpTinbt$vd&@kdG3SbskwHY^k|T^i+i7cQD37{^tpuz@|0kYW>;SY}~lc><(6 -zRi`qMlodvC+CFt91s`vs-=J%AsPe=crGv3N&AkXY=MS=YE_0r*xv{1CK3SeJR!BNS -zOFuG_muw7#JX(owei7LS_jZK<<=s@YXMRBQahu-(fe8+x8ElaB&M5_~B1l<;Mo4pm -zO~PK$8j-E_+FI*LJ}|B@F(1*f-C3u^qP#{<#N>|U`Ee5MiiP!P(3uoV*`6ZkT-CH1 -z&CUiHT3Dv7RYuEh9%Yh9l+@Be>NDcuC$4Xdh(*e4M=MfmiG3sOq_S2AT`r<|WRC9q -z)8%5S7S3OSs4SbvYEa_^2%FTgB36cbZU#v@wItOH@{}{X -zM6k@7S2f6dAX}*!q0Eh`n8}C4CO6=w$PX>$d?HIZ@lb0AWkaPi>GBz{^&v9su@>{x -z_T0KOMMj^o{>Z?sp4bA(nyk65LJ%oCbN^F#8piZDAsNlm8(^9Te~ezstQj@z-Bpi; -z-CJszm$WU9th&IrQoz_q1p?6s4f|RmYJ}hq{T%BtjaxudfSpg07}m0avt{9Zy9o+> -zJ^T(qXhRFc);x9N{*5^Y@F%95uH?W#7_$;<#2qLSlg -z!IQuSP -z22mx5@~S9E#X!5ieG$Cdv4;dVHNj=Ar8MGiv=3Dnw3972kZNL48rO!Ii!hsFQw`K8Z~?2Qj-ND~vlUG==@L2x{(y{{%pJ_J7a|9+$egQQ -zQ=}x|VNr60GGZYCPh+nzCsaf?t!_u#nOSH$YPaApVWYv?(K0>d4IbxP2251#)dhor -zkZMl#{h{=RW`J#6+Eqqb69&Xq9A=pNzH(-*W2PD@*=`G!XldG*1u>l>TmQ&5tEDF1*W}#|7 -zH@7PuSitE!w9g&QPtWa(<|guiYjF+%#H{BzU&JyZ9pWHBhj-Vo?Oi4>kx2OV7 -zeQvP@&64iMGI6fF1L=#TVEN*bJ=y({kq3->I*AL5yT%1-J*vTghBcSQo3byO6=&=T -z3tg>GvK>DR*+6AlTOW3J1K5mmoaTZFu1*S{g|Z8?5Zl49gJ_7T)kcrkHsAb) -z?6QIcZ=vOglee1$cYW*r*>)s=sGBr-Zd{D}Xk3+xGmFH!<|s$nQckYJ6Yai -z^FWTnS3P%$?b!!zJ=^^eOz>cuc#XNK#Db|`|6(XxKn;ho1)&6cfUg+0(A*b!Z*Tyq -z`@Yx=e3`EydI);iQTX}3SO(=;;Q?dl_fS1_b2X94Ff=pYQE&CWc! -z*RZ!BR>G9Srj`I=*RmEg -z!`<~%qX~420qPSeD5l&7J6TDW%oY+__`8Cjv@SRk$PdQZg=9i>nvfu7{KCu^m{UYkHfBI@9%6p*bt-WQFRr6} -z;TCA#2_oS0iSt795GWW&jBW1mxWd^)aBe|5tTDs0@sB-~ip-j~AN57-i%_{;8C}v6 -z0Z&qan)TZ{c=IWnq@i`OK%ozH|W32NID%G9S*eNOIXm~$8c -zi-6_Kn~T;>U(?fETB8j-$rJPO4gjODQ3}KoU)uuanKl$&Q`Rbw|MsP?2MM_vo&5DY -z6^}ZQc2Pe_5;LMKVu6^A#+2sfZ)Omkoc2}BN$*t}9FFa{^iL%c^6){-9wW;iNf0&% -zwI%JRBxRqs))9xYjI`<6S$wsL8dx!qK||&IZ-6i|hquiHE-M|FkI^@oW?IzPo29!* -z8kGQn9{Senscqjj1_n8*S%b+#GxF}7+S@r7(wQzHZM6`o8Z9u5kK7GaECqYa -z6eniKS#pFQ6RN~YhG4KaRlZ%}os1KK9~>i?#}y|&z52PQAASJh*Yem!h`4!NhoGT7 -zPrr|d(w*wPM((x@&HM(wg!vg;EmX#?L}Ui#O=dp -za@Sw#caS$;_jINCI-4rD_rc^fkLNz~MX$A>kZ#)oU>P0b%8o!i8%)KX^-Em|c+nsr=AsvNa958^0poMaBv-zJyf;L|b*hPb -z9zN^EMrgaiq$AZ&pRX$>Zy|kIuV~#?wl9r~;)0&(yNF~5P*z^w8KG@ooHr9BXgM6Q -zF+O1y^LlTb)+$d)0NCCYs%aACnNDoWydx5wq}9GU7cp|vNKWiT>~RR^&aKtoK3^`| -z+&x=K8^PxM90 -z^%K1);+^d(grcz06C2(V1s#%Dv6E-dC&8f}v*}tB8OGbGg~e0NqGvi>bOJ)}ikTUg -zWsf#S-D+NYzQF8MulJ}!CTKar%q7Z2o8v#Ml%Q1UUBV#kbx;j_K+2N6I_d}_E3R7~ -z@Sdo6)9D4w_k|bn>cZWf0NJWS`vz{yE} -z)_z>lN=)Q@M`Bo?hU?YdR8)ys8Zg1ItP50M&*>8`HFv)$5G+w& -zspm#z{fi#2&dU*P!0>kK@uY83?s6Gs}#6!VQSD#xS_cvut%Ewo6 -z&(@_7+9F@?snQ-gn}RueuD`;ib@ck2$q7&Ej1IB$VEXgL&e7=h3qJD2c#seD`3##{?O -zcyrHU7w>+HQ-iUM-1{1Cuix1mIHGzzJz@_Rs<(TZw(<)fEkDrHcV_VDIDW8BJrW23 -zFzMsSTF#IR%pMJ~%qjw4Zcl;jY0@UwEVI{G$EsHOY<+5M^Bc10SQQgwyxcw-PlTVN -zG98M7*W{jQ`lw=acGdG9n7c$bNi4kQR`~X6Fw1?wiQ%8py!L$Jq#M11=CtgEZ0tp| -zYV265VHp12rI^_>eT9fU_5uwqu?7%igdM30UVt%n+0@Hf;Wlks1NLqjG;p6EX&3nI -zM)vsLG@Yvb`zCg{UMIEQ(q1_Ylj^0d2R@%Wq;-3-xK^e0^(dB2l^T~WV&$Wj@1tnY -z?t)R6A!F)Jc+9P#Zgex*MKm#{L7?C4X;46OkTfyf9 -z1$g72U=%@9GWROEXzQC>KBmN1+_;2`cKp_e<$T|Q@d4FktRv%~#EEeT><{3bf`V~I -zG-Y&ZY~AH?;+Cvf`xHRTS=$&TDj+{t7ky^i=jNla%u4MOzRF5=6Z(5LLJbhi4j< -z;wDSwm4heAo1FXM>MCEb`N`~L-m-?k55hTGri$v3&N-<>%0`y0)mGqJxB3$ii8=A( -zvY!v4vmQs&uz{Mke;94gZ}H(&FCC2{AGu{`V(&B`efoy3;x;+9`1FlRW~)iH*UV)( -z3EpWD(MTV@`t0`F8@FvPgIb9jS0?2ay@2GeFYt+bR?dqngl~WNK2Aso_Z}ZA?r#5; -zjs~{Yn8}m6@^!Zi)+S_stx)w>KE-&y+6#kMaF8=|2X`0Y>cPS=Xy{^aO_E7)@?VhI1=jbcBUxFkv -zH^?Q`@~c(CS^Z2?9t4j*$EoX -zV0~R7j3wXsg10gs14i5-_HQUBh=kD?GmRcoABV$;L+pUJIBS*xT%Ewj+i|^ -zvvkgR-uAY%9FDZj$fU4L%yQbP;Q5SYBKf+ih?#13N2!`nJlyuw;ND#7ewvkkF2P3Ww8i+TN;x&t4<^)kpI~ -zB2EGbNy2p8A=fQI7>JWiFi@fK^JUxxh>pfH+~-$~mb+)?{n-<3Or+kpJwRyHy=-?& -z4=$#&T&rI9+TXBgud~8%2&LMLP&ey$d&eUa;m9J;RgflImr^F7>v6l{MR0-H__?MW -z9j%k=eT`dbtU}URB6wV -zyT{JV{wDajcU;j<{Gh+7R-mq3Id!dfJP;NAZ}StaO4&Ou)KF}$<@CYcF>(1i-B&5c -zjn|Ez5o)DZfmYhvq;b+XF;9s!pCR-*huXJp!Q&vpk&Rycr5_uiD-yV0u=cBIRF%9N -z_2FxC=x_K0SDXzjy`S;a_(0Fz{cwkxS&QTa)_2SgQ`cdSG1io_%VJ*^dt| -z1{rZi65qDj2=*+ACvVm7i=v0?TbHt$V3+w?M@F2b3BBl|mh<}X*=vy)l+jq|uY8s2 -z7|4tJPwe>;^NUws?G4o8lG71c9uT}@cb_`Pg+cyCl1#i~QpEAXNbzQ7z+4H@1C}Oq -zO?Zu&=3b52E0N1KCScND#l{uN)6Q+uoG<8@YcTM;Z_h-C%?D5w{sh~`NG-&`__}mL -z+OL|!w63QnrlC5@L>2^}UXQEHt`)eCV?+VrV;jS#d>(8^xG1CtI)v&b30W-lw|gzg -zMEnM2b{lWq)6H#~O+J`)S}b6gGzWi8GBp=}EMFe*C4SqLz3DtVC(RqYHMZ+HLrW%g -zX)cNtlO6VeS$;u63*rZ9BO9HbxGc>(UU4?bw4gQK_y~Aoj{SjzYHX-OhAi;(AWrtl -zeV_i>1aVcBkK@`w?EX6Q>YJRs_e$REm+7U=Rbx{^?rcBsR2wd~pWfR6upxsAi}V?}ZS?YehE*ZCW`Xxuv+-0LCL52A4CT<^90 -zv%%+`0x2hxzXH38hAWVkir7rH|E(fu4+m%wtL%p((gUg8Y_Zw7iF&3AR^!<-io3b_ -z&0Ea}^tl(~I^XGBeK90?6DBrxL<7zy9tgPy6N^?ezn;(ndpxn_+xc -zf#c@k+In?xjLQ=U(Gb!6X{28qK?r#LV6W9Lq# -z81HAY`plko#EsiA=kR06-Ya`(*^KGy2ipng&Zlpr3DL->Zy1T>4I`2~13j-$?#Z*; -zTU#>zcWl^z_=jFd;8bZi5e9N%f(EADjZZslQS$myI9 -zVs{-KY&v!j&(pZrZD$b~m3z~snPgVp_m^1MQKbFlxPkjAq`Pz=ZS*N5V}b)K!Zz^D -z`^nheUnaqZ^T_O_fZ;6Svu9!@GNFMUL(Rx&Ilz~b{@}y0F|7~CE%SDjdRu1|P`G_|`G6a=Lm~j)c&mFP92O1J`ZY&>8;A{lGZ8|bZI+Q& -zzA`9i|R(~vfsfnI(GLNaS)rUjO9=IDbY--U2LNC#dHH{Q6qYR^KCX&Pct(gcZPk&F$cUux&+(K#utRBs-f!Lgi|CL|CVdp9(h -zzv;z2mTF~|y1?u$#NV)+Hr6JxR62#k_!?hH(m+Cw5vIMgPr@h5xQ5~9Sz3?%?G2OfePcMH_S%PeG{AXF-#WIf -zYq}D5piV3!cTng+e}CDJ(U*&RwED6819P;-#e-fAmqZ*rhl#%*9PM_BQ>5E>~Q -zMx*^(0f`BthQ8<7F|Y464L;<|D?2DL&9vm>nG3sPukRpsi{oCdiwK&V{TFhUP -z&+U>9gnj$tLuy=8j}A(e;_}MtRxaaDg;f!`&n0YpVfSN+x}9%6*($-PncW!WysV^N -zwShWL7fn+bjgUibULPDkdHeX_HrnB>oLSs6r&G7;^;d;Ga{Fd`=63twHgDe6c4R8P -zWCw(6#ahu!Yv1Jaw|e~I@7lxcjl6Mms7qzyzi+l$J+n5xEp}+GgGP9-zugONg56M3 -z-yld>`iR(LPH(?syUVC(2+!Ij*_6(tCbF7DtRj6+3=X~IxyK^7#p?dr!AT_{PD5%X -zwkB)uBDK$8xXE~F=5}>hdGf)DgtOO%L6?v&>1Da}@k|WcQWM(2K3_dy<52+CA_a>bDs69 -zU&`byYb5C{#8|;gVjr6b1>CT_S&%*1Mk98MW|`@=gGY}@ZM_NUt~Ia#ms*vG0z=i! -zHq{MhqnjH%-iYs7Z7jXyqGVgm%D_Of-#iR^`HbDZdAEiE>fkM}Mywj^Hoese4!mE7 -z)7|kRdrI$rUltAQTRqYQ#B#A$RN8&A-0?}U6>ig3c<0WY1MHo{G$V>dSdg0JnCWZ7 -z!9_uP;HVNMX>ufYQ$?zN>#e^XJ-C0;o6TnZp@=14Ph~$QIp`p$s}+Ff^AIp2D2n!p1Al -zZhN2hA0K*g9>z%zXN`l4rA%pevyiAO6{iS3+^wJLKmJXJQF+e!Mq7-u%zfo0YDOZ6 -zLvh-nG!dOTl~{R1R403VLUnxi|MTzuvM>JdU;p_p|K^|nQ^-&yF`Ohnr3Me(v#CMLbWs)bVtcn4Ydu)6W=E -zAequ(K3XTKr|UHJ);dxB%o(76i!S7vv{x2QUls~6`-J}28|FV_pufP4h>tFxY^|wE} -z{`QZqzx{*jU;mZsZ~yA`xBuw++u!yJ;|D+fjUWHkkALGo{nLX#{n3wq_s8G+Pyh7C -z-~I7#{OLai;vZ4rAO5F*`jU&t{z%XK&-~o~^H2ZTpZ-JN*6+~k=ruq7oge?!|L=ct -z*#GaprvLvJ^xyyIOD{po%T@XMOSAqh=A2IrNy_Be|M2MVU43c@yQ&F1xl1s9<;7s( -zx0G87roJKZLF(xbqZPtuux2+HP -zW#Q`OQO)QV~W62Ql;(80Ch(EXg3+?2(H1m_>*~S}*W_?jg -zc!KL%CdYhEirYDdY?=$-ecU~OJG=_Siz_FLTm{mQL5izZ{<#VSpDQErT(zRkRV(9M -zwZhDm6is9P!PH5N+4IQ=yA0sb6nj-*tqIS8CPBL;%bLn -zakWvPxOxdm;>w5+S4MWY3WSEMKw7xkASPTL9>EnY7Bx*MmF?DgKtR`o%9DS}obsJ{ -zNgR!tPqxdN$eF5F?1=Xtodgy|i@Sf;6HO`Ww5DAum5obem=fn|s`$}U3ip)ix -zN}=l}cB#s1!lY@@`hc13%=-G1{!1lnly09UjD7FPgS)*Av_NFQ -z-6w-Vd{I8Kq@6hyLy_7{px=BYh>lAYW(z^1oQSc=OU|B?nF|-I^>m)rV;aPn!bity -zQ=L*FL6`NCovfYqU+;yY^AUeysQI -zusTL4*c#!YzDYk#Q<>2AR3#EW#Z5d>SwhX=w@f6ml*EkiQ!R$WN~D?jfyQ_W*^1A8 -zhoa_uP-%=GWu%F)_?gPE39x74S{iS3gmg+ZPZraYay;o@ie;a3>cO*?p7SBpsmkqtIuP6QnJ6tuc)V_&%PL+$R>|{GmgU7_1)t1^OQDMuY_U*!lB($nTB)&| -z%*WGu1;3arW|vhq(9P--my2watl-8#kR#TCp{b}=RaT?d6Mh8+ZlXX&JD -zR^`qd&L(YX2LlJj&1s%ja2q;glGv@)>VUlK8dsptt$SFC9H{=)+|(xrr*u=4T5U68K3+v -z`0!ybc9-F9eBx+ao%lp_?*TqB>?W%RY92*pGk^-X-!aYk_yuYV?uQ~ii4{jTx#I0< -zx5>;IZAVumJ(=Vkz1fzKrRLQ3<^$Ar$JiY3zR(48e0un7-ISqd8)T1~l{>x1$K@si -zir9P!p{C_ZdwMOMXW7BwhhP8tJ0E{Sk{wv7>{Bom4L5-Hp%jSF{7;=~7El -zQ>i;Erka&&%k4vHvQl%yng3Q|<2MR+Vu4$re%oB$xB?i;s>ojw)^ -z-RJ`wJp)XqH(&kf&c#LRyS#%QH|wtlXqcWADJ{h-QI^#o|7M)Z>WWxd(xkPvCU&!m -zzG?;MuLhT8LAAGCtobo6TXDSM;)wWl1AaGt{@ndx&35}nL;Q-=hvZAP!qKx`5WYC} -z@%XR{xU!bMSSw%LmHs3Q?)_!=xt~B(#Y^sSWVX8=ZOcUG8?Mkc<{ -z(Fvz$;>6-7!^#9~J|W%4qzXE_yC5x1lb(}h3tG`8eBYVMRAU0XomFDMGRJv^?AEya -z#Q1X>6Qe2L55XgbRT_(2agYKI -z7&Ru%jWjZI`5#aYW|N3D*wxO#pnL`JoBOFiQP;LJ#35n5uzhKg -zlvW}!KNoTnVHndYnrid*fq-E+!5^$T#J=Y?Nk&mkZ3ba5;4wD!r9kL!QPa-c*iRc! -zi@BKru8#X*5sgqqLXVvllA3VgDvHG-x9n{|D{;sU^+nW9-+)*TYzu93$eadiVTBlJ -zCb+D~iCFy<6K|oRA1LI&G7k_;LmIS7D5BITlOalr8bM}?+BH*a(+ERJk*8S-!*oph -zJO=|bY=j1iR%?_XWK2#hMe+)Z$4!k}Guno~Rm7AQ8Vkusa6G~XL<4atEj`rE7szOH^T65Miuv8IFP4_3ptGQCgaf^iuOKaJzl^G -zxUy;7hq*3IZQ{O~_K(^{oTR=aCwK2km+(b_$fwHv7JxR{i9KdkafBY2QB5)q{I?TR0P=rVwWVqH!_S -z7*sxBZ~*$wvO9A7E6B@rT(cxS3_e_s(?KvH)6|rClITxbFQMSI>t;9y{mZ$o2I-|} -z@gtF`vQ$l#2?RUEmygoq%<{HTiXgbPBF#J{&NDLWpETBL5p~-isVspG0m;_aqjlQy -zzfqNHH3D}wA4x-HvKR165p{YZ>$dth9j516WHzaWBF4*)rBSA)GNSrZx8%|x3G8WX -z${5C!h^&Ebx@bE{6w6lSOIj`>V9S@Z=1B>uh$fTBZEF)^y%W@)h#B$MNgCNym%$fL -zWHU7O5z?$^Hv+Rsuw~&ESVPTO?w5hVCW7P%#gT!4z@-;(UV1c6>q^nqG0$!7f!j0; -zXbEeWj;Kv*SIHzzOc6;an6#Sc1x;aYa?ww;g(os;5c!bps0K?_^@4^)<-&Dq%6-Ha -z$*C1xGK5snw7bzVa@w2evnoaz#0MK&7$H{at1DICduTEFBU>nF{;O!14qZq{V(gi= -z1IQyrYaToxi-}F@+Qk^GmA0WSA>9@7yd4I2_z+;yB1QcCakbEy?R}nBb_bvmx|rrT -z{g|W{hn+hW4-AA2h-Ry5V6x_K%$1fYHMXcklaFb)w%^(miFz*d__;6*6&S7vN}^3o -zQlvQJbJSR3gKSDHg@ii8 -z)3_p&hrhXRP8{U<{M>-B-e*KTu}q}ifU@kJf1s`0d>k#bl88&mscL8Vd1FqnjOL`N -z6-HMQEJnrH$4nB=DkUanB)m$g2Q1ms=tr$2MbKD{X1Mxdva%pku{narayTRcPWT;m*Wn)!!Y*QA$G%EcQ0=iQQ_><$B^nZ0 -z;v$UA4o(s>$wowW8jv)cn{A*@X((`fZGJ|EN2&D$QHs%0GbEj8lp3kA -zfWn*cV-3mBY6J>*iF;0?F%H`dAc=dMtwbOYtwBhl?!4=(frXr(OI#3y=uuo3p -z5Ui^a10ST=w8m{&r=%Dq3r?OxvlN8r8xS#sFg)^F@Wg61{%51N1ip8JtdU>dc@X*W -zUfi27YxExfLXK4VVjhPZ{0o1g4yWF`6#ko9&ZVcqqYfkK(~g>~pz`?&qLQnKrdYvf -zbu>-TMZ79(i+4J~s?eZ5prFoCDP%iiS4IS-Z5*egI=c_~V-_-pULr{v0 -zq|`dBnx$tqOoy~LCN?5xB>A;iBgb@WPKTXV8|0{2SgpjSqw=hW2CD@yjc$EoE_|8R -z+tm60x)Kc&y$G|;3wIZ9Wd6cWEjhibgBJG5&76q#wHXJ<#Y=rz#%PrvbJF)tsH)=4{qb|N@qEd6fXsD82|+VYPZ#|nPE -zXTuKrsLGr3zPbHZs2$g=Mz}0eYzWuXUbvFT`CeZr6BoO6p%%q -zlITTZxgyysbZ34Rrsi6|Fq)+s!tAZ3~&fU)}x^A6OVpsV4IM_R6NFN!uQYX*A5lNbjmo -zT@C_u)@9r`59>vdq;Z?TNz*Wk1nGmewuRF;YKvCxYjU2}ktowX3SY5Tsc%Erp;16Q -z#oI?0cn_2Fdp5E20L*#|JhDU6gjw&oDNb1X?N(woNfL679CQceWyrB+(TyGKR7 -z7^^%?)kzy5?PMcoBCW@Q-kwf`6+0)5rp`2Y7h{@YP+>OGWY37K+*qRF6oYh6&hw91`_r!E}jkb_XE+GOz7Kz%-FE -zcW;p??i_Di3we3Qr;9{>Y5ihkyJl?iIvWvkZjw2K*9u-hf*#aLIPoM%4H4vz-buG^ -z-#EK}zeit2KcwdCZV+;GZS>WUCbxpn -zspiCvmWXYdG8AArdhfSp7@+x@U=8&B=zFmzFdPS-f_@ -zH0M&G+`NChs&P0rCVqMo=Fj&JW8!#4eFJikgN3d6r3UQ=1GRFDV8Tf?b$;Pen?2_6 -zD1VW|Nn8V&uth)j;_eJ|LYKWG0N_$BqUKOOap&F7ugPI>X<&5Mhz*}|T@`TCAw -zgIPE2Revl;)bmPIyN$UYef-XQA3k~e07ubNQS}nIpipZh -z*yoF#FPW93RZcHB5QXlELA*?e=2N-zAsDp!X0^LZ%kW~FG#R0@qoJyil5dm>Koc>P -z?9;=5B29myjN<{pBs?wRMIlbl0qI=C%R=(AfJY)(%G%f7Wf+*1Ps;Q!w-yc*63Y(Bn?}vZJ3hiZKmZ)PSS)plOO+(w6~9m -zc?>R2wS91+X+|fhpGKS_+i5ZsWeU^1qdmC9>28wHYLGQnpYBbXc#LI!lSi|3V3WPp -zO|_u?WpbnE-WoT>Bp8R_(`ZuroF;deR=RE{GQ2K>Nu>pH@OVr3h_BqHUvIzqI@XLj -zWy&r$t}-*z2IIF+k?61E-5&>R;?-AotW}c{Xk%NB)qI5TzGIcqI8#$ChdBC4wi+HY+A8ZP+HxNLl&mOm8_=*fCRUbHf%P6B>-Rltcar&Jdd? -z>GU3r`%jmfWx~I}F;>+~4#lKi?4Ox6qBeF;llt`8@ur+0&~TAM06TF#n8?L@;S%MKt# -ztj!Y)FrC%WrS^S9-6iIq+(5k?ncsP~Io0!)A@aEh?`-5e80mX=pB=1>*x55Y@L%Mp -z-DH5uUBhxFti0a*u4;#{JZFM6)Lh -z2RPwBo6k$^nDO-A@Q`-ZuFsu2-WOAKbAIzI`|?4XX6bxzu`I`eD81~Oae_36gX-EjB>V-b=r+om?>cJdt!Xk_I} -zZ&};royUi5gU?p#BuQip7oUOvzxnv^%_kpx@Xp5DK;d*|ZKci(&O -z%@3b^{PzAQK-d=_zw_27Pd@k=dUEZ7TV-oXxp%+!@}f6PMsN2{oXL!Y0vRNW?-fE$5cPle$(Bg|yGAx9qm|0$H|P0} -z52xw0(HBFdaFz4f@%Y}B5m_TxesII(PaA5S)S%??;d2p*WJ(=nZ*rVTk5XApQX84) -zh8y&jcy`>SokPE!Q&j -zoWi4!odE`XWz+Zt%-iB}$C~0>qn2o|u4-HyPY~KCCfT+CTR^10>etibJN;2Qe1)AD -zNH4M1{08Z+Hu*{|hDB;Koz@?7|Bcj~gyIw-h^Thp8~H*ZzAt9u>7br1RWGK>h_8-t -z68z*Y-&?%CRwZ9r9qw}+-{j-D5))kEUQV_rnFo)43H6~rtA{&|7>(K6dTR7ff@i>n -z`kcH@WbYHR$zJ2bmWZ^KEa`dg6C4O-^&Ol(e$%;j_(pmQv9LF#_WQSP9rk)&JfGCY -zz12?q>FEu;@Yk>L1kSP}IhmO2_WeQifLmCf>^rO`giF%Z&^!ZZemNKC<4mpU^<}RJ~Nwbh3NjSd)Z5 -zt_N@8it1bZ*?-5z3K^xEZ#iPFeVJ&%m-D18@0x_Q_rc)%@h5Ng)@W&11vGav;%v#U -zS>C$k$t)j5qbg01`17j)O(I`0$;ubHiHS++XT5;qS4U@}JQqgRjqBK%8 -z%oTkgCgo_77K#|3p6vW?OFzMfi@Fx^_~}>>i3Fr!3?wh~bkBEohgj0mvr?0kh}Qg# -z5Zi~O=Y!*K(iRzh*o({|ad^p8wEW0Wlbs;d_FX18qXWv~%mzDPJBU@<@{e=}%WH)XAru`seZC -z+1XwCnUrqyge3^mbP*XjYVD^_t*xd+u&ZKZuV=h=aB6HeJ-ncjI@RWG@hyP?`V`s> -z6p!d^f0gs|8#ME1XB|10aXBs{{0Q8x%yX8e-%M8f@tTBh$m$z{Nop -zCvFLIl{dxP5;dQn@Z$HDp>ICM)v&>9&Otw{1XB?#2YoRiMPwjsDnXV>kL*5SWzq04 -zx4GWCOB^h*8lj}M97iUun+X+VgsDiYVZA%s3(NLa$I&@o>f5)CxIjE_sk~GLwh)d0 -zKqA5e#p?|`1U!Z`3-vZEuJbpOfQZ@%~8TYu}~?RP$T=dDlP -zdAlb@RF{Lz?QBB~sIy{8G&>QUFMA;|2GVLDKG7=g6`~^YBUYw~lkXs`;*uU{j8-;j -zc#IR`wz;rCQRETRDKUZ;pc$gFpCZY7QC6(+MVWhrxqE*FUFg`W -z%p2;BuyH6>nX1=&;J=X`+p_ACVo8hfj5H?6E70Cl^)@SCG&IfxW5!?Y#gUcHZ -zQtR6O=HlJtU{IO(5Iv4tgn)s&mUg}%dGum1%6mk3!&HwE`ZGSVrJWm-twWt9Sp-3s -z;_-yJa>`DUFC@_-8opDd;ey+{1oatYs@ -zp7ln_GAbJq4Ml(06h{QwpiU@3q)wKqwiUo2fUqD3-}MmHMR--(2gY*X4!B#Ges)D- -zJi1IdULn14snnoKvx(R`j@Eitmi+-q#(inmEix{HDD?)FbRJcThFeFtQ)bd*%ae`u -zj9Nk?^*lu%7a~d+QAkfC`3u1qJc6SH8AMxQ&l_GbU#Q0dk*p{C#D-T6PUZtz2vF&5 -zK+~PmP&^^c0X-ME7b9}r%Nv~z7rZ+5zKfRJ#X?*RDmBwMSRiSQZ(DI(!Mr@vh0RVNR^Et=8Oo=y6(u76! -zoCNH8y6BG@$cpK5T(DL^gK~2B?pO^5tR0liAY;6ywQjQzFgO(ntur~t2p-jPT&Tg4 -z#4`G6N+Zt&Y6qeL)8nqiwQ|0aAto~d9>26%{x%%1yuI&sFU1yE^9H>AKEN8o7Q+#- -z{6>;4F)#+dn-POX{GFMU6MFGf6l$uhB1Vs2YX17BkOzil`imM<*F#(*uv3k=npIjD -z5trluTiz*ce4=WZf1*^T%}4acqDp4831`iGeq-j`$_nrDnIK6m38@B%?r4fuUpK5} -zZ6H{_aLXOMc&ijRs9;y -zN4bx$;=^og?gFgk_^{x$_Q{(zzp+_KYOmll)NAa6k<$Jmbb -z@!_-A4{p%dH$HyvCT;c91jz-=+RhOkd>0cUarbT5wX6PE=)VoM_X#NzB17;h613o& -zTJ7YeBBOH8OTI54pE})$?Z#Wz?%x$_mg>tI{O!Vq(tN!)NS|jW=DR6v=9^X}&5@`E -zLwbC0+XZdA-D8tjG|X9l814LyC&|~<{DRgIj((mpL+cSg#&!Rf-akSE#X#3hve?ak -zFYeBhBtGww&{eM7fXnN9@ryJK-F=Eq-~$i?F= -zqMg=NZwgoOLM`P^inYSxn<;cUTG73!$pJLoePT78T+kZR@4?%Y#AywK3vToho$84o -zhFhoNkhdY7sABiT4U$FAYvDWWtELL}g13<&gO+-Afhf@024#i&m6_}V -znB1ustQMS}TV>Z;Mh0o7$2O@NX$Jv8Z8rLe@RWqIITVZZx@|hzXC_AU_v#g!wLsqfg+qZ9_8Cwr+y>!i;$Y -z&d9<0YxK`#yQ`-Wvb6}4aic_+_$0@)KZTaYf_-QvIhv6=HX#~j)hn&*sTm4MZ;H(9 -zY$ps>)p0&VA2v1pOe>-g6VjZ^v9UC1gm-PjNdtyBjE>G79`)w)ISsDO?_frqo9Ww} -zF~tMYun-`fheAwhUjGb~jah$V3HO;uBX<;QOXFrCXwSu%QuBE2Pb3WTIKeSoO{=fk -zJxXySTTq$DvcJ8r<_gM_s2WI`mt2by8kn_#R^%V#uvPgQQQ0o3@pLzS0PzC7G -ze@kI(mp3snB<6!5;$S?T@*0d3zNN3k&CKe{9t=i(mX!vbP -z48KQ9vTqIx2aA^w>@J!rqjwgxbF$}Tx{sD$IluvK>zIrYrJp3iy~C89kNAwr6k*qJ -zMGo`}#J#KAh+a^Q=8J+BrdHYc*D} -zfm}wK7V;#$3Ejdd%+Dc1hv?aJ_(|%v(Hol_sFU1|4g%%EKRlr)_%s*-u{kC-$;enO -zn@UpIlyo(=f8j!~tzurkg=2HpFEB>Eg@Er(8t)#>weGT%WRCRjoj>3>EKbEa8mAE- -zbvT&jnr -z(h=t7>^&6`4S}X2n}{J6(L#$Rv<(R7rWBpF2L^~gA6$tn9}Rpuwv7x__APeqj0$cxt>JwKxDaHRNWdxfGP3chzg -zK;Zm~pdpR9O|*wTk2HT17*Lc{CPB^75cDOJhRe+asjzYpIj4sa4)x&$htErB|6(iV -zC>>$cxR|MmhA5VaHEAN}r19Hh2z+js30UcgUS*{snU_dqf=;EQs>vd=`YvTXP5GT9 -zbn1dwTzY9Bl@SLqJ{E&?a^?=pFq$@Vp-OmY*c>~Hy!C*f1zwAz0ac7lh4S2`?*c?J -z*@W8R^5TTg`-Syhp&>AnYNcA3AT{jG6D1=)`GmAM&@dgwkS)d -z$PQPCx~WXZ3>ufZX|0G -z$tIlI24?G|iV~PbZR(y$irca=672EMYFI~{gPUbfr16%ZK4bE>Ag8=D+58NKW>L2` -zaipc>#*lED+hVt&Dvywc(gMs1*i8nBaR}kpa1*PgIdU~61ZziuljNbmGCB&bGR<%U -zYZIrfn$pLY!t`gVYm;S}$}>^XR}SlhNp>zKa?0*i?YRlDoXKR6T>7JuqXi;lOMVRX -z=(}-amOoj%m~Dx^r$`#T(KXk2{ag^^tZ(i&&|F~e0V&}`h(s}BK@!Wmbt>C&n8$Wk -zCm2}vdVXqrQC{F^bi*->b+deUuQw$%uc?{+!a*lV#m<=E!Oeufalq{Up78-hBGZb! -z#wyxlNCAZ)dJZ#8s@wuUIXP`uO#$;G>NEg*vyq%Rb=A81TO*iN5;@^F>R+Zt9t3MG -zRO|c!T!|D{smYFJE*BCqCI*h&QHEEuXqooY;ZDB_oQ=GN6K$OG`0ed}53$*B(1I@t -z+&7QT?%!h&>f1SM4}Gms;ht2#2X&(;fr=`)5lT=-2+ -zc^FAIxAuzlQH>{zEBIfPbD%~pPt8Yg3-tZ{$s#pnS+awP@c>Nt!DJd)0|@uIbs;$- -z{jg9-XKP0f_oQiGkVPRDJk)YR8%Q>?zw(J6J;?`)GVv~e!?eN?8_zv9Z6-AHp~Ffn -zh4nT@7fd?9Q6{Fu_l@0!i>K#iV-{0>7(b|tWEAY!&O)B_bCH|Y;A1gyyKF|% -zpvmey4Z8vb|le>4#WY7}l@5mL28UtAJ4xQ8A)MqZ`Gj#9}h#aH$|2@iY^O -z`%O(3)CHHEwZJ`HR8t92g^03rDEo>($T(Xasb*piHcMeVG;K=ga&&TH)WL~k0wKdO -ziGlev)h&y%e$&^y1x-ox=Bjn<;u{M0q}dvgxFjeO*{*Pv9b_0>xn7zY#+g`nfbl>HzZPA -z@t{qIl}vzRDMnHa{pYjgke1t^DJ(S*)2LEjiL8Dh2Qk~37*dN|WD7lZU251aWW<@y -zR@4Z-D{urGuAdA7V9~Y7YYM-mfrpmt=G)nPj;72xe6@7S-!Mh?XPTX9Q5arBe63KG -z^oyRiNa~kZMcCBBogI`R%QvQani{&qQ!|nzxa&MDGnF&#&y196>nsOpRhWmDH|@!0_fV@0MsD#37R@xZt*zIW0i%~6=>5@KiK -zY@Jv2(>J$H1^@h!yL(cS^cFYtI!S_O8Ve^eY?1h_3G43sB#BRq1OG6azK924Z&c9h -zkS7qk*_2-hyvwuNog+PHeJV(vdsg*+SD6q$VwB65x`e?~L{SdbV-h -z(4CXxFP~S;Oh(a|eiUkkti`TRaD1+F#do>4&e2+v!=@gnEaNk=-Tv@(U2Ub(Jhy#| -zV>>9c?G-yz;}`w(?W{_VK2oLi#{iuqB8i<4ZMu?!mF%(K4;{%ekKhbzDWwu$@c91E}BHdscJN_Wzl5Te6sT;;b~V+ijK{ow9y!U7@Jlj42XOm2ed_DCUGjF>fv{lMN43lv+BnCN3t8D8! -zZqIQ&_0LOM#4npiZ7n{8Tm1XmRnx(o)5*nv$NBCtYjYFLBf;ng~V|RW2l@j -zKm@%GwC6<^aaq351ODuF>ojdRoeik;1oMKOW1G4e0k^fp5azdF!jz9 -zBTF@(m#LJ!bkqnE$qfaixD8U9&EsfMyA$q^!o;_$SQg^CMgq!FeN=`H=dw -zY}4ACA2QJea4gGZO%jVRSHziFDBF;^l44#F>@8GJ&<;YRIQJVENfv3LS`eXf@&PGd -z%CN0wP}BFA3Uk`;(^}mmCdLhX0#wfjMv@S@&oSH&88Z -z>Ts*|vi0X;wTdI|v0g1%t46v&yp9{y>$q{Tj?3rkxFTK0m0|;Ku>mL7aeBIrYc@PP -z-JOd)jF#4|?mfSv -zG}Ax>i^=M`bai)WAYfV=jG%`V7ApzDJ~eo8lAcM@S?=_RWMDpCcFf7hsGh2{Q`!f; -zXo1=4%sgax_{DB+cFcImD}Bf9Z~pM5K7QsPQwx?MDso~HYzif}H* -z`!wU2x6kMOWs&mgR;JtlmoJJG`MX{iwHhr;_nlzwl>zYn{=fd~umARCPO!ag{OvaT -z{fn-{-$yNg?hh~arbyytZSD6%e;utV?Snx-&MiA_$4*m@!!Ov -zgtv@lj28dIcdx*-qr{DOX9$F3RYUytZ+GIiq~IT2Kzi~E!AX~rM!!p-<{R>H;o!_=Knl3FGRZ2zi4vDONg&$ -zleo=(TEikc>~0v|w}X5!buIV*z8(MnJ%{6%!-@m(b`PwVpWc1;(|Rg5;%8SE=s%pB -z@87(}N%(CFwIAK9@{|079O>Vb9_6zio^kly@u&TlZ$Eog`APn{dDYu5ysG@}_*41i -z_jgUzxlT5d#*iNMKisVUCeRScziSSDjr-a9ip=`;um8Mn<-f^W|I1(h`>Q#Gh{3;o|Lg^St-$Zt*ss3-{?A{>$k|AOxO;T-@bGV>ZbM%HX|GqwwK(Hc<1zMkZVxsp%Rv!af)K?hbiE{1?FGideOpROVQ -zsu?ElX8BXOxH{p8uVFEc_&<;?=t$pXA}r~Uyt@wap+mKQK|9j%-K(X!*O=V>ySwC` -zZoW!?noq;qUx=5$l(~DFpPs~?d%D+S%*3f~wBirD(!hN{;p_PF>%O*4Q0RsCl%4pL -zFvWp~!E -zj}qa3`cAe2-U2-> -zr7Apq@#!yV|9^Gm61Y@beEWwUX5;hnEMJ^n99ofJ)9c3$K5PyY1j#E9FkfBjnztU}guc`q958JGL*;w!x7f5q|cRC(Ev -zPt2|0J`cB_yEZom==uY*4k};4>Vci~{@=e578F{3k&<2HN#~#bWBKPM7V)>g{(ArJ -zm-lb({_>Z<{~cQ8A+-)6N{s)9d6|BA$Z5SV+UR~KqWH@{{m{zC{!q)#8{O+XwXcyE -z`;h*wZ@hp1@#l~FLP4RZaTQA=?pfnk!nn_C{T2T1GB0mFhRVYI_%L6F@lE^20)Cj9 -z^Z0r>zl8Z|Z1Yc_%+LJgUHEvCiL!7NlyNic36&-?A-mNE%4U;wtu%IM!$EyeO=2nT -zh8Y3xLVVCD -zd=Xe|Vz=2NX}n16-rgQf$Y>f{`KXEQEI!G6fY!^#f65*f0Wv6=(wY{cPu&wCPs_PheH-{V3A!OW~7ZC-Ig8+WsCqH3d`&8L}1~6^>s!K -ztc?z#o7`UQXChH4X#*G8Em!7}7ok&yfGsTzr`q>fBqF!Xu4w8^ALtgjN!8AmOFiM| -z2CWrXxJc;HofNJ!OlO^Mv=6R44}m-7(ilUNBm<&uRLSV;}Furo?7SJ?tKat@mq -z2eJV|WYL}PR$IaWi@kMdQ2T)=ojn*HHW(NDPF^ejKSW!MLVKgKee||A6n6v`0=2~p -zHk|F{j&JAF<%11thIICzv#@tWY%tELCJtBZw8rNu+;!uFaLdl;)4#!wf~aC0Cu*7cF5vbPb!4P}XJ -z=v?lROq_g0&63~5d$Q4JTK+Az?(smU3_&a!2~Z~ZB-#ruT(*ytXnC>CbedC20_Bb)b+7;eb -z1DIFe(ZNsB>Z4!lRtH#>(eqJLH>8suBI+5!l9;pG(;*BvgAQM2U@!FmJsJ$o4 -zuK5Q1SlQCQ9-2HQQw`Q0UKVzjDV#-ih|b2Yv5}W5Ml?!uNA3KJa4`8V{&K18oiGMD -zxpsuF9zl>b_uQ#D!0gH$_{hxK4n7mp7W}AVcFcAtU^$sBA=3*uPcr0jG|kaEh>Uik -zT8S59KXURP&?bhny?8-gdFsF`v|+TWEN-)PG1#_3fgX2L47$Y*J~=x$r|gtl%|x9A -zng;4zMzP1RWE4=071S^haSGkutct?jG6HdaPC3#~ppO`35=HMiPG13;#2K-HLB1V( -z;sI_{yAjJF*fucpTj=AsnAL&S_KgHO!=q#ca1jB1pfJteCyc -z^H|pvA;RQL4%EiVu@^OkuS7uSI@dKJZF7{xr}VCTu1jLW6F}wm7W>u?E(v&r1n0tB -z2fD88kWrmgtIH~|$Y^WSL&!ij``Fm?d-K2=WTR8d_bkd9Fom0H?HLdLSITXaM^FV4 -zL3Bi5M{#dN%Gwt?f}I*%0hueWNL&OEz->vPsg{(P37_XMKjb!W$n|X7)bi0#0Zlj) -z->N{f8PXACEb$lSW*6Tcmx08sVN*pXE=U`Q;|DBM{9vmpfnwJaW@kZO#JeJpivvM! -z12$Xg&RQOczUoEYGtg8P*+!Yd=D^VQ8zH9a$E^l6PYJ571mW7I?rwtab};3p^B5*zKm?x!OO@@#Nb?%lM!)+ -zM1Us*z{CW%FJKQab?2>1>|MgyBd{rvdqSb82_%qb#j -z@-ZN#5B_VggV@jLVQV9ih9M6myqZv(pp{Rp{1`KpcqR{=9RD@*lw?=fCqsdexUf*M -zps9uz0=jl+<7DFG?235$mK+H@lafd&%7eI~b+&0VBP!6M($m%Mt^Y#q)@|`XW}fPfHj4#4}_V^|G7TVM)aGp(dD%`H_Y@LaLRiN -zbEjGx))4_t#R>RUIJX~nc}yJU_{`z?6O_m83r8DrUr?uu-eLGGLI>zhz*RWijr9m#7ol@ -z(u3xQ6UQv$f*jF+sxFuHSQ5zrG -z-$}pEjrg`7fRmo`ZTmWD9f$ruGnhl&KfQKr8F -ziBp6-LD81cPR-e&i?_{(yne&&j-naS09+XZ16{&G#pwhJiX3~Tkon$=z2Zxe8VMIQ -zVXmq`nQIz>EiK{dw3>~BVcLhbRTd$H$FfqJaQJSdhO5`jj3skGV=$vlJ2aggQ+Lve -zy}MNNR7K)yWd~EzS+L-Clrb|4f=f}c7TavcVh=16IXJLqyON-FjTdb~9CIL(ID0jb -zXLDd%QLxNn?C4A=Vi2@O%^fDpCxAEA!ORwRAlcPXJ6pG2(J3=;_Siq3kPc|c2D_F& -zdEorS6Wr{LuvRs(DH98mT!ayi88qFc108MqU7%>ELR~8tEF~>mdMta1nKgtgT-%SC -zSd$$KD|luu<&GyFk&wiUMYfXoGc$8m3YNUKXnohYV#*M!B4E(Ru3a!ydFz)^`vspX -z?`?3o$ddK3p-GE>JMTu*m;s-ncyy%}D^m3&hnDUM?g_bWom2X<(*T-z*|;H9bK=z -zU*Sw)R^-#i5AA!i&O~H)o&3e#VzFw%jAFV>WJ(LVzqs1=$6HLmQYHHa5Iu2_*G`8O -zj|cb}Q`2Q)RJI@!TEh~00hWb(5@V9mQSt}6XtA_K?Xj>IYLbJ9OZZb3}#D9kRQ}O@x%QYY2C65_>EKJ0QNpS0ejXOyotzyUK5D -z;|)b$n?q`jJz~8@kTrS8EioAfdbOQX5aDaQReUW|3SPmYhBgFiEBUfB9oD)p|Y*d9lH)p>48Jq -zqKAV{>0~fCW)YfD+jKlS?RFDGXJ7OyRQq@lKo|_>RCIE- -z)^4g;1zq95c|cKYbz*>S -zZ0W6ldwRy^RfSNRD*HOVtD*yatEuAzIEmB`W7~P^wIbN;hcH -zur|S#JlX^ecUULez%1B~AR&hlPd0-?KKbg2NFl&|5Sd3EqyU=~ -zN2$yrfnPI3&s5ojdmRk+D**?CW@!AtkTp^a5Cs`Cq1J(zmi6_@^T(z#JwF0NkRSrMd4MtkpW(s2@96U7 -zvOV)oP;keP*3Z{qj(Q?2&4CHyD`^a -zy(}1aAy|;&Lr-SzVeexl5^m9^l_vHiZfiqnF$8;1FC!w(uK4Un<@*IsSSDzBp5e*& -z#7KhXU@}EiVOxkCa&+ZIc*c!H#T>vbGReq0svV!2fv`q& -z-!#lDfCv#D8Lw%|7QWeZ_5(lpWp^s(%nw>f+!kCSE&K;QNqGXB!}ZBHPx_xNWl&N)G(E$yi42{Tr3c -zu_L26ImE;*;s^{Lm*vEeV~qm*g*j3?*e)s%V7rD+=56wJ?(rbb8!q_EuWWRl*O6d< -z1`V(nzjl=-7K?N?_dPa(A<3>S1A0}a6nRDr5__5TvV;(_0z7SKRKqFX!Ho--uRXZr -z5!qH62P)fO=$f}5KiFGB+`Xt%@Fa$X)M5Vp@ -z%Ci_4#7PN2+GP!bTl`ATLY|OPLNHYfIj&+<*q4p~a_VkdW-&*bGbK_s6iF+0z!;c! -zA8;#$f!TsLXnx$s!6XmR6^~x( -zwwL9_pB9%8Db8$m86E-CIT&W_^qT{nCTfd;C%hUHQLcWSLNPl2#33KhCn%t8HEbZO= -z5R}Q4$!L8D8>B#v=)h#ZSeqF*hOO-J!;kaXP~?wTMY8l0H6|M;+aAUC;W}{vxo-GQ -zc1_V(HIG$WiIfmi=8;(0vFd|Zwxd7UeFt!JJ%TmcKI4e6jdqUE(dK6)Yz;N23u7G0 -z8)-P{JS(2EEfvXZ+tF1Emq`KS2+B&vn*(2)hlqZ|kD5$H2ESO%S$#vO{>D0i@}%5WsFtsL#%HNG2D#%_g3R*r9Vc5q>zpThJP3)2y5v -zI^C8ING2AB$(9!15*GHPS^ja8SKBVptJg1 -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 -G2Lu4P?zKt) - -literal 0 -HcmV?d00001 - -diff --git a/components/resources/adblocking/easylist.txt.gz b/components/resources/adblocking/easylist.txt.gz -new file mode 100644 -index 0000000000000000000000000000000000000000..8fb4be557105111afa2d989ac5804ea056b5a628 -GIT binary patch -literal 124964 -zcmV(vKyP9%a{oSm#l$uamxU$GW4*g} -zFlkKIcSr(X>;yjh!{H)O5?jq_Nz{>)x_hzMzdgTVk(7F-*A5ogt_PpRV!f#O+hbF8 -zy53*^)J^vKF2DKv)jwUo-^qG!r~bR^Uxv5;xwF5j{{4@`t@-bNfAj8_?|*qjlV7B< -zN)O*%-`(8Z-`?OKen9_OxlVp}{U>2hKPc<2|JbWRZIx{P8O$F9D7|hx;k)bGo9iEI -z=SR4{z5DL|_WG|szelq_9Y`efaylXfSiBS!u-wi^QM(xlP7P{YHggEq~G -zmDpUH*0(8ywbI#D6Il^GG?<+BBVEO*G5!j+8D_7I8ZmRN2WJ#_K3`L#?AVEu=Uip& -z=xF(0eLD8t&biUcz@phtb^jOLOr6Yr7M0eWz^qm^lIJ9-5@7+>sd`TXQWMNN@*;E5 -z7&S|S`E~UqYxi~BRbk1eZ7&-o#!eoo@yAaGVa5YAAz=22fH)A&fCmaow^P;Vdg|q{Y@?C0 -z?DPm#&0DS8E{tR$FNdz#R&~8S8*~$)c{ySrxv*lVxHnKSIDtdm*V>GMXzHCB4bG!> -zmPwGa(?g?L})2FBbr?0{V|BO -zKLucD0{~U@f{wj#K{{xr@H!f})Nb4*VBCo2fq@TtkfmB*y{6dWRg#HyQ=@b(x(8o(eQpeJc+IgC{6HJ7O8fs6h$*u;7HoF47oEjK~t!kO9r@_&k0^FR2;y%CO -z;pEV&fxB+8{z`-)w_3;cJK2p%NPh3!^~ur+5Y(wEun8iW1x&ZGNw9{Cad>8dR>mSW -z72Su)hifUBYCf;yDs5`#HGo>%AgF9V&*&oK}Xq@ -zWDA%-cYR3@UK~Ajp!@7$Mb}5vBpV)WQK&coO!IE4Qt!AJKTNot*ztMH~W6Ofl55;jrX##6ykvbmVkT5JUAG?pAdhN7@*oG6d_eVE)?deA!-4XEG$99DnWJbPv4B;nFK}75idQ*g7rjLrl -zO@hb)zKW2I;C!3t8c|sg&o~yiR;?)9PWI$seHXM~wy9}U53->PLqxG65{&?8N+o)xIF7Z@pA(2SF^CV -z!cE9u6|FAD;ArFZc>U(xg1o(3AuNW*uzRxN$o>J^9n5^+nhwq+BBjp8fsvy*U=} -zWSVhfm6O;u2e&+GfVar%6f)FAVohB~IBZcCX1PM}0F-kgZ%|CR~Tq7n5k(aJQU7 -zkDbD*^nL{ImZS3Z!+2mr*+Xq@vuJd$o028h`@%));W5ibctJdm)sIa&!#qNSxlu)W -z?x5o&+(91Lsti-xrpbpxpKa^m3?C1735Ox9eYK<@K?k)P-wo11%zMVDW`IN7+stXQ -zAdcqBoTw5Ti@O~&=&;GCEJce8WWHDdfu)X##?DR -zw@eMQ=#aE{dq%DeK{TQu+>@x8OJlAkQKR!gn0`(pceBFInM*Xgx{uGo+0+_gP;kW= -zq+r6St_SQTPMNBU-oG4F({ehQ_zu)DBiF61%bqQViZ|Ask}W!89>(<*^Bf&6Xe_Qo -zBEcI5gB$W7UoUKI7c`4>#2`xb*K3h7*Q1no|JgH~mt>&q9UtP7(wtGfsT`+@uM`8N -z2XMNo!9`gL1gk3Ms)3&RmXmhwnNPnY55KEYv@1m)#Hy7}z-I@6@ -zvhI))GT11faCTId@kOmf+qi)4(K^475q|s>c$}lerYq&DJ3TeqPQ(J`@Aeg+Nj)v+ -zcY4^sDq~T$hD~Wxx=I8((88y6o4R^f7Y8HP7WGcJiLW@UE=g-w9%NN)KKQMA+l|u* -z-r0}Kq!3hxv^O|6!aC?Y<*Xjx=>ywGf=jX?)&I10Z?`t>~yhb3zOe)RT_+J0A%QKQ6_!P}gF={1EP2G#-J -zJD3*L`cy5H91)k*K1uK(#ZfX`Sfc|GCa{Gkr4ob3V?V#O0FUER+63x)ZAau^L_;Pv -zDC9r6oR|y2H37l}gu$lLPo+Ktn%c{8>c?fY%7}S2cs1Et$e#R}u;FP2zha9c{Q&jL -zl?f!>>Aq4z`-pQH0DwfqvEVZ-N*$(eG<>D7yM-;0gyQUxCOKpmeN3u|h2WOlEhFdk -zUEoH_9C1^?CbO5OgJ>Av2{Q}RYm6XZSA;a6cl(A%exaoru0 -zdXB!}3B_8*{Ktgg-7Hba!hz)sfZla#pR~Z}32UgsbXIIt*7g;YVizd2cFMnfEn`{P -zZPiL`;S>;U=Jjxn>fG&~Q5GStNd568>2yZgpLZJ1hmGl}mHK$NwOh*;G#L~*Cz0)f(x`T3ORtoHWO@y@F@2P9#1>mj9z-=+Gh3#-Mo69}9w?za_7-DC@8t@AC!+{<3y!mX)Jk=svP)OYmi2XW_8QB=$1*<&gzww+3bLaxlOtstix}ZrEU3luX}Pls$sOFVtuPj -zw@i3I?2>(PbypUZgXM+LNCJm1~ZD -z!3q(^W%A4B$R~OYx9sEe%Mbr2x{v7+aR*i`ru$esP>@by0zEUBBeKFSH>%yaZKn@BK5xknTSFABHm3*C{Nbt$QS-(_C4Zn|GQz@q -zd4zo_wM@0;@~Uh{#|6$iS82AOSe=XKPc{^1xo-Q} -zMz+%~^8(}`>Um3Cx^AK@jKn^Qs*_oKI=?EwHkNuwu|+kB~xbH>o&n3k{2G3aaLY($tA=2IXAK -zpsTM#d>*;W>>g{U|5CtiC4I$FI9rDomhIGanJ@aJhnpq0)*&IU(5sP6V?;}NwdqBp -zx=?&Q=iz5BFVI_sgU8XutmB-k+6jr^1qQu&X;QQctjW0U660`!M)GbOSZ}Rdy;C(< -z1?JdE)n8;yEfQ~WiB+WqJYzJHidmO9u%Hs*&?OcXe_`S;vG#Hh>JkGEYTPM%k)?3u -z#E6jXB721VIz;4`ct}De48cW~q>iGwIHu9{PNsu=#tbjH2N53Kp0g<-Xtzps7nj7j -z>sB2vF`a<_y2zM1`xyqk)nDSw>NAHu!^iN|lOHsa?R2?5on2Y{Al^Vgg*?9y6z2{y -zR3}#qc+-JG%@>S*>JX7!U?6zHMGqObX_SSqDGhO$1$q;+?q@74Q64XHX8JD6pJEn2 -zvIqcu<>Y~NNzCKEida&e-BeBaCcphQzlrZ4C3Go#uHlPS)sqDU(ZG&MZ`zD>S460} -z@`O*mqWK$B#o<;ZJtEM{F+=wWdYzze67(iPZxi$`L7NDa7fmnop6s$-%Hu=5j^61~ -zp~KgzzzDl%-4PD^fg*8bf!}~D1QU=`{Pf7bk!8>_UmngAsr(JnUHDeVQ|X0b)z;Je -ze=9H#&nI-ww{LEaZ*Fd~1my`Tud;h^?!F-!pd+9QA6*6xw+$i7dxFRjGNQ*4O(y|0G?RZN|DdKVyl!uv%wA=_)p(9Ta -zAY$LYIo`eV!!iUKc9GF`2q+^!8%!Uu1_Ih*9~o_kfHDGpuSMKz07!x2t{>9+O4|S3 -zYcz{cp!xlmNfKo`t}^X9LV1D$mwTj5miOS?dgwJ4O*<;%*2;KxW!zF3cM<@m_$`%j -zO93b%P+sL*df80TYVnn?SCKu5?{q$;31nz8zxhTy)&CKjcn+?9V7)A6Kj-+8b|A)5 -zn8#9>eU{~3efi~=kIfGhHQlb@JA&!cOhhr2hRwXWWs1$zV5DdQR#v(>mEW9UZvsXo -z5eDay%c&mY0%<1vSIUTJGaih-U~G-X#;Igm)2lC&{RbTB0dS!`u3!%PvI8<8PxK#@ -zW`p)mGDKF!GO|ttH=)gu1CKT -zPjw()Wu;{P?Uhp;g<%;I$z56XU*gi9ze8U?JVrB-Ba?h-gZtOn;yH<`TVrP -zS^l~v+3eeQWcnfovHNLkBIu(RXvF2v#0qgF@q2J4I;8deLZ^=$OP{O-0+h^z{nIyl -z7-l?06nn2)k6c#4Jm^|@Rj@lXE>z09RvS(CBGNt4il2;rFAROY&=G~+lKFi!OmRji -z*CW>5WAjso{m}LvhkB>YuWK%}0;(52?e`=-(5*OTbgR!4a$y}#Rvk|53oaw8KS>~Z -zBjLcC%ueIjd7&XsA9SzIdpBx+UqZkoXWq`p?ZX#E@$^I`DQ~-xs~(H@{=Kh4KJ{Moc4>>@DQ1BS@VaAj -z2MgTSp|)RKYS?xfD$Gv=mmBE3?lWihcb{!SpGEh2R)~{A+2UfeC+$;ie+gz4sw;64Ybr^1r7W06|J3AnjJgAJ!w_*C2>T -zqr1`FgsqJJK&EE7xDbf(+Pb8C5^_8qofL2Kb7Zi?sxx69>1iuO5@5##=v~QlbFDNx -zf-K`Fz2l9DnL*jf2v_z?;Xwy$kl*b^$r;IDh$1TcA)O6NZY4TK(N12@O?Lzo+6FYD -z40f`R@^Y3^1CrG_O=v7G=c4ZY-2lVQ>%|Ad&Z&C|kyshpr85j+l8r4^@V`ef@B;DG;d -zD(;-ExKrLFqW1cnTq0!0;q#W5XoQQ6>ZxPI1S+qtGJX^?rrr)8B=j?`Ea}#VP*9u&42fR -zRn0+vc&F&P!0$HOID0e=8%_d&AT@|uB9s7mwi08}EK{(qfFmF^(BXCNkw)Bb93iG9 -zDabp$5~&WPX(WwtZAS$cx7W6t=5-50#=bihSCEfdFcI2SJg-MgxU|7#mGf;fQ5< -zu(0Pxc;CkwB3=0U9c6g(0fzek44}%ZO!zlAond1iW9|sc-gGS-?|4Pbg;$njYiO~k -zx3%%vND~R&jN-LMzp?u8SaYY5P}ok6L|*;EdGzwi5@XLm7l?$W65rcn0$sH{b57^? -zd%!Q`GG8JBA-18yJg2#?@%8X>vQit(2m&D%j%#4JeR!i0jiE+PR!K{g6^I@=dz4_V -z7Majpdb@!nRYjY2f4(X}+ekAiyViL`p>!S+x*tn16k}A^N`5p%^2@psP-e=i6blpp -zc`slK1?szzym~bOf{=(o_~#$Kb(9Q5k}SUvk~QMxCEp8A)R8ho`Y{AiQMh+0Ge(L{ -zfGl;3YLYg`3-OXnwgM6gB@r9zWTRT;kefL&OhxX=tDXwraf>%V-D$Fw_YRmbh=miC -zYP8^Bwm(7Rx}2vCqh*q^Qt@iZ;7#ZZrmP)wwHu5U?6W))OLn|sjcCwzw+(|<&Dbb* -z4)M!t^TTv)1&8+dEWQPl8Hq_Tl9*m6fq)6n4 -zn7Iym%uiM)o&$nMWu`~jmq(XwLnb6{vFU|Y$F1z~BxTMVjMC;!1(N-6Y)rqnVqC35 -zFS*?{PCEIHG*bap4hp?j>LewP9T6YeuOqpRHIu%9gT(3-jqnaI_D1Q^4v%Nb&SHV? -zg?xLg9O;ue4ZA#vZ)))*#*LF45?a(MFUHHO8a=!b`1XH#7WGdZFE;jOMs0M^DNxI4 -zB3*S}N9|m+ZfH*K`}~~rW{(elF+V#4edhoymX7ru^Bc1>$z$`#ilcX!D4aMWPMr(| -z%_PU&bw&MBVpuo2dsxUofR3eaFbLu$=xVQggYz^oB6xbbpw0p;t4b{nOv2Fo#92VVthJQRWflpPGUvE2yvc2NC4Ajg%%WZW+Wn=gQdToxbA2Q!{H||ku&(2b<%AR -zIlT$~$f2r$`z~TJG*^S@A9rTlJBY#Z%&Y6z$lD}XKK7pMFyK-3$1XJJ>|+@zVY6%Y -zRtWmq2YB?~LBSqM36n=hubaw9N;6GPs{?HA_KS#WU_sDrR{G&$kU74f1(~FRBn>6P -z55f9g%^|Rl&%irRF_e#Ye5-n+%TaD1m2z^PdpxD7RY^p{7;pK9*2y9K{ -z9ql7q48oj%l^8^AX{MJ(-MLjBy})|+p+bEp7gl%!5(5B4+Lf8|*EZm~w|(;-HJ$X1 -zHncV=GaI2PshbNiLi$V?@xVh-ScKiKETgP^c@@K8ILc#v#Wuh}#P|aAV~vlm_UtsE -zlHyQ(OKU;}JvNnvjj5XEg{r-#9uEfW+j?5an*qyWQ_*N5Knexe*dWjg?4UfhyMx0_1DfWnK`FjHQp(4- -zdACSl2uB!9p`D_w+WNX^s6vhk<6_bB1+**oXAxFlYcQiF>)M2#(4jW7wU=ceQ|ZVG -z6R2{$VA-p-RvbwR`}r(<2KYl86rmz&AU@ksN%5{8YJ`l92xdM5z${PH4WRjXSw%#W -zvb&&rm~DN&*KvT{?a!{u?_vtl?5Pr8hx?cVV-K`+cnowG^MKK_CwJ6!l3d -zXy&m5#g(C3qR^2Jyg`O6y@8+=!_HxCkz0>N+zKG+ -zy;ET6^1^Bw)kRdsGs`ppv)+_Mn-_fiq}2{7#qB?;tVK&ta31A}r8CkMO5yxuP#$b} -z*blse{oJ+OzuUM1ix}Da&37z5%gAv9V(-D&#xHeasL!448FTz#e1f@WA~+~&$oo4S -z!DB_pY_#mj(EnL-8_rei(7cX`rRzc7qkZ)%or`J|VMFgEHBMlXfpch7fMr3^aQ3kXo -zm4Pjc8_5pw4SRU3jRs_#b9CXAbBkhaTkmr#f`dv$6PxN+6h|6?K$u`6R_N_)NCjb_ -zD2cfAC`kvR@fp~4!(wuGE0zggSQHLWFhJiE7tS_jSHT0|V6j3D5$NuQ$~u~{!nOtg -zppvwjAyuPJDPetBNwMn8s6)?9IP5)q9+$Ks8$iA9X -zWgfgw5Bk9@vJU2BSI<2hep&60qCQy9{Y4nwgT3!}?1Wdm7MMsUQYIp?ZqgNZwp;5| -zHGj8D@2&HWh78-7w4XnG!VB$>vKlh;;tv)`ECF} -zDTyb2do-k&Dci4H)8cTm;qQ7r2$Xb}ymu|ydsW`Oqmn>gq0(br7Xo+8X3AIGf`Vzt -ztEPJ3!bc}iB?ofjLN|p@(}cWPA#fh1xsthNazD1VD=MVGYqP3kr`DqH2^{(#w`tB! -zc#~*pvxRqoMdV~2Nv-#`{7A{ZYEkiJaG;9=RAlhCk+8B!?rdU-IolRe2mC|i=M+6P -z8e@eOYX%~1%d8?7AcV)WeYN2yWsanARC`7gW@Dm>2(rtV)u#d7L>5I^`FAj|o%fX1 -z)M9O!a?cqZomO|8f&SFmZOg(#m#BBL*eL{#az9*Lbwq1GHo`AforN>e7Z~$}KM|$- -zs5|!)G|t9tA?j^fGUcUY4}GHEI5$ZW!igA8H -zN_22@NGjouF0%$~2-{o&fC7EsZwmW?xT)FnH8&06iF5-ujgP8sF*62c%#eWFdVenR -zV_55L^mTPxc)$UHH@Pg28$`;Lb5S8b331g54jRUS-W$3Dc2%gd;$Ie``l7z*hCRwFepz1=75I+rMb&9juq1(JUZR?KdI$&7%ko3l?emER -zcP6;AHY%P14u;>_CLV2DsV+L0%>7W8mqtrL($=Juq385{R&Qr4(WSS8IgXAO)W6$i -zY~Rl0D-OS56F*@}gzAK8H&mYD%_y>e3)|{E6M0T%I>Clhlsq4&qEKP|njK+eMw}L} -zX+3sxF31VyLGm}fK{QZ%F^5HtF=sW=jgEu*b@@siZ`EIM)r3z6!D{FaNtzL5-KC -zoQh1BC5C{<_%Vb>gvs&LU&R%)upMIh1Kul)7!uU}EIbc>V##IkuOeI2r%G|lK12&@ -zl&3aqK{+1IfBg0HkDoq#{6%sl#U%wV`peJ1Za)3_&;S0XKD0m}Dj6CyY!&e(KF|R{ -z`A~AF64B))?&~FS*W{zu{u0{$O86_uhX+4I_|3n348&d72x2O~D5i+8GQf$*Dj7;6{%%={~?bJ!dLYED;vJ; -z{xxF!*wIt}_6386n~yi~hMq73EM|Undb=%6K5|-&1&{u_2-385C6$26pF!(teM(6| -zdTD>6EwIg2d>8s(zX1aIZ_f|@;fHU(lOIU}e(>G@{$6ST7z8^-CI5APnl0krnhF&V -z=#?0~NF2sLd^-pFHc4lwsl%}XYF|Sf%q&^|?%!Y(lZ|Vy_UzTZy*hZhOxP}q+g_}h -zt}JNTJ^5=Vq9yQYDjR%8;}|{DcDTeve4h|%N6MCP>B)fQ{p3V6$1irMj92axd6%a? -zHrPcJqym*~To6_DhvbRv_ewd2GDm-(_o)QgC(9I8q#DDPZr~r`G$}|dy}+L_x04sh -zmxIdB^u(V)T&lKg4~ptQwTXE7B+SU1;pZX*vHHCi`N>JiE($9-2>oyLciS@=Myt!J -z%Z3inP}N>Q4pcRx2%wGO4L32VQSo{@Qj7;^j;En6>R?dHC$yD_PqgU@Ojd@O<(@=* -z3rLiDL5yY0Sy{pMf^GE#4TCZP0+W&~5f~5A&QD2|kjd$C&?X7T)%xsD50Dri%87 -zqHmw>*6?ler>rY@L;KJ2L8Uy~U<@$I8}=_N8K!oVY2DSLxxF;B=cPaqent)_ITtqv -z-n8$##qA9mF)8W&cc>bQ{TK7~3|d-KF({JXIl_y3B#ekB9`!d-;sqpGuaZQUxSltUmJ71fC4uWNd>W20OD_%2l>SL*hod -zB#oCO4(ZTWcfOw2yQ(gngv#Ih<1X6oQg=RgRAO&g-X{B9lD>R -zwq3m=2izB@^SdmQMf$hrx}NWc4ouh#zZ?qdN|yV_`B8Vy10{ZO2|`!D7lcgI_8ZKi^5&67G -zn;2qljhy&B!!GpgBY*)lIth$mPkGZPH!m<9yS(oZMeYiArpwi%Judu=~`a~C(uUD%@toNEn -zg0|cidM3QcT|_JDu6>P&clpkb-F{5GB+TX{Tjct8I*&XGz0wpk@>3Tcu2-6uy6X`3 -z@UZK>G;5l8&F6h9nYSlov0nAR*=iZpag^%cFipW5>DRlrFN68c^TPgj?d}`O-ZK3= -z4&N3*M*80(Y3?Wig(&1{wvH;VUp*er<8ZwydLnD#h@Dc@VuG@1tsJw<*vKjU2l$OqIpnfzk{vZn6U=^ezs>Jt}E8?bPD53pGAX|jf22KlIx!bl;vcp#jg7GT_$I&$) -z7A!m%<}i#_?odLhpTvt)KYS6KeY*AG-=p|Bm12 -zfjilCY|ic5opKQ@T6~lH=gprV_4UEOyEd)5?P&ad|4wrJ{=A#|`|qE>&9$KZkQC6r -zek-26Kfm5-6wDuY# -zKXgUw^r8?`RPR53eZOYlPiNM;xO_{Cgv{D^;GTbO-{F_f>8Jkr+_Znea7l8iYxDj6 -zsD9No?Z@kt|Nh8-<`-`#Df*ps4`r^kPZa&P{;B_5qxkpN_s>5he|dxc{rv1MLGo99 -z|K1Q2Wj_74sy;s&(&(>c@V6$J+59nzuhH+czCJ%$!v7`J>(8Gb@89{|n?D~k$(4Bb -zHvP=L`~FMK{4)J}^IbJBmj8F?H7Pu;Zd$GX{*{CEHw~(%QSS$xntRvh_vSTb -z5gO3un4h$xA%5IszEf2hpfOj*oYj~lQHQp*Moq%5Nw7ot_Kz@C%dpCDpkgtx^c -z;}^aU*>_%_*)NE;IEiESUWrffaWHAh;7sOO{HSTwLEK8YyMeXRTEjZ4}RCWR$Y#Ridm2#^uIShB}E&vbe`2D&`{oZF~tNI -z*YYgxQ(x_u$m(P)V2TQ+7+Dr#yY3Q^n; -z`C-vCjxkD4dbZ0UN|ZjS>t4_vOGCfaoq5K9J_%QJ`HRQux3OS!z8Y$p;xRQ;Cf -zXGGSk9anLqv{;@F{jFVIf4o+;{)&t6q>f?#wybC4lf>*xQfTG2w#tNjepI0<$^M#9s{dXjn+bk(o<9#o(^7Bq$@0jH(B<@Dg5u@o1@(b|3?J)e|O -z`EpB1*}pw4gD0W%-$}(|x%yXYISUr}XIiTu%jSOi>YmMKhVR)bfM!j6Hf*UD`D}!R -zCN~V?+FAR(cH<~M)$i#o*>Mtk543yZdUmM6C#UB?2S-etL`&g#EIclH>TDFB6{ZEy -z>m5$B#OKlGije1mvW%gU&F2Zg*?a|UUymyVtj@w2PeRbwVkwI$F5-66?QXN0R(M7Q -zrPPnI&znC3KWlgU7477Z+ibO@1xqiw1i*zXICvAEj%qLJWR0GFb6c+IO_YLtlD;|U -zS2zgr1<_Y7#q|D$$=w9yiC+2h!QcS~PWNyQ;#4HxavKlRR -zVusI#7i@M57hf;p;-ZsYoQcyCXB9lU96IDj@T4^*Gm!h?$#YE6gRVy -zldU$9(h=12IVSeZjK^UQc -z%PT?z8jXsAl&-W^CKyR*m$>P7(SFz|biz_=#_ys#5PLJrwPQ$IZN<iYj@ksN4}1mR7V!Os(BvI -zpzcZ`9p+|YcYW%PFgxa)-A&K$NU{;rdaQyH#(sJn({4TQvLhq_dhqn$H)8MJK@LY?>j*X;+`~lE!;T0uD0AxN8br!rfmb?d7_!VC|6FYPz(@>al}70?cR=7Hh2Rs -z6fy@#6jdd5(TUrec!%@?b#@x0-dv@@fv?K}*nMSXDj+4ET=JITTskq%Q1HN=~;k5>Wj{i@k>(%y%Q0-FX9S$h4S$zJqFhp*@ric -z&$-a4mxYR22PCt|P;pW>C-PB@)7$T$>%L9Xi%&Y#7}vxScWp`(!EMzzQddZ7(g&fW -zpKau9yHY@;$@CLMv_iSL|Mo!P#u!pb2$J+oC9;3Iqh_VVjcL*RL4j8iS3nKW>Cd5OcQ#3SH!H^iK+ZTE5D2N8McqSnjhJpPuJCZOK@DFzdc8w*(5Tl~m8s-bh?k*VSg$JjX9nX*q)k9u8udgH -zvn;>V`yj3{-m4}f_oFT`Yd4Nb-NIW>E#xVbYIz|{V#mY7IaV6K_JQ2Q!Xm@1wO3q^>nWQc8v0H=g?R2!A$z71(kD!M)^TX(YH4e}IHWBcEe9^B>E>0PNI~FxOk{xVTVq(A -zC99uAHOdrS{EXOgqFqW`ULC@lGLv!dcQPZxRyH-Nm1GJ=!G^mEpO6TnwBCgs7c$% -zNS;4EbVC|%;yIrGWc6!Qe}6@-4Ju}FPnAiNGp-lMpmUwcjAs)Ga@o?70($1o4t^TE -zn6f9)#G)z^BZLRJc9HGu)o~=+SN%WGsvXas}mZc;* -zI%R8EfhaCSMrKrEsHxW-H5~I~cuMmq)v_jzV0x@S2Dx)Tr*8S;D0Hb_M<1`FY&7 -zfnN)3Bw_K!>i~#s{5q2o$f7v4BisYU3x3gV2cfAO%z?yP+8w;dQn%#Tz?kI=hqKPT -z@ZHl|Me#a+)KWuCBWK -zvg?slj&dTFpr({XI|d{iDDl|`Su`ZgM4zB4sa@c`2c8X -zRa-X%@r<*Qf)OHX!I8T;jRQf1nLd$uPLq}pPB0^UD*oX0mWPBg*W(d+@QD^0;ANHA4MBHMq8W!8lyGaY;PkMwNgU0B1|NZ7e!g5F){n=OmlA5xdx2G{qB -zZg4!tYZN!K0j(inb7R4GRKh3e1<`UglkbzxN7qiQ)o;sO^@kIL-0o`}lO|PQ7t|$m -zL&vLTTbGA2-Z#DG8}C#9 -z=qhF~d#05}%ZgUbH75J!Nt(pWgA73ErOJL_((&yzL}&AiQ9S>y)@F4F{UQx3kKm!x -z8m)+6qAR9x%g6YrD)urTS+7-Jtvd9)h#r-y-5v#_d$hVG9a|sS@yI?#(&WjSz5FWG -zcm46}?+^Xwis!7}KR*BJk00MJMpu1&{CcI-R0yJ1pPxV8xmW-EVLtx+z6_u0$M@fV -zepWv}uSHq?`uzFPeExjrd;Wg^`Q!7?uli!P)o1hb@86G#9W|9g{>}Hl?DDS^y=wE@ -z{NZOQ(rKc&fDAJn?^XfQLXb(xSQNK4PK`W?`nopE(utlc)dkU}iM^D;O0~3^>X6%R -zQ%*#-QO9FJ51e+TT*-LNIr18Cy_;4yb1^ZW>CvYM$&_$ -z7Kui$iF60(zL&aD744C>y8LkGiZA;#SOr@{4{?@`n7L=iC8&X9FNu7q$W$bC70Q^? -zk8O!?6&4#h&4+r3cKa-O%>00e-7Fd)jFKXtN#~KCBqB!R-%xPmWl0xOB=w&d -ziWhfGO2vkM2hc7}8m1k^&C3y)(}#;N%Sx?#?$o}_ -zmPvb9dc7F3a+vlY)BgHI%txssS5o6%ORdtOy0~6dwYDakp>FtBBiT}B_4Lnht?Fv7 -zhi+`Epd4SgR>$LU&Mz9t{)@6R&6Fy -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

PbXBQB|TB_;A!$x>;#Pyh!$o}Z -zYRfqRZBp^(ILzeMF)EJ)2Ajk_Jwl@O6=?Rv8h+Ty=D{9;A5f8$X#V_RK*qWT+U2ib -z95+?hpTB;pzgqDe)=G7n7Se;4K}{>pBk?bHrs8E#kGdv9m|wRJM>YC>W5QGqm3aIV -zW6wiUKjvf2$KUslo`dFV{Zs$__3LN#&J?4XrqBIOtB{HQTZt}yHNv!vH2z55d6AMX -zHATfwK0qE{zxnaZ!{`tT`i<1>C;g}71=K7EMS}3_8+Kd>9)Rjk6OT^)@E=@C_>1qt6(`H2@{5q -z>}W^=HH-{U(BnvYNSFhn5tDR5_%}fcJxQUGB+dI1u7miD)TYVk?3j!b9|gpgqaM-U@?@v#m_D)|ssSezW+j@&^^X6qnJF0BzR -z5G$BXhq$CX(Nwb$2O&bE`sJV()MX_8Xrp+I$vC>B7g5hApms=R^Hij$g -z9Br3`sOgNF-bkiSFK1Js05bv3T?B8t+qK3c9YzXRRGC(r5T!&IM8F)5HQntM&STCu -z8iQxPp7`nW1hj%D{rE4vcK1h{#Ue{gP)1uPLz_WJ3=m2GI{*hdR&wQ-DFwwkZHI*3 -z)aBNR0X0yC0suB|FZ2lFY^^ReJ(Iev59<)eT6`NJ9$gkz1Nx$&86{L(pEJ<60dz00)Ce=X<1_TmE@zQ_>cfmbgp)Bo2UZ^TcB;)qb+&wVs{3b -z#1v}cR~wwSVq+jmpMRWvVy8aZ$h6>#BXCq@la{b-I%V$7Tkq1z-Xn#CkP0T{iVSfm -zWY_k|Z6bA1i69iBb+q#GILVK|87>ZYjygvtSpg30OHIEUpe@n&yqWPo?Gp#0w(6me -zu`$!-{f(yF(bR-05_ZL%aEu0;t)?h+7A -zESC+J&JTE94VL1c;49_bRn{d&WYN_8)Sm5A*% -zQS7lHZ_(|()v*!8jnvh7sIDkn8fcxoF%NTdqG9~I*NH0aG3+fN&Z!rvawDVOoD)W@ -z2d;WS%w*D#oHZ^9;!2{gh45Lfq?_8+!&Hegh8hT(x#lM1V%uJSlLSEd!0Y4`#FCob -z2kUzr0a`DAT>2ISU7pT)@J`X+q!wXP*V~v#0%;N#tbnsj2?Ozky4>4fT1G;$;n)HO -z%vnKDdO6F1h7yKZX%}Quo*yD1HTqSWPK;y8>&DrmzM*?UMv(9*0jh}s4eAo}8%uHb -zgclVqJrt5;5qDxe_@6wp+eJeio|- -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 -zc+iATLz}a>Y;#lU*4A|4Uccdc@~ka+d3Qr -zgeaNQ`V6c@0wE6Wmj8C)TtiaxGQ~4M=OS*z2RkJLJG9xKC!I_=auqHus;RBDs@aTH -zyc3D*L88Km&hXf}ZMS*we4MS@WguJOAZ4iMJ=S$u4Q+dQ19ayOJ5;$EELVXiCSj(q -zGdLX)+Nj2w){K+>HEqw_ixF8Nj#Q4cNwk$$_kZepX)6Ko==<#p5b2r0M5F=G1jy{W -zr&mx9$v2w$Bq$*d9QRxfjxi;-gmh5hH;f1E&*l^Y3)lUSm*o+Ggi^&N;*o%tDlLP8 -zm?caQtPS|rj;^S-4V)s^fiyJj>*;N0C|_;I8g+QY -zGl`>SNP(RY -zB{Z1(cuWGMkXp$c6S49nz6UP*w}K?n$V)-Sp+xQOG#MI7LJ$g_P8qc*6Ts-)m%`)a -zk|?DG*?8V!|f-EDHw&C@B1) -z5^MIUO-{%dUtpKV5Jm11E&z&1i7XVvu*Rmo_?At;EktDOmnDvR3>{gW=61&dJB~Wh -z5yx(&#?tLKj!ee7oVMPG2bn}6EMJI-U$f~xAf$WTucMczPmrVpw36lofYjk3zb)C3 -z43>!8Gk<%rgqRh>yhU^&%HZt@a&_5Twj9jgj`lz+F~cy?~t7mfjSl -z*lwy&DX3E~dCPJluZo_sO(yrJ<@*=`YhJiqG%=5lJd;gECxJ; -zhUkHPRM8ZT<{uy$&;BoqbPcfvrl)woi4Pne;l{c8CRwbjS3-Z -zNDe-@ysjQi9_Sww9}}9+=!OR;iRm#+bFb32j%yGH0AtwJ%DH|VX7*}~DLRibo88>; -z^p5KkXbim&3BEhjP;Y@26YKrHPnK)DYVT>p^UmkD+NX`C@@fX88Z1{i3TmUO4#MOv -zN{N6aIfya`#dnxp?yO=j#BN|Ua53M -z)?rM<3cVDOp>2;6IwlXs&f1!d=Izg)fFh~RaS}t+o!#GRLum)f?h#!b-09uQr%e7U86Lo$5l4iOx(2Uc| -zbwnBff+!JYcYP+NFGSJSLB-5-hXQD1_CT22*~Qtjy#AYg18{l~P=_9x_30T -z=ivYm5D}RcOFiqr+49uUA}CDUZvrAA4;_+$IdxI#M0po;3{L9zxtc`!`KLZDa!W3RsWnMvV=k=*kyJ -zI+Ph4r+WBQr0h8RHnobaMcbrU&I9IVG}8Bsu4nEXDIm>(D5`YpO{Q7LgcaYm$MCk<~|m -z$`ZcH*biIP%=OAsOcKX9Qb;DL0FyN^m6Dgj?v5u$C~ePMF&rHW4kw_yIqsB*q*-*Z -zgT>rtKPVLYt4lDolEX<_=$imnBLdKgoE=FQxLKkJ0l6bq`_ggnkY0Wo5!0)eUss(l -zy(p3e3!tsI+vw?-($H3#i|+f}%2at$oSmjm;)c5=Ox@Lj$P -zkaK-FB%93;N}d#ND_o!1*fcN33R5=IcZvs+y7@M8VPKsRJWHEO@SHph`^0mIoakOp -zVXnncAOxL$M|R%^XC-$)BKiM>(Gs2X$sleT?I4CMCK7|Z?DDn@wylqyMO}W3X63J6 -z>-KT9{26OAOieoZaNxTiUIQvi^6QbF~!P&zEYagB6^Rcqre&n!CYFc^z&BymY -ze=GC;@xc_*Qm*HiU%#r4&+p&)XxjY#`Te{4^M|V+o8NzkZ(6Ep%8JxzEFbK`^3C=t5^t1`kH@->Jws9%$dm#Z9lbP}p7eqp7Ibm&B%{Pp -zqd`eXl)jw=W$6;q<0IdljOpXhL+Qx&#HOn -z#c5C_mUHyU22F}M7Pq_nP`2aihp|%Qu@1&x@iN9BhL6LYslB^C@Uk^yG)LTqdGjm( -zXzG+e5}|fNH9l_7kLa9#4Amf>6jNU0>mJ*<(%8;(?5~T;k|HaP9dknJd>j6?)N1U-b^}KPg=n8DIaSFB(K6;LwJT -zn5mmA2vo70ZuSyR{J@E8l6)bHkDZp@uB1Zp4OSA%W*Skr=<=!)b;^zIdF!;OMsv7T -z9D$g&o-PlUoHWTqBu5f65YeJLG8qFMcby4v_skjIJV&x&`x1Z -zj+>+$rcKG@d7`aDm~DHs^T5Qz(++Xn!I*ADBMS_;9ar?^)zztCN~`9H>}s-L`2D&t -zFMXpP?f&3I0@F?Fx{I&qX8quzQijI#@w;z&WJ0}ApbZ&Xo8c+|#9+cf#G#BETOvb -z5AJd$z?93B7*~a@u{*mS3ngABOCnsh^uRmnv^*0taD+!&zm>hj@#;j!q4RZOiSuNu -z0OoS@e}T`yyfymv*^z~HrfVTp@#dI=re4ndbWxLp>~qBl -z>fjXbfJnyUQ&I{`UR5zzN}Ak@2Lamvr^}(h2pZiY%%~POeVouf%0?VGi}%2}_%Z-W -z&GOyGA-Y~2YK+Va$yko?Gq_;I+s0;Y*VXmIy0?NlgthCAW)K|3ExU?yyZQF08eYjr -z>T$_P)ODyX6hF`rsQ^HZOJy@oD$ZF$^B06`C{0s5DSs5fJ~bTLc;6Q{TaF3oC- -zm}}HG5IQPkr=wAH5d}J*ksRDP9Ptr*-^X#|w+*kD`#R7o192NgwVimi>x%y-U62%0 -zjXy{M+Hsn@h!>o+5DcY!C{!`~>UH5jk=LkCn1|d7>p{@8> -zvc2G~B@vegGD7(}U)=JEtl7H{uax*w0<;RHy-IN78=Uq)Z+-3iR|ounBDtghaKP}# -zAvR3UvyPnTjzFksfYI?^azP$h)mfL8`;NMtu3JU?Lk1ww=l8ZvoIq|$er{Shu7fDK -zSWr`TQU&m)I%d%VF!`;?u+au?5+u;Q+(^bZ%i8+kaJH|fi}U${aOjb#Bp#0%4C>xw -z?(fMyHhK|GCwj8Fy~>E{KHNm{GUgd@D?iL&DI}T0Ot^@XGkzY-;m6Te -z4_<&c=s-4ll1W9z!~24SW$;ecp*=w2pI#y)3BUvv?}pwZ3#T%XfLAlX+j{UL0se_X -z1niB1$9pkTXj?14onFena-Gv8O3(mh2%SPcdGIGPgj5}I@++1WLT*T-n>Yd87~$fj -z3;MPepRquvq^;xIoKVkNd=3l(E@6in&2S2!vZ%io_MMN-t{G -z(f5}4zC#RftP65F4k}1<5_Ho&F8FgCVVkSey1WP}=qT|)j9b#XMa8`Ddn4Wml`NCy -zb2<}!TFT}SwJA*uZ%Y5?)VmzqPs&XtU4k1JX>4a|vb5!6>b!sjLJ0m$ -zFVK50NDEevKF@~DE`AKZ048UR97(FTXGG`{z%7HQ>FDW0g3kPsvOg|i+`&@uEc$5L^ -z23uK7IBB|S6fS_J--nfS>0=tFiLEAB8;C6oc|Z|S{(Y|Hd}Xqs-s%T$q^t`p1hbt? -z3N15}jC6L@(1sAaJp0G6@`UBwR(V-jAg}diyCZ2oEmMCb%o&_b_!bAKYU|XJg7nLu -zGMv613%(rjU0;Bnj38$b7zyGbj~~nFBq9-N!6nupK>84o$LB<5eLx(wXZ^6((JxVV -z*QRBwIU&Pr)fl(OSPfjm?xL+iRs+aOd5vQv9?orP$42#0cj>rb5zT({H&SaUa+KDA -zdc}(y+20nYg#mcKFA7vGrsL{{Za7T>R))GzQGyJipi5Z@*#T~!6DbmnF}e~3E6iSH -zlJuz)WoU8o>VU8616dj3_vQ+DoFBH(~@cu -zHi#CN+A#H$gS2!DnR%T;>B=aY4}Uwxq)rrDLEQ4)fZE~5<|E8!9(-L8hG?}4U3do) -zP{OFW+iP}=BVl%+QIR%?@~yAsvD0gly0{!ZEb#Rgn6=v-O3}=Cn~6|_*yZ=w!>wWs -zwQuhnmf`?fkh84RkfJJYJ1PM>N=+*QVaRr|HK%x)V=_Su10O1o#9?y-4NEe1S{ -z_2>sitWw7)eD9CPIIe&%*wnv>{rq3z(O-{l{+M4?^ULTfeY*K;e*CVgKR+&w8*cN6 -zP{>x8l!y-evc(!=0%uKUaaGeCC|b#Tt5I!OH?LOCL2DH4+Kus6sqHe9$^zaisD7!4 -z6;xN^Pa$apFtaczw1PsP?j+!a6{rd==V7fe2+r~>g5dK%>v%F -zQEu2|>pV~`D#D!#i~<3o1tMvKXUR0*%F>O`t`T(slFGN=Bo&WM9Vp7nR9b&y!%eb* -zU{%9gCU5Eo2VY~7v*kU4^BAX%D9h>c1$JpG;>2*E0))NWuW@)zT7ET^}z20H_J&INRP=1;&_fu1S -zewya<C)qZ4WjzNG~H}!Y*>#u4)_>*dT)I;Ycy@%@rsVUXIg|uku3CBTK_gwXTy@qb) -z#ZYJMnmJFc+Yc}9M2xzzJhS>keQN#5fc=gBMPq9I^0SDKRH3a3xbD-gL6RN<(4Q3! -zuS<;rjLt2UqcNJKR=C0jU&?6$3-AD*jD~}i(c56J#g8ACY;BNQOa{aC5e6Z^d^(9X -zY(3Gx<&`bJ#sL4mzE -zQCrENeE#QtK}nXS34yAPuq~mO(FGYbxmBrRE~X`}Q2kY##id9wfDv*ln^wpxy@4yi -zeQhbb{jZG2+kJp}McPgy_6wP}fHCce5kB1jjoIov6}-^vs^XVoVWVb*m0+GWlc -zY`U}_4YdD?XPgwkHey8h)os0Cp -zCV9yo0}Q{(nB1hzAdRWyCkG3+WF!ib~m~W -zdJGbvq)=%TqLM=c+R@4+K$7w6V#!izMHTU82y5T4wm2ll+@}@PmRi*<@xzkACVXZM -z4!Ma%)Ke`;gGmQti18A9YjJRLi6o~cpfa8(a^mXA)~}2z!Dj&8U*dFB3s7SkM<|>e -zd4>yy5|fML_}eQksK!MT7E7*f3G0@0}}l^@)0WI#Pcg!igR2aVG@hW -z;RsH&G=NMh6h{KUy>R`UefRKIq8VhvOcIz65O(m8MXOu$g;)}3pMZfp%ND%8)W{KR -z#o1ky)oYNU>k&LnLLW{wxLaCbtvQrxzyai)wA>EBo~6)I{z5#2_U@1;eCFIbMs@?4 -zWO*48@dV-&Fxe_4?na!TH9iC3;y~VtJfLgbfkOt!syLPuh$5dvHv$C88HS+YEP!|7 -zNP2kS0H$W#n=05Ujup%N@zK-suZkIsugEB|d01Hj`+e|BPQoB2NshR@>n5S(IC#-c -zaPM9$om60Al(hP;#f<_3GK|d@r)F?u=tM!CXnQCg!P7NffGiOVAy^=>trSpwoz!oVosxqh -zNvY(1;5gI^GBG;S3P5d;=ptfN$a_tu)Q{uN_|}VtA}&YwmERE6j;vEbsI0D|@uH4N -zgKR~x;}&Q##50AD1_gZzyB~|=Y8YL`FFOxH#h8yOxN#Bf_Gs3T8CFzn!FEAx-9mO8tu{U> -zZ{Q@a1t&r~-P;4?;uuPZmxW1RvUWv~B3+bqce0DgD0%Z^1t#w;jH`+DB)k*IxgsrZ -zRB;hzEMj4YC{oNp0Xo3#k_Rm=6$^Iz%MW`}O9}2%-@W1KmTp9d(DFwtoCuX_P|q8F -zj;mpO%+;j)?TQH%IIqEJx~|^MJmhNN4?3+cQg|U?6O;m8cLlT>Y_Wb;d8mQ6E>H^2 -zE0793ic_c;sv6YVf(Ly`eAx10FzuqVSc|a)&DSUVl@Yfi{+KpEDRIUf5n=Y^vRa<_ -z$f9rT!?A>pg!v8cpnzT!eZNye0LI)FAK<9UkzKgkOr>|qNcLrKBr?Cw`8tDr`06b= -zzy+`{j0d=qDRls1=2M6!H5-|vWF+&OB;4Ul$nwwC_rdH3gv -zrbIP*5hUP_lS#4Ut9Wvg+fsc3K91L-%&xV%Dv)#+YbgnZSCOx0VPV0Jv!i*aLXxiL -zJdT5~x1Y&9WeWk$;AY*S_Pa``*K-5wCA#0Ht|sGs9j`dyhV*8!26H6y1BgkjyVsj) -z;324?)s=eO_tOC~XB%Ux;`54Dyk;yJQ8Z3PO5lJ^aAN8O3fn=ygZ&h7f_(KQQxaZE -z6qX1hL46yyK!~!7?`BDcw-P6>PsYC1+dK^OnAtzG -zjp9jLBDugXv*m%rhwrq@gXIdq&9?2z(+YBITkbYzjz4S;T|XH9y*aveZN_-)Q@m8g -z_WMTP{d{u!p&C|0NV`R51~AmZnS$K@_vDC3kF9H+-@B?8)MiwDNPB3R`JO# -z^anq^R4+up^dM-67pEZ_(wCPm<}Z)VjjXqcc2fju6pzbOQu&H=Yt^9~F-SYzLiWOCMy-(+bT~eM4}-6+jQKb9D%sC0;g8 -zcY;WpY)gr3{WXF5BnN*B`Z$T;=24vf5;dBDQab)N5zw?uiTREqcK7-EXVG_rc7Ew7 -zI{nC+PHSL}k{U<`(X}K^SmIE-!3Qxu2$smBI6g!{Fa)M~QSPk;{DWKG#WkwbBymC| -zCn7DG$?o3}M)Rb!7YGA2`m1f59Ir$jrFNh$WkSW}%L#z%Cit}2 -zao9Ek={Gj-{g9^WDm2|TzIl*7nH#qbyy}`ME;`cU%v{%?ZI&T4oqsT4ANGo)%aG>1 -zl-e(xoz%9e>K_NMcNp5pUV9pRQK(LBXGB5O7Uc(4b+NLp?{aMhdu+2{J*d}_2gE>& -zs76vJX`2Ip!DOHYi~ZFd-DXowT#ep`HKX!N|Kh{^fX;wJ-rpdNNqpRHu%lWsowg>gYVPFZa%m3TyLk=}o*-3XA08D`Wb!|r)%f>+gE0jE&!6(+}jNFg7(yfAloZS*1i@T -z+@0p13 -zL|E31Q!`rM?~CZz?SaX>y!j+WOGjy1D#>ZTN^>YnfWl0#j!IPaA%x -zPMc5Pe^`OL+y)i268BI@JCn^@Ynj4c6*NFrZ)u64EL%(W;c*U?SvJ~ZZ45%{hu1=U -zt*n58p)BpGIRb-^7<|!ZQxLz^1uM3&ZmIVMJ*~4(4<5~6k~aSg@uV)|t*Z@uq~3P~ljM0Bb;$zwGd{x~dEbZiB1#z6*_=4l#C48~+Bi -z8kWPjAsa#{UTaryH^+$ty=0JZ%%xi3 -zq=T}-wS9efARe};_L+PrMj(vDYQ&pQ1$5{AP8ie|0*t_~7ZuvaE4!`@h%Q!(oKm1l -za3mjMc5w_PP<+}@;+m}-p&T*DlQs0(Vliy(QE#!S@*tF)$sUhQG(#LrJsr!!yK6cf -zYa;2VowiBMexJ5(&duXns!}1(%G7bMcoWUmsbh;Xk5*LG8G|nv)YCn#?^`4)>lS+j -zvbF{6i^hUv+A$(?TU6Vn&LQE1_{Cen73*?@T_4)=xm4*WYM451w6iQmeQNDeRY2!? -zB;C-S$?1lFj9z~%Nant|=}T&`p($w7y%byvbnoIwLmHu-o`?$P!I8X(SSJ|;r79)u -zb}Mvhbb3ohN048&DAEDiF5g&iRNxWCQN#J(@-c_SO05vG^Ab@w%Kopp1eY)^v8S~_ -zt<7-YBDHZqh@b&h7wKUuP1vn3VGzY4`gn9397)M -zjgEN|7v*u`_%^hs6lGOCkc#(>FWE5JVM5$TmiXW$xS-U5dnqY#?x#Fd$9ha`uf+5M -z?QG$}jzDTJ_!+1L28~~?m5hM$F9o(iosI&Bh7&;OD5A?ltKqlJn{t6iCDm@tku)hm -z34YlxjG;F5&VT>e@hz813s2M1HCx?x)h;ZsCF`N#LC#8ZMeR20kRO40IZmBjEAVBB -z5(^i5qj4RQASWbyEH_z_ErvsUaPHMGyR7TXERa-4gciIxwUwHX&)ImAl`o?#ZsC%u -zyNOJ?-LkbpHht<<3BlkiVeJ|5<{lixUIRf~l$6)@7(GrESk};*9cjUsTXtqk+?;qy -zTm(%AG{%@fuFebTB%3072B};=my%C%s~(1yUB5Le4)%U$rG!ft)Z_(+R -zwtRovQ2FDv6uS41x2r-yA*jj%y9G$D2b8$PJx%ijZyQHJvfI%PX>f$LQO>bDd?p~xCZ2UrPPzy+a2&`|P0B#Orq!$wX9eG&{CX0=EY -zA@+<@#fe{HM2gJu1#_c=|1$GbcJN(Vq;+p1tp`*SJF_uP6p@IxOxKY(&R?4BdZcZW -zHKQcP4*IrdA)se43sid9jrT%Tgd2MxSg918WrrUcFhdU-N#0%*{=B~YK|3ppQ -zTL%x=Ys&%#yu`Z6qi_%_m4I>mOc577Wbo+hWtG|v73nQB$JhuDI*}vkXd6;>7@evF -zQ8c;3J6sc)pfitvv8lz+X;qn0KGBMKT+-XmKVtYY{PKXV6(k?FF7UzzqNQqinV>+T -z5_7+jGK)BwN|KOFbVddVYN^~n+Y`ZjF%`pkxpk+CbSot$>!G0Li;Et{RR|HSyBm#G -zpjX}PFpucA&fhxhqtP^(U82-;Z~=mgT7CZ1zIq{YbX91Y)G5sms={_`lbO3_+PGS6 -zSI2GTWWKK6MjwxGTyE)3)2OFr+T&L3{5W0J!lz8lL4=5*dFxFxiD(Wr>dYT;F~dA2 -zv)>XJ5q+WoB6_2gZJgWA=S7m2%4*`(h?4&{o;A_At5kdi1`Sk6!Qs$p-BH4SNX*KP -z0@Pjw;98E8fM!3t;9=H+gcy+$O?Icf{OgihPLMrnn2twDl#xTRBJeQPh&#k(6C7?r -z4j5$n)EjcaBo`&2Xet;Y2sgiX(8h{}-e2U~qfiVnAfFHB^hjhCjoOk9ur(wlKcePNw%|MOrqJ|+&GI1$r48a-f$1=K}RW0BOi76 -zAlnH?+e)-2^H#}w!#22h5~2~s6ysRE?&BtQ<>kRnf%KzAy!8gGd0=mq5>Plxz6B_B -zO>PF;kkNH{jJmF(y%@-(WJ*0OHrJPTy6XmpCPagWuUqz>fkw$B0-94;#PLZ->OBqF -z8Aw{9pG1I12NQ(XJLzKeG(Jx`WfA6k&llXSEgqQ?!V(3-2}lLcfGqhH$u>d+ -zlxhxOk|-$^{y~0*5-tCU<9=OpIx~QXDEM99kj`W?->|n^AX#i%)9~8pr`YA{F}LD} -zaoaXG*I{K;;BK&wr9Os+M`u9A)ajAD>zS61iA)F8RQcc!boJV+xv$B{5Sk&J`6i6D -zpfpRBTE3g3^~H^JQ{2ZRhruoT|R0qzxd-XN} -z3%>5eX<8b^?N5<7Z -zyAaU!LR5zPdJ#V@N6Nrc{)SW^|9nm<#XiFAGKmcxPMnPKY8HG9t|aiHFi!!5gwwYe -z7cwq81ZRl0*QAqWxRps4xGjL3%vso|#MFGLw_3V4(m04knL+$&tHM62>I#A(3QEfX -zruKFW+=T~i2qFI_V!hqE^_zIzDXnc4-OMjMZFB0Up|W-tqG8tA^y#RXfzI7#w_1^b -zacovxIJM2r_n%Q!k9%WiBcODj#z5Tr`Pf%ITg_Q_C~dg4h`;1DxfrB|zBmGs6dcsp -z6dKbAU1_o1+D@Zq>w9q(Cj)g00w1{*rs5Dc@%{b#pO5b!Ke#I$X?J8}i!GPB$E5(nb}KyFo8^J-hW%JjStS39&MP`SOThMv{0+O5|F -zZz`-hjKoY>ByOW43As%bv}2}DM^o>r;|J`dE>--qy`AShFhYN~yWv=75)H5JbTbt8Ayu&L+22A>_C9Lu#b7krKNP2?)AzN) -zTVxwVk|B?466mBvB^J`P?Z6+}#NIPsw@vai>Zv1!H864AiT50x7__!qw8Wj~p=doV -z?X;>~BJ8f~foTZ%p#>PrPvxM%c=0t|y=_g^q5+N?eF82JZ%)y|IJy(b|qqCPC$-h`5D$E1C4hv^u(Rs;>BfdxfsCtTZ4Qc1l` -zBb<6X0?ri(^W%hwn`2tz3nz -zRDZ1Vmu^$nC94{!Fj3MqvK!6)WzYO~)shrwIyuMHg4oS9;tYT9YIrq!!r{04$#{;!^0|`lqFw9>LQiUn4u9=Q*H-9d|Nn -zI+th4vCZBLVF|b04CiwKGTSve{tcGue-TQlV^XbolgS6fAkIO$>^kuU9;G0_eZZ8G -z&~s1@LfZllj9RTv04v%1Di+~1+54FjKh9#DDd>`i%|qeqBE*eHl%>Y86Ne_ASQM)K -zLJWteKS+j_3Y{cY<6mjXYvZK7H5D@GiUY%bh(fxjvf|I2-i?lUr*)T_M; -zw|k}u^(ZRj(v+w|b^oC#xIis1b=t#7Ivq(Vc`cK#SG+1n%a0~$v>B8UzGfTKOsfEq -zMoJ?{jV}h;Ry=5{FSljTl+xgX6hc6*wm98H(9Y8+m%S%e1Sd+%u3Rji=KjS^Kq=3N -zXveyf?c88v#bVb4SZ(N{o-eM!2%jJT7G -zC?%nm`%=fj-*o`QdZPlST2P-eQ)eSaVJNFnyx;)-b->FeJ^d&AF>T_(=xA0kEG35K -zC1br0HD;4I7V@tTl8{CwiCZ6<=0vJ&Y$@i1`L$ww{)5AS^o0VXrV)TiT1gv`dUCr-U_E9BHlGt;#-SciAyA$ -zfb*p`J&9N=E)OSuYq$fcpkwq_X&kbj80p^7v`NOA;MhZS!~ql+)PDZbS@;2bO3{ba -z%KcU<9Ygg*)J}!j|4(0ztz;R6N~=beGY8fBvoKKzPR{T%oFAuqFoP;n-<}Z95*aS< -zr2h*zAJMldY&~bTdtWKxQKCh}EG)H+^}Qy=v!0G?6sgg{->3cTf?taCQ`&LmEL3(o -z2Ro@IUbufDBY9!^LR~E??mt2GjtS>hH$wnDt(7(& -zD6}-5^+ZmI2h;`sMJ^$7tv<|Nrc{s%Hj*qpXp#HWX87jurtc2J>U3Vz4UOioBnJ^ZkUo5!aBdiI770ACx|BkwY)|WG$+UUP1w$ -z+pvi;jp&KEl$Ss#HRn6w8MDC>%iKgWSJm<7NA=F*f*3ypU-GdmOojS%htP_)A~)vQ -z4t@g5N36}&Ly}+BFq?^~zXV*Np=2GGFSS$OFLSf+p_<}#W@D#eZiYu1g+!Pjyy1!XSKjUg{b{_h;v3WeZX{h-_XK&WA=LgKwAS~_Ju8rPoJ)1ZS -z!4tuEW26BqI<%la`1I7$B2nXzj;-3nC4rPcBby~-eJtRamtI3;kwshTO$ck0n!wxz -zET(0jt%bi_Mv -zA9g_sCtB>c{`{C^f*rh{>tVSZ=r*+iQr0F9FH1vuSLQ29>@)&i4F~GlG>0~i34xyc -z0qI$pm-aFTRM~WlN42dF-Y^Svh$w27mRb#nv;9&*5CL(3syec)d25$5$Qk;U%hi*# -zHYB2_HO;=_3-i=>Ztss>)#CA+F%ua9#CxR$xNJj)vCxrz!f&6u@z8ePFZ>9LZ1gv~ -zGZ)WUSFK;$1NGpTx>mgx7TnrZiybyDd~MhCJao8r(u&~cz)H&2`F1g8=z1p^oU2!|@%K9PN^U^V*irp6&SWuG^M_$|G -z@6kh-E}WRIo_!h865JX{kWOq0$ -ze&If#PhqKy$_UkSDLXzDqfYvEAemoicSJ&EomE_t#=H*4xG_o3;fjdv&#Z;>Pf|eo -zcC(o~TheUK@;OfJ>i%Zv%f)5|+B%645&1{G(4wCFwcOc{Sdz!Rp|T|3RKoBHia?(E5Yd)P)rK7j1&Zr}sTzP4?! -zcFa0H*zE1KBe5aihkCvIeeI`SZ=IVf?%F36LeB%Pk9|TQDXj&55u*^b$Vl?9Q7jP# -zNDT%Q>UE1keUSOcPQyZ@YsPh5dCZu|bt}l!A}**p4s7WSAUBofw=qK)t++SBQj!t< -z@iY;6gBjF(duq;19B=1py@`FC=g0UM9z0uy3`(eabrzu-h04=FD-cjJ18uzGQett| -zqs&bsTa&oShN@C)u;M*8;0E5*ZqO!P0KlbpB$H(tZr_k~Tcsg1Y!md2-5#_wri$OQ -z32NUewTSrWzFE4iTBmr#m5T`Vt+rE5rZ0P*3=gkq^c!9g^rLYI -zY3FK@{kW!@QHy$?$a;FAmB3kgP#Y(hWcvNm4l;NtCOe8tX#AQ~_ -z9=UEO#nwWfzM)cudjJ0Y`*msh=ta(NpZ@U6C-3X_!Oh?oolpelgK10crcQ{`M4I9? -zGwFzjzmAmqMbCR`9z6C7S(A(n+AmYv+pgo3k3Oq;6bm(1UXh+9g97^u|N2sV2_ku_ -z&!hmNz!xnET3K_p{2iqW{xV_)m!!_98grF517@<~igwhSaKr~x_rND*eI^PkXW7Dg -z!)u$`hsb5U+T-&LNt5Zv#p;&XJdPvOSQ)N?N2Eif1oR}ft?~g?q4e#0{a_7BH0ESg -z5^sJ+@tr3C?@TU{gNV`)1*%I7u1^cl|Am%KROB1lJWA5@B(Ok!^UEHpsh2CquV0lJ -zapY(o2d~(*dOSu?g4$Fp0anr==f`@i)wuqHxQU|jnlYTvb -zw{ob*XvAY4fiw)}NShMc$3{ZM+JVE=`mNvlywYamS>62Ty?;bg3Hi0fxIO6q=du4U -zZDWOQ@7mff^-LPwb;I3Fs?mEz9EZ2wj#_B_5&2DzzU#xp(e^~Z+348vj}B79^kWde -ziS_&-p9NoSuAS<*T|+vMB%iBc^CZr%?;MnFOJ;5A0w>xJD79dw -zOTVP{YBxRy8`PlN(Q|WDBiZ)j_!tkqa{>Le-=4uePnvKv5Q9?cG-x27wMBNI0=5s#}#_H7|;)2J^kmi`w!Ium)#M`B> -zkd)Nds`3xajcEHhrxAyuUM{p!1ga5_U2VqJVgu!m6MlNvpUY6^OT3riHJJeYTB~6L!=Ks6iK@77MM8z -zp!7%%F^I2~U}+JV-8^xG$+$rv=%^Y?JA#GfU?02P2hteWd>eY@5AXao3s=O@w~v~u -zehj@$$3$kJq77xP^!TCgtG0Hvwd!D6Scds{?EGc@OoC7ySoyr$NkY!mL9#@c)Wgtr -zwO)7Ktm$I+!_eo;cpC=Nh{+yQG^Bh%CRw&FiqRSnzqfAFkv1|#_(E5c>8gVw2 -zNaVa911YfV>K#xcrjJ|87Wm-C*e+{tEJq#=F8Z!}@U6zKNhJ6*FWu3tx-)Zjlj9c| -z$7OFeztkkrnHY2)vNN+v4cAZ|M$(JAuuVN*cm&dr$30%k_uz@y7N9&VNaG6_w=6C( -zcvQ3&trB36C!C*(Y>&YMG6z*vD?wV8<5@=Fwc-53nzWl~RZ^e?-*Vv95CWNZf!QTM -zx&;arwR>eNCHi|*R=Z{Z$tz@ci~f!7$J-cHve-64Gm41`5oOm!6EL2lIaLlq!`Dg675GbuaAWn -zY;Hp)$-r%g6>Dmhk31wJQay(}&)c+bsuxd7wcXU*4172`tUa-XBaD4yNwE#X{#bm^ -zXq&?}8z(L}vR%nssyo5Z_HI1LWZH(Rjl5uYRCnQPw$9RuPaEIsXq#ofE(bpp2U^21 -zlHjk%%o4em!?Bpkg-yW1fplgXUX({_?DS)OX&CO^-~8|Ju$D$wQ^?NXyrG=xPmQ0+BPV-PWPC4TME#Jwvf_LETs_pbj@uRGchL@3{vALdQ41tyakD9GMob2c -zD~Y=>@UkG<=A$C}a8nPyS1d&nY1sFDf3a!@5GRumdNGQkb`FX7bjg%kao4C19KoJY -z@+Fkf+*}TTr$1=*c+D2ELqUltU6Pa9P$hMESUDjH)eL1jS6DA%1C%PT7NJZ@1gSVU -z$XqF|!5@mKiOzab`=lT6tgZvpDu}cyOCT7B0yV_t24^J63G5%VCan)#b)n5B5)?_2{Ua5|cL4W+Y -zSi2+bd&tX{SN^_QFV>OteCmU)PFuBEzjo};cJ->)W1h^mkYp8|> -z2W~L8eO5pj5}8J4O!Jcg7Ep$^64{x~RHUg0OgU#zw8xn*s>!{eOX2})HPzx}Na*Wlm1E-sOvTxAIpdUj!gwZe!PinJ -zM6xFk)`%ml*Sj=|w=6smR%u>< -zwlcIxOoJ+9T2kY3h(s--#A3d_GQ1a2$dfvg3)3WVr^fYl -zJn0Qb0ntWEr -zTB;m9z{;dvXpy>e+fI23Y*-<7CZ1?S_k>AwAi7(Q;R+x}pcsn42xoV1V;K5*5~NgV -z+lYreNIu&n)WF`2kIJnN6EEkx8!GP&8R!F_l(|7GvbF%hef@Ok#$oK-%n>qf7)GD< -zy6$|@02^REyqj6gWa8nO=t7e#_Q<n6`tJNa?H -zOnv8jJ*Mh77OsQm{BX?ca6J65@~fPo!R`zV=8*{H;O|6vx>2EF(`sB%#0>h}8=zjJ -zfIO#*Xk^LMBFUYP|p;Puj~1S_(4WE<*rwFsUyWO~etdTOea1FpjUoSp(_; -zATax-RfdQ<=E~!c?plYaL{w+_OW4)E1h(EtX>wtc9K0ZnoE3|hcB1tHELKVjD9~h~ -ziS|OnPfJ4PVId9VZ41%y$dqbK0HBrEB%oM1BbVNW>L2MSc-wp_sM_p^RE$N#E?ITr -zc^j=_<$wsGMMmI-N7i;Yimln5>!ya(Qy&(n(a=1(V>Br2p-8p}b8DW)>^Q30#Wy~& -zb4V1}wH-L9!^K+nGc%ZIW>UcX)U$ie#R273Vn4W^?anx_Bg*p*suILCsw4Wje+Xg1 -z#DQ5+d_3m$(VM;>|3RELW|gU5=diXcWYXNW4cV)spgeZ7uT#h3B(0U34xi0qV9zBf -z4f9MI_QJ~aV#t);roc+U@<@HlQO+(`z+z5F{SpdPCiNI~lbAs2?=qpVqSSP9U8cDv -zNg;%Mt_QFp+Z=N35EZ~;m2g7j`pl|*riXx4Ass-T;?gy17s*!HR=@G~$y!yT@=7Aq|46j!^Q{P4sghC1*GEI*C5?X1rsSxY}7#_AkYNqZ&qY_ -zP{-lOw)6YNXL9j40ag#3<8rrs!73?v@`yz#I6X|=5?SNR##N!+TKRV6t+n>kBK!wi -zE6t&e6!>bM$7;7?+_#Z5I}QwV8`Raxh&)HPZF)^z$H3}tAGcpXy`v -zrti42gWHh_$t_w~-6WJ@przd){y{k*mzV(^Fs+ZMs)ymdb~N2^c*E=a=pU$DkE0G& -zG%g}eo-blh;tkenu1L9Zm19luBCejd{IRJ-+^IWivOuh;#@k8jGJf0Qu)r};Jh`1n?A@zPM$ -zQj;v6No{!DdsG-n6H$XQBdTZOh#~5W;hWN4jrMt(J1VV>--Iv*NGAT@sa1f!fRci| -zl{R@&I^05diF!J0oZ6DWdL30G?F(uiJQt&COU(T>eMD%(QffOK;v!oYfDU>Mc6SjO -z_@$jUTqvAqqHM;fI?_uRv7Opkp^hnW4tu$A0LctL(}|FL`y%Ta-N+_!ojfg~3_QwH -zi(oG_B_!ZXyF6Cg}diffNAL3_V^(KdPEOZA-oierMnCER~aB8PHA_n59*@ -z_~c(4nTYNn6QUAexPZ-u(v!?0f1Js7b|-+csD*QOXzL}RAu)o(qXJ+S57`CW+LE5# -zun9D(sR{@a7gyr&NWu32P3z^50yzVyVgs0!_;eE7qY^*1$T2`!ctIOZ?4w}CyT_?B -z4=cVB`DGCp4CV&jJpyNYgH^(cE9(2&w|W)~0|3rIn>fqW9rq68Nc=Z`cvPvH#F9%m -z5g|^~IF&ka2f%aLoP_BDnyJmySQUgEwBcI`2I_o-E_K0(&&n-IQZT2=I1=RU -z-HxPHGvo{R9DznM%XLVUa?-$r1l(|cWi^eJw$sERLStpM&>PUST$u^YNl1grbq_ZW -zT^_{5Rx~XBQmV%KPfaS0{{Bg$|I-79{f}g9VbGef -zrp@i*S6Bg37E -zaDd<5NM@)}P5{tGFQt1lrQD{tk(rW+s~nNPCXS6p(<P)L%iAM}cOjnTtqxACVdH{6umI$(Ps|PQ-99TXh7y5vK-Hox -zT*|&A>0F?0)O9Q6Zp92ijGfkh%?1p}#mQsq{A#CipetMnXOQLBgnKMwFE61Rm)nnm -zm@SWZXvU2AJOJT{7r-$YxU*2>5GB}#381KAsYGa^NGDIF)a6);3UYUvx5xpOIIly} -zTzmtbjvd -zawRB?-P~J6Qpz{1Aau~x$Ry+`v&yut)D5_VA5Qy7^&K*Yxe(z{BCcYz6@&&!@}^Ejk8S77(-&{%JjYiNc#TV(DyA -z^&MyRkG4Gv#;002^DipNxMj}bm<65ed>pMCfF`c*?^PflQ2y3)LKsmO#J?t4K=T**lXI+3^zk{$6vfB{mY>`0_K -z(caDGt6mD+k#fTH+9MWoPo1>`T -zSgKOv3Xdu#12}Wg8NJ;`g6x6EV5qoca0XE-M6vLGk -zg>ZBrW{TPpV>#_wf&nHKO23MQo}R^@iG4)C--(_hdodJ?+EdV5Y{c6_-9t?bnL{@M -zb;j~Y><0Bwl-NZ(O0g*~;=RV+bZg!?X^04JA$y(qN#Cw<=OUA1zieU7qVmnyBi$tS -zLfQ_kwYInIz6q0b7}hCnX}BCEqx*$HJV{aVHb9EaHQFCI5QdU;{o6pNOULg22k%%PmWI* -zKo~Q$y_@tTDh#cI)k5(DjS52h@ -z7NDDJ1y?{q;d_gH;;yVG5E0b0v?HDtJl|M -zSISYAs?=iGPPj@OOEOdrGS%^MV<`dB`cnSGi^rec0fo74`N1dfAaVRn71N$+ereU0 -z>kfoFlpL@ZDFKA*kQb5i{@=SDrG#S|m}H3;{Q7lmWH_`faC*d=oZhA|eM1n$K}#z< -z*U_XJe>ApaBfWMgO~_F)yA`9_!8AH>40~un8|N8eduo_yf-hb(0Z3PNN}A5x65aqD3peH@NO{BCsn -zER?gj_$0LB1gJWe9I2=|y10u>6tK}_^6T6%?#7tNo~g&l2wt`P_-RA4_QG0T^w2eJ -zR?TdC7Cy_S$Kg}xh#4i{X(=`?NT=nqr~|UjX!1qrnpY2i!EP94V=1+nP>w2lvEcJ` -zG-UI&^buOf{v4m4q?-dcr`j1{?|q4{Iy(}e(4fLB(%A!iU*%Am?e2Fy>Cd9wf{IG0 -zFq^TVs#Su|jd=-UC0a{CNoPsE>a7nAX{t;?L7C1BTPJ9pJs6^LT<4<>MmUqUYM*LO -ztsS>|cfCF~zQbll@K0{{!(wuM2yqYY)`hirS$Ky?s$!JpaktY}A6HEFxKGFvzjom9 -zEp-PmpJvGwPsTwOkCWJuL>V;WPLbS9oclT+z3#?f?bg~PZUT=Rv6KUqV~v(KE5T6L -zD*>My(GMT3To4Bg0&T=ovL2#ol%qq*l -z9xi?72x~GARNlEhZb3pMZY{V;#h;LS{N5R)}$X$tBBQ^99GKz=1%m5;u -zk;?M2c2LRGf&e?qYZ66F3(kwa(_9q_xjObzVJ!^E5HcZhSRnla9-7 -z@b$EJF-IFy-6qZCsP=lb6+fiTLp%44+8LCD3>jko`3M}3?6pqczq9)KY=3aOU`CH7 -z^GGAc_5dy>i3i1ACQl!N`1orH8-!YNZ^`_C|4d>qTx6UfabHsslTPpH2G) -zsliV+gEnccs+6R@gcMPikOg2h5%x!AtBnZ**;hgFyAC8(NYpKIxziUSe9JB!O|y81 -z(4cb7<^v*(kzLGPv1HWn9pVc3EF7EABCKS*E{z^)bu68X5+b6*9^9~lud^{OI@Bl!Vy!C2PbB95QT}}+(ap{xkfjhoi*ryh5!)+_aFpqXM6Vp& -zIN9)AY}9FD3A`vV&*I{^RHEV#f+gN6)NadnQhq$1-tW>b!HVzV0Yob2Md?n&2D(!+ -z(sN?^LfwoR$Z)w4!CT0DIzA+ER67#jz7#Slqz6C(QR7wYtW-Knvy_#84wfV|7 -z=4-m%4}5OmyZo5_4Ml2Z$70L#)%gq;jc3LGBYE}tq71&=!P2`${NG5J8oc^Gv -zC5hN%k8A-0(Arqad-z2leNpGA*iAxBdah{dtnFE`l6kIemgxx9zz2jN9d9~{oMvx< -zMo1e&9XvTW!)(ZOgO9BsKv!g7pS8zs*PQU7N{kaRL7apN?gu!6Bbqil@w#LKye%Vd -z1RPREu+Ndt>AhX%iUes@U)7U{6m3}AGZ)|{5qn2sx;+{WqxSu(xE81%s!$~sw_`nx -zeKa|3{=riZ@bPe|tUjnF2xu-J&JqMwaX#`@8tZ_BH~kjXp1FghCUkN*a8Ck|qJy}= -zrsvII;IsN(I%vJwreRXwb&16oii5g9mX8~QxCrHv0A;W5B(2!veNq}LUgNC#qDr+G -z>Dqzw3jolyIQ#O*b3LQ1o&45|TF%ks(go@<3i>kp4Wxj?a1}K3D0#D!wrE5dCLe+( -zmY{j?!-N -zK!K4&s)qG~P}(s1r#x*uax2UI^|<=0aH)#Ducn{MKm+HA-!b2q3w( -zL89%%ZIW=X}!HQ~N2|u^m^``EQBXPL0pF7&KYP`S*{Ji9*9ow<@8)w>i -z;;i3Fzrl9xV`-P^>p3m5^J -z`PZ6F$FcG81L_x5WKR<#?}E^bmwxoeV;k78_e+Vu!CW2Uy3ve+)6kUkO!$RBBs~p -zmN=yzjvn4SzINERbBU2Ue%uD4aq(0zW} -zl+clOJFi{Gf`4D$#KAf>1@qg7nr*6-rHvj%W@@@1;i1BYWY>vQq?={#JcHyEvQHdUAX-~NZmDSp{j;lS9aQ%qC-W} -z={1woE|rd`dzAHUUi5MnU#t~WzY{surMfyW0W*Bzul00R8{_Osz8cjj&iLQkSt>f# -z;-81Jp*JLyBL^|!Rmo^2s|d^U68Ip~s)aG@rD0DRnJwpH#RvIP`;p=l#hWZ8ngo8^ -zTfG_T$X+8P(DHVNs}l(;s7g+D)yq;0buBb)YU$C2nengSmzbW83*v)CPLnk1!yzSh -z@m|!x;NWACP?N^nScd>dF0Xp){c^~}G#(#l{uN{OhI#~|8sCLB5KfuN*1QK)P){Hs -zZ6gb@nJL;Gp*7-bNgUjuF@hOwqe2yT -zL0X4ljvRUy8odKRklVzWCO(@im3lPJ1pFEPP~sfp*KQK*tj(N6#ACqlMVU6)6H(Wj -zl&DhV`Zkj2L}5XBqfV37dxXHD3<0^=ws7gSfY#B21gZsfzp@QQg)^d(YJ|9RK|$>a -znmJ6PpRAxD3lV~kLCQm`%vR4_ -zYdy4;LzOGbgtnVh*cs|SwB5Wm%_tT<@ewjGuqn1Z}y#;muD$Qvv%R1WtH)*+B*&5kT^Y4!yRoxv{!wHWh(v&%`0rC -zY!L;!hi(*0xF<_1={yXA5>l_`feUwrK6hjbUd_RD31*WxRtbYsVrYjH0%KQ(0U;oE -z6ppSitYMu=Ct>N}6iFo=uG50xG}v3ZoV-L3;VINAfzR;VqDCy!aZ+21WyW18#O4-w -z7#vC2Wwnnod4&*g(4QBmaO`ghj+jpDkjo=Was{{7YqK?mC(`Z9><+SM`3oLunMkeS -z^)YHo3N;VqNbk_QZfC+^7p{Xx&7D+j&Dup6$y&{

6%HQk8U+9Sdo~JO*P>0KLD& -z_DBsgF5F&g%gCt(v(dC|V#Nm)VMySi?kWqt69hmxPPTlKp0V#Y!Rj@ch9vbl#GmZr -z^J-gPFX9bCAP~aFPTp{LyrwLBQug8{;peTpkMCb8f5lJwJ%#KD3tanF4 -zrhkDCxo9OZfNZ=;Ug%?X#7*+CieZ^jjD}BT&p@xUQj?&CQ!Np87YcJl#J)~8zJxMP -zQ*N?2eETkoCgE3SARLcN -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 -zGD2o1EJIiG9wUfk;PY=GkWnP!HzyqW -zB0TUVacN&QQM5?gDwTUkVP?fIn6U%e7%wvt<0=a#6xwj+LA~~PAXY1$3T~|5G!AKB -z90)XbnmnY1!x^Fta_yyA7f>aY!G9*iN+A$X+!^(gw0{G>FIU(tWN^VV*rNDF${w_7 -z5ZBlO*y2UN9T8oL#)cw;L~F^0s4ruhNm7W~SE5_mTmjWgNOPfAfOSJk(I)%4Hz}78 -zqmUEw5{x`BdD4sPK#V=~fCxOiR?@;%UyPKDD$F}$EKk^T2noDP!!W`4zs7c|m9U$_ -zG=5=IM0vqw2-+f`MX3pSTsvK=A_x+Ym8Jg&_+d*4Hxp1_Q?WNyk;rSQ=2Jxx#sHn8 -za2&dgO6o~kZ~)WV!ow$V=~7#=RH5Og1V$p5J9Q>n+=7r)79Uakgei{(wr!A?H9Vpx -zQYwxnAr=-T*9k4^bmEJhN9OsKF*zX)Y~jA~uxg|eg}+}kAY -zYJ1D%pUT-zuXy~3himvxu!?WrfDYKJ%hO%UDT`BBop;PR!-hx^*(43(2~$k1J_rkU -zs%*Dxv~P3W4r~KVbvyT_Te2OPMq+PaHbJUVZjy2w3N+Dv8b`#zKfIu=M*`a8xN!yT -zsnlw&Ns6iFD~+^CF{;EK3ZE)x>m-aF#{o=;FcO89rcwYt)vBa$y%c&9!t|I|wg<_; -z%GBA2-;~1g8bP=0h6Z)A%ZXn$XK~h7x)PGS!6wN9|8LF}K-fx+1O#7na86ppT%{!4 -z83m~Atk`F#w9#S~>-g-11z`5Go`vW-D<&n>5b3ONvX?3_ZYu1Yb-{>Sa?<7`Ld~-@ -zJeuBG$pqBC8Z|=3q3mRY4R%7s3)XU8gj!-X;CV{AWRktF -zJWg6vYLW|+SlJ>TI_u!ntB<%5ntVqapZ*!7rV#K45ZksNX!B#AgMQ^T;^0N9TucK -zC@GEc^=+=G*FvG?h>WS=kcns;5CB*LdviI_{Q2i85bIilTZ?A{fl2q<6Nhh4Bj~b< -zpF)vXiR9)_Y6wFo^7sl`Yke~ -z1wBr$6N_TJm{j0ZXaylu?F-7(dC7T{Rb~vESI8wYkxp5y9O~B~wyY$RU(!Ha0FVya -z9&(gRCn*Zc{gEYjU-+D1h}E`)e;@KP;)XS#&c4uSX%|G+FXWO+P@4iID%DB!78d)0 -z)hMC7%YXZ63}CkXf4aZq%yU0*oR$FobzdLUjdDKutVb_4L?|fGi$o*?5vZp%<7$FL -z+Rz(gQoiJg(VcRQfE+H>78hczx{<6mlqz&OAf3Y3l2-v9x7B1q0C)Y)Qo|VDgfoNM -zNkM0kzb=Q?p@gUUQ%Dv(Spl4VD^vH3rQ)sv=N;cJpuJlUDIH(lXo7JL{jZZmY&spj -zP;^09BNIf+#WUq^sk{Dmrw;nuCA2}alkW|*YvAnyEfkLMGf|7$qPtD)S+C0swr5L!KuR_rW#Xwt5r;)K$;gPE!hG&Rg-| -zKw6C5`3a#IxS~IMqH#OdV|MJGSWv%RIMo+`IW<=FZzYH7YSVm?V%hHeb|vpn@oCOt -z%#g$@03Lyqpb!tT&~Anaikl)7^&H*Tm@ig0ah>-`o+U3qoe{yDjsaSamk^PWoK|v7#7}AA!tHVf&D`lIpi=;eQD*4o49L|DnFehU;ZIF}XWh9fw%*Hc -z`ifb}l?bTF^&{`I?Um+TluacRl-!!G?{|IG7bFT?mZZwGLf<%vVfpveCr`ZpxOM^ND;y#FtZL_EmOC-{15@~hJ2=hd0z+QCnf;cVt -zQ=Xl6 -zPvL%>Q^k@L;B3hDx%ez`Dw|FF`2B+n!AhH9J0bw?xsu`*9fOeVlpD_C#wsZilg%p+Bbm -zh+_)6tC!g}JfTOuI!=n3j%ao}2sUx-T5(1lHOT@fcz^u7M_bL}^!%~u<%*4(d4bQW -z9S$?^7rf7``prI)Z5W_>Ci|ea9ci2_3uiS}$2N>^J+7KuvpV#==EulaRjxi}PXC&# -zsqUsa!^w7$=0`>U_jvSN5^%0!BzNrmb<)x=+WYTF;G5YI!V-)igJLcpS_2Xqv@ug1XWGOf7KME2bioZD=wN@!Z9oE?OwUurItn0+>tUp^v?4Rn~70yA5(#_P5=yccKt=!b3p92~Qex!1(KA~)0Er+uSUwQ?-(+^Uu6amCrRtrQbl{Wbd8B7WGj9ii -z^#xsw{u=VjcV(fV(j}J9W+R(>4 -zURu6C%)#UAT~vNmm74kWUbN`vixw&s$Ero_%r!;<@L7GmZ)@y$GDA>aRkFRTnJh&n -zOpj3oZqB>UyElRFdn{v2&C2sNP4oNj-~NfYG0(%JCn@MSK7Xc>+GtQoi4mhm|En~o -zJIwRCba99z;W!w&&YJOJ@3m4h={X9UDEBZ0nN~k3T(NE?IX_Z&b%h%)C{~HjAr>r!x!Q$RMAi -zH)iv^7Pp1Z$Z6-R8Am8$8SXl4-ifM*^WKL}Rb)GxgYQi;+ts>RTT^cgf;8_3+$%dr -zjLip$!2amHS|7ZI$s#4zejTOIk{%4;x*vXJ3H2EI!J2&(SjF>$*pr*RCkDlf|8e*V -z7~aUXd=VpUQ}%vF1s7=}fYncm3srSA9MD5lt9$0+IM-!^1G#I@`f)JFMTP<_bh7Y{ -z0<3?9HFIyNM)gXgl4K(Qv+-BlL_^3=!`@DDSvR_6X5s{kHEy!%)8z8>yWonSc -z?+UV&2VyqdfuyAY_lwVmo|mAuafOxYg6t|~p2;KtPXB7|9V2np))W|MsmW0%3AaYP -z=FoBcO92*bq5jj0DnUibfjr3gI0PUUOh1kdCK4)RwOdSs; -z&ynDLM_faRH$gac)5O5i1sSV*U}TqQNQy1m)J}xQp03gJ-xaVV3=$!ve<7MDL?J4# -zmq1FCXHV4NK7<3_VIbcm8cX)B2? -zhKO0`P8g56vncy0yO5%%OW8NtI^p&fVVp{d$j+<>RFGilGCnTA6+n|dj{;Cjb`w&- -zJe8K-ptMw}^0eH$BS65G^o11dW4M#jl08IBxR(RRnKck?X`0@kIlZ(n&`;NYL0+O! -z6iWtSw`oc!yn6Y8ajIVcrM$?)iUsW+d#RH@M}j2Kf}?wzL;z1N5(not`%cTa2|LDG=%%gcZ -zL7}A`nAuPAO)qNlPJdBxM&UEID2%NEM6mpi5plP!%P#IXDq=vdQ2Ab4#ei#q&Kyx8 -zYLf(hFE7ip>)^@P>Ila6@&|)SNU#th%1O(vgG(mX7%*oiJ0rQYkwa~P*{1sDp+f-U -zA4mV@g~IP59lY|BEM06D -zNwKOV6*DhNTq*!~;mQo^Uan5bs0<)NnbSNj&Y@2vjURpJ3P)QJ4odnehjf?ET}`r_ -zK%SJ9`)MWCDOw0z)p>y@Q}~Ah7pVGGE+SaEk#fgT3|bTq6hC!_TcA3-372ua?x#oH -zL$U_ -z(?Ydk1E+1&lXojEikRR{&1)qgZ(lJbz(uB4|8ksNROI3d4a=ZXULq-Tu5*j1!y`IPg4vJOjBD*~0Ciy1^xx#(V|d3-+0O7j~@*03<Dn{b(Y^-1nmT)kmAPD?Z?*pVn7bd -z=B7cFQnm>Cm`VEFmj3mc1eL@TEi*L($rt{o+q@tQEWM&y1^zeEl1gY?dvHca38rs(Wmn-TJ8+07&sM)<>@o6UEB{~f3F`{VyCgPa44 -zAB5u{G-kg~ghSwhm5pT?TI`cp=qHZ -zIqb9t0;X0A3|5v=e!_*DW`z95>K=(?hlnj?doNuj#DF1YclU3SOc@#jue|uaT_5$D@XwGZ=wJACxzH3UH -zO9++@r+p7_@RoB!+u^0kuA*O6qdT^=z667$19{l7q&kGSjKnTGLuz`dhe*`a0xa)% -z$8zMN*YiHTC+o*&jZ@?1!ctQcWQA^O$IM6s73rYEHvf}w<=_$Fz06Kt_ocT)X?+^^>H5#B{7P1HUdsaKMj>>|n+pD! -z$d-g0j$bW#aUp9U$31z})V>raW(*G%3Y0dNBv1Eid7r#+!0%5y7F~fJp=VczYFmbh -zKh=8Cxj-X_7v$=Bp0&iAC9Xou&7`{n8Q`8vk|K}`=Vy71WV*J>O_xy+j&yh>DThWN -zBWbm}c+(w#wNKrEuvveMLe1XCmu~F54qJJjx1r;|#aM3Xcfai=EH}K9`V3`Nj#J^0 -zB__i$?T1%Jsd+fW_BrUEk=T3zf)1Z)9pcp4PB;iYpVu(>;!=t~N`Hjd5`WuCX?p|r -zMFr>;x*?xqO_Xse*-TM&bay`sfo97ESwk7oH#x;-01#&(+SZuQ6Tlcx=g*f51XQ}n -zYGN@tX@sGtpMBN=$uJBpldCx>yg#(ZzN_g>m*PM~29N -zx`RoYIKi`Hi9C&l_N8)W{+~szX+-jogtc_~#zNgOTY%mb+L};UyR;Ep{S-tpC0`aj -zq@UWiAoM%QGRCa9{l-UhVA}8-#rgR?g#x?vb0R -z$Hp(?E52>}D#+}ydD}#He)s+y&-FsYsJFdS%~mF{h3a66N#K^!Nzp^dBWlP6VUgLypF)MHd!NWLaMh7?#>gO#sBGRAFtkyKRW -z>!1&)aA;e-P=H;Dof+~m1{frLKqkViukjihZBHg4EQt}dPa(LsL$03N(d@RRIe0ic -zs%%{%z7Cr2cPiGl*jk*h2(}X#xr;Hr%l2vAfBN>fdt}+jA%%L5$zY;tKFWfe$6Nh*L -zoO5+Z>YAW7-AAZ}wpO}f68nkEj45Og5~MS56SArkD67(bBW -zk6ZWV4@B;E8Ba+&2k~}5(}jIR2pv*T70Y1$j{vt+j3z0}kv>o6XbgCP#iaRP2 -zWB5E;3a933WQ8`Pu#ypZ4V`Ggq{O?Xfeovb17RsR%_kL&ferAwD -zG@-Pk(c;|=$-?d9+@ -zy3FNSyt0k6atJ>%uGs8|jKFyM6es>3UL;?dILaDalLUeCRiLIJl<)u|IfVG8Z`5IQ -z5CK_W;(Z*Q9HFtXdlhH1#(yQU1pv`<8}0c#bV}e6(2!S9(U%Ek-1g{O>5jL>q6&g};0Y*4Z5a -zzY!5P^z}?qf4fdn`d?)W1c(o$fJ8ZfBQF+xe9_J?D@4*W(hcz^Hhjv!z$t?Oc@XX6Y`(`i8TyMemfqSKtMT{M>xY##gSYny@u4a2($fN1TxQ8cjH}=IU -zeYLmj_hM*@{ia{X)Q!39?fx_Le*nJ1dA2JtaZn9o{N95G6lQ44*+M!m?J&pA2q~Wo -zN*|mwWlCk -z4uyHGu119Q22pcIURwlw^rAb{_s!yCsa-G_sJ;0$TG9TfT);|5kNer-!RzD|0^^r& -zs;XZ^BW6|{AZV|tfcm2y=*AE~3`f>tj@iq}HLmle#|JzJS)Ej -zF1vXMq95Nr8z;1y0d_+=j#f*tl&ITrLi!6@Zejz6ynIfcf<4s#|0yUg_gNsBJq+j1 -zOF@F9mP%$IrT}}9Yy;Aaeoq&pVzlmLquZGFz8_Vb% -z?TGU!Zv}u`fm{+w(ZjAqm0YtS>ow4cG_sLbv)omt%qEq|Oo -z#%@LMn@IFR7>W`nkZ3EM@&Nvjn`?qU#?PE0EhlzD4MWX-fON+vl^=tdrUjZb;{p=n -z6_Y<%hE_IRD$x9>!9kbi9oN?br6Ck!{aK=enD3dLD1U42*=w$!b)3e~9Z?kgi&J*C -zUq7}J`rx#8c`aOHI%rn`vE>Xv5%e8t7QPi>rqMBmXz`wd&^JB=D7|N*hUhxzx`A9F -z6c0KBk+Jy-lqJK{a>OR88#pf{74CT_7)+F&twg(_J`SjN>?C1K90Btify-|eQobK< -zc@EqJo?&zu2D_P52JzN;UJttVjeORu5HWgRy|ZeEMZt&I3jat%3E8)3xj{DEi7kN` -zuxQ^xat5>eDX5v;K9^ev(M((sN92xgV*I8h{QwRk-W^agS^N}2ZUg5g-V!eg@a;;1 -z6%i>7;N$df+1vA&-BI1@vnS~f5E;)+H~Y!x!R#9e;Ns`FNT~tj*b`F+B1elD6d<)9 -zBSr%YaK)yS@f&d_f!!g)0a$Hf9f>~YXfmAZb4A8jIgLlxl;W4j)#*rr5ud%NU~qGb -z8C{I|oM5n(2Dw;o97rW;0n5@ -zHLAkBm<|QE7mj-(qK0MHAy%t}3sHUzZF%5mx7G*~=@QzxFFCW(|7zu}TNRUzo&2Q@W4(Ov81X7;U~MT1`MxT3_gT8=^77Ra7}Wlo5tUfVne -z1OUM!R>%Szzie#ZrOWwuZ%=O>U|`GNS>oR$Mn1CxgLTTf605wk0_YIU7KT{|b3N}M -zc~{)M*e&v-oo`q1*>1}))A%TOho84KyyEZ{f3^UgkXnGp5g$fkm{Yq53Iv#e)HtpY -z9qkjiGX%^Fb&(v^H%bub(})Wu5rQ2__*bGDCbid?T~S*6Dq@nOXCQBcSVDD1WO6kp -zSMScnx5X@k20?(z#Vm=&9jXg*b-`eupnMSzVzdoVSey^S=^j1u37!g*V%mUv7*bhD -zYJ2(U+&jRkKi`;VGYFyEXW)ITqCd1GFp;8B2~DU8LkdckrWc=)ZxtfxF};W=i(Jji -z_RH<$X|FP%v}7`Zj>buriQ``zRn{{=a#5S@Iph}ZtL`kCCkFKc&5S*>wKyD4^ewn- -zE6NpLT3h@9Q>P;&T_MB}5jf+dGOltUV~<}C6?7PpPVn=GriJhMVhKMCgC!GRZeD=^ -z$hOp0xX5&pMIs*)O>6+p9Y?w>Ld<`4e&(IG!#E*!XLd(X{N0 -z__oWaO%DA89@>EN?KcxPQ8A;|p% -zWM;b4;N>9Qh!|pqZn4w@j>e<`HM|yq9(wk~E6<|59Ru2)^)uT>vfb5)VwjOJ^%y5h -z7*vUP{G2hwa^TS*Mp|x6%ZVg6jG&+|jeq}=?L}1^f(ehI?`qx>{MEcmLtX9r^iu1r -zG$4K+c_l8_``vONwPxfzNrhH(S`I4Jj(SMZo^q(wu@$D%rP@g_l^eD!O9u)={70a> -zE3xb-uuH1wTFn}Wxk0kCh+m3lop}1|%V;V(5=uPLj+SS~CwTWz`$eo95n~R(88mVv -zK$sEtk;JL-=?i<4){)K!1c4|?>7#bF=D~_j4H=t%mtMgYK(7(`LfuhMV5WIVxh+U8 -zh}}wjRrdKza5#&^RsHlAQ?1vn2VDha(5p?87nN4xtM}b&K~7&G9hzjjrXb_S@b|j? -zz5FinAF^Naq~7nsSHr8zNo|ApD>`q&1QYVdbbBM>4rAE<`s?4KwYH0BRsK5SC99ss -zSL6DUr%f00qQ4hDI?m6$*;t&{FqeBeW;@zklBr>*VJ#h23*gq>UJY?quz3baHYEq2 -zi~R8gASkvISTvj$LHA~bWk*DyDrO;SB)G88U3{#YmL>^2Shx;l!wMBlDQbX4;r~RG9=fK+OE_UB -z!OfGlnlxl2X$V@EU|W9PizF`LlcnaIb5^3CShOEECAOq>s(SFONU92O--s8SIrV4u -zz;730mmZL_G+{dC_R5l#-P7@;B1w9x9w;3JX|$h1uXz#!roI`SJ<7eD+F*Nbo9{;$q>pFJ5_i^`mk2Q$^x{jV{## -z_c5=_vmf+0i8pB&j -zmu70>S{9_z&+@Nrnib0PXh~f9;>AMZEouDjhW6ytACW|b0Wd--R-fyAaQ0{bc~{lN -z&~t#@1FT)dJ|w;r;z3{N=gw9|-p)=W$uvN!bdtWBFCprrF>srpjrnk07>`NZpn-S; -zebIwOA*c528X$#B>R|%lo?*GaHTD$PEYzNHF;CisWLCKWem+8p^jRh@pa_@fhZ-op -zHtUI&TTEH*y4o$6xGML5*}B%PSydhTM777d?9*fP9qj>*+f)R+;$q`L!0o -z&r*btgk;Wk21^(dL6~JV?T>Ds(vNctk|Wa2oJ>lHY9J;)LISy@aF%USQ*g_3J9I0J -zVk75F*r;md}}wpEzwvkZQ<`{D&qt=377^%clwv;@9i- -zz}fSIhS90(+e*gZ&wK|!FY-jRD4QX-4}8+Y4X*Kf&F03c)6ps@*yuwjLB=#SW7L-9 -zNeM~bOVJ*GwYxb`M~3n6J1`rrQaUm@cHt! -zs7q+dlFR9K`4qJQvX93?K0s2UdXSkEf{H)@teSzIIX6k&w>Rl{u#qcc-D8P7PCNuP -zKr9Aqwqte#vWEJo?nKKkw}-}=Wmy3Tj2irj{9f2h4d7eli31@0WmZORU%EO0aNrzJF!l=!(qCe;@P`YXlBV)=H3)b329-Gmg -znT+f1fYjA!ib<|vupC&`1z;>j)tt6^zA{8pE&lC3>ehq#T{EYnatIDl?g>4F=9bbDFUi7KTxVPdK -zVyBZIIOZAlA#DT7R6FMQbj{+raa1xe(j(^u$9?}r+yyO4A;N#=c1}xeCVg|iM6Nn+ZZZ^C(^HXR -zt3jC`IF5QSm9bwMJkWQ3qhkh#*f%xr6kxt4B{^c1u=*(sG!$%I3bbQE-ERztJJRb+D)y{~I+JG$8{;J+m=X;mtB6cj{1@{=IU6jwTN()D -znKn*cPzxEU&AKpRi;~h*$H(nu%#XluTM3aGd{(*mZTj}|>WiI!{jW62bF^TGy^!&X -ztt~VXtwe5;2{e^qHzwq$_$f+rSU-~HA20P)+UG!Oe;Qsc3L-{ftm_k{k^cV%)A%9A -z61gnKY^?+r*@wYNroY9yP=G-b`LR1T4N1qd(6w<@ii!lVZMe2)^(r-mIyUk|$*~#K -z0djFY(gSz(G~`PShHNBc3Vg^JKb8vm;KZt|dx*%0C5_>GeUFc#BL#B-ag#J0oQ1)l -z09oB*UTxKXKI0%akhMj3-&D-}(}U990Ejzq9V|>U{$H-B%Lg#N)tyIdO;IZVuGiAX -z021Pl(E-^3t4nk%i2(0L)~1?O@O*cgol7r&nXE5=)D;+s3G|}ZtRdSo2agOWg^|Yu -zA!IQlC0Wo$KP5!Nv0@W=>t9Z^CxDfPzG;Y@y~9TaDvu1AWndcs{=VFhJTjH>UYIXF -zlD6@{Q^`g3@P~>m=W<@Jw|la3o7=CvfZ#UG70KrCtiz%EaIUy;a*22%>n?s)_nc02L&1_zs=&J$frc!o>He1`@`HD6dr>R#a**-|&2DK;D -zCk07EBvHst%HGk2GpcS|$)-8f^>qYPrRrC+djN_a86$GHT2P-aSL-PX`m7a(Li3NU -z@Ap@hqVk;}C=6^g}K4(fSnpjN5;Ak&r#chK4oL}lwMVy~u;qz;j~_V_vY -z2U%a(gRYQE8G2`nx80%kb5uw$6{(UeAFH74$Phs319dlpcirupk8}S$Stc2{{Y+*i -z8Zg_cs}rF0XmYjVr|$bl_AXy?oH!yY0|>3RtS(7l>PgtZ!eNa)Xb&m3r{R1`#%UoNgHw#?$yU|B>Ceer$ao#>5R!$5jvIoX -z>HvyWMSY>DLl!OwBEU?$EWrU~0QCFh8-vGNPA92HodlGjtgn4}c52ue<`>lxN|f8A -z;8DwPaGnX7IiwOtQB2+>gOFm*dPnh=tm=LB<|+1zw@9m67O7#xImH=AJ;M_%_UZO+ -z05X=Px=HK%6KO?^ffK5jW$b*b_f -z%rnM*oX_J#+D-MD2Kt8WkWmD()z_}D -zRlNd0iyblwMMMKQ(dPjmiu~3}#y1jsRrwuIf3IU8YA|;+?e@1JOPs)rjr{;ewITpH -z(XvQcwHyyc(hezx8#`F>1r_P|M?~9}4cRu0NcLE5RFCyGZ3QmPB^m3`s#!d~x@itW -zwW4z;^9X|m0p=Djc}>IVp&fNR;YE{$^St?Qzb} -z-}9#mOwGG(8E4xBuZw01(&ZNVrO`-anwDdq``C6UkXEp>B!6zty=;?YUl)Poc_WAF -zgTR1exqOUnr1_)%9;H56mT`<_;9@!28!ZBk;QLbntHM;z1?3Ay0rZgAHz!>FgX_g5 -zcJ!$l!|9bI9?)`)GrhQ+4wGG;nVKnUNfwv`Hg`y!=)r^Jj5gk`Vk}Zbo7BA+58)tt -zZ}2yt90qXw^XvCjeR)b|oDHaVzy=jxDC&`0*0qp5DSwC5KSYX>{^Df+wYzZ~uemIP -zh!{dfc>%KOzhtG_8Rn5NtPqC7CT#%HW6X^B4W~*;5w)sLVd#~3WIi_ZO;LQlWo@;I -zv(hSZ(U7MhFz`jOabMAZHRh=&ML`z-#j{bKeM%ket28sOKYmVKY8xP`TvxqzALL7> -z#~FCKfT9^_4g03FTzZHv^$`@6x&CFq`(WPOE@iV{X(PxqJ|7G6=dL$lY43O1Bs-eh -z$YL#$mJXSmuQ17=n~rh}`o#)x+>1(cMTV9|J-7ZXr2CP-#9$cY*{RbpPWJru`~2t6 -zb%x4q-`M+YIwrH6en?K3+~a$+_mPq&n8aT?WpmW=ol}OVyALryg~%*fqLNKWNtK%c -zbNc}J+R^H7Hm?kvirAd`+XXBfAcy}lRD!V)Jjb_-^2y8e-v9X9v;TxRmb2lr14l+-0yA_eH%n&-{ -zs?QyuIdEP~Q&Pndk_S9B6j)%ELAkg`@++s+Xbns9+{8x3U1)3cb>!C9jW$KW5_%nb~X<9AJW$a7U9u9ZF_3>W2lBZbFAwv=yqXv-L -ztj|ok1e~k>_(E!ajxh_P$4MU6JRy_#d$sQX>tc?bv@>^V6k1$ -z9&&$?MIE)5S{Sr5FleVpck?V`qiV}beZ9kwb~om?xC)!i-JExFPgW&+rS$JH|NV!p -zUY6}9E<-c!(M09@?(pNufmoTx+^=^J0=L~VWaK}y-TgOuS@>BD_rFLs#n -zO2ay~?O>JqeeFXxzk*Pjm7^IvI0@kxX(5g82TGJvve++D%jyVSKS_}9Q1kKNL!9QI -z$s*NT<;Z1OmZZ4l%CWc2u&!M%OsVBf_KTE9mv?{koj3*WI@CGs$0~aKy~VLkc&XOrj@sJBQ3dC+jW=5 -zsGW1$Ff5}Fomg|Vr)yGv&&jOvi#;>HCz(IBCmq@dA9w8!CyTufZcY0T(pH~A@`9@W -z^+*2S4(i0ddVYuNcAm_Q&B+LiZZpbbspBwoy|qIY3Ei;fahq+oi7+drTSxMa*K5Aj -zi7XB$J{z@uM25V786 -zL>ydxS@{&fy=YFtwOhsFt6`q!ZS1U_CnduONi&*I1yHkw^p0Csr+y0KE?2>Kq%A+s -zO3%})g=*=8yBooHooP!@Orv%7@Nl8zqd0D;t|1+d;JUi9k~JyMwwR`=xzM+78Lt{J -z_F@}SH(j7wYdO;Z{{H&+dgH~$P{4rH4_8p6B5%~UmaN;a)lMSv -z(`c%%PTj8YMmz*!p+8UF(IkhFbXg?E_WBaaIMwbs5t5R=@56u*ijS -zzyJJI;su8}W(KaFybQ-hUAsgZ1>}pw8%MnU+50nc@S}$uMkePY)gBO|P`^$~T|G3A -z;f@xkFdq}LE?jR~kqsxiFQniqTGbWWxEdi>vHgjRr1%3H*J%;RL1=PU|qwTL8xa5JefSn*;% -zA>HbVT-)N7UTlfA1A3H2Vh63r?=)X~SU|u5P-P+{O(#rU8ECtfWxe(_tbS2j9eI-> -zaLU(g%%XDGDqSZkc%35S@~dt{W=Rp=joiAD--pvN2{+Vr^}(IQw0EUUj@hU(&e5JL -zsW)ZotTaP{)Vpw7VV6;x`3j7BAsS+2W;5vCw~C6SJN@Bcf_^So@_K_^>l!^kK1cI@ -z-=B0Rr3PgWV%yv6B2fKb=u7LfjZTW1>N6I*&NPkPJ_+JQeU^RP#BB#|r>`>*%@BCi -zPe2Q_MJ{?{#AAW5W8ws_g`@;sWp?n&YT|btxhJ>$L<@MiL%q?@{p5a6c3VuJ6RFK -zAw+q+7e`5tthu(mZS8Xw^A8BvDWk|i&aqD+2V7MZNX_`3yM(N3;kax2U{t2#Qki9Z -zAGNq%OuP~vzg5FG$L@@wC%FLoIRz*jsKV15AeoXAK(H?3vA)BB*)k78O&TuOJ=htRe+l#i_(|hb`CzOTu0(`34{cv`_ -zu8M%WzMsXGVM@>I_cvn?3yRWb*GM+OW&nWl*pucmgYfh|m?cPwn@?W-uGlp;(^rR7 -z2*6z=cJtJq_{oowB1G*nM9ITII~D<^*vuHa`qh`4ng0I!AGvm-&A>`CDEFLNj<(^M -zW@_>`#kC;?Dw9Hl&i>I+jVhx<+kH;)noH2G70_%ntpYisX9Q6F=muQp2}H>sCmV=l -zNhh#uX6?zDez&7hvSa}0Fiu5@JSh$2(=l|E?!Zny`jT-{PxUPip{%|31ECfT4iOKVi#vav_Ib+JC5 -zhu}YN4GrlLq)A_A+i7nRQnn2cSv8SY`CW}0DSA2W-k^~NGWN)MSNeT(gJ%PJp`k3r -z070?ww}{i`d_ep*qQqO?18jt3lBw>ZJdYeyHGz;=c -zOZgt!7m#g|4W6X8hkAn(|3Mk0& -zVm$4{$><7}zyIc>N?zdWZ+DkRe0sXDkNn({`2FZT+j)FB3n4bmm&UnZt-aZ(4ynAJ? -zXL@EqDB?+vn4U4fUKJ06uKO%?aXRzNJu7Mir6ZQ^*x>;d*+DWC?;hsNoe*fS+mZF~ -zDBJI4-e8n0%)J;1fxiG$Zm~?7Q7qppwB8*58jVSzSOD;oqK|JB^S#{3Zxs9O5gopl -zC8z8=c_u^AYsH^OXKGeF0>2VOLiC*9V&uKBc)oI1L?DnDI>TsNoSUf=YXbVRAn-ol -zFwQg(?u;6xikmonWzu8v?bY|eMzNPvNepX$oM>eStfD2L)2JPB<+;R$A9WG;VnieD -z0rGTIwMKHl0S*{EV05yH8Xg{3_biLq3vw+!na;m$8L-En0ddFK7{k#W;VUtFxwsUqMn{{%N$laUJgv4xkh`OzPl%s1XVOJ+AEo0Cjku& -z%<`}akGO7l+`!hca|?8rV`VoiqC>4=T7+P9u#g(Bmo>X`OK#r>Biqn*v_xGoz8}UG -zGwqO@E`D9cF%iYyUeTV@=Cd%n87{ZYv_;U-#tHGms8*h)1O6C#a93+hK1}1BC`Dr0 -z$CYj6zB7{@_nD>}D2qZl8g3uz1=pIwbxM}PsAd&RFSCskX@VH^f3PKdbiUWc((zM@ -zOFA7X6u}pyxY~PE)fu|Rw3wKB@5Y!9f|dswj0PM3SnaqSWttDe=2>!}qRucO0=+GP -z3%YR6^++bflssRf=wciOnjSp#iC&$3kHXq_$=7iVJIT^1VFOfQD%8b<^=nj~Ql<$KuS5!tDDJ`d@^CfJAq%Awdq<(2GO -zMRE+wGNT3*5LJhQ1x_&i@XN>>k8HnF-3RK4#sqJ{jDBKKEFd_e61!3fBa@C(F8jc# -zu!-C~8bDMak`MVb0N30e8HOK{97#cdVF7!}bE81oF~PLMI|%l1-l7XWJM76P#MH4! -za*obuNZH;s9IZ>`Lh1>UhTtR2WCr?n`~BZP9eaFJXlo=)oCLb@nK(#qoR1qDNYtT= -zD}QbWOZT9jJo@Q|W^}*C?xHZD(MOQ}!=WI&l1qQ&cM4}ybO+9?sJ#sX%*{Ca|YJ`ntV)tzbf@Z*-9J(-5O+= -z9YB*^CvxCuUbYp^c<}XXB=T>^cF|LG0GS@d2{Lp1A&fpH~75ah1m(jH}0fG -z9AE-~IeCM$_uFpXcgTQ+({&E|;*c*OzYnH4(Iha`dVP5zf+b?`Vq;x!ViKYc1BOAv -zt`^tfg~>ce^O?%p4)H{Ey1GlUd|`Py@A^7Kdw$VBfw3{{fqdQo*-0kNt;;7qRi_=^ -z`cH@rj`QT@`*C&q&7Id^L_L$&k(x0%bp}p7=i!O#4%;o_rm$8xydCMI^ZXMqqpV{? -z2v#2^az1@=85JGqUdnh`NDc6EuSv|3iH|9sfsWLuI6E=2M#HBH3ZaEEEdHrdefWZk -z==?OQU@QsHX|5_zKA;`j#H38&+_y$e!4Pdjd|KZ3t;yPh$4f4>Sug8Lk7)y0XuJk|!d$`#^4Z$I+Y>*O@fNlCGzIe24Pt(YuHz7=KpK7H=;&`~`6QaoE*F&{k>B -z*08|Q8h<&3K{OsrwiGe8R=+5Bcg~rE`8X50aL(*jCSvejDIRC~45n>pu(>sjPCM^^ -z@0hPWqI#UbM6847IOvHUU##exFSEa_u)N;TI|NpsrbV@iyfvEXtLpQj=dhfKD4yTG -z>QYUSoAplbR2Npj*g6W7%x0t;^=y{v$?q_mRUzt9*jK$V>c)-$p%lr3D4Rpbm!g@v -zsG}k1!)(eiWwp+4E@Y75on_~g%dF

(_vUva~%pSO*<`Q_5@gQXuRa`q0? -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 -z)ESJfmF`Bp^Iw^GYAKJbi*O- -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 -zsS>$brb1 -zQ2q?0K~zp4|BPwaT*+21S~C`eGi&v+U6Y|DV=yO6j6aNml%(0Zju>k$OvIk;pvd(6 -zQ%KKf6lonpi`<4dGe9u77L328t8mn#bU@O3UX%k(Ws*Iau_1@`?#F?QoN%P`-%0i6 -z&?6nnl0G8kEG-`-mizhOl|O`};eNy8fc7r#qrxa3hL&^I1dUBY`aKecCl?NpA%!*W -z_D6FEl+23^8yc$7cMh#XB~FfFsAo%36jtyr+yWIBQbFB!qni!r1JGq-X6Nh_JF?A% -zoAXO-RATew(XzD(Y5~*4UaqVs1xb06^2J^#)%n`Tj;mcL>)GH6=dm;N@+Gw5s8|s) -zm91P(sy4uOGHuBw34Z$~=MyTc*Hg6>CEFU2YnjP7fWp}OK%*7W4eT9X0hh!+m%1dU -zlb?X3^{<=Gh;~F&3om`fbvE5J!$V$8zhfjfAqrdsp#6jorByzeF=;724~)zPE)5FT -zJ9}d+iu9u-w0ua=Tq~CSV0jURW3MV-@fQlhKE^duW)e)%`5eTVe+7QyC?ly;?waTUsZWMK##cGx+GrNgRQicL=N=>$H6|-9jzdy -zkyc_Lo9;d^s+@1_VtfW~NEZ#a9Iz>qbQg9s@KuJOFOV#yQr;3qP5a9qB7U>n -zQAJcElQd{trK8EU%os1?a`at{nQ7-hBn~@=8>DMk&I)L2z1V|iN+`?GKJg`R5&puh -zVgg|lI)4c&fRJ1{A{{DyPxYe}f_aPB1V<+7 -z!PcXC-bfQ+n%0lV8q{v-9MfvZ%AwJ|^VWCgE0PubDCI}WTDOYL+RsmR|7FXE432CJ -z)TE!gXo@4L7Nk@SYuxFMjOu|0EwaYB)KEI!l6GN>R4zRVt@a~_M{#|-o*KP#Y}IA) -zW7E$wJ14ED8qoeQfX{ZVNLeHk;Glff3YB4`Azjiawgl?>XBgNAXpQ}f%~-8%mkv$M -zJ44zYZO)|3=!T2AMv%QRI9#wjKGdM}Tqjh+$)u8#-C`Bj$@~p|e+`ZU>3FI0%O~T2 -zDLOd|PMsTRWUqQMO}KZn!7h`v%yZJ9wf7#f)(S_6+(}EufD8O|jUp2=7Y27@kWDLi -z(@ky()*>JdQ(E5HX}veodgr~+nWnk02`RX!LdFMK=i}v_({xSj1ViMYAVIT4fMdO8 -zN83Sn_+WI;yS!}fW#wxsYbHNPT>ImwWGgkWVIzpB#KP%!L56!@jVSrdHQg4M^!M#*i(_!v8H)Lj&wdS(*54;Bq2_5@`36QXJ!)` -zXx#6bQ+~A)C7)C7mui(QL~U?Dua28Xs~jRamCv!01z*^j?AP^OoymqNo&HeX`MOXL -zBbC6pnU6x=5H+is;6i51Y-OgGxDbw*5VC -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 -z7xzb$8kO#wcY^fUDQDc83)Q<`zhT%=> -zvF;Cxr!AEKG`W~jD8!cquf#iC`ecO(%w8a9_!<^F!hJ0o{a=Qj`?;JemLwkpjkSvv -z4~L=s9ZUS{ua0QVB>6`e(qKYonM(6w*y6v03?Sp3bc%~z6JnyJwYUr+|d#34HB+u48#{sn? -zc!BNQoV!4>;T-XgVOW;wxNs41iQ(Fg?gd6b@J!2rYi*z8Iks*ZK7E~MS~l1kpWs=h -z=h-e?P4O(lbsf_pud{vI@!&-Xo~;M2X9ofGeYS;n-SJ(W>{jRl*S2;23!d*=u0?gZ>)L_tBHR?g^Bm|v_uDWIG1kcx9)9?)PJ$=wk+XS_tcgvLe1~eYz&61w`zF;c@Fs5H((e-30d%fU^@HW=dol+#%un2H%AgqR+Y6Jlufys- -zi>=7!y~ecke$+NlZcWw;%!j(tKG;XCo)@Ik!n$u&zt}9^d|YC> -z3m)POY%5udOPk>dLjBtb1MHqB@QqkVdHtYc6+%1~KP2+39{TO#b#cY|T7IVZ9qj$j -zSNm!2>LW>_`Xf)CVStUj+s-gj1ljz|hN9EW=v>n9i7hIIh+J5;B=20-i+VQmU*H0Hr -zZV9Hem4rg9(RUBbypgFUS|svzzISoJov?pes2c>JQGQ;tik -z%L_qD;mX-Bt9bI1nNpjK7{jB=?$3lgZ+D~wEe~1zL=&<1!*QSk=F{4Yb=L-Uj7D88 -z_R}C*Br&%%g(^4fndqKK7q7a%TfZc~O(qy8aehS^Fp9^vH-O9=mmtr0<3#4jpQrO0 -zrYmCzh;4k2+h0(sYleFgvR4)6jN!WrFsAa7uIEvbXiNwoYOkS&56IekvbOQ7Uiay3 -zQ41T9Xq%P`nz`+3sjYMtOa%)6@=3*F2Q1cJ#A9b-kJJApANSN=jRdN-Db=oR`7j;J -z#PI0fQdU)%Ylx!zp}gC^x3axpD$ju;yAUvH+N9y9Q%9bU+c|WiuLz -z`s_5i`?c9NKOr>qI+~P`SsqEhIlR}UQjyF%sYaW?hTEUDF^gkzN<*3R{Vk+FEU+db -zp6$ZrYC&TycU6>E@wO?ha4;Wj%Q(08P&o*gJaB?y^1kXgz!GoZ3)-=N6*<|vf5r(j6- -z0;ZlLM%7->rQ?UGgT~wi#^zm-Lrbv$t8Bi3g-5kZZR!}87WPDZ4O0Y8>Cn+Md$~0n -z&3NAMexO|Zz;Li>!!ZT>+U{d_tD%R(X^LB{h=#O$3pyZL(dtG%5YqzW*O~WaOAo*E -zN^6q&x2%(cdE4$!)QV1u8V&ta-MZ(dN8A5+u>{N-V1m#hRLCn&Njs{?$d)*EAjO@T -z>(P^vD5eNq;@kSjBhPpk5xQcyq3LU7I_`+)f$@RUIF@g>h!h0-#ht6;_|isN?u;>U -z%!4*Vc~8L4WXZC<;_>4*9Nx#P?t)nQ1K|f=H)w09o5&c?j?)L9jSol^SRErn+<$KkK{WhOG -zEL>B!8*feeY(EyD;r~08BbM)B_q9$MhIP7oI2n|dMf=AI>t0yD-oM|!|N5Sa{EFB8 -zs{NhaQvDYE&;Q!a&;Q(h{w#m{KO6c#f1<$fmBKf#>EU~zqPht@pcm4G-eN=`c&DbQFfd9K3p -z9=4zbCUbgvdS~)4I;&mEr&!}2w2B@Voj$d5Th{ILSV;k4!z=nQs^@{$DA7e8UyVwb -zp+O1^gS8~ys3-b^eg}v~yP;-QlvT7wjoqO4k2Os#o4&*JsGU;JlNDwWq`>Uhj`{RE -z&&cVPoWr6NPyP9f8(QV&NNMJo*uF2p+-i%PE*heJ~TR7e#KGOJk9J -zVW#yEd7~Y7UPz8jGi{^n$x0(?Ji4LyV6mVI{Gk@4oiq#GHlxC@O=BkQgIUbJzs9vw -zbDtI{OTD$y-b-Sg95`lKJ$*0C)+WQ%M?#>g*N1L4J}*4FFY0lPbKzB8-Rh)3V?N@t -z2*bpzNIPjZ%T#sZZXBn+o`Z-KAc=&}t -zFOpzx_3#-zX#wFpxt%=R$|dcGWyXFW#=gi)L3Wm9=5y7gd@?iI@ml8KcHU6MPhOD< -zW7!dZwh)FnW<{E6%U#oTc&2GpKS*z2c@RoI1VkZjKq|H6h0DUcXv)0D7Xl)gx?x;l -zenGk{%Ns(w*M}er0@6HKz7gnStye)FkR#9XJJC1X@WSayjtdL!Jhj5%gPoF|*(}K^-ia7}B?k)nyvnytT_SU386|Mf%v8P?Nv_@4GUy2aCPMXmQ -zQ~S*tCF%GWy_^>4y#*#AU~%awj#aO1IV^!=L=OEGk71~|Zu$ebcv7G*nmC2~082o$ -zzh|N$eY@57R;)LcG1xxc4x)T4y*KvD6p%K`8ho#9Tip%3j`SziFxt7r7>pc;^e5Kn -z1U~DNJRMTfWm%IIKH?^>?W89yjBPlT|ETiP9Z8*aY4MVg2FQ=2!r?qMM)DJx -z>6V_+KXbh#y;W$hRWPUN+|)Fm1QC>A(P?LkdkNXUBKOM0vvj|-N&OMcQun1^ -z+MPv?@=@=`SQpTaD;bDPbv)P2Qdlq$DgH7U;h444^e{;MTEnse-{iB^C);lwoi6BN -z=V6lXFDv708w$7Uk%sqv>_5K0mg)a+W{dmKe#j#pjCnZ(6!BpZkz813mnsM;!|nYx -z#x>9rBVj|7RNJ<)hA<)8i2 -z$4@GJLpqlk{R^w%wimec(=OwAGy7okN7%Rfc>?A0s*r6*rj9+2-$c^B*@&)*{U&z{ -ziIZHo5uNs1dIG%na5;^KryUY33DdN<4dqDTk5$Q<+yA%sufMWn*?l1PJ^zY3RW)MQ -zRYvAHdCq-RqgcFM&3;JTWT~q~j#yol*zw5BI2o@Sk9-`Z3XnMi*oHJ7!>_SD7Dg6L -zX^0tEvd5SnNgjQW;g1}CBME(}0b#?y*#CpqZ>_a=?1;>B@2yr*w!mib_KCe?N92zE -zSbIHxD;(#vnjLm&d{D=}2|GBnNmoN3%_eBXz`ndQhxy2=9ClfDA0$T^j4&(LOVv*g -z@2CyJ-K*IPzrZiT$q?%(E}qObY>XCfh*ei`{l*|bOdTqE%QC-cbTeh$KOnVONz6am -z?$f5Fc|5n<`%UvBjhPXcKD944gLgFuE62~-5waeXRl6=cJ8Dvw0j()(F8QIeAk%7U -zldP<_jHx-_WX(L?(&Dhj(_M0Bn*4(6O|L25CwZ0D)`(dk72xVPqrE+6t4xs=cV;>I -zLPm81gt{)ro2#qC;>p!V7j8XUJU_g+c;|)pP>Hau0<&h{1uu|^K(|1zKa1nyV)|X) -zi4FEtW3!84$=@Im-|;yuY@2py!cL(Zb9~Af9qpJc{RH$)57zpnr+jjF@>VHSf{FZR+ROs%(h{lD}d`IUr5_qQLQH$-)+Kh -z7Sl8|Y0GA2_gtFh>m3mXw$Pb)Ohn+l*YLqLCx*tzxX#rwa_7w5`n64kWcP_YfXBlq -z6$_%X*vG5tW!(JWr$7D?lbeM2&vimGoeo`t7$ZMz5mTKi#*}-7>Af!Iz~5&(W|+1S -z{G26*Z+dVzcwGpSa0w!|e?Srs9AQrhPT}IKza|-|= -zl(Z<0XkT}BnJmMC$h3~tO0wM18W0kzK9cq}d+mgP!L(FGZ00YI| -z6s!~E9n*9emFYlF!aJhfvG^gO7;t%Bb<2t7)N&lk{BFK)?gBRM(+q2WsLJ+Z*qYA1 -zqG78dEYruICWr){zsHtWyDoISnB-(_YQ|j^?y98i;}yDXU&y`7nd$seSo@{?;Fnr= -zFLpnjtld>(uU4*I>bjG*_NvJ2RpqW4_W_N2bF)4h9XFkA3eTgR9YUI1_`o=FDN9n% -zHF)I^AtUZpeLP&%J*G3h&JOptyDr^6xj{~l%Rl%RXvi57k*M^JeZbiJmt6tk1R!EC -z`sd|6@=IX1%&W3s+p^-WDX28|3J){SL7cbiUE%u;U^{945pPq7;|@P?$VimoC{Rgx -zxd8vL -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 -z7S+4Ba^^z^xADcu+FwRtW -z91UK=q}3r=k>FY!Fd$nxDN~D+4ogcGB2!;tNhnE+B!&f&M9Ok58}=j>{~y}-IS%J| -zZ~4cap_5Cp;fOL$#1p~31u!&?(<;@^VN#FGYiYy0*ctp{T6DywX2;7nSnV9hqP#Q- -zUMn>nUz|h?sq-A+S$}{;>)4x}KM5C`TwG8MJR@d1sHHN8D$-{0p23Dkr$Gjbjs;q{ -zgMk&E26KjF@tnlQaF4OMw|E8g&ASIa1iV#kF7@B~_FaUU8Ccj!B+pILAt~uO!!diz -zr$e(>iaT2dpa0G4>C?Y;`q@nKD*tTuzNcWd?8eQ_GZy|J^5F<1d1t^m$l*QI(_K)+ -zJ6*atq^=+ob*L6uuY+EEPLVK>F@eBpvWkP}>69mH(^aBxxS%vBt=JE3Ky?!+E5vW1 -z1xPzGMJ|X2BB;peT{&ZLt;=HgsE$SOfr_09nUm9Lne5>v+YE(v(5yo}@podTInSrkY$IGh0QXuAMm~O1yd!zb&Trq2%#xz09ZRe$A=sX?TR7aF^W2UW+=t9YAWvS!& -z(rxx&)QGvDrAzz;t>J%{cn0Dw+=Bhm-!=mnm4`5K9$*qKeGYM9@39L{8f|FSU)%KU -z*^z#|GMC4S6Jfvxn|#!4?V$}`X!7Wji_^T=ifdNJNcXE$Tex0d{>kwR`Dq{UZv5339~f88 -z*$wTzsHuju>S9}UO~Kne-s1c@BA_gmNv|Fn@rSxO`r*-6ef^Oi((P}}{_;n}RD1+h -zJ*nA-``ORt;5tYq>QDSqDSJYI|ce<2lf<*ts<11!gMVPud2*axWR}H#uUWx -zL(AlVJV62JPiVC?C4-uA$j>T8!qMLJ2P^|gD?wP)IWAh5?cmBm*A4BxOgXhr{wPIdkE*MECnE*t)Xd0e*3*SR-N!PQkkV8 -zVAi>(DvMNYQrs&;&{7XlVPHwXZ{XA-yu>~uE`x1JTw;N#iD1mantBf&R&C^_MD>C^ -zHHmX=I+^>PAB#37=lrzm^^jWy3(00=Vr3D+pmWf`5ZGx7K_Hb@%#SkTN(0O32iZ7y -zE1>qLJ5$_s21YKuA~xvGiMJkiO8?@*1QyQD@|r#YKGj)+&=gkvGol~IJxIAS1#!Ue -z;+6r`82_>uvhOuB!%pb!* -zrXhtI7Xbl!3C9OG3w3z_Ux6d;!M$~5Xv4RdSh!T#{MUUUZ9ENSeoA^)-Wt0%5ZTy9 -zCRg-Jj!QXh(?Z;;vSc5tO3O4&YOc|)SY@HLJD!~rRd#H5GA%89b7{iectb)Oh)&j& -ziJzhlM(i*fT{KU^36yt{x!|DmwRwV5TZJ<|N_jhAeWA}?KTa0~n?21-Rca<_B>18| -z3>R2&EjgO|G)t|ks+FF<-~}M3t1N&f$yUGe=vTS|Hv1rD3q@Z}b|(9kM{ra5U^3od7=VJ&W+s_EE`7^W3E`(RBq)ohJppl+8H9lA3gp{)yj;~yPa?|^V}v+ -zc}KH1sDWpvAG`1tKX+`GSYJyH3WTcvFZ~<+ZKSVFNihHBfB($`OTSX%PuuJ8?OhNy#dVo;zO^8aK89R -z-G#8j5ZOhj&cp2_Ww@g4KirHVQIW{@fe$;+_2xNRs&^IANGi=^*XC!7E2lu({keEW -z@{#%fPo9##J0z|pRs_D?RNNz!1LoSIO@U-=KcLF4udev3tL(*7zQ93yD|g$vjMCV| -zNpt<2&zO)Ma_CTHZCsKIqcc*$KJ4NMb^5h3y2K`z+palku@2-EXD7+DmIB+r|BZP= -z4qUXY^WwSdVc^-F2hN8OuJ~&`m%*e=E7-i5jR-WJymt#^pN76vWT%Zu#QbrBr&7O! -ziIEiVM*57+k+`t~kLlL5&V#j&LBm_OB{B%};}}zM%)5Q@9CNI$0pPIcqj$=HpS$OI -zE_eSwccJUG>UP75Z=dfxH*%RPx5T-yoIyOVtSI)M8+gVM9gmK36MF?^Z}C;>fs`}w+lXM);Eo7uWd6eFSJaA%LX8;-&m+} -zA`~GKxd4aheR|JC3=7h)W!60xGNLg@xOIWP$wjl7aNsF1l@S9y -z-5>Lk_|q=4nR2IRVU!1m*E}u{amVtYRZZBSTf_sG++&VzuPk_s38|wWgqbO}?+zbb -zEQo9$M%);03SPv*DH$cL9&;3?Ep#@jLyC)6uSBJH7DsmPa&@KUg%}T?VHqx0I~hfOtxL~8&>jvO>NV}n9sp{{8w7MD(yzVY+#VjzLvMB6I2c`^ -z6Z9;~@OZ53a6D|TcKR1_3#(}>TlquDS}b2*A*9-!exfFb`m;qylbjDl>Nzqu;aNyo -z^cq@kJ)3`V=U_QFdwPjm7D#|S;}A#7Q?s!uys^)0?~LacRfno2RikQ&f61p;yJW|H -zmh`vOI5=xPAIHQc#0-_OXG$NC99rV@aGYWB{5)w!tBc6}f`mFad_ag^!% -zaGe`$Sr_lKxLPduTyQmcF9r2m^`ek54xReN_Fli?hRz}@{S^uS=>vOODdDb=W@1tp -zDq-Lv0uAFx3h)=o_!QWiy#c%+etUaiNbjok>8?uFr**nQj-ykvTc1cMKi$!Bd1&De -zd1~lP-?c3~8msq5<9%g^7s4D}2R0a&m91{NCRCJ$UHQNSG}x!%B)olnp4-*@2WZ@C%#YRG4qNtQ9A%fi#auM;Gq;nm9+( -z@}9-tR$Rj5dau`3tPgF#O_;8u%(Qgi@h7c2+B#NCCH9uuauhskNK|BvA~aMUHrIu~ -z%JFeJjI`9D)EC?0E_%ygN)UmR`e}u$%YM-hs106AA*|-WQVKu*+eK1V+dim~Z6AXO -z$13Acn~oZFL>AJ6Mgt~?_c_3hQZ2OMHKz*DCMfAb*XD)NKxuj -z2;~G2k=tcdSEhoVb_NF{t_f>|>APTR -zWE1X;g+}s=#82f>7sfMod-nr8FLJ8eT?8u~uNx}xDFe -zqmOl$-Sb=zA~x&pJc5(U&qN=EOMa41d@gbhc6SqY=tOkX4}^X`1h;^nw%77iZw6vu -z{(b<;;G&XeMF;z-7Kq>!w1ZL+G>c8DNn=~$H#Tlm{OY)93 -zJJAtcTD?=M^s=gT%aZ8fuGy{C*POAWdO6m#7aMHIMqjh@q>87{ht2s!uMG(oOltLt -zjqr)dR%rp|0Vp&ud8-mboZW-ktP4{MOIWKTD`C+a^`D^6Jtu@X` -zLtQ=3n -zjr%RuktOhZfi46RR+5WeBzm)0*26nIq>y`^$kxBdD!(+$Q=Za7(LEs64J5uxGTxiy -zxg?`$?1Xr?De*j}g(50g(E+PAwAY7R5`Rl1bOYF07Wv*3ftg!l-g^2XRpA6=y&9>sc@l&muL<1iIZ<#jQbuE0Vk^?a|7 -z;)-U9uE5A6+D65q;Iej1%inYar%Z&CbL9d&yRU2!Imxd9M>Ld$iNKA8<57idB(JVf -zKaakm*9Htk!jnoI&v^z8MG$a9IiP~{#W*-LjYpB9J@0n3JQU77S0MhzU041!d(bn% -z;?46w$m!{*!$RMDY=xWPOJcjaP(ATmr8JWd=JUt1yRK>3y-yP%U-UdZoggqugT-nk -z*vm6)g{~!j+E~0^6R&37hW6iqOWQOwK@Vo4FkvaN4V+Qh6ef$)Vsqy>L}*bw#8lSVOy -zuW#giGh!1yqViHe*vn!Gzba{SBQK`D#-@PNgj~&#s(Ci?aN9Yc>dx$P94@;r&Ti#k -z-CP>#;k|Zi9X1$al?19HsggSCng(QT+;tYfoj|I^JKm0fi- -zarT8W8U0K%S8(5tv`JEj0J3F^8FAYU9|yQmshd78AK9#O3#7j&_jcd)i4&?iS>x^e -z)J#J*1lBng9g~8=B5pH;{E(SA?at?%q?vWP(qDHG-|O<@w$9|^>MpcK9?mu^$Xc_8+eC{CICL|}*L -z#dTU|VCUvhmSXKm9tor@sna!ylgqP)tXZ6cMhWQdF5Ma4(qDgU_Nkodzrd4afiq@2 -zd51L#DLVylcj*@Vk3JMS9Y8X}ZpK(06ox+HWE2BFZj^W9P{Vmz7%Ac#3k4jIFwxhr -zfd`qz^Iz6_`tU*Z;XoqxHu1ODiqp^Q@A>yHf9Nr(e!*EeCVbXG+@gJ`!{t%I42eG= -zs(X#^%e8kU_siCb5hq={tSh36Cr_TZYx`tF -z464bJdzIKj<`tt1u9A_|>F^Gu^4*etL$9hnX -z02Cn6h(_c@M^b*Jacq|-tjuZLV+a?V8DSW|;@$#A}k4aZ=G{Vgz -zY?rVV1X^LX{M;j<0`<|xJD5ngf_1h{ -zF;1)Ql_~*N-QjF@6SF%8I_cPsI}Wd6;cGmvIg8wu`8k_r>Oo$)QqNpno^r4oZP}Wv -zf0-KsT9Msg;=zFEM9}mi9!cFd-Y~gzy@PDgPctI46l@qz_Q!`mr;hYyk-cPa&6epY -z+dWYyR)vdeKyis)2EVgJKh@{^X~-_uikshQ!}) -zxi=UIZ=Gai>*m{J)yDMBQYIrtJ?hiiAc5XX_UAhO7nHh#`%rdV7FAm1_f|4{2N>F4Q?ul@e7GGOaI{= -zcz=oXVbbjSO9QiQAIcx~@mqxvD;(qbH{-I&-*GR*9gp$giBrx)GH* -zyh(f7fZGAA-mVn)1U`*aYt4n2e^EAdZR2uLtW6jy(L|B;p*9MXD>l6yscFO^oMa%K -z54==`8uo}yz}LwL+G`+i$|au=$>Hd1^5R}itSrd@YVB-@`MN&>XiLVkDeHcH(3x7% -zK(Vch7Gain5j8k_o|lC?aC+hA)YVk$UgFCKC&<60oMDI1=xiT2o|&&UuMXZ>Odk-~ -zwTAJ7A~8WQQfqIkKLU>u70bfoWE`J|f}H60q3elWUh)T1&7C5vJ7m1;D{zAEjbVU1 -zJ)gxZ&_S^b_1pqs1&`+*CKfNzqOFl;1Z?~hVl7WD9fW(!*Lmq%y`ZaN2)pu{N}?$q -zf1O8rIpCqk{D&?8LfB^KG1Pu|GOcIHmY%Xq`)d((C8(ol}EgiKA6w@xA#@W%&UEi@YY9RR)!x;PI$p%SRg)n -zIM9qZ$}Vqal8ca`xI~P$ROQl`_JjD^f$FvJkk&uyH9R(>Cp3d|TdHVXjr^pC$v)8xlr?Hmf+R`W49O-J -z6RK_CLqe3q*4<1lH-0*5{6i>@o=nh7gmKR9DysbC<3O`B0dwwpLOd5kVKj26Pzw{2 -zESr5NxSr6lR14k&dWdLkdQGj-5mO)qDE7px+scCiA334lMV?1)CKv+f(sTfBHqBd; -zw1&z1l+GK#e>O*65`4QNHq;cc_2`|#onW094?Lf$DuaO0yo71<$iN=bHR=~V8>_Zx -z3Sf#&T2-Yh)rp#I-A7*!dOhsws4v`pF^K>vz>F__|CDnX;DLQJjk9(eY|1Q+Rf8=x -z+f;lTrgQ;)2AWn8&MT>Arw+t2MO`8+EdSDXSZg)mv`j+yxOHUetlS5J?qLAvF-wyv -z(qQMD`;Uj5m^k)1q;&y>usT;v<7B%j!&t7nXp4V>^57NM>)XZQu8f{74mZu6Y05oK -zxJqxOxrd43#n6XCaz?&kV0;0GB6`|3{VpAvM -zFUd@qHfNPI0(q4lL^PPP$jvFij^4<;>eBmL<&vhff+Gg@Q&38J39&Cpy@Cr0))BN*IH8mu!frvPqeM4^JPJUsflhm2YU61`{(&yT0AE0G4ZP2chgp1I>{*8E3&li*|K=b#wFl`IBc?lQ^pl -zW??E9Z8>F!wBSCic&nrRj^uzzh(uP5swo?8B~(VXwfJ -zaU*W4n&2F2k1VV%P+DwPi6O+GtUNrg~jGi`Mz7CBFK8zgeyC^P6X{ -z4&m)>{q*+OhT$Ti+IutrbzX>xFYMN_ZXHQbCgGiU@Y -zcr%mL6L2NTCHZF?BKMC!eCIEjBwtpy6)gz#;Kj(Gfx4{Vm##%ZKV057gg0P=EVC{gVgkpFU9kj6NeP -zNfwDq&gnS?3(k7bS+%G0ly -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 -zv}bIurb(RB!CMxdqGL*u -zy%E=)X_A+BDYRdp|BY2(Pm=@u0QelTbc?Ds41oYMFN6eq%!*iw>=>8aF$kC|xR%QZ -zJ;1=O*rR)u);7r{>>Ek%UBP#v(uia!RVAI@MZC8wVPz!{X!VcKtY>Z!(?PM*7nO&n -zmZ;ISx2LIOx3DeuzjdHzBrgm0@HyvBRY#ZnWx9@J712S*_*NWZn>IZ)RIiA2nXP;k -z(%Rc1Yp8WXRVS@mW#B{omXu!Gf -zQQB|bwAh$1ZH+xGZk^wCiIvhuHrsv^;80O4jpvNBfNJV3t$Ld_hRU!vtD{aHRd!oB!*^cB){I$@Seeb%OxuoA1dY~(n7*k2m5J|E -zasn$St%qZKRNgH|zv{cvZU-t8Z+KMJeaeKKgk&jHH7^qWfFtKH2n8GkL@7VgwsqF%+pX9aV+2>xnsBrx+rP;Y8?}Uei2e91Srdv920+L^ARHn -z+)#R>kQIM%?dwIZ^>3tGusGT=FOEcD?A14J7tufPT}Se1%FpJ{Ouh4dhSrms3W`cw73GlkkZr#L`z -zUF&=eobMG;i&EK+n$N7*&Q=Ym6xs-j7g{9dRMPA1n<*A8h?egP)IjPEI}!%${%yiu -zE+io;!wI@ -zb!Q9zj?>At#KCIB3e5O$K-KF#9lehG~Uy&xmB26kJdluofahF8)Lw32Qi<`E@;A|N~1tm_8Y&e!x -z!sw@reB^Eej)4;r^0Mfff%Mk7%z4wDDM-YCOrj2%yOIHnis_DiqEohJZ}N^`)LS3= -zZA_=|J?)87x63}1d`#1QRLIU$&TK%?w9W}27)PRR0DCN*TTPh9dIkNc^=-6^YoDyk -z7B;8|GL;HJ`;}44>ncC;-j>o@B^6v~57xCPA@&>7=#tx~Ssc+xV(OF*WHU=*`k543 -zB~!RdZ`)Q`8>-S8KAYtt9?{h)YnI5j6d4k4443 -z_Uh^xKfoF??^r0iB3^gBDl1k~&ZMYnv7gM?3%*#T>xrjy>a|BN+VV-Cev6B_k@+$3 -zg{B{+{kKvbOP1XzU8FUM11cKGFYMV(ymih%OVcoc4$w1?(+8%>%31}&^xlnzmR+|( -zIvaPX{M04QM-(=~5=N2!xlTIzZ{mrF(N*%3WF*VhtQ(xdAG=Px^&pmc -zro`4Ji$`8{X@%3ce|gecL9JvV?)u87)2O)dWXQwfDb|L1nsLPocNHU4L3tG_-z=gH -zt*W(?ByXP3uhJ3dDsKHZKQV2Fm_=8PBX=oh09!z$zdbIWk%ZQ!?$_Oqk7d!!y{G?; -zu7LBuqxfr}$F%|_peMVCP@uL_gp*A#bt6Y4u^-mIWV#7OUI#b<9GB2g{R*k%7$=$g -zjeTk;Ahu-}Z9JP%FQ=8t>yqr%>DF2ypn?g$lYC4Bg|OmQPptw;WT`Fv2M-HVRnt7l -zJDbp93LPQQhDI)+!h*V${>q?1cq-Ojr^K}ZeY1I|c -zrm&nYCXn89eRXXsn$kK{47p(9duc!=%93dkaeTxp(6M;V#SRQiQ=Eg_)IK(}t%BcS -zSF)LD`|D`aU&o1ez3s1)roUd*{dMZSrzrdDhTkJnvK>rfOL93j=!{*dc|%ZvcD3>a -z12%zpgLP->m{>MF$kx`VDD>A_kkgz73dXOb?W(?oxW219Db{DCV3u;*xt;VrndgpY -zXb7kCn@OJ5wPm@azUqycli?ZS-3Lw6h`1nFOu|+KIFO->KcB&VjZtIt|<^@aK|bgaNRDGW1YB~yuPWpW~t*)fPUAkEbnV&a=N8CJrl -zC#IdMx~B{3vee}3?#|Ro60$O*W7@eo=2f{?PYROTa&uJm2@h1c>iyJq>76;!8Am)C -zkp?$9*AuASt@?+nz60YJ4N){<(D|bJZX=4f9&ub&r<#L4ZkSd~VOd)?E=W^bo-egi@7=#y}S#7_Wif|eop^6YNrZ!5KK1D;OE*^_}JFpr2gp$=t1JG_*O+7_!Ua4ooz|sp0!)5w2I`g8Q$s=@Ol*S{oN3TZ;opUdcc^Y~`p;@>wGUj(k3<7a -z4m62OnMl7(LOYR2OfPyZ;2P6omm@v&hWBJMA!?T#HTvG|`6jqoTm>XdZPWYfE3aA{ -zzTw}y-j>_S#jf&AAoY8xDmwAQ>^ -z%j>6C%coaYo~!V{3xH($SE&XBqJ4N2X=*~lpB!3RXGFHe(WbRQ&eE=Hmi7QV`&F3u -z=+oFqmRN1e8ANfd%*fa=fjzm=3w7P0cVzVhN)Azg$$MMkE$DDVJ`i}4hS2;*0i;z8 -zgmQK=XaCa=fq{J=**&KXaFjM*M=LDTN@jQEG?PdkWJ-RU0Ew( -zim>?PbGLEPt>I#$907?{#lX)3x)%C2>OQ?sm>8}?%YBjxAoG+aMPnRrG*ieuYrR}x!#V^uHPF;lTiU#p -zYB(^mel+Hx=vxY;kopMFYEtbkhse{Z(C)5P15p%DUg$z&ik9eydAn~KtBHIMKzAi0 -z33K_TwOWEQhL3cZa9SOf3-vV4+nwvxCMrZ;Ch1tzi%JmDFIwlq&fvI_BR7e2XyqU- -z$*C)L1l??R$v$m18a3vYDyb22M)QptA#+|CY|+j_XK__%NU$6}g;7p> -zX*Uj`h$1$<(?OTI!=MQV^EoM)DrgHQB-5_FU!u2IM<Oa<|@o?pCO*}!7dh^H_`F%$L;3;8IQp92vp|EeFpCN^|-1eQu -zfs@pkCLNKgJ>q2gA5b?ky)KH>t=*QskQgeJiMeKH?{)Cqi@aGPX$+VYeHj_i>M|cx -z`0v^`ZdiIPQ@krv=p?M%JvO(};3Z*V+2M0lgYz@)Ux5G!4!Lcurxc8UrbTE~B>P1z -zgS^$jr>JF!89lMXSWgPbRC{=FHP9C!Q>wHAEiG4J3d??4_$L;Ns%BNQ`BjK<>7+@s -z!p2I@iWIg2=>@a1ufT??Zg^Vsu-lR``H|?{6ts8E2m_Vr+tWu|!TSZF=926leKgKqf -z2H!nEBA!&~VPCpOHh>=q(qMnFzMy*Uy12k8BcArkwF#^%G~xn#Nvug(R#9Qp2uPfX -zQ{2HLc3Tk**X@8v#H8nui3(IE@=k0ys(jbhxbG$ry -zBDvHDs_dFHQSMZ>;?*1k8pde#u{kn9^W)bP%@r -zqhEd0nlMW~V8sSfXaK2aM$rLHBbb>E*bSl8xwb9FTfTM6uV -zeVbVrzTNBc_-7xy%`2sy{0S#99fjC^K$xNxNWUXS3ie+XuXD~`cjMlpk*k|zWBlET -zd(1on?C9Dh7?^Shj;X8$M}qppQZ!jp=ypiTq`i_H)B!b2x6mK9iY_NrbuOHSNRC7- -z5p>d)S+B_qn@G;cu#H4^RgcK5iOd)#4-+U0JD>0%T3A4K{YKXk|r^6*HM-f&(K0N+dZd*j%WRc&YWV6WE4z -zNym_I4$8!?Qj0vMbdLS{Ox;HsP8q=80=!bGYm0R2d+627vG7+W#wikzgeyvPu{T{_ -z#;RB)W~&%S%YJ2!h`4HzoM=w!05oYlp03H}tcD`K027nPf?2PhFmGrNmn -z+|<{Ac?UJd@v@foIjfoJ+~(oyRdkxI%}D{=dY6W221vRDX5U=~rk!^8-aww)j#l|) -zzbj}vIJINf2(Qn5>M7Sjfh(r8czZny{E7JWy|RXn3FZjYet>xh9Z#tt*%pZMwQZOK -zCbBS*fH_576TP>#p$B!umC;xJb22acP5 -zP+!-W-oe2|ZuP$mXoMJJlD3k_m=6uPO#0qmPjD1iVZ!RQB&DHigxJUh?S`!)8crAa -zN?g7T5zF`rHZ4{PT0g2mMv1GTAXk#xiyWb{aK3~pz@+G_&w@&B4nbDqL}hnHTsV-23Xu`eRwTxqRuTSPTc -zAlUy7i}4+*RlDOp?5r5OQ%&9^zwA6PPPG -zs~F>~3QA-lSX6Y^!uhQ7UL?Vd-)6bI7aF~>VdzSdJCZ}t?MZy2k#9Fd;*MpMK_anT$*W+Xi(5MTt2pa( -zKKsUEd(i&|3Rn@uM}!kalk4YK8j~+4%Wb7a~sp!EW(&<{V -z?P|b{b-sOrHfc-|BiLmjmkRfwbx5c|Y*=})QMV;T>>ckPl3JBVU=%YNkF?!bhX$ev -zbqPUqQ+6u5GN5!~QJ=+ihTmaXJE`gcjW0qcNgXShgp$(=eAT5^^diDeTm@10naZFx -zK#i(O_5fYM^EP)ej4OoMuzzrVRKpR8*s`&tR3|PP9FWEpyW3D-;5F-9)y8IafE56Y -zf>COuP0|Y1f#$-Pu1h*Bod*t1&8_>?=vm5KDF$|tRowzoRMEMGZhs_N=QfJC(gt&wO; -z8;nFXKF(mC;9ux7yIjPar4d8kj-jt;o!q^WDd8#KieU^r&G;z-gra1$G -zcgz{{`exsQK-fB!Sa?HL&db({LtWiPU7cMr$#)b@YnYTklzmq!g5IdqW{Tm9}2U*%9 -zeWe(r*2ai%fDoL#e4;k4>{;hM3?lKav{>vsspyt<@X6rB?AdFkurf5aNA)t_hpEuT -zeFDq^UQ1e5KLbRZBJd<_V&#tj&nHpr?zACZ&+3^+Y+8RWcrpTu8GE&k-0pl&){Bm- -z7Up*nraiDjx=E~-@(u}Kw#2F)8%zCZuy!U}*+b*KEM=d}A*h;(|{0 -z#NVb(eoPg%c;?toK*U#wK>aL@KL;0fe}BwP<+v2*QM)$H5C2=M3wWrF -zAbn5he7WnF71)f@MFo9>cx|(&X@cw9MRv2kom8}I%g+5M?mXXJrN#4a{wKrc#gnA_ -z=HFW;#I`+|>~G>Sa%+ZobvSGzjl^Z&eQ9x+!0m`JN!V4>@K5P<2o2I`0C&uIb{0C2xu!d -zo-B9E;GrDFC3#P7t+qqB>DUlwCu^F1rQO|j_AN=XT~Zg06ZSE-0v@YO{EB?2G!`EM -z;Wzm$KpFUv2i7;DdzK@(qeIsky3a8Ga5j`h?@|`8s@{4SubOR2Oy1o#THKh)Hg~C^ -zxM`XPT%PzSwS1#t>v+T4apD{cajVRiG)CY-Va!{KG?m0lVI3x_Q}O_-IPwH8_Vx`# -zK#A2&n{Re~C)bBVb$4}l%`5$O*WR^v6Sy>165@v@F-hl>lK7SUC<@G)G1}t9Qz6kv -zLvSkV0KQ!<&ZUWAtC*EF6p-2vPd_rL?cdQZt8j@&mnjpaXkFQyD!6^0nfoK8W#Soy -z;PpW-T^pu$E>|$jdbagwZB=cy0b*(}4L#u7?c!>(Cyuh+;l5;@;A|u#D9G7Lk?#Vc -zx>leb#?*5ox&y8RKz>V=Q(wk#@i2K0&$W_#NhEwJ&=63TqSQ0Mhj4!_!%p}NW*)=B -z1A;n2xU|J`<*Yv%8px19E->;O(#@ko1AI4M<9WZAvUMP0gB~1N8+OBy2QC1@#kEm! -z>4F$T+o!h_6!-9CG;JeH(;1T79TYa=xt9zBhFQkO=Iq`4;Tpp>KTBP!v~2;ku-+&P -zl!^l+BB-laO8hMq#cb)B4~a#R&^%?Bc2GoYeAk3CoI1xFU&gG1pPYgXDYt0y-lau= -z=aJYuwr)AJme$|whF=GjN!ba&MwIsu0f18t -zY&Q{Yr=fG@0AxF`2=C2<$qHsbAqN2nSf06PEMKpK>&Z5~qyI!xwSM&*-~Zlb(gWd% -zI&=-V%!tt*m;Iieu(A`{=FIfoE&A$B@bp%LuUJnbBxV!*?6d3ZMRoYu=a+9Tp3BY4 -zy4#HkSd(?SiWFZ+<$_I;MvFVOQ?eu0r~%zuR5tA8hycXadfHO8dvu?KRa@Rwi55v> -z?u+PxnOxY6e4`0O2tGe408prT*ID4-pbcr&=LZ@cRd#>N8oB2=ppN5gbUW-NwBF -z`4~7tYO-G -zSNFSrtla(P{wMqG#O!Q3OW-WVO{Fnx>-0V^6;|QSWMx&PirYr~{I)w2C)iEcdi`P} -z5~aGowwg@v^=%~Fd}7uffX2Yx88(!S0cJQ-F7-j9 -zFlCs?8uG1igMG2#>cvP@y_Z!O58OC-^I~=P$vuI5)CZq41|9rU`b<4=YzvdFXw*PW -z6|KWvir_0wGn|#yAaV|kO0vFMCxiGPvdg!egNSCqTlhnev>%w -ztRyKcs42wZC`vl^2=N;q*o@1WC~~VxMeql_674D9Y#gL4F$h~b*sHE6h?iGIBA4iZ -zyVG>@5<=`45m4vU2nsxZt|xyki)u5iV&dNy({NJnmY-ka=w^679FRv}kQP -zH0@QUN@J!x4s4LPcN$gGDtM8|5BTt{<+wu!8e6O)&BhcW$yvIxp*Bdao5b{+UJRt9 -zieplU?g>1l#5vLq!PeTDC`v_Jp*#`MyvT*UlwHQE_(T@7s%^c*FbyIU8}A-X%#r_x -z0n#>U8`cUm(;##ZnJDZsq*Im#CHDH;0CcahT!zFYvYcYHu(P0f#FbQFBHX*i5yefn -zT9ujQ^~7roVYPh>F>MgQXQY`0#~SPYH7tqIc?DKiAZ-4X2svQ&c?AEF3ip8!s-O -z$O$%JZ)x(e4WU=ka26seEgL=B0n;ID%2lXLLG2e@YLO~t?*T__STllyEx -zfqRX54!ECYXLof*;l#wj;%X9QWry@vu~iZw_*5N9sx(5^nLlEk26r^rTrUQT={&!g -zO^W)uM&o67QFP5QTntz?oKMv6N$@2mlLcR)j}|pl8W2}Wj60qkxYOsw2Dup~84Ybz -zdSX2>Q_J+OEc48;81mw|7litIdp}(| -zTuI_8S3WXi91(XVE53KZOY^?4xP7KCj2)&^+~vSwhZe!v(z!)ftcZgH2hppg;;tro -z;=50y@OS@Q5P>HYHOW9n>N*-S-9x -zu(tyVG1MGeoy4#okZ4cdDk7xUr5xu{R=zU{EZ^rcFU}+>V0B{xqsip&k -z&Jop%@x2lNn^`g-N<@Z1tmp)!>Gl*6%y1&$A*uUJ67=vQV+ -zBgbO3k!ZOp0RLr~r;Zq>J)V&S&z4^+-9x#UtS$3VBx@2-0+O`p*Cq_2Y23YR?UKu} -zDgw?Nn%#_XKDwfG8LsG8<6Tv}j3+V4SiR=6I97q5kZmHe1`%?Xp0@xOSZX$~NdvX0 -zV>3&0fVI#U8R$m3wU9bxXw#~yd$=12U&~akzW;;pQqoGgv!X+AXPW*C+ak&VVFVF( -zY-cW06F;bd<531vx@2vq50Fv<_jF+PoXBYyaEcwvdb4_!byhp5E{Be}xG8SA`H>|E -zyH)fa2a!(pOn=tw66S6a&%EzA!>U+G)c(X0R(XIBH5tXFA|ev*>AoB=M%_cR&8M3b -z(G?Q}gk#lN7Mtkt4+Pq94LrCuQEug21?q`)rh}{op6k_WjCzo{Q-L0S2U9xzxC|@;) -z`dA+Dw{{{_OY`?l$%F7Xj@rp_M{wCU>Pxo -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 -z`_)H8eqgRA4q;bDlyH3F`bj3-?66c2{=Ewch -zCYo(q%{eSi=pfhPS=;MBEIL44?nQ*1v{@YVayS~(J@#MkZCog(;lSe`4e1nR_ZcX( -zWN>^>K#~@YP5b#J2X%V*GYx$Fe3XTVZ-JhE_>TNGZOS2C2BD;{KQpS}NggAxmZI!Q -z^D0wPO+N==no+MVKeI{jG=>&)qM8;+JeF?BgPb&+2gE{g={7_)XP-2u4fta{2HqenZ-V -zIsC!mw{Kd;On+=HEi`ev=4JdTOnV=DAN5ens``$p19{+W+RFz00jsz5XFln}0bcW9 -z*KW%C6R7q4hu`8SzYlVjM*7Raj~Qj`V^PTXuX^h@{Isk-L7tK~J@!MM-3wzG8YBTW -z7T_%}Ig#65+QxA*XpcoGCM&+PbC4*w? -zHSRkT_l@GdQ%PT&H12Yh_Lu$3HvMC1{}{>B?y?`W{NOH&et3$$-&J2*^^etkgQow9 -zcJD4b<4vrH_G$dyhwlV~lhNmy)B7i2EtkbwvV!^Tg=|?#B)EQh#hT{* -zx-{AIvxXpT7aT_XHn8vo>VW9y%5#W(P#Bk`K|W_)`WXgw2An{(b%^K(n6 -zOXz^6IsNTVUdHTx$Aom73P3)ylV~{}II2wDRf -z1}PixlCHYbnA6uhd^LMgCpn2}49CD-B*{hu6ikG_JnakmrZ+EC;(GYI&%f`XkpM3~ -z+tS`}DA==YJ$RQ2mW*}aBr)mkBbWzQ#Cv}5NPIqf$@A)weZ8r+u{EVfwYfj`>N3rd -z5&*^`WpyX}+i)VvPiZxHped&x7N7q^`{mQ~$-XwILM -zZ@jsA$`78Id|{Hb -z7Re|?o&iV&v#d0Qq);$ky*N8eYK+tk2 -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 -zFJ75+OrN+82zDkePkO4S&APIZ|MZXk*&qGm -zKlnh{*V8=fA$ZeN3Xj7?w|gHV*M!mmw*5C|NIaC(f{M0{R95wl4KmMz~efrb? -z;7|W2fAUZM^nduL|KXqhPyggUM}vR#r~f`R_^1Es@0|Yl@BQ&V{Nw+{AOFw(_@`1uBx_WYahQ-p?&01x0YxUd*=_y2Bt~ChNZvKs$}9zvzY@UYrYqpj -zXtn4DSuXC(F1S-Y{<{)ZvUo9Rl0(OSj8w_!K8(%94r%njKuhYO4Fxw73Fvh6#<6ZB%;S8FOaPwJ9JbnJP6tueEbDSFxdcpcY!sUGjy5fTh3q#%Bww=PWx2{a -z5QUqE_1Xt0pfoDZx -z8o=tT805!(3in#D{27KIz&j7>f|?MAaX50-tVV`yBPkya#X-YnX2fg=*xSVa5WWGv -zTr#WT&huyPM&nYX&|(Wq5`uLCr<;zQ&6BKgjiCye#JiZ94uJ4{_ubLTRYfAscuw>| -zV?bYBCHEHN&)nZHp`}KM6!IBW8OBD2eMNG;X^yi7-y53A_477K3alSm1@IDXW`_eY -zgE4L8eG+<76s?0i)l5?cewH1H53$XO1P3gPLzP+-v=F!&7-! -zbm;Kj_v*Us)@+A$$neA%Uw+-H$cGghC^bR{W&yT4jfPF&lv<^umv_;&MB4d5T@F3# -zpiaIX8AR2^(7=Im&;-*`H_Di=!m`Yr!!+PXnoYB*?Et|;(LSs=U$u_2D;9;RQw6-* -zkQ{thhJ)YRz^#;t!A60Nk!?HIv;~s2{$4#TQg-|B0BTw8|&}gq$_KLlE=`OCl -z$B9N0bJHZiVg<{IZw8j2+@u06@~!F%n4H`k*h?A({7~=6&uLwggH&L#qk?Ee<7kxE -zmLz_=fCScMFy2sKd7Gf=!W@j5{hEeOv$bSR?VoAs$l^qaikSAcn7rd{j&53xx8E|k6|k7*UH^cUf -z%Z*>rKP&xq*oFt6h8?P|X}qrP)0OwLj2z&`<$%v^+*ck3FQStjDG~u7jr3r)PO#F} -zX`Q*M0~RDxW;t1gi{9O6=$sqV5Bwm*Fz6{@vzPXnqDyXEHgweeOleQ_KxVr*Jxv*nqmo98@JgOslWe9jz3x -zNsWcfRV-i$B^&atxZ6%P%t^dh$Z?9`wL*T5PFPyB)QC@QO?3=`V{P*TfHp2ANQE6y -z%o)yyf-sDjb39rFtE@C_&}_pA4e0e#y+O@IZ1Cp{rmP9_oYhTcLjAHVTN0Pc%WNo` -z%&Z>(uc!g*S)HO#F$JS~~h4{bif8$Nxz6IMVD1oIZmOJS$%MZwO*W)>zj6{@&vk)p?g3%oxTfX3+A -zr6|dgP(s8m@<{J;_8Z!%NC3;WKuyoF!ww)xn@4y^(3>OZ8=MQ1@ycu$MB~PFCVWO~ -z`}#TmI4!2>K=8U#jQBdkpS{JE9q25kc^y+X -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 -zgXbDEC=GtS9rU{0`=xI1TsL^o5IaRVQOQ -zk9lw|!u(9HqIxa=MI;6p2iG{oSmv`(x^p_7iUuKl16jSFj5!IbrXs0@NL$oY)3LmE -zu_Xiql^rkMXFVOUgH7_5&F0cbkEUTO;l#DX_gsgSmXG`FH-;{t@2z_)(=+eWy|GbO -z?aP5Zx+e1%0wk`;eHvt;q8c1d_#+}(&PZ=H-SHE -zlNemMo33+n`PMpZRZV~Kp1PKt-`j*lJ09pv8d*5e^LyOM*L@~8fQGn)aq7bTE`8v^ -zqlq|W^J`zPd2%k_EQ>_u!Dhp|&;)DD6PwRlAft9!KQP?z*2vMxhnEU$q-C@fN&LcX -z@r-=kH?n&5!Py}hB2L@O_dKoF*)7^3TfxS-c)w+DtPx#0Ym~<3fi+6s>*3p+-zPS4 -z=4os%H&BgA{y>Y3X&Z%jx^UMr+z&jQZY`3+#rst=+smDSJV8uy?C?QlAf?@WU)i8Y -zItySlYc@&JKK$IEh6LvhnJ0U8_^f?NBJ^gqQ$dQeW~BGYN8Z`knzhw?%fW5dkZjgf -z>l;Thq^YE53xt0YuRN?E#sy&EaiVE{h`}C|iG3cHN9~2>9aj;rJFdDPzld1faRnIO -z^SbW5F1*l=Sm0sZs9W)1jed_9{9$?YJ=JLDt5JXI(fjMs_co*7Bf(%;9{nB(2ZJ)x -zhx&9Vf8X@0AtNRdiCKs?Ykscj~g8gkl4MSnxD5ZajU%{ -zY5t;Ndn4%r+MP0HX(91U#5NDLWiqc1tIFv7Q|}9Q1h_E7bWo?j2Z|m#tcdqnXC}^& -zVgYJws+gag^7Kpwcu*HlwTus5Lq&Gu(bmQ7Gtb5hp@F_8Ns1Rz?%Hw-cOlw2Y8yGX -zL;L0;Z0Vu-C~Q{U9U}Hl5nf>EPt%*Q!^Zn{`tHcAOAqz?#Fn3uk+{Xg>=cPny-~CA -zNPTw#IBg^LC}<|@5D*(F%#a_nRX -z$0lcvnRXCsS5T~watnKBR-0|tCTtVsTVS0@SS?#|+T=P7_Be^%oBHGwS*LYU&N$Yi -zcM~Cgc$egYhi^44aSm5jjy|$j=v=K!uVZPRo*?-~?DCs(&%Th*yvY}(>w?qoXsNd~ -z2$(%>aMyvfj@n2yOIvWUp*dv<3G>uf7_RJ45K&4iUJkk05XaeU+S=t`fhCZ5xjI$3 -z_XIqD9y|l|cNq&jnl^wOP#rw6*A9sjOmu5@$hLt~0SjuMBrY-g -zXb=&|(_<|H^x+#}duwHJUVAZkEL{OLuxr928I%XBiz9u7m{Mn$i9wn+fRw`F&jCwL-Jh -zbWsFkOc`B#baO1WK(FhJ=XTL@nhQMMijm_)r;g@(IT3HH_Bmpf1JjsIY8&rH!_Xo* -z0ROG9Q;;Q_s3kut0b}$6c7oFOI~I6oT*aDPNDiBm?-WCuE~fX`8v|z%4y)?w6_LGl -zr+y{n9$F+i)c4Ju)U;dl#hDyVBVVYuB%e##l@F58*Hgou26~TNqCmbg&iM-V){JB& -z0gKtM4^_dR%?WYh!xeI2*sRS7DxP!udr1c@Qg_ZbSL&-yr^vkBbv9p$m*?x<|GPjYeC*KZw$$!!iP&5(UdV`vPe0-e0iimm>Md497iHUnf1ruyI-xo)V>9B#)%<1=+;mNoCPaeF -zTGx<8==1Boo}m8cE|`yn@G}AXYYNn30s5O3h|dJzHwm;~Q-FOyVD;$7V7Gshf~>5~ -zg`OJ3n#5{#=WVPj_l_NM!lO!p{f-Vz+tNxmZ4-!X{S;MQ&~G+jE{kKX&Od3XWN*I> -zG3wi*+^<;w4wv&&W+LDWDM?$Ko-m@6B|w_*8fH7GgLDNhFVO9bQyJi{%0TjlW^bLm -zEVc;;2@^R>h*e&%FN@G`KOo*#hRb)+ocSLOMU>YQ2Dk)X5QrZbytjR8MdvYMepl(* -zP1oS~yX3U(odk~bk14*118_m>9&W@p7uwFdyR&Vrk2Qe@g*7iD|7<8C)*b6dS$>Rm -zhB}w5llF(WaAtipX|#=M45~CM*9&^b{PCRjOFQ+My-X<1>OcX`z7nzt71K97Uji~ewtgkN2m`7DOkQ;%d%SD%<}41W;bc?OspP1 -zHLcULV7(|~Wd8C?h3j`D6uLr>j1Gpw -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 -z9k%HDm7IMlow`2KRW -z-ZsXrq!-t8im+~ZFsfJ;#B=0n1P}{5TInkQ)(wB`#VsA);nBIgfUrWS5j&y<93M29 -zwx-!er6Kl16@N*V^)I~9bEc_m-R<-1NlhYYE%{`M!jTuc#bP5uFa#;*3x7;ZCB(Ryu!mp6%p;Kb=>)5`~3FV -zWWGOaRyl(leH0O|Fk#Hf@LnaZC|aD=Tr<0{Mv}C_UVQ%I#qd<)Oi{%4N84lA7Pi@o -z#WT^|ZJ9`Gx*^;QXlGr6K~Zf@@bbIMpV&LsPbWylnZ&6|gVtD=dR8%~6q1K-p4}qB -zmSDWi;$(2BT7|$34ozqXF6j_x41)U*vgOTx}C=fQPH!@$~6L8I+k- -zZdp#REOHQWP<|k(BHad5(jWec1&*yu8V{bDt4Kem0ZWmE>|wO1(ay+XV33f6Zur5Zffm_cdq>+PV|?cOhSgXg-z -zJ9qt5xB-q`|6@h&%hc|w?V}7|~{^IT!h`OJ_4^kW? -ztm6*J$GLztVy1H^lntNkDUJOy$|1MHbFy@OlvyYXqvX+m)q)>+v=Cw -z>YLbBA-0uO54^aZh~N4^&#BtT7pz@tckpRjm+O*?4PvhD5$e&F|6iyprXnd%1pv#` -B__+W8 - -literal 0 -HcmV?d00001 - -diff --git a/components/resources/adblocking/elemhide.js b/components/resources/adblocking/elemhide.js -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/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 . -+ */ -+ -+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/resources/adblocking/elemhide_for_selector.jst b/components/resources/adblocking/elemhide_for_selector.jst -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/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 . -+ */ -+ -+// {{filename_with_query}} and {{url}} will be replaced with actual variable values -+ -+if (typeof(elemhideForSelector) !== typeof(Function)) -+{ -+ function elemhideForSelector(url, selector, attempt) -+ { -+ if (attempt == 50) // time-out = 50 attempts with 100 ms delay = 5 seconds -+ { -+ console.debug("Too many attempts for selector " + selector + " with url " + url + ", exiting"); -+ return; -+ } -+ -+ let elements = document.querySelectorAll(selector); -+ -+ // for some reason it can happen that no elements are found by selectors (DOM not ready?) -+ // so the idea is to retry with some delay -+ if (elements.length > 0) -+ { -+ for (let element of elements) -+ { -+ if (element.src == url) -+ { -+ hideElement(element); -+ } -+ } -+ } -+ else -+ { -+ console.debug("Nothing found for selector " + selector + ", retrying elemhide in 100 millis"); -+ setTimeout(elemhideForSelector, 100, url, selector, attempt + 1); -+ } -+ } -+} -+ -+ -+elemhideForSelector("{{url}}", "[src$='{{filename_with_query}}'], [srcset$='{{filename_with_query}}']", 0); -diff --git a/components/resources/adblocking/elemhideemu.jst b/components/resources/adblocking/elemhideemu.jst -new file mode 100644 ---- /dev/null -+++ b/components/resources/adblocking/elemhideemu.jst -@@ -0,0 +1,1436 @@ -+/* -+ * This file is part of eyeo Chromium SDK, -+ * Copyright (C) 2006-present eyeo GmbH -+ * -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License version 3 as -+ * published by the Free Software Foundation. -+ * -+ * eyeo Chromium SDK is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with eyeo Chromium SDK. If not, see . -+ */ -+ -+"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 = 3000; -+let minInvocationInterval = DEFAULT_MIN_INVOCATION_INTERVAL; -+const DEFAULT_MAX_SYCHRONOUS_PROCESSING_TIME = 50; -+let maxSynchronousProcessingTime = DEFAULT_MAX_SYCHRONOUS_PROCESSING_TIME; -+ -+let abpSelectorRegexp = /:(-abp-[\w-]+|has|has-text|xpath|not)\(/; -+ -+let testInfo = null; -+ -+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.} -+ * 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 (testInfo) -+ testInfo.lastProcessedElements.add(element); -+ -+ 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; -+ } -+ -+ if (testInfo) -+ testInfo.lastProcessedElements.add(element); -+ -+ 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 -+ //

Hello
-+ // 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 (testInfo) -+ testInfo.lastProcessedElements.add(element); -+ -+ 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 { -+ /** -+ * @param {module:content/elemHideEmulation~hideElemsFunc} hideElemsFunc -+ * A callback that should be provided to do the actual element hiding. -+ * @param {module:content/elemHideEmulation~unhideElemsFunc} unhideElemsFunc -+ * A callback that should be provided to unhide previously hidden elements. -+ */ -+ constructor(hideElemsFunc = () => {}, unhideElemsFunc = () => {}) { -+ this._filteringInProgress = false; -+ this._nextFilteringScheduled = false; -+ this._lastInvocation = -minInvocationInterval; -+ this._scheduledProcessing = null; -+ -+ this.document = document; -+ this.hideElemsFunc = hideElemsFunc; -+ this.unhideElemsFunc = unhideElemsFunc; -+ 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) { -+ if (testInfo) -+ testInfo.lastProcessedElements.clear(); -+ -+ let deadline = newIdleDeadline(); -+ -+ if (shouldObserveStyles(this.patterns)) -+ this._refreshPatternStyles(); -+ -+ let patternsToCheck = filterPatterns( -+ this.patterns, {stylesheets, mutations} -+ ); -+ -+ let targets = extractMutationTargets(mutations); -+ -+ let elementsToHide = []; -+ let elementFilters = []; -+ let elementsToUnhide = new Set(this.hiddenElements); -+ -+ 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) { -+ for (let element of this.document.querySelectorAll(selector)) { -+ if (!this.hiddenElements.has(element)) { -+ elementsToHide.push(element); -+ elementFilters.push(pattern.text); -+ } -+ else { -+ elementsToUnhide.delete(element); -+ } -+ } -+ } -+ -+ if (deadline.timeRemaining() <= 0) -+ deadline = await yieldThread(); -+ } -+ } -+ this._hideElems(elementsToHide, elementFilters); -+ -+ // 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 -+ )); -+ if (matchesAny) -+ elementsToUnhide.delete(elem); -+ -+ if (deadline.timeRemaining() <= 0) -+ deadline = await yieldThread(); -+ } -+ this._unhideElems(Array.from(elementsToUnhide)); -+ } -+ -+ _hideElems(elementsToHide, elementFilters) { -+ if (elementsToHide.length > 0) { -+ this.hideElemsFunc(elementsToHide, elementFilters); -+ for (let elem of elementsToHide) -+ this.hiddenElements.add(elem); -+ } -+ } -+ -+ _unhideElems(elementsToUnhide) { -+ if (elementsToUnhide.length > 0) { -+ this.unhideElemsFunc(elementsToUnhide); -+ for (let elem of elementsToUnhide) -+ this.hiddenElements.delete(elem); -+ } -+ } -+ -+ /** -+ * Performed any scheduled processing. -+ * -+ * This function is asyncronous, 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."); -+ if (testInfo) { -+ testInfo.failedAssertions.push( -+ "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) { -+ if (testInfo) { -+ // In test mode, filter out any mutations likely done by us -+ // (i.e. style="display: none !important"). This makes it easier to -+ // observe how the code responds to DOM mutations. -+ mutations = mutations.filter( -+ ({type, attributeName, target: {style: newValue}, oldValue}) => -+ !(type == "attributes" && attributeName == "style" && -+ newValue.display == "none" && -+ toCSSStyleDeclaration(oldValue).display != "none") -+ ); -+ -+ if (mutations.length == 0) -+ return; -+ } -+ -+ 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)); -+ } -+ -+ if (this.patterns.length > 0) { -+ this.queueFiltering(); -+ -+ let attributes = shouldObserveAttributes(this.patterns); -+ this.observer.observe( -+ this.document, -+ { -+ childList: true, -+ attributes, -+ attributeOldValue: attributes && !!testInfo, -+ 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 adblock_utils.cc -+ -+let elemHidingEmulatedPatterns = [{{elemHidingEmulatedPatternsDef}}]; -+ -+// adopted from applyElemHideEmulation function in: -+// https://gitlab.com/eyeo/adblockplus/adblockpluscore/blob/master/test/browser/elemHideEmulation.js -+ -+let elemHideEmulation = new ElemHideEmulation( -+ elems => { -+ for (let elem of elems) { -+ if (elem.style.display != "none") -+ elem.style.display = "none"; -+ } -+ }, -+ elems => { -+ for (let elem of elems) { -+ if (elem.style.display === "none") -+ elem.style.display = ""; -+ } -+ } -+); -+ -+elemHideEmulation.apply(elemHidingEmulatedPatterns); -diff --git a/components/resources/adblocking/exceptionrules.txt.gz b/components/resources/adblocking/exceptionrules.txt.gz -new file mode 100644 -index 0000000000000000000000000000000000000000..dcfd516a6bbd13dcdcc7409240d9ca0b322e5270 -GIT binary patch -literal 846414 -zcmY(qL$EMPu&udm+qP}nwr$(CZQHj0ZQHhO?>_f-cf@<6ij|pPPHK{owF*BP0s>fs -z)D!^N)Wg`+!Nt_W(AzrNMAh}pWlrQ-iZ+&H -zYFATpQH!v4XeSW>NdOIy1S^vu5^D7Km6sf!0HDcSY&C{uk*Oq)NbjS=bMNhR=FcOW -zG*zyB?zi@GraAm&$KUTU*qoo%w2RgE=c9QqwSWEX&+qN9oSwhO=QlfD=nhY{ag4e@ -zIajgk<9lOhXXxpR`}^+T1NZjN-jw>|qq$wR^lc7nr()EtSCRWe#)NxA(^N&7=c`v!N)5%Ltfh@L-=2*~*3?>5Nis3hwd!hyoPQvi -zot=H|I53_erLKN{^4p2IUnkUV?bYDpft>%n2Q}B$qdH?`OEZ?iDfevrCj+I4Nsel*a_q7C3w%Y9^&cjr1~TYIO{*nC+ -z{?A~q$LHaabkjSG4-orlBHsUdNT%GE7TeqU^Y~d?vom!lo8G@~&;Q|y|9h(c^6zQN -zDgC#;z5jPZ-tY0MUH{wd&+Tuk{qHfkUH^CPQvL7sn_c_k^23^tf1Td%Gy3&5eZ>Cn -z{_%}}qgU$Bd(rp7bjN)H->v`Cld!FS-QVi>2fhBO_vhJtYwT3L|8LsowLY@ruZSA2 -z@B5lOc>DYN<6tG7-?#g3W2?P?^geFPh1Tk!BpY><8-v=q9YqKSkFAJR4dqHaSaMm2 -z)xKe!3ZKMJooK3I0FGcxt3|3F+cMsk6l-e@b&EqQGtHZ_opp2HbyqSotd%d#%ibpc -z%zMZ7Czrf-M}H-Qt=1*KlX^ir)-Lur-8-`+cU`>+`tEeIg(&rwYDu)3>TgS8q4Wf+ -z14yBpyS2sW)3$KSX)(s%ykcH^1^=5omLTinK}vDMi783Nr7R)#^5@)^{QltmJ0od( -zwTiP#GivnO6uoa-N+DZ5xpw2aKOk9$5(RPYUtp -zWf0hEixQcv=jIST8AKzS>4Dz{8E<0;{O`!&-kr*`!=HEX+f+-^pvI!>qQ)kD-v_3o@%OFvmFRFP0_l*GBpvP}^YAN5s8t?V`w)EqGHcFI{)3 -zW@|7lv7|5wp!7iEr(L;Q5mTb7if>huM1CunD5n^JMk4RKx=SJ8w^*qkB(13lRg8?Q -za3%&vbh14Ha=h02%Y*>y5ZW`q;3aeK4RB_Z)CM37j0RMVtSNzn#DbQTH5ctS7v?1dJHALQrFx*;wFJFO~o0tlA-RO%%Ru{u_aigx48g3ig9Y#fMXZUqU$xrzv)_=b)k)cmZg5D2g|528QlSPL8p&->MGfz*32? -zhDzJ&MU6X*QqUYLQnZLz!ql`0&>|oM)Kyp*Q;;Ia0SP=US}Mkpp-e75qeniAXSp@I -zLKOC!rR5WvJ>>mY7!i}hB2W%u>@tuupzMd>jWi{-@p5wi@T2tp1spSoC`ezr+a`tp -zQ!*Z5AbyxJ1CBj853T99gc%SJ(S-M~E?$)L_DPH6D)Ys8|=@K%u3eO46e!2LY%bZI()%lt86cC*IKD8US$N!B`52 -z-MnwYV=%eCD$685=a(D@l)Y&g1QwkVQIJE>FQJtLxbdNj;QmjbRyY&^W&k?EQYnOp -zmP?mCQ>H3ax}L!o;tQn1q(Q(=h^j$?0e2i-I(qs|=6RwXG19N{?NBeENN=!|d0R1-&W0X!ATE=z6rc4TA -zwHA4Ehg2lk9g@jDMzem$<}sKS7CmpPHq2JRi53W)bXGVTTt*DAn=}j$!0Edn60QuD -z7UTs&DimF?Wa{wcDv-zVaD6^~MIVr95Jnga64$X)Rrx)rB|@$R0LUZ3mb}Tzc-Mk! -z!sssxQNKa4?;5E6wjvjj|PUJ?HOyzK(m0c -zU?0`IFES^(3tw|--)c1#9i#>5C?qEhL%c->^Z-&y5No^^ld5AA{B)KzmIK6~dZt<{ -z@v>5bfrDF3a0NlI8 -z%tGRzHpIHjqL2u*tB74lJp9+QKejwY9*U-}DaDV;8MD=uIRAfC*`GTvljQ -zHL(?v_C6i?CH_WpDtN&zUXXc*eeHn5WIg}9M=Pw>di&{?S; -z6jsC;7nIl-6iGQxI(v>R?A21NKP?vmZ$uFhoWf?nL?|hFK0+k^BG(1M*np2|-9K9> -z3dY5D@H`2IR|O)Mj2IplaPggZ4FwD!gW~2D3uY`)&G`+nQ?V#2QT^O&B8Y;_hynfz -zAWDK{SC))P_{6G0n*;t-js-(Xw*nHQB?>UGg@sB5ltNB9%|#C|jB2*+lo>g}Zs#oR -z)N!SlX!3H^#yI8 -zCL(mqTIF#3fLOc0shoP;AiPOLRs`7+0|9pRw$wmMfRm`jG+&}ZyIh-dnbR$Um=je% -zgmFhgDJ(a{Y%Amt#C5U)xEH`A0Dl=B|1EAmy@iymp-mSx0Gn|uibD8om3G6A+~dKb -zcf#^TOBbD$Ucfm4YTxKWbrnn?!mhw;f^p;>k=boUsUuZ?(2>S_AP<0Ibj2m4<6Z$s -z`1<)?CF9Lb1?;y(0idQPC -zSQDLPY2Z6XqS6PPE+Nwkq=AHIs=N|)aU`p^x5GveIoL@v*R<%$FgBA-xj@*En -zh~08=i6e0=rg+`?NrR{bI~zlDgO%W8Kqbp1Y{o~X2#3wV{g(`rXMzimHVqfB+m~Na -z{4l_yAbgNkJ)Vg+e=n%bBex0z-E&2l#hX*tgaubX*{K%b58{L^EA!jQ7YYk`I`3mLT+Lv{m{T)$oKsm#?7337~m4861xN?Sfe)AUlJ? -z0;ZVLbSXk_@+%>fryq3kB@cS42B!=%(F^RNKnf`Vjw)$_0v-cy4L4rK)}3LJ;~_kw -zmEg{Lq=U?AYnkj>nG)`63!~d{FAEWnyc-o>hqfICr9(&qOmE)hKmWEkH^ -zz(g_g2zRs!ur5PkS8f`VPR4Kq74ZmiO-zg)MdCoqU{wXdj57`M^#L^zqGy;$sDCMy -z&jh^XOfieont1!CCh~$Rf^h8(-0m&*gH$|mbh^_}R>%(B$bc^+2;U*0B{SB9dBi2= -z+mNG%^zK}b+DkCj-A|vkV6BD~AT(cgBEd*C>Tuo_eU7@IL)^=y7IP8%qF_eST%QBM -zJT9SDwBB$zumQXc`91CPXO#&z0?gp3K6(PATkH$2z2`te3-xtDka=aJ4WK!60U@-9Ql6G7 -zSL*TqGTtj#$n_Q$k^*5aXacE4)dBj=J$oAm@r)qt -zGbS!?EW?)2m#QVp7AwQ;G?PS0NUw##>Pc0Kk8H~Z9&`f`j1_dgrsM>kXni6QjV2xE -z(k}z9bB2hjhY5&fi5Nq8$u8>;0x1bXOdlsis*Y*^w?>~Q&$`Ne;fsaZiN`aniV1@W -z4;AYo0f)|F;Y_eolp%0xghN6QFmqGf6vP_25G>W<=U5jS4|hZXIWsV56mEgN>ggG1^(U7+Uw7H_RTr6<@m&iHxu6+qp)&(#ofyR>wly)C! -zW)54d>`J{TCYRh3#yTxp>YF8|7{eNjm4R&~%r+{5q)MWC$4ZGG?$WL=sM;c!m?fo^ -zc=pU13?9y!$AMN*4^(M_xXgs__dv7BP?ViU-q*OaI9O0LxW-x9P>Dl*Pcu&p6h?si -z_Gqx=LMR6)mMVSLdOxhrkHwz8zlpmc0o1z;zoUJW#Rl?Vja`!M2a*O7O#!pkUG+(} -z5I&$X3FyXOj?>Ur#?|rIhfHCc$rILjW>F45!+{ECw#(x-;1;xLYlgtmhj!xwVy0NT|Yv+GwgWiX13UCUdZ -zMKb>sl&o!5ruI+TY;lqMShXAIw=y{%1OpU!P+kb{T)Se+!f?@CG(9h}5mS~wB+*iS -zE{)xnSRI5LnASk+n+U>{<`b`==aW4yu^ -z1)KF+i96&a>)ieMZ`lQio`|Ya-5W4KNgVL+;XT?McR&?M6MuBXV^UDT0CbEG1WPwO -zo@g-VkuMk}fv^D75O{k*!?%DGm>?HEHLd+hxy8Z+)lVMH){clM%JqtPe%RmMEL1Ff>`jyBCkgilN|zL_96O -z3ha+uUe4kg+)wSC%6$Tpi7eE>gasxbuq#UeXP^%_GH=gZft>w@vV3;LVVus-hwVam*`gXp-4aaO=Y@E_7*dVV8GjDrhrzd{Nr;W-2zqhHZAjx(PGXIPt+$iritlcH!8fxlj;15>F+wM>JJvF`7{3sc6p -z)y2Qn6vnZHNk-h-u)$hDmDNZNrUVCf{Q1F7bweevxi|<@t77TCH!re=>=$+xw}kKHhK~mu0W9PGu#KV)0Dq)WGP~;&PJr)nE_A -zY2!#dNk^7h`o -z?lo_qbyHTOmrR2!Ps>ow16~YQ0~4xC3STil$=m&qq$!r6fzb$gS#?IgobSXi!}}<- -zqZsN>7P3?_NfVM0y5s1TU}vUbfO$O%QVd*2-Pc-XOL-2GMbuvPg(sn2ThsWPYTavEh+XGA=5l$v#ziQiHj&`3~ZA~Oql=@i;u~YUi8x| -zCvO)gPQ7REg>{VCy{P?)S2&=?d@N6va6$Ivrnn;8FEjYiLf(lr8M?*f3UiRi5s#L& -zDpzA|#$(@mBkgAh+i6x@4Z`DKjz%3TJ74w~2v-WMEY@$~z>;jUXEep5C_^Fwfue|< -zz)aFvX}zo9M*&|Y*P@`XeekfXWsgJ`Jy1y~kOt>!* -z(Qs_zz_z|Q!={htz0#El#f~F7++uP>z4{UK1+dJg)ViZ(q_eCf#XlmuZ?Pv$rVMy1 -z7ChxqAGa2s*oW;H5>$&pZtFf1*U5SghG_ -zM#0cr>!Pg7KK}AEPxpeKAFahYzZ_>M`pr`GnJ4QpPu60beE%=`U&RM&@pqPD-T!9) -z2f)An-@q;3|3N+W$HqKDSFZaLOP=~~{nzLCf#3M?{f)`A2UD!jVFs_?EMC8PjQ6hN -ze@}7N`z&6+|BZUnx&Qy!`i-Nt>j&%CcUJH3uRLD5Bs<-IO)e09fR0Ubu9jDqJ10n2 -zgo!+*{gyI*PO=+|nv5QXt=;Gp&^-8Q1%I7Zd) -z4bJz -z&;3=#L<3?N#I+kdS+V{~mVJGZ6{uL{s6vjAui2v!em&r1`p-;N;B@HpJdUG2vnntR -z-={27tP#ezDhaU8?-zSeBFWW8u(7^QG$I~`zn-OB{b}>I7P#C*(fT@R%hHbg>D$VZ -z{$&sz23@^80cv}n+pn^jHZb$Sc4W*Kzb<#w{HgK-LQ3Bk((YH){ZbcQ`~4s3M>h{w -zFl?=OK?&aId(tX`OLWj1A4h3GU8?e#;0?}($*k1$4Z%N;6JW8Vz$)WFT;yGO#So}2 -zXAQIh7=n0fy@?nW?n!Z}QmeL#;!4v7W;GZefMIl-(gp+^;ge&1u7P|6vxnUat=AXp -z$#vp513_B6yWzZ0C(FdDB!P*>_9&yuf;B^SAzGH~3Fw} -zQcphCOK9XNA4lx+D?ls=>~bxZ5sF3i?q6w^9%xi367Ap&48^f>K52hdNb6NPpzG-wVRWKig{zZc{u$(@ArqOZv&nKyJp=mMIbuk4%h+a%>59j% -z>gRz322a{%@{y;KRCj*;p{Dv=aO*i}LlK+gS|8LqD9_-ZA4UK$Ow4;*<(C4NJ4(tG -zDbP@#beXn-+uS>u&U@OQgYZ{PLqY09HnGnXhsY*@udgi|MSZrJJ9Nz}#LjU-!N_Ei -z^ETS}U7e+rmos%+l&rh68jm-iWxmi_x0yG$3XUlf{(ndP>Ym|^KjD+Ry&v54pu52` -zd)?jsZ~6H$-`DMDfR$U+v3i!z-K7hAzpV@B?P3|{mPOgVPNbF8lDd+Y@hu~RcaLH`;+YS5|A!;pJxcM2C;w0UhyKTfc%-HZrL6!4m -zVdmn~BQUlwb<)lRda1KFPg>Cgd#2P;d#B94luA{v;#oR*mqQipTIOvg$Beq-=@Xm+ -zV%yjKPgmwgmP*Ywzedmh^YLV7=chWnTm8*_E}y^W>wu?%H{MR4@?GkmI@Iqi<$Ja3 -zuF$4JuLI)QU(~>s=lyish4>~r7xE6%;^Rv!UOW@xkX2{bT?I12nx=n|27Q)p-wknU -z(bgx%zSruA84}HWjMPK!QWKK*H7)5r_Z -zW0j`P*2jx6cYhsx^p&CC4$b}&-KX``$cqu*jnT>0h)0*`m)SxeCE8l_>l>5i7U*`oNZr$eNHe3e^#sZs -zatNI{$jnk$pJd#+W`Q?Ze7_{x;>J2(rgJEy^m{)#(y-AfNpwfej=y|UCMi+p$o8Hl -zkIgWhAuXo&!-X7glp%G8q>_zjLcGsTN?Uf%*iZfsarOf*)j6<{M%$<_WAMJ3%A3Y8 -z5oBxCsk_Mv`p@%f#wmeKUsF*8=SrqY6FJMN92r$M!*7VHBT?1;koZuo33t1+w{prs -zwCtqH9vQBh%<1RDPZ?62EprG(D -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 -z{c-=z@=0y~ci5_b`=V^a^h<6>9ud -z%RVK7YD3-Pllm3zI9OCKW%@k}7FI5dxv3WfTxQ$vb`2@>TFf?oRC## -zM%ZSq1-tht>TGvs8KO%Jv}tuc)Trve4k`?F(4#5?9n_fWAV*b(zQUkH-eY&(lGLuR -z8eS57Y4E;d1jn;BGbf&~F;j5<2@e=kc8n?jgyRnl0^y3fij3*ySn&6kQT%Hpot0Vf -z8^f=PA;)YY)L`yNpHXNHw!QZG`xYZ&w}a_>4u>J}$bWT47%9`&Zl?~5HirGq#ERW}I2N(Xc$M3PP*?O5>kC1Fn6eMK%H&aOIVg(YPiI*caFQia*rtS-ti=a#EL -zT7ctnWPwbe3w}Bkxj1$_^ts{@xrBSmG(mmnDJa71IP+(h3|9F@cnpH-G;kmGpq|NO -z;_!vX8WVoB@ppwIUI8n8DNWgOa6sfWP*`d_iOoOP%6ABg63byXFPe~Cmzoj -zanec1R^MwJYbZ1FF$}o%O9=4OKE)Y_OMMlw3j>t$iwBp%5Gh?A&+S~d3E;V-N{90< -zLoF)_tFWYm2N<%y7zI$d@X?(@j`<{8i3{obsufp>i{u%;TW99BeoypEWTvx_AOHQ -zZ?GQF$to$-3P!f{G7BS8_nTc&~uT*jBez?Bgq3EP&%9t+JSyTyL@xBvo1ge47gAsdsfjn8pGCh9To)~QU}2*A0?|G<_(y2eCsGh -zTJr1Bs2|%Y`3_2m@RBxuuMk)QS%mRPvSIy3QUGAc2dLLdb=yioHQMa>C{iNoe8F;u -zI?FbdG^I>qE(B~0(q`&^kw~u(8%&K{E}*G?5UGHMaWG@0@Gu}#UK;1~rqZ_JpZ#v0 -zK=tBy#XQED>-3CPOD2i+rIqhxf?92>9<>}dAcig57ynknTzpaZu)BCHHwUV4*+Le= -z?D$o(_4?>I9X!cT-gb``3m$EYSa!+)KMIuXuz-cv$p3~dbH~=OK`tggm|+PAbLw)t -z9>!ouZPBw9UFE&0GRI9CD3I^yxz0sI{wa@=&kNH+qixRW_v*N@jPR8ubByR-ar|WT -zNq`q8-7AJxc`39Bi`jD_fwW+g>9hBF!fSPru#^CnwS;(Ztj2}=j8LTW9972jZy`$+ -zIXA5hz^f!Isjd%^&Y$&$1_&~d7$+8qhr|vTo9Ee>`dkq<$Gdr0b+s!>ibv43N1n#t -zAgT+&>#~>dib)Np^iPLxFW5Rjz{eUcO6GHeabxn;c@F-AG?Is5>MN{Q4(Z}Db-}Q* -zi4ek3P3ck23&f4)+*TiC1c?Ybu)y%u<$d`j7J=rD19fJ=RT7y}P&$Skc8usvcaUyi -zM$K`eApx&nriY-zAPb3?$nXR|2^Pg%USt~|L{7B-r<#7yb%=WJBP-}6?x!4|eydimL&5{>Jg2yWvZktYkYjMdNTCof_F3T$hSr8g3 -zIT@$xQ!*2W0A1|sJNkmH-HV{F0@o>Np_SvjYpx9bB4RujEGSAL66QGn*R55vqd0yg -zT;AhOdvqYPl+}GMN|bZa0bUG*68J4t_c|he?v`Q`FzmMCsRH;dP>nv0R+8?*b`vKe -zBHYn&kqoqaI5%4z4ICg*+%ZIdXJE~J% -zDSR2Daxu8f>prw<&)P$ou}T31NpDKdE$b@!qOE+ji+cN^d2U==(;gDX&V=Yy!8zkm -zapfdRjl&YFaoHuj>pWXb^MC~D&L6nH@{Xs2RJ0htK?|@hfrM8_LP?II$GAfdifUtV -zZ1whr4M=!hF1FT#;$u9O=>eW)q0fOIm0)Fo2=!7{Af7>vKntcMJ>voa^^LZ~rkc~u -z&4ZXfF2t-4WTl{B!#Y3~a__veoT_)IB4ERuJ1nPAff0M1q5~&yWM{P1s9$TboQ&|_ -zy{x048mUo?19@h^aqfgqi(fpM8AoXiS-i_ky4YMa&9erXCa})+i9Zm5EQXhBA9yY< -z^fn+8!>8m-c3ls|d@MN&X-JgXFeNXhMo15XlK}0QR~e1Vl;cq%nFTY4Ss|O`$jXQ_ -zPYMnqn!RaS)RO3>i%Y{y%p2_rh%ZJ-1orV|B@pM;A!A|uL(oOFMRAXw%>O6iUg~9G -zCef19D%`G}1`q1gI2f>^FYpw>}9uJiJLbJ -zX+Hem`wFRC(WKEEB8qkME(2e)L_sJJKTx52&pho!vLGB%L5uC~j5&-a1>;ap16IiX -z?K%vKdjLH1CTEMQoE@gVvAToJ$rQ30;lwI#)Z|MviA&4TD6(N!;E&&+&Mh&F^xzBx -zR))QGZkSqO!g92`BrQUVm?D%j6qGfKu11y^vM#yL;Dg$F?I0a`6%I-`C!qJAi*q5` -z#YRS6Jh&Lp%>Zhw*4376eh0CY=oiu)l^@6PcV|BE2d1 -z6RaZ9>0Uc*aRJlD*UYo4wGDA)rX7PqH`N9;qVr0VPX&)_P}|i4Mp`;_6zeSbcDv}7 -zB`l96Q3zJ -z$42T)7%;rb=?}u>zLfYfQ3-g@y9yJ*Yy&h_SYjMeP|^#{tVSz9IZ|9o`Qs&DiW-Ys -zcKjAGDINCbKJTr#mZJ>8p!%!i42WQdSweDS93cS>p*gZq^HT}-qr&)i;Sz;|&ODrE -z{(6NdbseM7dYFcR&IU5*ilEwQDXNXcR0nQFV8@Q1l3WY(0TEio2mB%@@@4(hC}u!2 -zf0sG_BHC(Ki)0M&5N9D%3GAExFy>gjWs%(D5!hJ1mPs!EbAb -zvhd>0WPOM?2v&57liL8T;(lki8b>kho6tHMCZ)C*xd@FVrhm1eTu~GxEnm45+lRp- -zM0t?W`1796%gH(%=;V(7*JbKHsD7WT9uPCyzfvEiM?ZplMa6$5Kp5Y#PKGY9=5|w) -zTt1@F2qz#WLnLsb+z8>J-F77r%pyqk5TF-~H=ApMpDD1GXMrIbie098&xi&nAtcWb -zGs<$~lKF(&++ME~-ciBWzWKRKEoIfQ=_G$_O&{j};`5o-n#O|qLx+H3VucY6=YoZR -z)rV~W(U)!J7}yN1B7FLkH~T1L#dJWD(IG^vyFv_mo4Sz>t;sUo4FD^w>gUVfh83(l -zg?(rUK;$d8*&jU}bdUNFJUclNqG}Bv;pE0VCpfF!AQ)GUke{FgvvZp7XUvnqiOlTS -z#P1o3I(SsIC|`m!E(tRzL97w}0H0zg_}XRl?W+I@Ilu3@xF3m~ND=-n@}9fWlIVB^ -z5w0$$*^8COfrfP -zxrpCJM1ztR%WIcOKY}+9w&2u_n^eG6#0i5R3({qC7XiwVmGgZm2&t4%=6U^%7s(UA -z(Q-uINoy2|D;xEm6vnW2WWrVMVr@D?2I;K$?%G%|SA>I?`Sz#Z14Qg#cUXAF-zfOR -zd{(ru7;E^t<(8iW{&pc?&S>aq^;3CVUd|BHF1fKgm|U*15;=}h_XyDjHsm+-jS9fh -z2URr_iULF#hLyNm@5C`cTBb?VwUY9R&-0axk}ja;zz0Af2#tp?v1TXgHI!`xB;a4TGO&TvPnyhxcT~iRUyW)=onzIM^uE{e9vH7m8AQGd2E?&*lDXH{ifn>2M -zUWnINQx<4fsX{b-$%cqFD~xb#?gtJ?6%Rjoi8ueH_k|nL -zPiM&)Sv;f~a+dTCpga?6Z@~&TAf9JrHpE~AH^M-irN)3EEdmMwR@C~_^O~I=;_#FJ -zG0X?pzL9rBRZCyQ95Uc#6Lx}m+7ZXw8DMacnZeW`A^2P>8t>LzIHa#08#KU7t;y44 -zFOY#8#`3IQOsd&`WKF??B%3&|qQM(&x{VReiNK0DAKq|e&?8!Z1%1IJ#Y1~ESB^Hs -zIqLL|bY_~3eJCC2ee)7n3%Q?)Q7A`s^MHA7qKoX6BBYfcSO#9ml?c+;BB;zoce3=B -z?P476vt1k88mgg-tAiEw*%!9I?QVywa-)5q!bTS=mJ%U;SbzvUYa27}XYh&v0zrY6 -zD)P)ob!7-T|HUTq7o;J{MZ}g(FtXlJbnRY?J`*~i*}?eOWr7B>cG}YBIMNn-!&$YA -z5Bw>cqG17_Y9M4IVv&LGes4;}btvJhIg$m|_6(zJbhTuo|EOAMx?L80_|BP9u^18; -z!z%)t*qvFoMEE?m_%kZ4$R1bPM|8Q61e|dIDCyfZOB^2S9dsduhH~47g3^|L9tnc8 -z2GW@9R$!jNmRUl`#9mnY;td^Od-X{GAk0A)sKEK}fN^~INc&}98anxsSrBCy$0f$S&TWO{$2*_lTsk3E}g40rS1590`LV -z1sJzX#3zdA-Z8^23tLeD)vPl@`jWT~SxBKdxSVw7QC(fQ}C^|}#!O9L> -zm4RT_ofLeHzm3)~pD+VO#qWona^`0VnmfloLW`otBVrof0h@Se^ZbhzO+JKOBuIY4 -zqDjqQ8;TEEGQvVGI+tP4onL$HIum0 -z4I(BkX721l3IdiA%Ee*n;@D^)x@}>_E9chskuclw58U~Vxj7anArfsO#x4u+b~14l -z76M#fy6L6Jy7#bI_}*UA8-FV~O<4>-m|0cX&j8$4xHT|G@h09R_v#GP@%O1eLnllLbz{NmIa@zBGiN9wMHNj-4`32KvQeUdbX*icLHIz*5jDc3p -zBUWA}ZI5E<^@cl7R4;n|<;V>^S#*edc5HnyaUS^BidTs@E=D~8su1INU_67hp{E_Q -zhb}?{Hi(m>_jTm3l{|wAFtSt+MSIW|*SQOZ&UZsyh#K(V45saRZ6i4dn8!_iqIGlx -zo*r>iJ|1zp^nval%F7ljW6kNdSxxywhXM;+U04-pfo+P+&RTrk6JSIn+7>~9K3iT- -z7!6!dFN+=$@Iy{sk`u@(dHt?X6I8$1K%0$K-=>YlonU0tMx-bH~j{7GPS}n-4b3wqCnfAYT%Z24F0DY -zLmr5#4%c11PCTqwPOQ8Gd!B~E!3zJ&-4t+oX1Nnl~<;3gY(0wlR?B(?L! -zL5R3f7mbT+EeE)+G+;u=C62?gMTNy7E|FNa4-yMPoi0~YhxQcjOFyj5&##+@)F4nr-DRDx2tZ27eT_ -zL+}bwZNy5c{b$OD*&>7g=4N)O#6%LLYGI%#poA1lD(KXib~;f!Aejs4`grx>76~OE -z9>SS{aPWR%-3$HPCQ_ogQWn`d_FlL|Zn}nS+6^=ycrRO^gzj_pp1Ka^L&ff5<~BM5 -zNa3ckaC3e7yjfb)iXWrR-t;DtnUq3aw@;n-gv)T3jzY){esMBdioga`o7s=|ls#_; -z*s9w_=@5*tFY2y1+!M+QLSz&F)G+8$6cVxgK|yEWV9UdUAx-2$G1V0{Z^g#H5IOmO -z_t(DW88En*7C -zf<7fljf6{ZRzZRN81Gf8sLF~`q%Dn3IBXhQt(&lf^MZ!~u^q>SJJ#22*ZD{lI$p%F -z@0m=<^Id~eY;7m9JA@aB*+Cs{QhVgNo3_>IL}66Z#lz=LwE|i_gLA3yZCvp~Ks~0=5;535QR~s}cNzq|<-rcp*J*;K&W(Wi*xo1HDM@r=OnTM-v -zsvu&^U$G#m#BR|SF#skz3n>o>IP~f|FwEflTsN?oNZ)|MjkVQaW~y0@^T|AeyDzaUC~Y)?!p9#fk@E{L-HWl~J|vf*iq*1Ry$Ub3E_mIa5ADEq-Ia -zFrz6Ac!&ooK}mEEQjwl961mb03ZUEj0d%h`e>o=+ww*%P$xNeGs9i;63SHsiqQ=n6X~eP -zeU~jskugx_|E4M9_!I&14@bo}`IP&3Fy2{5?`z!i@%qMIi-i`hdg|<~bfAdBwh(Pc -zE$Th4x%~>qJD-MGQ!6`Tt>z+OczZ(%J9pUlsh~h1JzZvcN~MX({_S`TX?C{2H|P*D -zrs{!-i2%_odYv{vx9uD2+zfoEs65$)pv-(|ah77CmX(kcB6)HOyRs_gDGL~Y -z%w@P&^hCxQ*pz(<@&bil2^Ezh{F;{P?IwJoYb6eulK;cBHgNbCrCv|nP0qV -z+Ft7N2W?=c$zvfcJUX9Uo<=P``YS{;*C`ssgumb#EJG?Ck<-^{8AeAWxJ4qSAQZr7 -zDI -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 -zd~Go -z1{hMKivuq-nH%ac7)ME>zl)kBU~YV{fY9Y}y5OQ#P9su01jB|O9C1A&g7BSma;ik% -z_sj@6f`)66AMV1!g;QGXTLp#!3HxsvB>`r=;`8gIc7f}E^F&)1b(*t;E`0$I# -zpR5Ssds3i4JXK4`GgKv;X|EDugYL{Ik`+L3aEXIQE%Zz(S9LLIIxq^rMD>#l2B8#z -z6`&N*korZ>qMsd7=6bRc*HG@jleEaE57iS!QebPy9rXlZ(`CX7Rm-N6MW_j7uwIZL -zBomfY#f5k*wVy3eA(+jTStpGn*<)BV0P5D({scv>$wSxGe>>gUtvgq-h-cYLTBGvvW;iQu|6LiF$CBA?NjNolwn;&;w?Mh& -zq62KDp`*B-*bU~Gs>&Kqq8|}cAp%@^2@j(d@)X0Yg72;Ltb@PMFA^~0!?tB)D4u?M -zVt1@<{|du?82v*%xobaTj9#i3dTqBQwNWPr8bMDZlQ>EUQ!Ez%>j*!Xx`k)ng}-FZ -zfkdB_4eLdPzWmLp(uiOLoMBO!&GcBgZ}vM%lR#sqR6maEOAB3ev+ZA0%cOOC(VlK1 -zNzIe*bzd`JmA3KI<=V&LiEe@HCi6YHjS~4(&1JL8c3e*}Vg)0`&$zWInxOLRKoZRR -zBwx@)0}A`;pm>F!Kg_r|3@KA~s7B}ujWNft(erPrtjsEe#mlRqO&cCj5?gDy60<=KUt5^hyJf16S_fJ;#E -zE*<9`I^>wfNhVZTnn9AL0-xPFj2$bQa4zP{ddafqiw0q){f&>Wod&0Ifl}Th*agsg -zdb}J)GxdZCP^iEbeWP_dQlL71%m$@jbN${(1fp@$#VDIbf_W1S$~B3dLb%)ZG|ekA -zb&TC+nDHO$_$d&aFE~13{G72W5sBj5(Q$(8W!W1mvMq9|X2l7e#`@l!#B^$>q-F8g>6pJx`Q&8yBvB)f)&4__u%(oS|^ -zA<-%;KR57m_J%w42Fgu4UwJe1~) -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 -zsE%Okd`(f!F&ZRpXaf%W1C)(=)4&Yd@+6{ -zv?8qBNAJ&1kyAGAHaub$Z%VAN`5-$)%b;Yk;b%E4FABV?2PScyZ~C_q9FF#}w_I=5 -zM~z1P7O*qOWl5l=<*hjBF)!~U0 -zHTy*fOvcne(9z4(%r97#MPzQ}Ba4fdgGm#i&kph^QDk%7loZ1-Z0E|C9F4!rM)G5< -z5xeU5-}0gZ{MPV3F%MgN+!4Wy6*KQmaA*q!yr!G<8Z`V6{$kT -zgN7qTTIJ60a7KtJj(wxKS=!Zx3IbxHvvQtLD0=f92-I!7a!KYX+7XNkLq0oyiY@5X -zWXi<|Y*95JE5dbO7w;h-+p)AC@Px;_zd=h4;uvuZXek_2!m7g;oD{2WtOSHTU`Z<=TOB -z9%QS#&hPLT^0D~!V)H`(K^dK9;CUzdZS_moq5Ugs@~Y&`#K54dSgPjcuAlcl1^@oq -zCwqi=pcWCG_J$FJs;_(jYSskMRZ%>?w%F7140J-M6cWYNk9$xrVcJ6U -zfRBc5ZC#)c2G?`UFp~i24_*7yUS(?y4{A<>&M5lN$eZMetix -z$YmjD?mf7WH2wFM_J*xVP1s<;^^bOcSW3a6ACLJx+On_j!bWY|GmoDgsz)OSGNQ`L -z{eQJ%m+aGm=_1{PVyEvuQWDEB2F(J01=SdJN)mM4%}bP5)B`s7M6Q=s%ye|>y$)}L -znQM5Ndq2Hd&zZd+PLL6r*G`LHXb>P=T^266PpCR=NLlkP5*&Irn68Y2YD;O%Yb28M -zSUkVZ%O5t+sJtI*c+IYiq2uB#Y6y69>Nx1_SV={_Q~6PX9`wuO(JM+mTpkBVV@k3} -zwbg0%oUV!la@GKEpV{=Y(ED$o87a>Njf6ef>@~OKgK5sMph*c5NNy5xxO%gE8b!Ii~W-WFQp4OigzR*us1nA!6 -z!qWLUGT*)zbh|B9nK^LPQ1<;%=xb!<*?6>IFuG7Oi!h$ok|`S{BP$@@39Ko!M|x@R -z!Ncd%pE;0gU5FV+waPwnaEkJ5AgU7^QMy$dQSe?=7C18#YP2FtlHn0gGU{JNcj&(J -z1azOU*n0}0#n83>*tEaiIny~mFg76KRjvIzl>0+7Ygm8%qBdpKt>0DPncQ};P)4NO -za9N4-E@jpZcTX%7cHH_-Hl>DTpExN`loI#LS<8|N=0nMWIr@sQ7CpifHkXdYUxNeF@I0PY2o)l1;OuoJSJ99BcD`kr>Ij*9eYLq -zim_z2m2Nq5e#Ac4ABKOSEv(4vPXzPGIuD6-+w8iN!V;KdgT3y-C=Mh)8TQHnTuY(#gxgR5&15VT>9)w)Z&U%FwbnmIc=+2?=E43APa9^GIj21Hr?Wizc9}l38Hr|%)LCbTrH@k -z8bk*dj3r|kZ|B0?w<`PDR*QPY!Ajl~sBP|V0#VM2UFNN=hW!kr$}`Yi#LUAJ45HC@ -zeiL$-<$uGGZaB}z7M>NXo|GPpu+y1{veN5XNVMIzTX)obbyVWX74}&?NyrtR?d!}J -zzOnweIeu*R>j-=jGVs%xE_gq?x8Mw7DYTxgJoDf-D71d+wdM^dn6CVO705}bDtOVK -zKi!@iRKGb>V6!8svenm{vMFRZF9W5; -z-)fhZs=+|zV%!(#|8X>l$`wWTtKCrWG|Me*H^<}DEwSww_`Yuj-+fzD!Wg=~g^anw -z_6Hf0kw&{R^(8Rg^5m-Qx5~s-Sv>8L`0qv6ptfnAj6eP+!c_+^3)u4g-6E -zynxj%XW9(%b4@BA(t!7CB^Tgu6q&0;(-hUc=!1r**P;)L*O@`9F!$7gcIRdZ98*#? -z9$#%*vXg43_^2tOiV43z)FZKgK^!RR@yS}VMmf+jIe%O$2_}cb)ib2J{_{)}rxRgh -z+6#RFll=-rPphR|M(TFr!EqU^IXs_&M|vuhhziJKb-1wPOC#LXcrxG_mVLw>)gq)z -zyFudwk;{cy*&vgRFJM_act66U5({Jh2*61gMa42~m}{K(ba@V-Lc1Hn4EA;T+l(@q -z)~&-6Npbf!FD5{p6TI_lwZ31z?}OaPke%iHKIV;zEcgRWk+~=(rl9qa#q9jEYgE=V -zILJc3xL=4?0ZF)_E~I!E*XCk36LI+8|7i}Bco{;y5Igb^;v-yMi8MpiVoN;-9|-l~ -z&Ql7o=H{v|Fwc;~6%P%o&umi-3u52LDB}p@;UmbROSz%YDl?a=h5Ocn*HK{((0UEM -z5h2?u*HeaLckRb8H%;8XM!&9H*?m?&;hL~o)xYsKD5#Z!Dl#|o2_x8VvpMQ}5#RcG -z#M|*VM27CMCHO76J9U6kJkM)Qi^jlG0;07pXb7oNRC<#AkA!TT)8fwhs1=&W1d4Lj -z`au-I{L{7JpIc>sVxk{=Z&+FU?-Q7U{1u&_%7eO8ga5~p@i -zH)8<;{j`{soVvo-fy3a~x&BO_Oi1qE)0?urZ%W1~-^3P>hPA83KZ*33wzP}A{>5BC -zPqx(GoxcxaSAUiqgMi;n54o`wWxHaWZK#;=a+Tx78E^P+2$5WpWS04rVBPJpNot|I -z(V9{}9s^Cw0+0Soy=G;-z2op7=;S7hU2=4UoU&l -zvcu{%rvPQ3`gPMa_u9WCqtMME8X8`9!v*<|NvSC!jD1$mU1L3u%V|p9z`_KDy0^2* -z`k}=>-Vr%M%SH}T@|tsHJB~fRmEk|RBU&GaY&RYJJHxxX)0ubm>bPsm@S9I}=cl1d9o(L3%hrYkTq? -zK`-_<*I)naN46PnThX}+w<`{ty#eMaGMO0%8+`|IX{6WU#=Ujxf{6y5SU<=)t=Q9- -z{kOV$khwWP6P}%ZqQNx*&ohlK6Z*<$?oSs4tM26~b=wLFfE|!wkzPKxSi|Iy{e!B# -zVr^e8Z>mCK)h~EtGU^AsnPa<Xlo771b>2(tntK4#jCyh94jE+xDlEZ68}2^KUOO -zcQMM(D9!A{FzAh2bm?1Tm40=3iN{)1_BhaITVCH@JCTf~_}1d5=PJDh+YOPCt)M~{ -zPV=9ebLneaGP`^?n$DO%f5b9oz#}Sy`<$VQ!tkI+eZgyCJ>S<-*zTV;H12;`Y@N=? -z623+Tl!W>1v(4m0Xw`kOzwdld{22JqfBC~r{fBCG?}JKwHz==vH<)|kb(;G67{liv -z)y42GL9x$mJS5-n?%!CYM+{~4RX&?Jsid&GH!;-(23s%x#SUE>p_O~7l5_fa0!ODh -z4e!L^zqm9CfPxJDc0yoB3hJjwZwhK(65*>WC8_h_a6;e<*=wkvR?lnSlskQ#P-k>%}SCxLce*jqKe>yzij7&T`aYu<5fTjlKOFF<(D1vC|dxF7_W -zNO!l=^ba`im@VAvgRJ7=7cu>+TWCtDhGYzW+MQfU9>OczoCgmYn_}Jh6aKwFMco%$ -zpB+=+BY-~Md6b-~Kxnm&&BlxV-Fg18E3gVH -zNN%%G-k!hNGDdIxiNV}Q((Pk|b{XAn-uBvfwh3ZJoI0x`5vcio(uroxoM$fvk8=0} -z5y_zj{6V9OHZsvpznM@H1FakI`iAQH7i<3$^CCHSV4N(gGuxBs;u}A8Xoskw-d4uH -zoHAp^ua@na`RYI8`9I&9#9PEa=cK~gf=YGL6xuSsS|7~V3WnEu;XU&Gc -zr^OZi&j~>ytK%A{ke@N|fS4&m32yl$ye{ce);}%MzejX*9?MGTOuL6D??~QB6lOy9 -z9?x;>@Hcalg72@kxNG}*-cS9aa^=ASWoVe(U>mJJxSo*qYK4onomH1W$vDnQ?dS{9 -z*L5PB+^sbdcsUa-Ts*Yd>O*&-22JJCFOPq4pcgMv80*Ajo>7Ucjjq{pel-xEvwtxQ -z_r5CNF~LZ_FTJeYyr`_Qu^Pd>wL_BYglu73%ibUV`W!gj!YXgBjlV^Zo~7YVMbc+C!tnZZ8L>a_-~wr -z60z#r`aa?}BcuYpO7uvnI5$VV(A}W@m_T`S97LE*75H4@v5n{iN?Y1gBg$Da^Rxi2 -zB^Bu2u^w~MQuu6y(bx*+pd6-bX=l8*Lj%pNhK}#KS;M77c!?9zH9Tcb2|Mn#qzwI7 -z#sj}5JDFHi-}6g2@n1@y7ewdUbUqjDj1G}>PQcgZCQT~}y6))^B)7@eQ(p&ivr<$_wq72DT{+XtNSV8f6q;0j(rzhRok1#kD -zZ9)-lv%tjjx3oXBOFzchW_zo5WN&kDOhrQ}VlWn|M1{UZrCHyI3T`p3dJVhc5Em99 -z-s2*Q>nnDza~n4yjb2iDw{`@@{0-rPB`8Ii0ae;f=DDd-2}q=D@m3b_fgQiv_A|~# -zV0sgYBMYTWLYOck194prG}bAKbXp((K|2ElMl8lEKIL#N;6xR>E@V(NzL~4w{P;mxm{z#s?H~?Ks*LdKj$e)ihTxF!|M{Q;)rTThmq9?gbAI7okR%w+G(~n>;Tb6IMh7 -zW@)*pUWak(@ri06RsMa`L_UNP67M34GBeSA(kBDDxK>q@iL_Y3J! -z95kks@;~R+_gxw-k_#N=x{%}tEgb?}Vy4X5WAZ9lofKqk6`$jIv3bWmzG2*ram{vM -zEp%N|=OdY0R-han)ZKZ%(KFY=Li^(ZJ3N@#g{dJ8w!GDmJIYSKpJgY6_m2Y;nT%i> -z!){n6tKvW+R)7|E%5<^W(7a2uIve8XYpodh6O%jTHUDj=ykFmnqt@(B*=))i-rL>Q -zx5=$iS@HGL&|lR>hgfAHlzQt2wCy2_f+^@Zc3f0C!0)aw_8bGzk<;X~a17(EoNn46 -zxUHLIxd`=%g)6OxfyC$$EH$s*Vdf42(MF!IxSV|jj%@k-gEH{R -z5|ic28aJX6JxQ3tQD?666KdZ&j`sMZ*1xXZ6!T8~6>76G`!K{YmM`Hu(Oc_Pp-p2T -z@kcdl@+06ZkHw?@Tn -z#mgjrnTs+<8$U@!E^Zf&$X1fp -znB^@{IRI4A?mf#aw@jybu6e^hyoft0m3yDUN{~sY?=6Ddb&C=w{5fss{=`2$;;+_Q -zhb^bJ5xJ+$wd{sOQ?=@EKXN?@S+=1@x^_wW|2AUg(8juZo1?!mn)J;k$U^h!IvpW4 -z+5=<@dqq=JY{K`mib%9u=*8J#wE6x73ocqlv{Z~uXt49~l$x-b6QqIGeN>2!LRR9D -z$^u*8WvIL%Wc>-y4z&0S*xI(gR$UkT1@9O0u_O!8Jzce=g5GtTZofVAKbc&dSVi2i -zyBKs2+EHC@Qg+k5$*|1fgL -z|rkkeFIbZnO3v4;{pYcBGf4r?$uQd-Bpe` -zXd@C@?B7|S!0OdUD{r7APqz`b_Yzo$cr;Mj)GQY{ClSpjb4G-X$^;8%kZD{NoAtc7 -zyr`&qz^Dw5kCNFz!^&x*vOh_4c%l7c2@}$nugyiUiVlG#;uwpdfy1d87;vE$op&uc -zWB7A6H-X+hMGT50Hj&u~r%NV+5$>%ZC~3$5@(h!X9g+J-a!n?#E!kTsi(r}PQ{{=& -zukB_-e0JfC5o!z&h1B!+YBCnh%xpRHaQqyIK>JqsJYLKQx)OdlzM;$Q;zbdzk-0O< -z#u57sNKdL-AlB(cX%(ovITdEqK05{;IYX3Eh@D-}-c;%)ugUw=domTwjY42N_jJ&S -ze9cx8e%R|UEI}e`p<2f=9Qz-UYvE)HP(V2F1`n@`x#IXH@(!Ta)ca|C=@NdJsu1ec -zlbA*lrrtfpXwcjgVqN3*^e9&fC6SMaT8zW@>-vbXRgBFt#%AaXB1A -z)2EzRCPG|n8;q?ibI7SG2>Xm&Q|*aPOO1>j81S?p6#kh!;KB?LDln$7hsT&sJ7&KL -zG_%5BKURi_-m9sy`qxZtD2l&#Ph{4OFT5a~B=iprotMtW*P<<;f -zE^J{LbOXJW7Iq|gRD1wc`-!g)`@eWx`2enz1r}|6RfR%wzeRJJj6YWW=U(7CK3t8d -zy9BV~dk$9gqlRI`;wjYUL6Sk2;nb4KS@uzgZ~tIi1Oj|g%o6?sajMjiT&OL?{K|zW -zf8=BbHBsSzaOWZC5mXg$;nZUc0Uo8pYv!?Zt2!=0Ko_NdZ%8Q9``I(|Hsj8)%|l6x=+2^TPuQ{X4=c89+qP}9Dz@#U -zV%xSVwr$%^#hr6c-}~Oaefo~KdyMsIuem?(z1EoX`JH47tCgnPreQ*c{1P&aLo+Kz -zB9iM#mU2aCwYHAV@sRFFW_TN5lTq>We-JsypsOrQmJ9+bJ<8RESBtoReeQ02bm#xu -zYinq^Bef%vDc@J3*>zja;(;YaPZydBHlfj9hutTLfi+P?I5+Z_+{+WpjqZ-E7gKw( -z{`V8_@D1O8_P9*wCCQ8DHS<232I_CRIBuy6Rzs+5l{mR~k|}PYP>)2#wE3L6p+{uJ -z$n~nrx2;kWOO1pgr^^y#JKB-fk$&MDX}Nt3yT?V^>vth{?u@rgNgE91=iwHUUPFT9 -zP)L0zw?X(8=wRsfrv<_d+^9E(S+t;xgrp`G7?F$?d*f2>RTg*_`%?+wum3L+mx?zB -zP*gx%5FVlEBsAwKbC9QCR2Hu+&B;P_NHrT0k}tNVS?$iSe44c)z;3q|$ar5tWMv-q -zu8qhHw$)Eyd-x21wi0u-;e`|8E?iAjwn7I!!p(^(9m#qi9k);{h;&RDjYLKeo4Ikk;%_m -z!aRX3uaQ#H%;}m3P`2{p!r;0{nvIU`mLKIiWfknPjWX+-J)@;s%V7h!H8(JVr-$T~ -zHH-?x)^NIi_~y`mG`uQZEw$qsBG1vp>sU*B#N>KQL`&yWbKrxif-9!y91!AT>E4(N -zXL^i -zrdG2cUYOtHYd{tgo;WCP+1lvNI6>3pd+9Nhl+wSpI=n6f+ErLt^U#bu -zF4)4(t5Qi{SLbA56h}{7`i5}gjt&e(!%|c5bIB}~4fGonjY2k(|Ik@_uc-P~~D}wiBr;LSHz0uw~E0gol93 -z6jq6g5;WIQ3((tvma%A4tpB9o3Xlv@*XjdyDdt**sZegX6zChu{jU~RZ=J?cs(V(l -z6kbXMS=CCLrl4e}<{^JL2%9UYj~YDRE%sk@R3zf1!P#;dyMzUv^aBGCkQ!Xq+OJ9n -zHW>Y$brB5?MA@*!@7vVC&Xg+gPxf^r@3J|(S6r}Co^AvpOo409OIL!=_QFGw7zA~s -zZ0S8v8CI(p#U&sM#DCMc%;}8~GSJQVXBz`hG>E7K7ywn5$4%RvBtgcJu+#`X7 -zfr%FMsFo9FZ$e$M@DkJamj$CfPleDYFFtQsZRF>2K5HW%IM$y*TxE^J<_Y1}vFLNJ -za4ft~i%7>x(%39T`$~M9_H)s=caAM==y+8t>g(G+8oaeCko8J4fO -zVsY56C@JOR`!-Vo`ks(^&ONXT2%tLmgAdB7zfwu8NTo3R3$)# -z|KYHVp$Wpnc5*muknDX8P=Is6DCUZ{6>YQr3l4E7iq~O -zw6ip$8+O&)1rH|CaBhjF(ulk2m;*h>#|HU@4s4?Eao<(U^e9=icIi)s#m(0@2RiZ8 -z>hP2%mvIQH$QycCVbP8JFdxF?Fus`=vlT4TGPu``AD|Nn#SVsEczrh*1Bi5j@AM1k`NWff)o3mp|_s>DOJc}+sC>?5N`2QB>YdawBA -zDWKwQ!Usn>ZDnsQUCC@!yCwk5>MOm6cyGui<^h+2Z1$*Sv@7&wd$WADLWhuk+1O@t -z3hV`)o5$5wH0cwFy>fOgePk8S2nQ#q5xPDM@wL0JxTCHFUP~t -z*JeHzDx6b%|AoUvyV76W`2XT?b)5dg;d-{wO?zmKY$TT;sg-5e2t~)C-EUjx0;Y^yy&d+47REU4LxZ9u(T9E8^1$PiG# -zh^xlMeLI=-140P7*l|F%D6cToS%UU~HV2IMBG!q~inv{SjMw>^^l+=~{IAw63nYDB -z24sjUM>KZ;+c(9fNRfccFf4gtgZT_m)2YMAjYD^$u~zqQ43{ZLbUks%naF|bp8`|P -z|JLFPD7W5kO+6ed=#$LWZi@H)wzwWnec02HcAn0xyI~*Oij05P@`*Oe+S*wDX(;Ke -zR^g5203pb>bQ%k|-q>P#%atDtxtfSs;RHIAqm|PC>dAS%@I2sg8-SoAM8it00U2#5mQUiLJITh -zQ;J|59tpt^rNzFj%Mv+-wyE}8+XQ53wkT@0Hn~+O4n%_1==)L`JQ&`0LGI?Q=cEJo -zebQ}wbQoG_Mha;(Q(d{rQ#$J5Z88^oWOO9@5+S($GKzFYnc&Q#g`(_56~KE|EA+h! -zD$EPY$kH%%P}CIan}R9Sf}gWET*sPXZrZ}Few`C{BQQzmKoy`t7qvOmt#kB2Q=pkF -z!5{$6-?F9^lw2j_sZAec83R-5X-`R3J4(oyrcoWWZH}XFc6aP -zc~7eesTm|o*iDf34h6OB^7OHd6f{mZIfGM2@iqJX#0B1PnGx;%*CZFxAUF7Zyg}AZ -zKUAI7P+MabaVR8!qTV}8v+^i9YH!@5Ai{- -z*vFWgX!t)Bi4m4XegH2a*I&k1`acEG6;jpij8kL|vtn=X`8n&>JTA`t?Jg!$5g#0Z -zWbw{x3Q3d}WMD#KvOBWLw$qG3TPV-<`~53_IuN#^3K%rofMyIxeYMCTNCrwX^6T`PtzpMb?`BE8>Vf3lCTWyQ$kfd -z*LKk?gg7C(VR$jT^Xh_v-#pFEHNlCuW#l-(Ap;ta#Xv^>uZ-m^LB`ee2TSO -z@P?NR9yxh-;N1QXl1s+h;CWJzJ+pdQnahS4vE;u4xv*$^^PF(GMUYZrOJ5vj3za3= -z=d6aFcm#(SOf!qLFF;L*y#)V^%IHhG7UJbwGlJUEcO2B1SR}(je_s6e6#XLLv-C22-t8*S%H{tuC>#xJF@;{T7xg+MQmBW150=vJLc~AQv#6Ivx=0vd|^PTr>y~6~VP14yOXz -z!*fs&6sB|W4REEy|CPv<02cgTL@uf)U1mq`(q^3)Cy_9yLu`GKN_F+EA} -z>o9*KY=1i;zPR4=#uD8ZT9_Xh=~*um<~rI#%?>YZ7#!kPbLvMP9r-_tTtF=aNjdHq -zqAd=v8@0adf|Q6l+jglpz(&-&o_>G4ywFWO#p`mD17Vm3Td8)krC@#N*&rwyWG*U* -zV7e#%L=}2e+Ht26ae$t?%#4wacwdA*kkazX=qzft>9j0#B$8?Cm}H2 -z|4#Mr0}qOT*{gK+b0QX(!+8&jRJ8fzwd6-Xp1sLU$Qh_LWW;RPeuaX1HpCx)f;Mw1 -zr=#wGcTQeDi`R%apaPJX>cdrC%>W6l?-%7=Ks|HFe}d-yE=f|2xez6+lA9R>Beoql -z2*ijk8cRK(tTlQ_v_)Tu9k=8ex+{!WB;&wOB8N(pM?$BQk1CshBD@Iz0k#X^$rUNi -z$^_jG_Q}S@z{3%r(zOhv9~a~O4w^BE5ScQ+yC{(9Swdwr -zX8KwY5CLNCz*19((#jLhEX5?UzA#gU|9!T8^6xMg0cV1$hj>kvWj3j!g3DW%@>^dy -z*~%gkAaHARchTI9VTu-ysupRLZiCrvtG7n?dRJ4M3xbGXp$1n5qQbP+eNC(~GCh&# -zB7#HlB#0mvnD9b!^{jF6{0~=^au8b1iCvLJCg1ll;SChur6RzW(fw!O(2BYJvU>a -zv6rgLtk&XfqQpZ)&_1I@++K&O6&3lseP -zjILY}rQw@wUK0AhlDSyN15?T(NwH?&m1qCMSc!207K -zfHogBQVt(nyM(Nb@&rlVp18u@M}Brs2VM#4@aa^cZZOxnm)4Xa0shPK@jBiwW;wFZv^Nz~bV~%5SqX0O7Xd-9C)h1Y7|~#JJ^9x<7c&^ttkFn{C9PnR -z#xyaCZPxSWj|EfWBsM2(6kgA7o-1MJ6ioGJSCv&Jw_XqKt-e#qtSyp3+F{}dv^urk -z9!Q)-5J7MBl`!Kj@$|-Bq7E7XD-hRY^(rcrRnaytBu)bPSrDM(K8P_pl#EhiaB@E% -zq|c?j3^_VlNqJ7+QL`#WJc=f3z~g)SsU -z$PJ70t(Pr9OIP8JB5lp54fV_(Htp2co&S&M3KvEqQipe9qdhRLUQeBP$v!iqI57gX -zH@%=>dn~>(+Qz=b9t$@omKmwg-><}>!M^R2k8)?R3^V9uy@e_(`*);^3q#CKn4`|q -zp3)qVPb||Gfq)_$?6xH9^#@*(-A|AF7_1rXpC%-INQ5-4nkEcorOFyI4$_OO4RyZg -z-KB$3Xzy=I3tjMT5#s?UKUU0JZ)QjuO=itnRu-$R`->qzFVbs0yicDRRK=NB|8-Rkr6>{w<0b4 -zw>y9{X5mQmdn1Y@0}G?%qQ4k}rd_vWcS<4)cKXPJiypyQP76z0c`@1Ad$mAOB1a6p -zFnLh2DJyS$&@hY|a)4%FCMGR`LU;~?f##hX!azMN1>j7;t5{BMo-rZINemBVmoB=v -zdZ07$W*Ve^pMXYaNjpPa+)ECK+rBd9*HP7+IF~w>Ym9;np8)8L#oH9fbL{><0T(P)QbAxAbPAdm=k(#S=$P&H&jiy=Jkg>@>F7yMU#gTR*Oo)?2@jm&8PAZZ2*RGy$(aHU)?al4eZ^(jj>d$L+V{UZ6~ -zw*V)Sz1`s)P_ms@E2in{bDUh6U1hN5&%wA&z8O<;@Z18fF7YCuwLBgQ)b-aOiOlbb -zAmh6yFiZ5W3phnF-J@wc^B!TpjR#by->oY(AXn21+ZsNC+E%g!za7y*VZZi{Cz%)Zrs={I4b?ez)`%;?}$h$;B`{TcciSZZ4!b=HB;T -zdt-%rKnKX1gUNIfv -z!*JI8qjqPR2sJa=`0~Mp0v#XxC&(NasGCqB?8CH=0%^2bK@9S1zXL(CHp^B1gXiL- -zs6G7eeXivJeYZ5L+JtFC4{oPG)mNtSTGI~+9u6dy0$JW)I0}17An1;w>T`RU8qyn_ -z6sJrgG?}B#F+T9kE^VL+96bLZ9y6>?e(Xp5y#OQd`lS(rjNlbCInb|!>*CWG;VCz(!mO;3GM1c{(^bL#oeGI8cmgFsm6qCxnik8ZaG~v7yW*` -z1%pwe=xVxHsr!HSxfUi+9#F8gWPt*MA)SC!SVo@2c1l!cB&WmF8uONO5tf(~Osq~A -zH%So2;}ZE -z0U-v{{z(?}Po1j{s)%bEQU>ae5u@CK_}-9Q7_iZYLC+D>kHKRI72`un@g+I`sVT#6 -zj#sE}Iz%}=<3I6SngJ+hF*MQn$04A9W+cMn_Dgn9i+LNd+3;eC&JwXZR9&Ko4s8$=EVx>0!)sSp)&8wqmGCzFL;AcWx@ -zZBATo2p~sZoJtz~N@fGfhyPDJ7iS2Mp)qCPp{IpNhZ(UXT@+s^SxpPEgkn6RF|c8z -zKl;${#{hG55q3^{UvTKg?_GB50i65~82p!JprcLTis$PERSP6pDGoPqMA&R?f -zWq*~9+x$Bh$sOP4Df-dG-SgrQo?_Y|p~CcghDmS^M2E#wo*d{G^N<;ssu4LTWE||y -zGwC3$pRWI!=PJVuWEP&AST(zNaNiYGt9YEmp;mek0Xae|>5n_VGj -zrRS(YtSMo8cdV>|2OH%@04-XsCRoRC41NF^0;27n@ULg*dS>x`RNf^%xneOEa@0A; -zC8!UW>{BK$hoH_g2(?ZF?S_dOJi9W7q4+2wB>A|3jsT0voh%-VuVo{3oHPJSo@b!a<0q1pxU -zgly77RWUkO^nK)PHaO2X|BCFX3jc(q8(4j4vjRjG$4M=DuN8pYYmx4^6|V#B0qbwLE0@! -zcC45$5UZIHG4?cJ -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 -zl;E*70?wK^o{X;$t2enu97MMKnFz!)5iA -zLU*YNifz7>mgLjr-dBH@&Fvw?Xl5bnq(xOzl0{|{r_%Y%xFBmaI@u7MV9`fPYlSPq -zasJ?)I*j@?YLo$?!u9)AHV9g#&gR@hTL68&3*{;&oY|LjsBzLb#jn%9Mt>o)Q-s#% -z>-u}7A85m|^Y8ZOE9n6oazQ+b`It=h_e8O&mEe2xW@Bvi}w|)B`_uG8?dPqzi -z*Ycn2SY(~KF`V0qNeu*g_wqj@jlFjwT+D)f2G|LRxE^K5>JxD{4KhbA#Gg3r<`w1c -zI`H{rJPHl_4x0bg>88DJa_`WuZ@pkqNI#Hgjmyt`)ddG{%Krt9KSg?X5z%LJW!$Os -zVeTaF>wNTo5kq$8UUg8K?l=t>$Bv2{Y$cF8vr3wFDV7OKySTRUFps@xtuvTRL)A%7 -zMLmb_{bNudBOy#@|IkpEDL_gt@yD3FT0Jt|wOG>J`LIIdiB+>xt=mJrXZb+?^YN>D -zf8d1SQB7D1bGXrliKw}ZK68_s-akD1m{wez={%d@ukGE{x!;7jRP*zg*C -z1Iy~qeN(>Lji>g{UFv-3KLwM_mXrRo;V&N)$;KH-M+~Fu_J3p}qD!CX8~>->h|iN` -zA9k<5@I&v9B*|&Q8?gO*t^CY)!YuhoG{g2UTXN$#YsOEA?T3t6oW8H@fbq5|^&hJ_ -zn*jUwt7B|X5hv*Q@`0QW0~7DSa(?F0*UZvDbyxn6lmlU3<;4$QxOP&GBQ~72o#(GN -z_=Ha?g44rk|CEP$_MKO1$ZBQ)U+_JNGQXhE3>v6tkl6318g54L~;r>w`ls!cR@Xwd*eXv^FNXpv&S@%}Lq}LYS -z`pFX4=oq}&R$OMd$H&LRHY;pb0C{mdZ4UqJC^pUx^p{zfv@=E4ztoqt9&KZC)6I_i -zJ>^e7biMbAohvzTpBIkXHEtj6**Nj5s=WlEmDyt>{9n&1cLFjGEh`z(Za^ -zd2ozx0RD%rYlro<%O|9|jRoztrskDbH)mhuc_05kUo-i@MmAd?_AIuIEMcw~OCH{f -zi(*#7br7Yhm3p5A3_e!>PP?7ymSOWHALn!5k?c__(lMWo+-5?g9FhG|K}VXEuo -zd^_-2CyiHc?e78waJywxXV(aJ9gPyI=lh#dpW%QuNh5%9AWNaKL-Qxp*YRlXYki>b -z)S-XG{EQQ&)IL>Ze@P@a#c_dj%Qpo0-ni*Qw| -z6D?)Ah`ud`Hp7zaw8(mlzgl+I!QAvUfZ%g%_4WIzVf)rXt>P=XzcQ!ByV$rE?#coE -zPo*cr;0+o9?3P-{y`X~-OqGwO%3?|N0ylg-I#6ZW4?sxAXwq}8gckXTnyZol_ -z*D_+utNqud{^vHLFH4Hd4}6PdJI~hIkHcV!-;&+wbEMYG*NYaMPq3s^3Qsh)Zp9D{$WBj)Y9}F;3XTgo5u|%TgdT -z!3xF&!+dt{Q=zJ{o=6F|2#oMjF;|n&)2I!(5HU9zAS5&k2sc~CDXB>#3zq`JmqLvh -zq17mJcS0yZP8XybTfu788)T-I*$L`F)AT+He@b^zcUvZUSC24FIqC@NWYK`DUyWjG -zW~zoACy~S)gcKZNFli>F$}5?Xh#$y|RDQOAbUAtxkKD>6xL|6pw}&iH -zky12$2hU1l} -z%h?IT2!C)^<@od;cnKhn-nobqM4BW(sV!vq{7k6R8JCVDhD`*T9Y( -zm`AKRbI8lt);F6pI`qZ7z0<>{C5(f@Fn$SL$FWMprF-w>ONij_7`4=XUDN?if}mBh -zE3S1Oaf685Ukw#Nn4?@-SCe3b6b&j7a?#OhDg`xl-36!+;n~tMJcl3hY$#PuL2VPK -z64IN*@MTXMpO{@?O)Q$MsBl`y0{5c?y{M^(= -z5(2Z2GCnLjuD`An3+G&p3^p`1Gd2WgPX08-O?F@fpWsaYxD{}H0tp8)L3;c`?r=Gu;NcUFIU7dz#=0ZJW*3$0ym)k!6P%qQV -z7?Tl-x7Eds)A(nqF3MgnkOi7v;Ecc6A=i50{rr_I!pX2&6i#wmr-HXz7G7(9Sl(tI -z*cZ$|O4OUpBX(sYA#dDMHUq0$9o`Rb_{Am;cHUTeUJuJI67&Lx_&YVgis(spB?@YH -z?pn?1uJXjBh?RC)C3sp@frqp@{OR0?JN7S|bNsgL8F>xPUS86qN^G6h_GYwZQVBYQ -zo*&&7#u&~)=AvUPbxp8l6l6YKYX{7e?h(GUT8=SJ{xddcJb=Fz3& -z1U^UlmH<`X;3YA$ON455;ITh;LeytVy~ -zBHUpLPZ!+4QSPk)SFAYZl@PZrWdkQIyflu|IeOfOW`x)*u&eit-|N2QsYSYfAv+j_ -zXMLY=A6T3!h&Vkp)jd5Ux?6|~@8RMoktJT2)4hFk8%*s5mpaz0O~!A|0%`TO2lL$K -zfiXq7)%PUDG>~yAo^TcW6;#_Z-F#LRJsBeTuDJ67lP>2`V5)jzTFKQh02YDJx_6k~ -zql(43<#8pg4BcPP=Hq7fWdXX8>H?D?+h{8lqgHZD^dcqM)wgRyK&eyLV-L}xH$(aF -z1X3A`iX$yXhq!i8bVR)PV|0RGYfV3=eja&9^FwDnc8EBPWo=m%Uv~KY8c+e@(r2W2 -zn+8V9v6l_R&&X0AsV3uoQNViCME)6d+2Q-QrZwQytV4T=?;Tqfebz+1yn7t+`g7Ip -zHTI=+^lMf1t)l||=pgG$i;>-ty{SE;G}DZ?8smPoz0UtR>NURDSN?fa%CRIb^oMin -zZd`(matm~*K#6v_E0OT4f5Y3i0$Ff%farbSvHd};%O}6>GZFcjxJ)#^>{^hUMo*OL -zxNr#_j!}V7S9qxPn+N({{g%}L%)&`AkgUBIhfSNP_iZB -zwEQS3Bd4XGchxRU@Yg#jf2T${FK2IW`kXQG1CnJWFw7HM0UB4;5tN(KOJRx6p@OES -zjp_%%pah -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 -zdSuSvJgZbY6}y# -z%GrSmQZ}dau~(wdgwr?`EE;#M1Zd1_0*kh*CXP4TS2Ii4)mA1a#KFqrFuD)4yIY3v -zlm=L`bbGd%EiBnyL@X56TSWt=&4=lHmTJ(sx+sE8iCS4f0Hl%Eo^q4q_CPmL<7wCR -zZ+P~J7?4lhwZt{%iJqAt(dWP`XKL9B?p<2v5PKZN?u4$SMnVhuzWhN0l{KIH0oWt5avV -zHSd@3a(=2_{g{_upBeOrJ^1M5H)mcx)BNpC5TtInGQ(JMZbQw_j{FN*KyYsU1% -z>{m3O2RjIN-kikNdLHXrX+-a@KCM+IQY^3uK@m#Z^pN9^hTNdQJgluZ5t;_kme;D; -z*O|#uS3G>v*RQ^b@EUX#qigN59vC2Aos}kRai7-Ut3jv-!cO%K0rftx(ZM^|AKW39 -z{4i=^{tGPz+aLee>fhJ`=gkLOy#8Q|S08Nga^8Z4k^htC^N)pndiR~$$j_VMl(Gc) -zi{JkGJC>Zzx``}n=kK!dFa|MF(*NnRKui09fuNoWe>l1VVvDKlITjvq{S#3U=Ui5m -z&3V@l(ABUF^6oyK;^FAH8={X8_&3BFAc{5Hzbo?euG`to##xX^?EnXV@iz_Vs;vwA -z+g~djvWs}-vK@jnk2XL=fesu`{(0**(Mqx%-*0~VYnl%cv)F*Y`SxY=^nWN1`!_6` -zKse|zvb!z_yNj{oBB8>%7$(GeGl>-r=ldF=+>6l$FfD?PF%i#%mdHnu|BedXi6J&5 -zFx%)F*dnO<8c%rGB)r(kwl~{56)IcPR*0h+Aq6a*|i+{v&%lkg&738zdKi0ky75|C?E>cUuFs-#aHtT<~tII(6F -zn&MZV{TByYxz7lsKBL!~!omLgtlRSEpI5}?oUbkFx_Ku@mQHoA#bUI!bz -zJ>ex^!zyVNi$j8(sQvuI-rf_Qdx^L&`B8bd6N36pKN4HknX9o=n*z~}x{QKy-j847nF$eZP -z4vCdz$SjIt`3O$qBcowU6*{!`KoH@IM~U{ -zz+JFEI9j{cUebXLEDuQbJn=PZ4l<^Vx2Av#(`>Nnl|lQmjYY1 -z-97FwIvP5LM^hR)BuVh@c>C&AX(|n0-_&~;cZ5(%k2Nif*ghZjLkwO4g6iB*nA?<3W81OF?|Nh2C#vk$_NJSH|x73#7yg$T#) -zO|vgu$h^YAUIgQ(1Y5LtK0GA0P=fw%Y~Gbl&88Y#rRLPQLLE%@>d>IJthMA*tK7b> -z=w7ImmnRA&+tZXdbof2pLYawbfv%~9mb%{8gSw5uvLk9k-|fv)RB&wBcMs=YY+&)b -ze)QQ>2s{CMbI~}RJvHWwGlC$T8`bUVl4~Op2TX?l`)gSNz?4>GO -zTy85JKV8#^u+e(6AnSzFSvr&rDx3AC5c+>wh*#$0KRkAcE6tmEOsGj;FJH||rq9rgvL6t; -z2dq`Cl)gJxx-9%|{d?2Gh@@GR2YTjQ%_8T^wa>5uW39`7`|6FgY5wZv+pk__Z@+r;CDpPTPn*yqlj*

bJikXxSp-9W-RW4Gr(kz4ZFhOZb-Dm-LF+5E`#RvC3`whu?Hr -z>ze-l!pocE$G(~OYj3{D`)giCzHUtsPh3_A -zKQleTHv9VZmv-~^>o4=om#^P^^`Rp?KfGK!JHpLxU%h?(&DSqKZ{%Nac|45CVP0RE -z>P2q<_J1^R6(vUSxBq1Oe*iU)+-Lt|vmruczQJ8rBn%;R&xDU${DAF#;ty=vpZEi= -z`avJ~ft~k1@#r6$pZEjA{-^%HPYpqt`Kdqf!yxE~eZambYWKpG%^&n4eJTs!59T?# -z(COF{G2`DyPiBap$|wKSZT+vR@V|@=wI;k(jqg7^Hqpbdk9zh$^U#GGUksgX!q99N -z+VyAW{`6u?Om+UZU;F|39hzfDG;)u4?JSd@t?T6h({L7x{{8A!jKjWmV8kWX$@_tB -zO_2|t!5MaVSpT4wRA#+ihtc^cM(``?p@sDxnqBh6L-$6nM@^kEAH%phS1yCxxkWwX -z91Mz$=0E9`veNe_yG>U5{$v-)O5Z>54*6kyf2u2FrSDI6d#v>R$u5qyzJEeE`DE9{ -zTHikrocv$kC1;L1P$0jt=;wu$y!hfp+SbPop3~ez_u?PU_I3`n3S+c3+CR-#{_w?% -z++Ocj=4ui$E7M -z@R^INya`vi-Cmp%P3IrJ;|Z-p4N(yzEwI}CM^XhJy)x%!Vj7FtsO8IF(5%+UvMI^7 -zN{~%RysEh7?D48V!%$@>02p#kP5YSacAWn{59Afz$rEHu;Mkr$1|7 -z_!^e&CR+Ni>&xPA|DsOV)u5tJU+~Jjzpyf9g8&h41u0Fz}@)txE{puQC-T4}Xsl9w -zRXjbvJq&LD<(npZn8_!;c)`B1f}L+G_2HFDb}&0FUG-y+-e1<*%oc+UnBfWig8gfL -z2V=nY#cvpI{RoH|hqFGBE6~*?&B_m7&#UO6xmxI{YoLYrE`R*nFaCq*M~1%sB(jgc -zETB2RJg#j1_7mLdfBDPt^rCrsaa##miSC6P-u&UB9QT*CAgQSn5PR_DtU8ww1Nv>! -zzfZ!mzlCmi1r+k|W&%6?&b92r)1N+Y_&%(*|0`X_bB0m8`n$&d!#|%_WBv86 -z&v`!Vi$BmBy)(sc`*yJB^!b0qE2B?e+-`GP1I8l1==orY{SyZP;_*Z-ma?Wl{xlXZ^+kAFCM!WPY}qBGJ)A=B+BY*4Z+%-s@#o<#(fOd -z*+nwzVi_sbhi=WgKYT%@G|Jy*h684P^VOGMzI^%W>(?*8e)aOro7Z!?dP-pbh!1`H -z<+oqH{^l$C-Zw8_e)Ia(5BgAuM61j40AYCN@)yX@x|K9KXIFGTaU -z>ym@;yyKJ!82Xuxme%zz^1PfeWzjq1&(HtvbH^zd#G5<>pXnqq!>@loYg=^^?@Jm^ -zE908a{?lg~>q&yw&O6H80j^PJ`$CMTSIeT;%c3{SqPNSUFPBAMEsMTh3{nH%Ni-Ub -z_lE#hku%+VY7cCt+1ni2$-k-U{y&PtIwA*)v-)SRny3E*4*QF*@sepOwlVNjze!fs -zZ@qDPsqs}kUh1ja$z$R{Gx0g#Ce73#?crbRt{1THaU*bv^HNwaJ`sTq475(`%x$cZeSQ -z&SUL$KJt%y_w4vi|6}HQOWk)+i`o&7@i2O`os0u2)_%#{|8W{J_xnFaF>}BF<7VJM -zEc(DH)XpRI#V5{;-|S2(PSp>6`~9T>@OQ>I_Ufow>> -z`tEhKCfvb59rzJmx>^Lb9?XILi@$f%=XtL*tlwP!;YGi}ipqkb_l{MXA*-pS=R*$?&xkYS&f-Y1$s~!6# -zB6j}fu5O+=itXjvMW^wLAWY}qlu0hT5#a%Avd;zCRHeaO4vMd%N -zn!2B?pI$VfOYre$IMN7IUz9Io+<*K2w=rf}Ld9&;=;oy95R#=f%_4EXg7nR<)<8A6hD-6) -zx36BU)DS`1A%TVB?`4CbO-)62oiJ&$%91mHtd`sKY?tnMrn9qeNJY}xZYcV0r2$>} -zo$*Oc7FW#1wur>PHk6Xq>|m?a<(yW&kkHxPi~lOc{}&a@t^LCL49Hs*Vh<5rn!B~b -z$LXa7s$b9|%|BUzE@Gbepkcs?56tb;eA`S>x6X1x9&VInFLd>Y5}gB5QP?K}L0P#M -z2*|H(cl~^LL@#&d{h84bqd*%g=}+}@D}ctSjMzPUT6HR$9-)3DV&)5`n_tZL(bC~L -z^Ysk0oJ2>q7kbzc>rsR@tD`L3#`^4u>n#9JO*>rbq~Fy8v0_<$zR3oW@#->N0|QXa -zZlwZ&Giah3Z#uVT4WvhFNaJI-Zg!}mb2B+5lLIk70wc69V0#9}6NsGx -z=M)YnAQu7I8L(Odqy^YqxZ`fH@JoY*Uw!wDV4x>uHk!ZJ&mGjph^0AR2Qt%IUbKBe`%`KItcX{TH9+3-dm^QSmfXCT@>R -zX={XbH#CM>lhuR8{n@sDK2N*?QLMfTF1dAk`frWCw^f}XVsD2Cv9rp`Hb!{s1$yY) -zdN{0Aw5D-65a>B(@Ojvq$2AlMq>h*C -zuWp8_a{WU$@6XR;j@`rj&d=Ls=j)oqcM6-EH9iiDoSA{ny?tosUqj{2?1#&nGpFq1 -z^=&SK5P?{Ax&C<$Wz+idq1(;@yKv>fX*{^a;YO%h^lRqh3XAt}n6DjAH4sz}<>_*L -zGhdIWem94i$-58L^GbC?w3%t0SrrRV{T^{D*G^r0y4qpoP1BlkjSWZLLwaFh7sn!+ -z#&&+1qB^(s9+Y`d56qV>KE@Z8^l{G7&$gpKp076-xB9`&;m2c)-6xQrO>U>?KAPV^ -z(>rH*%gpYT*-bLJJEGfSc|Sxq!t@T9Vf_i7A2ItJmrt?x6kjhf^a@U%VBsm=onqP% -zw~p9!{N5B}PH|*}6=!&Gg83%6Zid~K_-u*6R&dr7OHJ_71QRWB&l1~A@yi0EEOE#T -zYs~P(6f<~SFg^X#^FBS{(=$Ci#nW@UJgL*Oxj2o{`Mac -z>Fb@@T+2Y-4&UwY-45T6I(+eF&RLD9ZkX3?kW0RrQ|&xc?rY9^M1AFR#xET@TG@x) -zoIAt31(MIafx7LSxB3X`c(Mr7Y5R%BQ=3j^9DY3D=@2gv5I^U)Cx>cr>h^@}cEIF)fRxd=Ilj@bDAJy;X2Z8Domn~#mV-<#+U#MLHxmop`2ANhbNRDasLUcQ= -zULgOmdLdP&Do0zpM4-N6O$FlX7Wm0mEKs7aSl}IBu|PSGbvmI1SE{$rn6K$LPzeHd -zd8LG!#dR{)tc+c2R@PN(R?t{nxsZ%Ul`9BrQoFb!CbbK>_@s6rQ6FpP0`pL_xa>mB -zW{&^InzJ)lXu?9x;`&03aEnWEzOP%z$bCgMU!~(o7}%yY#5yl-!;iQD=6Eo|d^23P -zz-|kCw!mOZoHfN#6TCFXL<`)rz%~o~vcM<{95TTg5uTW0h6oo#r$0LH(FtFi>Deic -z&ux5CtRIp?hAywsd~n)6F@4(UUph+n|$ -z!wLKZ{ySq|7Z!HbC^N0KZI^Uw4CqH)@Ns_^QShRnFWMhh#^Ni*XYfpAy5^$bE8l2zBcEU>E1g~#(pu|Z -zefR2>2+`X5;QA#`>dWV3ww5p0IxSxuSzo^JN6_*){p`zE7~!aD&J&GQ3krX%TCkgA -z)q;;2s}@3lP_^*t2^CApKvcKjyP~><1zA+L;K-u7g;XG_TgXr1x(j_SgaJ`qe`bCQ -z;MVd5@2BN!){`h-oL66d1{}*Xo3s#zX!$~ppydm`)|a1ae`Y)jse_c8c1x$&Rc4(% -zmqux8I>~9i1f!3@72$x7M|wQOUcCKhjDWJCI@bE#3qMoa*QJQnI4tt;g}wW -z=>eF6{1nKiU_AxoDd>*CI)dj2kRu3=KsSQf2v{R1jleU4%LpJNNSpzJwQawE&2Rhp -zm$;vF-%6MS(eL+3&6%t}2{jOPb_s5xRy3M7+>{vlzAp3Ht4rsDX-#^p+kSUiy+50+ -z#vfT;`%%^97gxN3ZRTjm)v7-#YA;l}3^9n(bahCR+I4q;xWU4kpfZi^SK};vjIrr# -z5@1PFHZgX0uhAugl6xEapC$?FFd%i<$<3{acaY9>Gvl`LIgIp*F~I&NccU -zL)+JNk*=Sc9}W+*A)Oh-?6Vg(=HvRK6_WTTUA^h+N@J$c45lX`swCugp}sm_*A(aL -z9-Bqo$HaJuhB+T|JjQrT@fa?w?vL)87FvQS^rqP3&niaxn9E?&-=+It+K>h$u96-| -zsLysNm--y3H+=y=%*%})4s}tTmbGTz52k>7>hmAI_{00bwx{3D-tWzBcs$>KWgcAz -zXWerRo-OX^yARJ? -z+x_ah*WzD@nEfl^fNa+%-qLkGNUl^X*VQ_CaT$5280_91-STspDP3N7D-|%X-4v(S -zZy>$-Wg)a!!L-?uhj6IASn_Bkm{ksR$x -ze{`Pu(p5umSJ$;Xxw2e=yviN{2O4f;cB3ifBMiafRoY(H9c)X$?+DUY>fIwYav_ML -zii7EGvTsebw-t5KIhZ)Ft`ls!*E+$+*S4@jM93QOvxt}CqXjHQKy`yd^t2}IF$Y_$ -zL070*ksJD&Re@fpTngv3YDM%TH3`F2x7zX07G_8ZDGu$*!4BKBvXAlludV38_Cvb@ -zI){SbqF)=Y)YxjZuErD>3r)ptZPTcHbu+2FTkU7C{K5k1a7DLP!zg)eNq01++?)WV -zt6)PQ(*Y}XOKYI9Y9VJEt3Jtnq!-HU3YlIS(`zEW7^16Siu@<&UJ-nZ(x*s#g0@FQ -zJ)-6j8Bfsfh;U;R>yhdz8jTU>h$`pEae)p;1n5!T9J?*?S%kqpfU`y{HR7ch6UDeE -z#x^m2i80CyhfJ_Wj3?GH!yFe(Pyh0~k0(4n)6-KN&h2d-FzEZ3R}3xl`vyt)q-uK-ssA)EBhI07VjXizcEW2?2lOYc+it! -z#siCoGJgER8vmGHX+1@rZS&Tea*ZqSH4DzgS1e&&D;8{%uUHtNNR?bB1AMpJrS~r(8~;=L?|Re8Y7wrh#^J= -z;qZs!9u9ao(%}$?V;c@?IGVG=I6Z#T12;Wl(?c~qM&rR*J1XPh7>~nv07j4>fqVki -zBOphhI|Azno+ChBxchIAc7wD}328(Rs@fekTAzr&_g!7$??W3J!Pg^ykJMAu@<#kv -z_Stv$A;vefqtS@gzo@fS6=>O>t10UDtL4wG*||>?fHr(P9>@6Q=vxwh-JV2nwit+j4;H?z?1fIytgpMBOC{UK*;f7G^|n6WWM9#3xIL|mgtJ!D -zdwrctE`jw9=nHM_alx4;E{7HO+3LD&T{nTpGYwA<2J-z?n5D{;cArkqdSwL*P$U4f -z>-9%EO5+&T3@TDv<&zfYMwzx*f4I~@4pw9B`iJ6^wOWtld#_hnFseizM<^S5yS_}e -zxqunU)ArD3s~zw9jFUHBcy~8!ceDkoxq!-3QCC}-dv}+X2Hii~wq6~2Yh9Jv)uahj -z>3W|Q=uF?8%`R>W6C&}cnhS*OQ^Lo#Kg?^6yrt*Y2C{KN+;pb1T@8)oa(dc?cAzEr -zy3nG}WfRh&!)ngWQfI9$4d<{qss;Y?WA6v|gq=Lwhpbj%t9mv2;9@b)^fKf`_+7q4 -zx}mj4D;Q=jSi!RQ^Pyavbl(mKM{k_}p1(YnpVJz@@+;FmBBEdqPU(FAW1G;?`L^}( -zch^gZ1dKC=}-NvP>Gy!2R -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 -zG=cUVA%ELE^m- -z+v}RS>zX}Y*X+9GO|M(t^mJ9T?7HR6uH4k@I`GY|+lK7AZ6KEAs%F>iR(9Q)$*wyy -zS#@2r`oYxKwoH%Lw#@cdw`{L%>8{*jYI)^KavYf}TRt||w%lKb5zzALxfjRaP5VR -zrq>>e^7{LQZ40h$di`!?*H0_IegO75YMGl}58C!hB8Hl7uA|w|bbCDzZm&OE+v~@= -zyB6P;50BR(GHQDLZtbsMf&JC^@u5hsZ+bltme;R9dHoiYPuDlSeyr8C7ZeA6xw`4~ -zKv-Q%ZIusKvcVb0(YU_pwbwp1y?zCn>*2Mz{%keZ-v!O}F!28R#cHp|+THcNcGn-n -z?)ok0uH_9lY|^z&`|GiGfBnVTU%#>a_2_N5ehY@{!R2uM#d$SJUBWQ@s-{=&)OF-d -ze%<);>-Idq?jYyap|SkBDcI|#V6S5}QTnE+L>%eq--AuRFVTkQ@*kOC! -zOt;tFV%sYht^cuYvpQ> -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 -z?SEq`cPwMKUn?`n`Oj&gjFpD}5T#x! -zL$_ulvtDDje(gs{KK89L!*Rr8`?>8qz1d#=ikh39qqd+D#xMjKPl)Y1V^^e{4(%U> -z-tWAxf&{NVRuAJeGU#)!dH-R@LL&34sa_c91z;greaHS)t?}QJ;k(T6pavWnEB)VG -zySoh6r>Gp#@{|I)z6j&?Un{@WkDyE*xl878X_H2d-299OoR!V8~di4 -zpC$qd%(17y^!|;SIf+!gq`fKn9GANPvM1iRvm+g8$3LeB-EbgTnd5Aj -zbX5nxyr(A`^a;n2BIG~5dcX^mcv%tzBul$@Mo>R&PP$!C08bwn0Xu1Y>jOyEdkN1{ -zdu{IVMh$eOm!C$@uQOur?>v|lALMpX -z&<$N;1SD_Yi{gOvN5HkjFr4?fw-)gZOgiQQ>-xYI>9Yk*JWQD2i;tFVHw>LF@Swt^ -zAd~N1ngZNa7F-%j*m9D;xvWPXqxSN6C+T51xvSj4&tQ55;~MMru1&$=$p`M52+oax -z(b=<)cOM*w6O=i#4|gqEG8Aqv$(%g2IQJ4d+CQ1NYx(Z|OKJrk(bMz)BEWHT^$)-k -z;J~{M)6+AgQjkOSY0XISUjZScr%+1CIpsQ;e?VX^5SjsF -zK-wpHEWs;QaJ?eif+{;H!DuEq_ZFI^FigGk9Ce$A*4iO>wHyAA^oG1irx{Qgm;y8j99eFe`1{7WnP2T1u@2&O;| -z6s#2T4`Y4-jt>L5UA;$p_Ex>I@EH7OJ`@3lt9x%8klBO6n3&-5060GWpRws)z%z+3 -z;Q7Q`YwPk~W$5vSRgmmquw(i^dgWa`{T{5|=(qZ6tzE&)4?@%HMmw)h+=n6km;lKX -zJ}`Eq37dG@r%@ -zV4->T+13M+ht|wLH0rj3%=SMiA2)!MuKy_JxAiJ#u#Db*WhX!X%jl{fSorrJCFiF8 -z!^J-y(Q7?q?IknG?KOJ@WDJ5K$h2%guMU;k5` -zle;PHE>AZ1(x(HIkrTyKa52gB`FzV2<@zs_Qsyt{Yjxr;ivl -zFZ8nA965iwplWYv~hjllTBIq=2(wGNvIM2YX?dyNuF6qjOfZPUz*76pwgfWFe|@mK*u$ -z{qJL8Rn+MB{_Yz4+0PrX2zxyIynbl;Cq!pE#^9A#XDi+A1V0QgHRfyZg8i@xIbGZ+ -zkM!jx)D)!&wKS|wGJ@9w?~pC?FNq8PWN&iz(mRI7<%p#ZSp_FP>Y<)r;N=@oKoS|) -zS}%)x?(6N_DS0-|$`PXT>P~nVOd8|OLqSR7oIcydxGa})*8SpW5i!EUjCdLI9!4}R -z%)O%hCLh;#6deB>^?I~@RV^G7x3enT)7P&SOBg{8&PHyo{;O~jcDtS2M(#{CJXeq2 -zd-@PmGE-bj`I&TYeW;WNnkQJ3y;3vctXjecbO^phj@HKZl({&~UvMGq2K-6iet5;z;X8?uxyq3L$dcHgkRfeQoaK?B^ -z>D-Gd&&aA&j8R&{2Z<}>oP}Lu$o1Byu>OH(Je3$uQZXX7*`Spby0|R7kBx0~O+5*n -z$QLTjIJe~xaoWb%E`KG==qkSI)6iCtgXrAl`%Sfwi7eYadpOvP-D1IX{vUKvlIH{?Ki;;>V!=uF5(}ysCw8r~%e5a42mymM+uRi`L0x(KbE0^pK>yvUW^o@(L$z2keAUcY -zIHu{ZgK*Iy8cO(TU4<^1al7&oQH>x8r%o_$>&rQ7@hU;#!ddTUdG8{|W)__)y7;969=W$8L3RTZr+&6L8>20H923XVF#1qnfhWdXZ?SjL -zA4}0n^AC@nWb6%X9pBDFV^|ofu^%E18uCph7>!L2`$hn%YrO4#xef=jf^zWd=|^0X -zp7H^9btSYl^&|`EiJ1uJcDAHMDL;rqk)!H5S5z -z_z-N&6WGALqh|6&=s)OdTmb_pL)OPt=s?Bs>rlEeL>Z_ZwjH1qNX#s<3RL_@`2JE-ZN(PrZbrMcD>GUjX -zt0XhnL_Lmuaha%QK%8qtbm)KZb=#mp#j*!_ -zIJ|MIdN`s@pS_eJP6{h+^?a^jeC**Fz6mp2vYIc%ZiWw5ZI1t2K;0MvuQ$cDHaFP*G2D -z*yX^3nxvseU(v1-@yb3MkTg$AfWoIS%EL$!!%(X3@)S{PKb^RcZ;MlnHWZswI!WlR -zHfR+lh2U`=%wPMVtDe1mARR00$}R%RcA)!wcVdW#2w9I6V22547gY@zi|)u4T4eB| -zQLfCGXdxO&vJk`?IiXpIPMsEsNd#V51ui`x1jhWyK97wOOfPr&CFRAi@;U4$)oGQd -z|KpQZ6M`;uQNusz?{UWRUBRLqq996rMdN{D(24iAShSX6q@i28HO<1%YKo+7zHphU -zRuO%fg;ibh@hwhCFh&NxOp_5D4zYavuR4{Ll&R?}FEi=&!xYV|GmDWAI~l*dtvI7T -zJ>}irvnubqG%`o{(#h`RHsk-xG*@=|gjy|lv9e$Z*^z|r%ME)x_}OePMfoX9l?DM3<#h_ -zczNVh_E3aBVRqL{snwf5PU=s@p4?6)uN_(C>VCIgZ#B=srMJ6-4J8dy;bfH_{@mV^ -zZt7YuLWcx~$AL$NS{9=8+=s-imQj6>P!AN5dVhJI$=5xB5r>Ln-IaY#O1F6LOr_Dd -zKhuuSlb446?pZ4B`dO-_(dW;;Ng;|$(QGc2(ojM56`PxQW(%Dm_Sj&OquzgGC<@)R -zea1={F_1mE?|e?IOOb@^C0EbZJd#;b=xs$EAqk}?pp0m4jDUd;0o1#hfD!6R6^qg^ -zX*PB1rw20?fS>rTvG}%jhnf7|=6DPLIO$!%{n1rin?|d2rroj^Z%NfnJ7O9e>7uN~ -zxopFw8JxTmV+Ep|1OaCYwePD(!=7VErH%eF?gEEbQGEWSisNkHbqD>xS2HC-772$@ -z9C3$hcg~GO4AE?r1MfBGTDFEQYs+~LO%`?@e7cX`2v0? -zvRz}c8bFEji}#jNmfZQz<*#pq3xwW+SMrl)Ya=V1a-TVc{4#FMkT7#$7AoMqxS~n~ -zl!z+wrOxZ#14hM`l51v|c7%Bj?G2>2lT#CK6vEhON-J)HQ)yNEn%HtUZJ9}rw&5|l -z@*Vz$B=N|S5lU16#V4gAED-k{BErVGELa#kglt(DKCj!cFles(@iDlgFeOxspNL&9 -zBIpnf*p@CX4q>QH2@qSEoL-FMx!F-o?!8X*TjH(BmTzoMh&m(NQ890=k27VWzRWVS -zolkY7FLJf1U!)#6S5a+%J7`Z8y72$1+?Db=I6D2F_i{P>U{>yXJGV1BECA34?dS~& -z0#c7pwxz<{p4<(Y9Ak#!i+%WM>TO6ABCk#_p1)=-ri8V^p3bKP0XkPpLl|>o0>t^A -zxkz8K!^8Sd^hLL$dm~s7>6qz1mtFC6PN-B -zOhaC)emug{uVaYuxBBwcrEiV_q?@}XQEA^4+#M|2c1oD2Qg`)b*$2+IW}T_)hr3e0 -z>AAB|ZEX(FXk^`MJmFwi?&-^-akg{2{KhEw!X!UonU@`i1MId4Xn!5(_de2>l|>ZO -zrm`evoDe<|1?9B+vGjn?w|0kar8nVrVq6cgBaCHi^?GT(jD3bw1@T8zF~AR9;gVP}aYfk*HAA&x8g -z5sv3~_X9$^p#5Dp2o3?2pU?iFI|Ys!rJc)uxmzDj;tM~!{X{o4oEdU^_0WswbLG&c -zCx6AzfTy?Ba7l2x#clTSPFZN~$7cb{V2~Ds1dIskp?FZLRVk6zApKMp+WDJiZlPW#jdUm -zzjr;C{Ha0H);a9NX>oJpgttK$*l28lk(}&fm@A{USY -zvaluE+wgd=EQZ=s5B*r)C*WaCkM}Eq&enSc9hI;Ma?RAS8THi3r;DaxJ*s#s0!tIL -zaA&RyzU=yk5$d%qL80pwX$DuDz7ksCkg$fYc-dIkWzK?feueVjRcp_v&4zStIwua; -z3gOM$u(<{VOjdaEwp@1xQA6eKybW0#LDaVlcivFbe6I#Rjxru)3wMr (!0krF?S -znuK5+Hq)84wlK{yFD{OF*RvwdnN!^w<`hRm@adW=5QocX4;BVTv`P>`#-eVDzp%vShPb@~wIQR?W40oBj?k&Ad@4~oFh=57GD -z?j^Lr!_?|*00FUG?wHdc9SaRPgyj)uQXuN~fF+A5g(g62lPO@d>xXErH-^t!+e3?@X6(tp#Qaw9WM>6QMhAGSTKe3@)WR -z@7|i#t7p-tu=CyQn9WrIKSZ;MZy+~xZ|XWXB~R)Uw|IAIFVupQKi&ed#R#m|KP*qf -zTt!CQzaya*uIIM%CEACup8N_Y8m$nKa`~QtQaGLayd;&G6}cpplts8C6`fVSDD?-< -zqdMU~zO<@@OWu~sgdN_ciiCOIhw_ABUcB;zR$iX6gmPZZ(gXmnM+p<@jY(c7=4^_W -zc>JVfBc2SK(Sr$nO{7U;jS6eB$YZ>O{`|fSj*mwklkl}kaA)FF3esczkL5Q+WIWZ^ACmKYTPwS -z?(*%XIBHYl@_4n6K_9#}CMTt}h{nBrN#6j&`K}OD6K=)k7|k(+l&+)2(wWROUzs~@ -zJ?ZNMQXoQ1Fac_Hb(WRBp%mw(dCFK|-ANSblId_Z+hz!t6KC1EV*tzML_agenyl;y -z4B&?WkzT7A3G3rZ@#n6F(4$TQaMZc!PxDE4HWqzYwFfW$hGL*&t;r)}tO8>&^TN16O^QDksLFC(9TVykz -z;ln;95^?C8ysJ+tu88p4$`iGK`GU~I#lww4xu5up@WhJ|M -zJqWZmbAg|Rh9u|hhcF~$_H8D?ige^b(}i(Kth{mR2-C8XxI#{d%6sswv36gezLt%3 -zR~eJ$LDL=kzntGU&0ib|^Ybsh%kCoA)km^A;OM2d9hA$WMy5uHjuB6kDme3sDyGvs -z^HDmRgT9ClFDdL>t?N;?Mmgf`(XNpq?Kgj=G82e!Obh3qv0=Qsa`_sy2*!baK6L}F -z4;Z6@KqHdKC}5T0+CchU@sL-fdF -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 -zwL> -z8W|P0GWBWu!pP8iSFI9en&tz@e8QAuFEZV)JRM5ea_^E>D%NjTH*-Bdx~(wI{i-k< -zbTVAUX9A-?nNFhN%xGE9MPr5$k_{gYSf-^02mVGlHe(kGc<27$-Qh!i^~tOGeOlu# -zt)*#!qUoezt6*mbv>b)J>%mP%%7a)fK$Wm? -zJ+QV)HbvG(Pw@N`gz*B`kR9w?Dw!iIILJ&oR~_(~&m&LpqNVxvy8!1&1g|vJlssAE -z5Ff#16qK#VvOZmTt$mRU^!=10tJMR`3=7jjE8_RLyLg@h@(GUM;?+ZAE7fA99W%X- -z)6j8ft$>X3Y6I(-K$5m*21c7?t^N_m+5Sj)%wO{+Q9`xc00;*fwLhMjwRI(4Oz4No -zN~wgR;1BNB7&gCYbh4aenpHTp*9^WF1JEm40;b#wlBd7I4gtM6QQK_(#U~m3!0mD` -zJJu+xL90*HZd~gc%q{csqftUu6GI;wv};byR-}9}L`VAGJCm%Kllb5B`45)A{|#korQXFwCGC_4AK*vHYEH^K?tjFfdcf*(IBnjLiM#tY=J4g{FZJd&cjH<}C(qlP6+7rU( -zQD({s&GykwNsG3-9`MS^!Vj({J}4aP^qE232&qf~0wxV+gq8Zso<=&TSpzEVq?W)Y -zvs6Y@bR4+A5$@Ev!OYau`(;-mrfIdl@|Ie&?-cKY8wBT`9P8fwB>oR8BShO_5S=@mdmi -zL(jt9gWXMJr|J*X)}3mB8{C%u8YKHRZ*ppB;_b)kCdJx{c%bhVdB#qKgY&W+HjUN4 -zy#v^E@J>7DZeh`A$KGFh^@4v5!}2pP)vq1`;jQk`A1Plw1VnjHqY2i#qRK+pT#q!j -zuMv^17svUHWB|C43YtL(MeDgfcMklA{YuMRLnzjjJDl}pQLvYbq-h4vXKuMWzs;8y=ozsWn56 -z!ZsS~?|+_ZV!1{}G_vaHreL0aGE02wgcni*16%JUH;K*XfGT@&zWj;-d+e(KUcNa^ -zgzy8&C;nVR`I}+o(XC(PgvCBatF~%?2rMixRIT&uz9g0MOjwi?PaOTwn?9&d%N=E% -zlQB(-;rU`zE*|L|RPvKK=@FRsBI=v{bp{xJFS>F%Hfz-5p~nlr`J*6N0VMVcbD>Ya -z^>NZK1gRffsXlrq3I0DYs8FFFesQ*yqKnk;tX11lrnyz`Xj5JS3I;NvRjp*~{bud> -zier4})Hw)fcLV%2VI9A0IX2Uv(ph4RvtU@LOLLH>%ka6)L=LH3)SVebmf!R1#R_88 -z&02VTM*IUI-T9tuRwQ#;>)}(=-w&Jb6G(395NhaQYvXQHy?SD4+yD6A3s>!b8^Z_+ -zXtZQK_36eZ{4E>CVoHm=w)TU~*nd>bc8;OES7hN`|M5quaq0CM1NuhjGq2eA249y6 -zn+1JtTBJ~z$|||=3=J&mCRhwI64jtA7L1lRHxbL0dhuHX87&R7+X}2%1)mmN| -zlAlL-qE@;3e^2fp(ZkWkOG*xqRda`w2XLZ2XC?s?(oyC2k&cg?JL)3RGainwv7-6&kj*gR>+ZL{l7(_-O+Q!5yq{{({S8|yQQsWwYl6}`nNTJ* -zH@0QognvcBK^UvR -zvb}@pvfLsx!tu|3ogogVV08Hkk__x5iWNqc%QHd1puQ$}rp0kA!Ic#3tR2l=SI}duM -zw3F&fY6UYY`eKKh(v*7*%PB`Ezr-2Gs%k^sxgfNYIwJCqJ!@JrNjgCly4DMs3ygN7Z1M-=BXfoeoR^s>Vf>;TjJ%Dl -zf`_h2*Cdmx*<+=mcfV}?)!}vyj3i0Ez*iIh$NR(Pzls0+-VMK}nD8I_LxPEa&{D{b<=aPW0$$=fG=c~4t7~;+C4=cLO)BZGr`&}sm-Pn? -zD|JGfy4@f(4vX%+`^weJP~Ex#MJDc_T{XUU -z&)5$>=MhBZ#X={qE71uzd9CVeAkYNSz{x8KD6 -z(z!HzU*55#LoF?WAJoac6En2^&7^T!&25{fMS>hbGWLZB5qi;1i6x;6wI|ac$O1NF -z7cgdIxLJ{_ktH=tgopJswP+;KWXWtjkl@w~WNntJSWK|@Y&+DH(9(K(%tv2}S7dRw -zxfE%_osG?*pnmT;sN%%A85+;%JdHu6`$B}9a#>APRQDv|zsrHuhz)D%M{1L;QJZX- -zRVhCs>*mTD{zmdUmY+O-te=@l7ZgtW6~$tay5;xdl9URRV?{#k6}n)sK>vASd*na7 -zK{TOvM<}k;|M3R&k`~KtT6|`Q8^$H_LnX>@{^1c8Woeenrk=bAq>SCE?f<^&th|%B|ZkAlb3gwxP7R0CRIP{lG;TT -ztdr-=IOwn|7IT*_NrOyp$oxJ}jLRIQbX1($%|5SduIl^xMZE-Fo3;J-P2bblU%5F< -z%3zkZ#-1CmQa2Kf;V**qiqg|#q<2^YyJ*08(~;*sVCc=YfpS>0aI0*Uj9v}G?%AeV -zL-8fg7Hd7R>d?eRl!8+z)aWgLm#AeNTWINeDT~N<-8{yVz`?VDI;Gwk;cX$C_{X&f -z+CGs!hS!Z4VFff;V=-q5np(|vuSSp$`|7qEW$mH!)dz@3IqUg3I!(FMnqsbuUU@F7 -zaqV%o3p8y$u@THG73Iak>|6Fo0m1LEnY755&l^=iX7X44^6^!xkVdS*w2^wmW}?0- -zT^bh-@2q?mCr_+DP*QSV>u=Og&b;_AbSu~8`}PX1gR~RYK)O`fD!C~Y^}QYwa#!|K -zk9@A7&nBele_LBd^$+x;kAQ{^rHT;Ol#`A@?eA -z0#-d2Ka0KxRH_zxYkl({nVlVZkI)PPeoY6bJhk;suP$2rxusHrdTzk<^hauNMLyRO -zbH43jP={a}((zi8P?==-cvgze#k}#>#;c{MO~khgkT{OqJT~F4C$;{d`ZMcgjoF5W -z@o5~pvmIe5r2iQE5_^(3^aV=dCG=SWZW}W3MU3pE=P~Q;vg`RWb_)H9ndXQtnV -zp&oDM?za8x&{_crLV_=$^@x!lVoXPgU~q>g;pv=_Uls97UJC9k$$}^%qu^2oW3w{) -zlcVsv2+M#g1s>qZqR;N`!-)M_!pOwL?@f2cgD+nDhtfU54d@fJ9TF;6am5)o`nuFV -zn)mfvcVgM;2EyRRR3L}H -z3E5Ag4z^R$gD)wz+Nx3P)BafyA4Cx`zs7u;FGISEQCCmhj6R?03ma14>7F}ERp~+I -zZehdhsr|nTC|k@n>FB2*)@!R2hN}W}w&Y4Op=cHRwNknG9mIi*s-;|JAA)VqxGXvD -zENLwpcs`MnHK4dZxc-&NxjC-VRNf)U{gtI&RcdWTF2|Us8rSku?eCz*ALoV|vZ9u7 -z1yzFG@RK-;%KV#=^dh-L@w~L8;9;fn34^y2;8RG|*$wb`bnRm&v%A|jZvQ$#<}crh -z;36)TVo=>rF(o881Y*{TK&Qv{Px2?z?+ss{beJT=!3#Wrtrh8W=O30Y^{QZNA%3oa -z47q2wuy%XRH1g-}*E^P_DAf_8Zy+7g%dNUy>orjap%|K#Q<{7XN=E8t)i!cvoJa3g -z_vBdIsqLOmo?59JA#umk#>3L4Wmx)Qt~COwm*P}q2IPl7dNmWmOf&lUHXSRX0pHq+Il?h=8fKx9s`2BN -zk}S)!j#WDbALGNx9Tik67tf_Z!!FL&T%s~+nmkJ%aFI>evtD&+Z{U`{Ok0g(LP%B&nkV&nmAIkW;Z4)KEH=zmCyvVZKcOh%(Qp*$6Rcg2q>8fF~` -zWmyFE+LtKSwjngGem%e&|T??TOcNm~^IHA09RNYZsrjD_0glMGCvJJwRk6~@x -zIT~h|HRT%GsJOhX#S(dsJHo3LrL~QYSV6%+;Lz1dtT`-d0{d}JeOJAE}LACnMu$5UNKxiI9gRLhu(_*LgG<447C>pM$s945Az -z)X`bQ(^wx=4_2YX#`Sh6AJM3~(Xw#soX&nUt&qa2`Pl;J?a^SVlW#;>pbcWSzTx}Z -z53K)lq!^PXYWXHT<2Pz5HryE~Hc7M}rkuf--t(Fb**TEEZL)GzC_>UF%Xo9eey37P -zOoe??_;gy%542zqGurog4nl9sN(GO1BLOc0<*NyrD$D8x(5*Zk_~|{4L7G9o -zsM7^FRskYMnbe9ko?M%5`?6JZ@{w62>I_R8^_Q}D^yG-KVTHB>t;tTqqJInO_l$pa -z(brqlkMv~`+AX-l^3N`}tqr6!ysTm&@?Qzb8LK&NNID{(PBHEzzU!*S`q%MBXq389 -z(oaDTb6iVp)zQ@X>s=T_;{kKLquNK5jY3y&+$mWJ-h7lEs*Xv4H|E4i;UR3F+57SU -z2dZ$S1TS2ffX~2A^CGs3*7ep_9E*~wJRP9=^1W-tLzZ=JxX9Cg1w;0H|4j;h -zi0&!>Tgc;&Cp>Vz|Fp-)-Tx}}CB~?F?{AcDUQ<=Z4j1C=oJrwW)9IUfPwh-NoRK=* -z=bJ3nJRO&?G| -zt-OT2qSn>jpet3!&*L{48rGl(Ypd$=Gms6A{O$_s+B5H@l$%9o8N~bwy3lG<+V~J1 -zeM0T|gi*phlyU_7U(szNu9|Cr%okYb_PjgGv_MOKbACGQ(`?&aj;1n+p|O?ANAl_= -zV>v?b8#Jq+9ZImcH -zhBVDU+ckwwh5@T%O9}Xdvv?=1(ZV(rT>~~cJwYav2nZ#roDm~heKsDG6B?!+#>s7? -z?9x|xXuGeaicmr>ZxdNM)fQKS^4q*f)jEi-10~PAqtDX7oG^(w05%|91S$6nm@j#? -zFPcDash%I|FoRp+F>{)KW<+D-{Xy_k8P%A#88%S&f9$qtSHm>b73kIn!CXNqTlTEj-d&ALTk}_s?&}1e{#>M1=|B#R}Eh0;_ -zjyx)mJhaQ03}TMmI+#0z*<{DDtSo8?Z?r+Ixa)gL6wRGFR$($f98iqH6QR~vX6)96 -zGbx`pop&0b-yQV3i~Ue6Vm{ik`>+(7^by$_iY8XcbH^24)$3Y(KQrmUhBO3g3v0ay -z6WS1^H -zE#=LZ;HDx8i!SG~$@i}xTMG>%LkWzJy}2lVs>-yxE@i9r+;L=bs#7j|(nout|0u0g -zNsVQ3%~M^4>Y?yO_qtxWL6-1M$71P6EEms-YU>)lM!Nd4>s(V=GHcZ`>vYK6NNq$n -zOT~@v3nUXSUbzFC;53>n4`tZ}^n;;Pa=q-PhVF-I9iszRUJ2+tSkzbLD9Ujy9)Gn+ -zvt(=4lh(heg1p1#AbU=gND`88BaCM#P-bI7b^99csrfqJf4f1Beqj7^`0yUjydP2G -z*mf8k9BCEJH&LDQH5-p5fpiGBHi+hgxXRW=;HM*v!M@oH>A -z)PY(y_ZOO}(DCu9_^&;<%0VoLe9y!NXJ}#}?u!zQq~cK*CXtvJ!z6$1FoRP*K-Jy7 -zgNFPZ+yhO2XGyuXh!$2NlqkLbvwJ*}Qb0`X(&dksP?TEWBE(IIqFx_b6m_i5h%kDW -zeDiYMl5}&?#?|%t@15Bz)}7HRT&Bf&@Z^c9Nd-KIHDHRB;Z*%H#O*}w1DaWvXy2Tp -z#7pBCtK@Lc;XG0Dje_P5s{g!h@t5s(bGv$Zhk#3)v!3Qygh-&C8TQJ5CT={g54 -ztJx8l_K4>VuSQ#T^*tQi0n=rs%xDI#c=8;!P3l@sC1&oYq?b-u=FGbH*x7iAWadWE -zm&go}0!1~X4fG2pD^z83jWso;wSsiRqf`~OtJ#G~yb4UpVJPm*c^^o_)IW*s9nS^JwT% -zl4WX%ZF3aBcY!36-?{q)eQI4JwEns|V7jrds=gKe^+h`*#n2}sJxatCc!gpPcsx(U -zmh-Kew@9xj(OB&XK$s_=95Jn1vxjeyKy_ut=l}F;~+D-YpL -zWP|wUIhyQKH8*@Sjf$MG*(VZ_ZyJW$RS1uE7e7dey|uX2v~TC`Au)u49Qkd4h+COe -z7oiln8eLQ%z4_VCzZ#7G740oO`+;4rIq`|C&m-+p}aqc9KO -zy?^)a{`TX$H;*UAqK{RPUefcY+i&i_eR{`)+^%R=*SAOt>Rlwill5L+CHnihUQcy@ -zdb+WRfBcmyL@G~w)&0D#KVRDudUFk*xa)dDU$cAM;xv|y%0j^D0nB#2f7XG9Jz`#w -z6xCG}cW>xSj+HhHQKwei2gmCEDplL>_nXhlo{u-`cHL7y=j{W;z-4{7f -zu9i&wpRudzdYeQynAX`d+V=Kuil66ii=RtbUgs*hE|7BnI=wcDxPE#kLVUhHZ0Y|u -zJfY2-O`(bVl~;G>{H<;d+6EyLPO(Mt%@8P_aMY81`SD*B9~S%X&%1vT#qmfRfjmDX -z(V@4W-kiIgr;qm;oiY~Js*!f8Fjzz6y3SMPD`)r79F#~s1&wnOmKbmmD_sTrhcwyi -zp%gKRjB!Td?1-*J<_Z|9%5-CnER3Y<+UoR>t}S|t0&96gVC_>D@we+H6YM8gMS=)45`v#!lA|I070u-3dye`*AcHhTAG -zKm7GiBKg^LoVVdG6h|Sd5butgyq59p23NVH1+RQ_)kb0yhiWQotx1o`BP~c>hsL+o -zn)%A=tcxQ$`=5XR7k~C)6A2^NK1kI5pH%YZyI=g7E+YLy@{8ZEP9()`siI*vh%t^t -zygO%*I2XpyIN|H{k?h=l4BwEw@#{|8UM`YY0}MiQ^- -zBwicJVTH0G!p!JpQwbs>kn72WGv5p!&FNL6j}|_;T^NV4&7MK;+ae-N{C-Hap7Jqx -z)>A(doQLAc-;D_O%x8CmidO@bZhyL=H`o5xB?@`~gf~3? -z`NyAJ+hzYGDs@fV;Lo7CeRuO~W0QCMHu=9k{-&;fNPYv;)Sv(D5C7N4KmYE820b&F -zS5}-42F;D97j&3<@b^`7-z>}=X6RU$X0z8xCzjdiFKzeIcW)NHtJJ;BVd4>6S=B_z -z{`vnnS -zm{0|=P1PJXyx+2(9jKQ1cfTk#&0p3v+6=n-{nJ)9?f;i1*Ue90vVj#OisXdmt3(!b -zI9AeeiaEg@y~4GKMVy)>+(g#b`l=l|i+U9!y{+ckBx;cTq -z{cp1-kBA`}k#oW@YuAa7Y1tBCTmRg1_|YPM`1^m9(eEr8T*sM=cYpTVKmXMezOVl> -z>h<7T{W#af+CGZ^n>&^6G|t&idj_maOnh(Wn+D&o^GhpU -zk<79IF#qEBe_HD1KMkeWKjCN8@K@jc!iIhS;`e{D(rpmjtTejXFH5S_ZU51kV*wd4 -z{XhTv-<;j>c4hjZ;oP*({r2|KkN@g#{`wi8UNEfR8IZGc8?J0&ujTu*TYfleYpLPI -z$AXDpv_G9zb^EiaQuYLu&$fq7xmi;|M~yF7^cC18Y3x{g_~@(u`>QvX?7?hjYZHit -zFdO*Qer5km1ToCN1i3D8-}@D{s&Y6k%Q2Is6N`Q#HvUtSHVhn2|MmRVWeHFvE1`U4 -z+LN8D{k1`U(HJ7uwTP2k`^uGGJRW-aU++^Rs`LQAq<%h(K -zuMDlr!?F#8#Eg-KGW%JYi>vXAAAi-q@P9jGKyIEh!~N%x$)(mQr1nlU{KK}UGgkcz -zx5xDxh1zoZQ}(i#8HIUcmCJjths3+F1?go5nz_<&zZY&ZUTAD_bk3%LoY@!$M7?yS<0t%uSad@7Tf2HEb^%bi^LdM9tb{>DM$ -zTm#y8nbsm&Xwnj4Md283@;ggXgsJYwDNkT<6L%_XHP5ayN%basy8TZh#iq9a`;o4s -z4X>G>EMj%k!-Mm$`reGT+Z?2Q7_frE>VKmi|j-2MsFhS3htQ56EE -zem2%Q4$eFPfzK@ps*h%BbgV>f15JY0TFfkFt)w -zmoB|BS*nIM%l>S&#shYclc5X*O>1>iA5A?y?H??gp3#jS)JjFv+RxXMHZ -zS!BA_O5Io1A^y#e_G{Gm8Z`o@ALsY#eN_2y^6lpAsT=>R^t5i^-?^L}Tdr4Kez5_9Ki%1QcS=^yO}6kuE;D7-A?o -zcOkade@;M8{PHK5YFQ>3Q~tDQi__%dPD|xV*qnKGb>IH5jQll2QRV1S0$<2Nr>7%(#Rh_Sqxvd2DI53;^6SWTy -zcO_?zrpBT#hn=F_P;XR6i(O23>C -zaz(8YY{=vd(fpjiisZ%Q6a>tZkC+%1sS?wv(&lYC$#ZrGQT}DN(>qy~gvs@X`MI!Q -zxcl+D -zJ+bunU%#8D!`Tirst_&{@Q*uroRKF@*8 -z4O7TmL?`+%yJeoU701(J7jmGpKu%rb@4wWCf6${Hv~`7nxaxB|rvMT=i{;1L+iy7G -z`8Un!tD8jbPXHb1WXI&#&NGzgj-QXkI7@RI-PW9;eNHR|9h&Ph -z5p^xgckgd+=jTIWz)mQ2;$T03Mi60Muu5dDdVj(qQN^{^JC~+vYR;=wOyJ<+A*KYl -zEviDy&=;fSQ68id^kx;oT(aBo?1ms=jX&Sn21b)iBj;(ct=BVeY#1+(=#H@?&C3pj -zsOC?sc=z%4-3d&u2$9lxHmHc!mq$+4KXVlHT`Zbmx_is#Ki|%Cm0avj^PJ`o@d?bR -zEJR3T9k(YGC<0?M+v7pxBouE>Km)-)WQr4DDu`Fho!*@gNv!c^c2Ja}B7|%gMf#@v -z_U7iBfn|7fWW=7!6UumeY%(R@ee>?)48=iJym)?tCGi4rnh+UM#MrcU@`wnnPa$Tl -z=4C$9i2A@Lzj$_?1w1)tzZ6j|qDY-MbcoP|>^{Bj3E7V|rWrw0q}gtsvo-1{ig`5W -zoh#J+HWzzc&uo1pjEcP~Kjn9Au1|^0h*xrQ8p%8%m^4z@&g -zbsa>duKJ($99itm=ZxQ3uZy4m%vV_&T(BR-Cgu?N4aq6_SO51{)_;T^f8PA!_s%7x -zk@pR|lg##Ul@x!k&0qcIhvYZ@Ll1?KP3N1>SM)jW|r>N+T4t8q3F?g -z(edU{-Q6C|7z%_wK5x?#>UR+ -zUw0<|`!D|fxBt)I{Pkb`#h?Fn9C`b)GwDS?iT(9aRM)A%KfT+$7aas)E|p>*>ZnyY -z{PdiEWmTplaC?Dm2C()5(|&vdm(Ez1hz9?c7>)WMT;S10bl};;O?}z_W#HsT=YRf1 -zM;p3VjGbWqMUe3ej@ggMVDF)qAEY0_@r8!4CXcH@Sa@@Y -zQTm(kw!g=3`|EpayLlVBd273Q+uQJ=%Vrm)W{>rY4OD2v?`_<8b+NhmQbS$!hxuZI -zgpK?1BXj|TUw)`B3k7~4JIcSn=2f|fWrlmx`b!NJqBegVMs0q<;aKry83Ut -znjeziHu8(%Q3%PPpOr2VsPzjw?%(4^=I=?({oxDm=KSz>*-oD|@2-MYXoypZWG?K{00z%} -za@T8-Lg#;eDuQUOhhB8xiQv*V=ZX@ipx3=e$es=K<^$$pcRv2-oNr5NdGXhiI7jbp -zJ%!}6`m;m-ld0ogpUBiMlG_u7HAiFsQiVuT$6x&Z -zPd6f}HkmO0$x`8CzH`r9?2P$Ozw>kalRsJAtY2Ow`6lq-pMD}rz1f)0t*F;G+J)Vk -zTqSbD8A2R4|HbdWLbtJ~0EnA|RhfSF^RL{OUnR1!vEysW-_9W?a~<5A+rg!YwAT6^ -zC&>IO71tW~9mC|9@uxURZnxqa@jiJM-HI1Xj(L0g^}Acz^WQip=8*g`YqwiOmBZ1` -z^lRCSJt1YB#dTZS#HuG8ctpkgmiA(Nu@>G;?jHK6J##Ut%0{D!nfdgPjZ1!6{r}eH -zrMHJL9!}?TIk0Vi_R@UTpZ_Wx?!xQwvTNkRt^Hj3nf&dir>8j>baGuY7ZEla5azxg -zZOL#GCJP)cX|jmHv@Oo{%JI$5_)bR~qqcMBpXnCa9u7!cHS;>vlUhvn8chK;T%`H9 -zOZG9k{qSk#UFic8W?vzT@5v6Ykn?v2dd=;J&D>C;JXZCja+^KgOjp}UqH%X$FY0nL -zKY707+=$zUnRh((ZCjTLB}pcmTu390QR5n2?#lK!tu^%a72#xoQqgD8TrCf%#a-*6nBnue7LH(f3ZXRA~@y%5YPs@DVBW5DYS=xhFcjh>^&3Hwk -zTM|hU_U3>V4M-+0cPI9g-xF@%ez;O9DxA#RZX`z9^v9URbes{C=m13dvJ+MaNQ<>|@HY71yMcO*~zA -z0(EOb4Qv -z0^!z}Cu)wc*^WRdMJ{UV7w|nn$agotp0f6>_v@~!v0pShc;O{&@+tNQCD9gz_xAXM -zf}|X>84+2bk7{fEH?DS;zzP$*BB|Uyw608H|M5sZMD5X6f+8|r$iZv3P7Vq)nW*6v -z_E;Q^DqJlq9fdq7kLJoUkP>lZ`~(T6_VS=y?Wn|NLh0BI66IxRhr3OjTNqHzClDp+&8YsKP -zKWdqaKCq%w5I(|WQAB_*BA$fTHn`xCBvC()u;o16he7gw8*Eqysf2(dBHcf6pv$@a -z@tAo4f^O`<%q$)0e8g>m7hTM5AlmSDN76>LJtjI0waRP@^_hQcwReC;gqFPe3{OM; -zxfiu7U(ox?58Q$kIvIiA>U{4BI&Mpf#BC$0q#eW&6_ajbQ9sz$y&_7Fx!xU7ekd+o -zp=K0Zh*5s7cD>Qz6#c1R9YmhXCY7af_08bNoC*I!mAPYae2$T*Q|N^rt_d&vOo5M) -zbS}I8O!rzpT(#zr)S%Yo#Cg0Ed(necvELs_Epau_5dLy74e1y}tarYK9PI)f -zUYM5AneMt{Ifg_@U*k=FZe1ZcK6^TW)(qCqoS%RNs(qbwTgnv~7)oeibgU#@q_haz -z*Ws`wnn@xB*E{!-*lm>vm$C~UvIdUq{9{L?Ot|szfujwsa}rT$k`9*#6}h;V-qFy; -z6N)zZ(F5p%NCWl}2skvb_)VhAy@OKU+*fhxOYbyZhOl^o9?Ye`hGAv0#{qF5l?x*q -zht$^BTHeQidq!MYRMG(Gr{y0u-2c2ZJpP_u1R -z>J4Orwxd5`K-hmbf#Nq&^*ZHlsLl-)xPi*nSJL`w -z*-#N1s9pnQYp7lg6{?X6HCCGXYEn>)MygOx`5CA?Qw3+F(u|atp4#%*Q&ReBNna5e -zsU8Dm<1ug|uSh%&)rQA`lJMAKNn -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 -zzrFbwzl*1$s -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 -zIt3N8{0$d0U1pu6Tpx*kd#eyIVht8dC(3% -zYkPf$)w+wxM^drc#$qG1HFunZyc?(4Tc%7iP`ijoN!_$W2}>6IbV78-l$|V5>s_0+ -zb?XS0wFZcsM(0uju;0Bw?P -z6HmDJBJx0oZSSE42nkUmnhnAPi3)9Xp5h`BTSE|A^9dk^4oK1AuG56}0s(+-9r*yF#a} -zg)73jqZO4Lpyk9M^F%Re6!r>|RBi~%#Ith}N1G1FTIQurBqXrz;Vm1$h=9vrx&kKE -zs$k|Gq*~HpX)_X0kc`5F^0=u8;z;D)1fLsw_@y;tZ}wV&i=#0;fSxJ}KO&4sgg(~9 -z6<{Dy4$5JFD#YH&#v~b;&XP=8E$zCFNx*S3aH<}n_ULsY?x29poQ8KyjsWE#t$V{G -z41q@`hNNkdj^fo+5^9MSHPR{%aKnWXz(>WhFQEbqI2H$57>1qP1YLBjIOn-F;o4uS -zf}z0D4y!a3%Gu-&GBUYrn=J6KJ9|}3qH^0n9oC8&6CJ9E>myMFBHd2V(xi1WIwQr6 -z-g`;#&}f-B`G>#nqyy5*Na=zx!Li4CK&0&ar1%^JVW{bti50JL`^4z03G!heh6L`^ -zSwZ;PlR=2(fHA^-Xs0lsvzOMe4yM*zEnsd}Ak!kaAX?5YTUl$l-6EdX%Fel_C05GY -zy-8MAv4dc?0M{(&(vj_*W&;n#W8^HUl>jkT53V&Wa7VO9df)1wTc1*$%u_O4`3E7w4Tm{dA8I@X!hmz=FH(P;#g -z*?3u-j*dI?G1|wDzcJvq4TOIpE8%X&8mu&Je%Xn=pnSnz%g{j+Bh(}Q7lio}Q{P6~ -zc$7^N*p8guw<8UstaK2-NZBV^&;}C~E9Uhsm7=x+)E?$~+(T>#2&@_e#ShesXzk}# -zD@U#&Qj-$K_j3$hab@#kPhY?ZY&T0{S9j&{!Vy*``Jo!use~` -zNxH*lJKH-5yD=Dx$T~w_LPzTSC7qf=vP&Yp@!+Z=Wl~reX-m1~>mn}-k^Hf-<_n{| -zo;PnEpbj&YZxcl;$s-Vy3^ICA8C}O&OaCIAUF1-Xl8*Vw`&j0#jf -zs)H@&U=hs4Ry{fjlJf;Du0e;yExZ@dNV`a(1CcOoRL9zhG+If}D{plT{mva8WF8i> -zF&#l5f{kN0j|le<{)Psb&2a68T_Lr$2V -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 -z#uK@BTbGfzd+;X5XHM1K`?Z5eTIwMJdVFB -zba19PR9ZWLVmKDjvGJm&4dbz1YWB`Tv-4>$lA6DRNLNee_TF(fYtMCX&zEp?{6r0$ -zO^c-KTsrdV|iSs%@z^LiSIBZrkq9!YigQWt5qd`;9- -zA0rYz`W81@Rs-X?j{Nad;$Y{2&vT;2l~&!mu^~Lza)>s< -zp$#c2u0k;_(3>=@w4QT>R4@$Dfh*2&b}S+v>S-nRw04xIX!5qUtpiM_pkPiaX`75r -z&BF;%sfMi@GGQ6CnNoD?Xw{2pAjK!aZaLR}QgGiVRz7hjf-55#!7INpBp7X*O%6|O -zO8{@)ZS4QuSWh-ll5*(ecVGn^zI@=bb^EvCKh!491z<)J%ZB}Ih;eyeclhg;;W(_5 -z_sDw++e$}dV($jTE8Y`YN4gBY3n>9J2Uq*hCRq3CBT+uqtgSC=ogXuz2t7u?WSd}w -zskf!@Isy6X>~0oE*{_;(gADWrb^_ROlB4N-Y;_jltKLl#?wxfz6|Pu2CVgaHC&!a@grz+kBGpQN216LF*G0gor@#wi=%lo)J`hCe_#@7 -zuc{}m%N{<7yBbWG9Yrj;CqRryi;^XL_Gy1u{-bWvTEiDIm?xo4ch@Biyv0cfkPlub -zfLH*b(6+I(nFL-V?xm9`#oIu2AA~u!C5s2^a%_}%U>2g!4{6TIE+|P37X{F$E2*;9|FD30j -zEoAKA>p-khYSS9Ko||{?ZYq0a -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 -zR|(O%NImo%+i!#t3R#!*?86_F`s>uzjpo@N9P -z(Fsb?yT(rt6pRG?&P?n1J<&)4N=}wwvSH#a{?!A=15Bw2@vYivFnne3fz%QHRdTfF -z=n{}bofuijyIj*yjEI%{bPMjt?pV(32ka&)kAs&a5%mxjmHYgxHn9<{h7`fliWqiV -z%&s3sNX)t-sCE=FgS2aa`a%D8=!Eoe$dP0>5>yMuMjQ7Vtmi0)n>ZoYBZDa$JSqS4sMWbmj=>5KCgK%gjr>u@j#VLkXvs -z7T5lK!{Gfq{p=9zRCtX_luQbQ3tuEDkiBjy&EA2)fA7l$ho_wi21?u{XgDsQ6nre5 -zU-d`ttU=;Ga?NagI1eWZL?lcf3OQYamkIa@DOz+e!yQJRo}G`KWLF{|>?o>Tp#sp? -z)~~}}Gj@`@-(fH{!=_L1NW3=TtlUARcUeAMKojqt7n%HBT983(_R@hP1n#S9lxkH@ -zCnCK!K?W6qHSOh-(m1jn0381DxCxLZm&YDLZ;7IjPC&59BM|fFXb~h1H9fDV3gtdH -zyNY)fve<8HIS9T2aEzdaaNOmNfQ7~8fpPRo5tzi8r-QJ-0k&W?4dj~>Eeg(su(!$7 -zGLq~Lx(v7VagJnB;@*QZlzo}%vB#6sue=U|Th|C?34{s2R#A(vJD#A(_VMP!dp{l5 -zO=s6T|`2tFq{Z))^i6-o~X?W_xW5<8r0arj0xr(rK5FXyc=_sWsge^^*hjajZr${<5ZR+$-TMNToaD)wvv;CoNjj;>qCZc#_Be;NP4AL6ShvLG{u*c*9x=({~{AHYEkTe|<@u9kQ^yl!=btw1|!N -znCa|KL~ec9ceC+u?a)wWwT`eg8z1Wg(YR)R$Gz1)%API8-Uq<1XYcsiJ>)Z_UCG!F -zVlyxbh+TGU`}wo|ZBs96FP%(otB+K+oxSIWS32$$f`4%eJl(MGS+7h^QtTZXu^oxT -zlcYte9!thZtwf^H;HcIb_=DJ3GLcJ{RKM&JhJjOs3~x5vyu?8uM{?%~Y%RGV8wQ~X -zDSO4SlL!61inn1xl;dPJK9VLo7BCgMyNa;JW-ov~&U^y*MHDn(PZRHndw_|-mlfjN -zS+3F=X)av5F(~AUBnhs!!r@asvjy^{R7h{L#eJr-oQ2>l)m`GwG>v<1CE*@~J7NF8 -zs^V*afKD|@?b6_aHrSo4u))rz)*fJxsnlJ|2kp-WT4Q@Uvpy?Mwt$a{+a{=sBU4MI -z*yQY4#!e+Yi#Gdq)*2RLfV4}vKQX(o2|uGL_Kov4Dt#chCc=1OfWy{t``Wv*x5N;6 -ze_fiqQdI~RbVh054RbQv_&$;)?SA8BPLT^1Xq@kX-4{ii2zpdsKSV}^oOwr2Y>!Dl -zIhuYfyw@y62p%e&;A=H{R0Sg1w~54^*pQUikErb5!^gOFVjHTv=1;hc%&~vX!Uj5s -z6L59wibC*(yyyvwur1_P{c@gnRduwtb5O9n2ZQza5s7lHVD^kmF3R9fWOLqY(Q^6K|7mERIz_|~M%v~}KW1}+&3h1;fJ@aXX -zCP8y9y9pzX^V)!d$Z0T0(suG6XC9^gx+GkHZI}@>vQyN#Ba^gAG<{oCz|Icd+3@ca -zfzXqrSi!OJh^&1Zt_B}&Km9F5$$3CJ*Fg4K4#8g_>KW%3NxK{PLen0Bhn-_c -zVo6l$uhYTu2%l-{l5WiUeH&#St -zn;1)G?r6AE7#O@~HHj?95e9ev`*oHW~pwN6({kaYC^qdykdHj@dKMIKzGN)v5MzEOp -zLGKQP@6=ySf8sZLItQZJ4 -z`+(TA05pOiQOG;{k&>ZGl5g@Bbt-xuHVk<1K91n7?BeNcEHknV$&k!21(y#VF`#s| -z(AEes0==n_A4uTpF(mcdbPxxNHAt0Ai2X@lPzaZ`vJSy8#Hi|4@nuuwWO-OJ -z2&PE*CQeuJv1nRsjX)XifIvze48}uK7a)|BMjrrIa`K|159$GyTS+F@2&HwI(~1vVm-zqZL$)V`nM;^Q^G=3&Fy^2RR6E#`ERZwHS^?N1z+ -zmYpyc@tOvgHMNlGt&?1J#3qDww0aAC>#0&A_JxKmCpP(*=YbH?>PWhX5ONk$y+b@A -zu2B=^9)U2;@z@sh|6L+6te~v62%zqgCON^k@{a9Jf0Ri;EEa~qe=I3u!c_Zgst3{< -zSj}||lYI#*5$+1wnjmyiO(aOe=0ouLp-zbPKzt2CFULSS_AVq!o7#(CWJ5*i4Y74z -zR>Yp1oZFQRPAlVz9eCHl5TNHc0Z06?>f5P;vxbcLkvEJ@LB-4jIwGQk(@Abm? -z81<ERaEOS{!OUsTA*T6MqH_Q4 -zYx}0q5e!X^8IFo;O|=P -z_;5g!NhZ97bqi$)=XO=eNBDxE4%B3fzhQ5RJh^q8S1KbU&X?MaE=TM{rZ9AsI+5@;1`(6g7wL|)7Ht*q{*=3ir -zN{-(qxVmsMAF0X!pu}+@gnFGBx;veNOY^{KgYu1Kh?{U59$1RwFfAdKhVGC0c_e3H -z*UUkr!RQ*~OVgRLYMW&qksp?5)?hxWmps!Z_mznYS4Bv==n?|(@GRP+?!_MZ@IJN`>M+9^=)l(c(556a^H02)|~6G_4tsWqUeId -z3t^K86{_yq2&wCu%HN?AYq>9uE*FK -z7q?Da$e@7B#24vB)I!!3(ZLjm@*-1u&iN=q4$V*mmvVBf+~00cM?m=4rXl -zWYk&rqT^P(qQ#^%jH+Nd<(@l77fU&uY+CC^-+tiD;VAv2r~R^^&BZl1QA006$zMg0 -zUdPE~>@mcy;Gyt|^a#L-Ncr$jFDKOly}=r$@0>F)UzN(N-f-Ckox#wH$#I-J;p0wP -z(!xE!142lscj9sK&>Q0 -z0FyaOK*p&@+ll}#b_NWTMkpnhVd5Y^h~@>{HK3#7?x?Hg-4nX{vKTHP4UrWl5pcJO -zR0{ck^BP#xZ;^T^>%z5cs1nNLA%u}#ah!U!Ck-*hr5>TUbNcEoj+xjY8(qjEuvPF` -zgvYWyng^oi-aWNIJylR5mnF@v&FC$u{4R^6_wT*BKR!G*dwhd)WMh-}D{|25@_BBN -zZZ^bKR3u5#>44j^0+sc05I~1zqb?jaA0JA<`kma+ihRt0s<-tKB6&}gyN}$m46(#goYr(i`ahQ@xG_nN`z$9F<1&t`0CqeQD@S&X`{8DU)QA>m -zoovprJh9;F^PxN%7UI*pGN)5r5;r^U2jV&AW)n`pxmi}u+sj6#b6h$tMj=%cqO`Ed -zGI!zX4U)NAXRRyHgh#$^E7%~~KqdANJWTj)xcUkX%ZpNn;+X0#kye>-^3a`fg$QhF1Wu0>vS)(flD@XJ -za-MZhYH%^tN@+ogxth2H-j7jqkN-R -zHn)9o?2$8jSjcp5HpuA$i@)Qlq?T<$fd$^iw&I%z>WOqP<_%ka(bp)X5>9Lk>lNb> -z!uqy^dA+)%t_CB6JJen$=yD}z4r5@S$6hRbnOLtYJAQ^(Q&H}M)o6>9YhDVmQ39Nu -z$vXt&4iJX>%p39{ZNU=@e8iUMK@9`q&zxz%-`~?g6}UMOE`gRZw6mxz(z+Zw2n~dT -zW)qA>&~cBtJ;)>&Jl(CqPOv&nGuiPC0g-oR{UEurv6l}4x7f8mWW%RO~# -zPd^phFx)*Jr-+u3F4<~_yC<-BU3(0HM>KL3BXg!e8YceJF+cI(;El+vamC6=0@|bS -zp(}8zP=Qj}%6v#&&P|N5HYWIY+1xamKFUL3a2?08_I0a}v4*FE4oTC{K}*TSUK5%k -z4Qb*OyBarsjYRzquCJhM`dE7xz`6hHNV|DzA3wh>g(~cPiSe>;oTXEqer#Cxw&lZd -z`-YSo8pQ|V94WKFm~EFRLE!IcXtlTOtE&&nI2Tg~u(1y9H*CO%GQqVAiBWP%Yo4`f -zHg@;I(!YLZ52D_09sM$e=Gz&YpNHmt#NE8BbzF2#_7D5i2jbZcr~MWt=W@Wt$yZQ} -zXe*4{3a%%&5$@u|rZU)(bXO<~#RNf+C~6BGT~)%El)_z~fN1zxx4}Oi>0<1G=Wd~Q -zNn(pnA6Z-A3WRln999~L&uyG>gZBdYGmZDRu;db#ly(`mv;u(!i?I+cYErA>QPo76 -z4WNf7z&7F#46h<-+4zQQdCb -zC*(GRgj!!b9;tBZ4OCYP1(-3LPwd_NhM0gYd9GXv;IPb!_S&g#2xkoMRN3vwYPCI(GH(g`S7MHhM2c{hQJIbiBUN`)u^SIF61ye!P#9k}vdT!IRK& -z_IaE=^5w6sIAZQm(g7lWeuvT`6>EQEpg;hP>5z!CVSA@ro41lwzg;hFKRm*n|B88EjwOz1Zp7-8(>e~3MIgoYT-e=*F7m3q}w}$B2e_1jB&%~a+`3*b&^w@zG^>f4c{CmectXG -z1Yr<)3Xv4SV#9t=>QbVJRYUruGxR{yfJW$+4^u|sE5zT0%_B8P-}_hwPDhIE%YBl} -zzRvt;$|HhoNFj!|SQ`6WC72#7!^b=*(7u#aFzO)}??a~Y#-R*fn!r8fmRya0tZsZpn9x_TU1>^sqQe%TGe)jgm|4`r5 -z1V?P*HX&rVff7%f6GD6u+l=dL7pshGj-Js7?j?69b9?6!+i_?KqbM9EYN=CSO)3)! -zXHF7vGyHV0yCes}fg^UwM2;?1N>Of|OR+ -zI8*-s?lZuk&WpT3CPoCedA}H1vjYNLCZg&HF*B!Fb8Ue8CR8vYZLpT?{?}z1MfQ=n -z@qJ*QZ#Fj69sEA9-qOXkOOAu^7c`bu!aGZP_66@IAF;x@CLkRTN_i8AG#+_x}Q -z+RE@DM`J9wny{NBF3FH$!b4SfDIQzo4RgtCgKrHY0%M;h1WD(+P$9GPc^jpzrxxx4 -zQFu(DP?ZnWgsyL`w(Pg4;C_-M&bl?9*Db^fbQ#i-fr9@yCD=#s8wp8=copnKhzBfT -z3=;b~SO7qA6*mM{ReW>hvkza{dQOI8E#uZ^wQ)Xi?Nyo*FLSbuuRjx1NQ8rGpKn-H -zs5}z+5Y$}KKvnOT_CB>~7V22U`rtA(P5QGJUplXCUK{8o;q|WW{w956@EHl`%n%Sg -zRS$aIG}&+z#AW)w_vK@5Z$gyJ*+{EKYplA~8!_rxjpkqXCL5RG?_4;O6K9t35h!fH -zxYMciO|_)m%>M2Xf8$Qn4=D11qd1fD?0c(&o*EvEGI3i1@gWS6k$*CrZ(_`i$-_mt0TIFH~~iJY(UB -z_4Gr=ZK;e81#-#g4j~PA*#$eHNV<2I%McNrqCRbK`$Ylr?C%|^67^5SQ@VUP0QOP7TdvyJmY-`$Hc?1rP^hqQEm?UKE_ -za@o4}TYdkHedV1jcf=$=_;QrrX)weQ=6VspE@-p$_`6^I$IB(A@=gDv;&>1dJ?pLEP)EVBmShS0 -zX7CZg%Z>FFjOyO`Z}01{I69CV1u-2+H6ky&qC5x)FPc%N?Bd8KaBN6#GQiWX!>94# -z{d@n%w*fOgbUKpK?L9jbcC$({Uz#^}XnpXS5Dp1f-rh0TIwzw7cnI(~;#sE?wUsT| -z?wy!WVXr3=*(BjUHZU$nP^Y?S@z9*d-y; -z2oC!zr$g8b!q1MwY9{`KHa+z$--ek$*g$y8VEYojYwvF&5u7&S5QHve8C*Z%US$c_WZPDoz)1{8?;z5RDqa?0?&JZv)k=qR3f3 -z;BEz5B0F$xogZ%8gP`8uoNKoiqDAjq31z$qi9hgt@45d17qYH1+%3T=Ucl>P=$61Y -z0?^huWP*PonXUhlbfhpn>IDg|h;-!EJWPYRh!VF?HYs%Y;jcVxeo)R%5gA)TV3Tmc -z17ZEV+%q^D*aCCe9kQ!)F8Y1!a(i^*XgII~N71_VpejlKw`hjH6@ZYRWGo)d1+BuVC -zLaeT&(2;$d+yaSsKXhVfx^FD&vjvFZ50blTr--PJkR>Ma#MlmG9cU&m-A?%Lo4VaY -zp5-48s)&e==bq76u@1{<+8jq5E~Knq5;m#lcI~yR -z_b>LF}d4rth?GO-MjmH_cgRM`{(<O -zjlO;FM!$FQ>ui82{3VTLZJ)xRpE@C)xQ7>BNkP7~ngm?mh{N}8CUzu9`gJsWKev4A -z|M1nPx4v!^A2CLCM`0U2G<&C)Cp5$XW-18ZMF{M2Md)rn^y8l^A4|>m=e9(a|Jfdd -zH|-GXfl(V=@{-%N#Xw27OnW -zQ==ip$zYvmKKKQ95Ru#w>9_FQu)A&SR17bdbFE+a^4quon)_B=<;~-rXh{{$_e3eS -z0uRtIMN|lWxMq7uh`P#E%TM7`d5=FxYCxk}w5e@x9fKL@a;JH)CoX<)fC?+`&VGnH -zh8+r2aaVA8P4HK)NO`*}l|%%Eof)Y<=sdzNqFg(@8x7Wp2@oCA6#TdGLRWw3(2fx8 -zUWMCqzR0@i{_MQ9_`C3nsos_rpk3f-oVDdH^2x-IWDxe?ZZ5SA-?u$CDWVa!{vu2u -zQ@o=jwN9yRR_?dN2xJBV$?KzomsKX_$Bd(T2YIE9W!l-J2tkzSa#GHdn_b!O8~{ -zhzC+zju^6pHQk386PreUYvpsP)b$Y&lx1%Uq+fq-Wf>pY)3}WzS_x0RI??0Qu?EYW0<4!?3&mc2KpWx6+5eu -zKfWNiTanM{KbxMu$x&s6?ykJM)Ps8zQz1pOOudUXDj%BITOm#3dhO*I$&qnU``Tx{ -z`+hegpW4lrF(E?bwqnR9i6I`AZApdn8;2BE%7w -zcRo=zdkt`$g4l}S1mg7B&A_@KE6zjiNC0u=``%vrz&Rv5^!eXNfAX4TZ65dR!e@6( -z5S|HZ9}8A)q!c0;XKKlH;Vo7WCKT*}&kpBEn&gf|6ZZz$@w!!)B#`sTIjyv$wbV8W -z#_}P57QM>n$gjv1emA|HR7T*;*(Mbu`kv7M| -zu_j_`cl3<}*1Oy)WBDVoi*4i5X%LZiUR7M|+(Mj|Kw4M%F|r9d_+$Fpy(G>N*-Dit -zTzJr$b63L03BqmK`sh&BZao!n%9j0xYkXlg9Qus*a3oE+6XcVFI}H5^%f+^BB6kPp -z=O&$(6*oZz56Sh|{Kt`cc(7AM@+6XGrTvBE0dTBCOemK~vB_bOCmAtusIBC~@F3tL -z2-joe-eV_fpxAWF@ZUZXY8(rWVk#WEz)1<|M#I%FHox0mLz08pwIomocdI0e&DMH< -zrINO?+|zbu_5tx4q+qFq;3zkjtijq0=yxGeY&-Yq#PXJzA{!7YF70ahj>yw+3t+S0 -z(koouph$J&^gDfr+=mt#N$4wEG_qUY#a?rz(lQC8YL9aDOrAOD98(f58p+CD8dYuf -zsY{|LgYw!|9Cr!JGkvsIHX_ktK2cGPLNRwOM1S^Lac9bjFp$l_{bp&7et+0}*w||Z -zTl$>Ck(nY|gv<#D5JfCIWr!KKI?$m#aQq;PxU5@BC!WH=TOyg4Z_ZT}QLHBm$q0A#Hyke`kgPbi1kPJ>>->{9BB%62yW^}y* -zzCN;nX9LF21P?JISg26?F}3~-5Ko9la<&I7a2pCg5}qfj%DH}f?84;OXZ<+d5_*M2f&{$PHHW -zlkD38M#MqQH;}DCDg{FsZA|HXFy7sM`-z9^#a`K>(1c*6balGLo>{ZTRFP4Q?@?T) -zC6}@f)B@hJX2W%*>G-XWS=rhob8Ot1AwOozRlE&1iFSAD{~%hk?~yq8Es$?aw2P&U$X -zXB~>mOo-Ip`HI}v52LMAuz^OZt1S@vLsAa<^ycQ4G*c94u?(=ci#E)Vy-SGjx?CK% -zWG?foEj)@HX|Hz;iYf3v){A)uub?gb*GM4}4=wzb%8+Nv0hRrnSmid8 -zBg(|oTw1!5gnilfJCe}o@N#@nOS=Jevrjl07uhng3}_ARZjm6iM+`}xBGJy(+h7Q^ -zIiRF+?y#1*)$EQ;IMZzy?>Buv+9v4AT#}O5zx`nv&ViK&hVVcL0`}b)z8PWenmY}J -zB==n3->WeU)6gNoV5c#I_E-W9s|`_E*pj5&a)-2#!)S7d`s#j>1l$ve)DEXsu=lw0 -zVsZ)Lcyf0LlNFFN15^Rfm`RgN1l7s#v& -zxV57u2ZL0bXbqZY*)$2dgfH52YmZ+HC`i5L3Ps#@d7N1Gxc%^n`7KHB&9*)wNZ3j^ -z&|DMIP?)=g6q4AN -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 -z>J3A}CgqeymYAq*r~;14EIInh%_3Gq@?IW -zBn`s(gs?xc$Tvu#frGXa#m$rF#TMPKaNWL>%7bzu{)u_rSpqocK2Cq`d(jsz5}!xCoB#m{NJ -zEg9Y$#NPP;9_tOs`{Pf`yF!8tL2M8*hZOJ4{0%17B1+B858_1(FFlF!oq@TS4=6Cj -zXkAKhjoP9zgUgWym8u3b74AWUGBvS)n?gUgs&(QntYT)_ -zJakPuTPSZX{W)^QblDEQggSGzj1z^JH+~ENBkFXgUFkohQP>p#lA!hBVygxy+7mgl -zKTXHAkhx^1NvN?Yshu-I=N1418fp>c-h6FhBK|T7T4>>m2$;&4UJ-kXl8J=3LDBA{ -zqqpNKxz@arMjW!;$lBKSwKG44G>>3w@cpSHSbT!!1nf2qedskA7V^M4#Go-oG -zxtD0Tu$+xm4dJT{kRsordW&)QupX>zX_`$4+{bkSu@=E`0Ny&nZh^E{;2I+C7xjvF -zu<0nIw01>CO10Z5SGHSa>fp%&I>zUb^G -z$fLDY-PPWXh%n^RZ~+O=?6(r7SY4s^zBQH>+oE;>QDBhjevTTXUqf|`uJ)P@Lgjl| -z2otzw=0|rrE6Zcg$tTT_^Z;2J9cEUjPQ~6vvCpgpZTNVH`XQE@fBli2CukYSvDyTf -zyChKX+B>;9`w5cEPcpqk%@L%0-;jMpaQCj*Zq*hY53HlTy8&%}Yejo6IZ~)3vB89|Tw2^I(D7sV7BNr!&lwLJT$0UxNLzMQXz+Pt^hjdja -z3-+q8kb!WjM#9H8a6I1x#bTS&Cqh&qx)ETdd-ngAPwc7)`wnR#-0gd@^6`2q+K -z=-4Fe7LS^5TgT!)O3pCJYJnrPGBKl?P$Z>;*x373Cqp1I+`DFmBjviGuCzh9SGBzY -zAtBvX^a>_LE=I>`alJYF{Wo_?-Z@)7vgKALR9UvPitC;dOZ0a#ZEheJu;hsVFWCBN -z(R<$AO;;F`q)?8>`2UmkFH4RqOR^w3@4sk~JGvsslbI`5t=hBxlH1~Vkstv$5)P07 -zN__k^RWoxqBiE@Ov{dcAGa~o^9Paj1Qw`8d9Ux(8soi?1zikm^-zN}QqYpTC?SQE1 -zEl*(pJv0`cK!?;6K75P8RP*@(mt<#XmD}vV3^*BzwB~(Z_u3?s&FIKpeZ91Dt`{aD -ziSvs}8m|H=3?+3luyc@etNH*xsE*Y~jl{2Mfa3>ZcA^%>^miX2P -zfOH#nEnJaSb1g^{kA2udx5!F`!g6!zh?fCQAoIc2fg&0FL8Xsde~|rPx<>Bs5z7n; -zb;AfsU`NEP-nKV@-9t^O3?WU4v5V<1ePuJOpr=jetRU(L;?B!qu_tE2?kY|pvFr1w -zfi5uvoA*=p51caeecT6(NwuT)=J=~$cluE;`k>N+TFIQ9b)q&mE4Klbv8G@GwpQvji_YTKAQuL__95 -zEfb8*(-ciYk&(7*-R3}s?!Z{Y&1HcC>)b)Nuz-bXvicwRWDgI-yeXaTt!$_*9G<;$ -zo&k7CDHN0#v@zoB -z*r+0vLC>(w&!>LG7k#9%p@IbUDFH+-x8>~tap(3AEn`Wh`8-`(7Ll}^PZu&*$`6fb -zGftr0>o8P>8@14!D$Fb3x_8dROM1xa$3_HVmt-m2L6zG|Tga}Xu2!a)W~gC{)C -z7h;yW8|QACqlw!NK&Ezp8CjClLM-T<;6Fzyp1Blo)+!!q;9_4q3!2~Qv&3*>n+4q>`W>eEo;2*Sv$|G -z0b%^L8>7=5y}d=#DA`84Z7D=tAnMV`E4xB49=KfK-0XMh?~A?T>NY1L>e;qmq -z39w!{24aQ!I9}wRTpEln@d<$Z8HeILyOs$(LZWDp`QKA?7RSn+AtgC+ZO9nmjO&h} -zoikv_QNp}^%H^9zOabePe!wB5+@4b$=|07aYk7zp(@@Da8iz|EGQg(jVMSjA%aGWr -z(NfKuY(L>Q_}5&VTNc2?fOolcg_E=n6P%&UZ0&c3{PfR$uze-=!Kvbj9Z;155Pkp3D{h_7Ds_xiE7(X7`C_x -zx#-u#c^tKbt3M433I&dwLDDoi2nN)eW4CYD?89!x{tksH8%b4NG7O9oIWi885C%+h -zQ~vv`RnOKB#*NJ<$@|afU;zpj>?AH3@qry#j#ZLhuk!(BIui}AMlthwr}=mrEJ9(e;*qK(8Il`F71C~r63YsIoyS1wUIQD& -z!M8P{_3WD+rZo-Bfl3jB(9+R~M}h`K2URGNxpznRCZ`~7Bq)572O}&6M;zA?3a}Px -ziK1u0RtKBr2t=8AZ3%K~~y%K9<{&vOh?UdrMZJc+7VZ}nR44rmCCXteeiWEO8wFsKqT7}x9W(@_D~dz60<8W^>| -zb3cORrh~Yg8ElO|I_>YL7Stzp3SgkEaN&V4-;&m!bxeL_Ib`D8hDu@04B-4 -z*n&NmsF;|qcgWUj0OhJQ5~Qp;RGyIdaSRF6&>5G7kXn+kEW<02c#qj+$%7{fZK|H( -zQ*toF>m~!UH`l~0#CyVzpT7-1?PNshw_fcH!EQsm$Mr1awem=DAmzzhnxb%GD;|8K -zP`vIDU_sa}@=!?P4^tT=?n}?>zm^B&12Oa34ss`B4TEz1BxQ7pYjfS@e=+?JIt%d= -zr}OJRnr=kP+&G)Y@SKaOn>+NQcgGYUNdu(Q9)|X~2#d>;QVERVg$7VOqla#<$emI2VKsyqt0R9_|TCRsCR3mxCNAeu3;Z@5C@0U -zj9tdiSnO{e_$Fu0hbn27rakTBsq_i~xs|f%=L`)hzY1?ZnG~%O%vqd*{NFq>O5JwK2fdaV#CUY%;#?i$`xLg;@oYXHymXzHrMX{ay -zJY9F-q1XRV`8s$fw&32PW -zVotbd30>a!5=cjM7WK@%cM>NLno7!DYx972CT?o#$o-C;>-yJvt@AL7vGwZ=pSYL$Vm~ntpz(> -z=(p!JgUbTG9pQWK?fEgc1zGyh`RT%tkXCOM=o+;qN)y<08GZgtpUVyyQ5Noo;kn8C -zG-uzeoG$6QhQta;!B}Q<7BSD8)|mnY<@tkzB+MaXxivKvxzZo -zHps>(F~E6ZO*1d72FPfL-o=+78prmxXq*j0Ks<~^z~hz55SrUVDH`nnCvh&`ww-0G -zC{$FV4B}g0{Q)5LQzF~-!Wn)g69PrW27dB*G9n$@^LdFEL5zE7w5?}3AWC{8y+ -zRU9#dI&8u;;(`JksG)oU^ZdGs5K{D3)m-iiY^vowVx8rQqnu<|u{#GuFpM{vI|Yr6 -zWP$xnLYm8U$5y9p@tIP^3Za)6w@^`;%i@%{#4LV>sqXFpCdapt9~_3j0}A!{JM=nx -zuZwBz4`m^@8xp;BRPMI@9;O$jPvlMNwqm#wRn3r-X@?x5y$#h@SOZ(=u`kDBDNv&M -z&XzssBaS1$fLk>YCe4P+viL-%?T6V61B!Pjym$^d06=5;5rJ@h=fsw>Hifq<&%cu1 -zwHWT>{*vGmh6}F@TNVzawN?E>vo9~iGr6xU8HbTqpIU?1UV-K@5G{3T!P{7%9M!SG -zrqtKNh8Nkb2YJ7gTmqsSS{p!JADxLLkejcO*T`G5x -z*gZ-dmWJ-kyj4I2BrLkq!ska%>U#GnIA*m{3RljACHHEWA4 -z3j4fnfMKD*FFxTB7+>}K?CnIf5=i}odiz5x$%l{T%DckHfxi60l2hz^)W -zAh-lA-~pWh5t(bYxQdu5cED2;vg4KTZE&X5QK@-yz3GH;!MjRs1kkm*7oTHJr1h(MpH$Ui^`L6ex{&PAM6hgP|6`QLV`Y-jbg4q( -zi{3*u`O)>YOZ$SIuS%L$`SSRRlBqF)k@gaAvR@6G4cw=P_fWSyYC>jwloA+d@P^sn -zL7KxXspYh$LB)Iz7~;c&aoNUH_-H6)zUwqo#cN3RUQ~iF} -z@+><8IqZnJO=BUkzKpCvjJx%;=HudJfpB=`0|DNuK18vn_MG%$6h$KegAHQ7o`a)3 -z&RTw@&KxRb0C(%cA;O5*>E0kF+#*&TlsC2PizzUgApZ;?t&ty(5^NYYl`$oZ -z!Fwm(1MVGI4bUl-0MY>E9o|)~WtG1q1+iFpl1uBFd!A#DmGVjFsN)MQj@OQ@mTdPc -z@#gF?r-mg4gT@+01fYS~T<1p^0vha>J?0P?XOYFk5{bP;tu0Qd+*n0`Fh^IY_XBwC -zsNgC$C_Bs@?|cVkM5|rM2P=jP1w5@1_6Fq84&rc;wdZ)RCKf0WTSu_=T1Q4vofR_i -z*;$zu-TSq(eTNE8#+Z6{Ps^5O2I0_s0wydlsRo}@%KLwI6T -zr{mFGGb;?*rxN~vA=R}hZeQW+__g>|G>K)eteu9u`2iF6kbeCA{d@617x0cWF<4Yp -zci{aBh%)%|F3*3@LC^33oJtlB$X4@Y0mD8-Y25$3J$PeN1QmtAqVCaWo#eN5FK56&I%3?>7B^x(e<=5RQ%xS{u8%yCArM -z^Sp*FRj4pkWTQ|!E@ulJPO|q}CGZipD&IF*5&Vnl{pAGc%W~u~c?&~{CzjZZy5ccY -z{S>qo8l@I}_yk6`c8r@PVG?a4^-=)oyleXs10-iDOZMoYRh{Cl!K#cqQ>2KxQHc=^ -z0#5{zVQFsN{-H)&R?5fa#t5WwLRB0Qelsl8fbw(mu*sqkNnyrNJ;5AkNvp2>h>&=m -zcQm|SvAY|+Mvss0DX+SSlfcK#l+B5Cmn -zl_GQ0lEAVzBO5JYVvn^O*=3U*;o&zf1}VJRTePTFNtRHla%4AS**?gBEgmX}8E?CX -zE!|bZ-KTFdnrs>&J#9Gj#4!-Cg}E3zXw+#(=w`$D@^DrR!N{X49WJ_goWBG0vEEdC -zE-|{?IQDU>N-hA{7@^<3NcY{JRo2V;;cZ`?NiiY{rkn-@$x=s=T+nCbuv{LGL@szX -z<%9Y8_aT;$#gTIc`u24=jkZ&qc7dE&(b8Irj*XmyUK!N3huA%km|{K;+tW)L8m&`0 -zA?3mlcwK=@PsddIsHaBkm(?uKw2k|!ug-h=GWgg%yDy-*v$Yp1wXv$vwTwZxKP|cf -zt%{ijNqkzYmO=X^!Tqb%_{5$FT1H(BRQ?9uxqhoatZxSJ$M;JA?}sAU!Ys+5#qT&3 -zlm{>mps;@p|B*Jyg?LtRMl2O^HsZJ(>kho`oF9jRyuYBQu&m&aXdDK_JBF4FsA4>= -zxyWYbaJ7#%p}MyoV)>X^E0%Q~k84~@M?kYJdxX|oEqrWY{?59ao>AheCr}Ts+LJ~d -zr#;e%v5i+!t`wc5l3Atvi -zvgBJNSju~gBvrShJ93HkSISp^1n=H%%6Xc3hv6yEzuj26^O$}G -zq@)HGGM6WjO`q+O4QO!*0=k#y1PBWd6zXf#HcP;p!iSSz$TGlTEQ6NhxX&xD -zZcMTa9B@F}}IPn0%GEsSh7mKKalP -zxG@;Ig%|3@Q{F*LXXs#x5&i_K4n70DQ2VyW#ffIF|an(k7w24^^q6dhaq@=&~YAmSjlwk{Ddg>GwOdQ`B5GVzkEX -zP)6Z1JR;E1WFHZnw(8suj7#^gBw{CGBq1zq7%!6{XdWJu25I(`O24P_6nA3nfw*W^x@ZV6dc6wZp0 -zmwpmJ{QKlFBlU*vTc<4znlP4dN!hPwPSN9X^{ADjFxhnWOi;0bD@H3X^Q-A4t2d3rDORUS$c2$t9gap0fK}Z!#;gX`o@}%eDpKBqg(X-n2ePhS9R -zaMXuBw19r_fT3!+qw5NhUC2C1bdc%Xfc60`nTk6ZI@aL{XgA5fs&jz$7HfPD&Q`L= -z5&AnhoH8b{K~HPm3a%mRlm28&RUcIiftI44-UCy)c3@u6Yv1+UW3q-!sH`s+wR|h2BQ7p)N&PgEb^^eg?ND9FlKn4ZI;x6|DUa=0%tCCHzjPJcE -zZe9}31EE5Key=(WNDUoxYMxA@)dU%tA%h5E%Tj%4X})<2S{yC^+f4aXx1s2_%SPjr -zmrYE{1-*`dwsK*}t)Nm7c8RluZ1Sj&g57bL6?o`)o<9mg^jugq*4yCAgcS5osV*%?uZ)L`dp!&2x^=uD*xNrkdCk -z!|ajuABq8O)XYXp6nAcA4oW!H7O9^kCIF>i%Jy5C+A_RPsl{XkK-UvbSon3>$TX9K -zXmE0nm)z;w;%qdEtQhD*Uc0+o1!(jv0bZNtQq1XzHEQLn?m3kGf1-B-=N*gBKfeF4 -zQwszd&h_Q$L%{oblNCM>`LupuCCvEq=o;BkxRLJbBj7ROsf^byohRv5>o!>cNSe%yl -zIssD44h+4R8EP@MMlLn>;L*+l&TqqNZy}WzR}tszP}An7V@TEyua^!

M1Es?b!|p;+q8qS=hJ1ecbX0zVcY4|JUG_tX^gr&j4xS{cCW8W^gye -z-@?y2qNTTd7FJihf*LlQv15mF__y47a-91S)TE2ciJrf*iFb>QcNcvmyaNwGdEqq! -zI>Es%32r_)-mOW{@p!nUR42Tp4A>U7 -zpgAH>^Vd;uM=DGDg-A_ycaBKcAh+NN=cxEEVN`C6uIBRH>Sp0yE$FvSfucAGOL@xo!5qd>u&~W+ca91P-ACx*!u$hRymN0WfTYS8AV~y -zuNZNzeZJ#Ov`$EH>C-*O|0<0eB_<>ndybp#=ifm}KyN_W)m$ziJm8b*l8O%T?oY>* -z-{j--e~kekOkA6N^i6R12(hmvp7YG2A&wALpDZ3rvT#!*S_OeFd1AO1Iuxe9HzOc5 -zHKaM?(UY$4Ci8=IzgsVdPu8C6oCT>`$pIfrl!({WBw~^^iMn^lVIG5XB={+MHOoaq -z)}wnlVZda{W;PXTN%OGfywI*xdt)Uh0DOo~rD^039Rw!hnnl}BWyB~s!6p=6cL6A5 -zDT!I~^O8D+_0sKG^N?5p4B}6Edd}{A+ld#Bw%U?smvIuTmJ?6hO -zbC`p&6+Fpm1Nz2NdZ&GlX+ztspptQbiUgrqLX-{TNA@D>H~JAHK@#alNfC59Y($iM -zYkm>@tDdeBu@s80H^PHbqWvffG^N~?W2L%z$VR%rdpl&TY8^24P -z_@3AckB$*Mu9eHXBCDaq1DT4iQ)~)BGv)$Lem_$-iBcvxIU5bH2glIW7s~czL!1IRVtfNkjfiMdw|D~lSbI>|+l{7# -zn2g>mNRqqM8os;GeXH3NI7zq8cRwM&U&;xn0UDwl;;oRy3X%RCDeS30%m!G_vLl^}^5-d~2>Fd|vR0H-%z{4c{T<^0WX4=Cr400Y2(Gu$d=-wZeV|1jKH -zY;XZdwhzVL!nxhK45DemA|1XG4yKP6ah}Ty$D6r7jTlSH_=6$k_=SQu33|GXtVf-* -z6HZoMdpv7jh~2(0aK?Q%{D1d@5_Z-S-LZ=!o-*^)N}A;DJmg4T2NKotj=$(=1>hes -zi`d0r#>*!v%6)4x{DGIo4vgVT7kqwTB(lZwsc9W*`g2W4(mxH32u7(VJC-`gbxb%@ -zP_~2os<;^T(r7$#4kXG8?3DqOVitfi(wA{V))z(rd66?zlCc~x^+@U>nOy6}WY7G03r(5ZtR#%?t2j -zCDc(4HsKn<-$iq+McVE}Pq@WS6jL{lQz{o#hG6$S9<{?7>2dwx>VvSO=ekMb2 -z5;xE__?i$dl-Rponm>s9iQC80t*+`mh+2F&UySmW^qJ*eoc?@mKVE;r5Y_N?iA_#kLri00=@o!&j7(+-DI -zdbh6PlSpieMrprBK0NF+qxC%m4p{hsWid9QmMh#e^5(h#gVBY|5>1gD5ueN^Y9tQE -z2CV@u*h&lbHql5bREo$5J3#i4f^J6a)IE|$fOz-b`Co^-tc_JzmX2o3rtUuu_kvSh -zJ2U9PovBGl!e57bX7nG2o8n)GTT{T-<%xE53daIV3J0H8PfsYI_IFvWKHri@TOX-R -zX&xP<<5)xJkNJ|q=9a>J`nM#A$G=5s2 -zg(UnqlPzOOLRtA?@WOt2Wc#0?K4K}9Nix`3Ac`wFm;=I@ozC&1`s#Kwh;8lg#4$d{ -zRHvyPHpJ4G%!vtq77qchB-iJcW6p6nmlmoX)Qi304bGM@cwCq2pFaBQBpgAeb2c#8 -zSoV2?PBJ=qYl8q`WaW8ubBg04Ck+VqMR{X;R^KJKV&VyHH5fSBrmdqTfRaHyIeVYx -z*X|6ge=sx$Ak7AO3#oUYY@5udkxA(;5VyP-e5AN^hsH^=LQg4e7PG$@A2^CFF%v8E -zLLJhT*N3`oHo(fEv*mXN-|S!s=B|iNk=_BDKP-e13Csy!rAk5D)sHX>hXGC^S)_&P -zGV=-x+>>^K%&;|e3GG>;1MTWH0OD37gN>|Tj*gguyP_QJTCIsSh3gA2kK%WNF5-pC -z{UWK;;)K1Re53i*?tM(6Ho~Y~v|2HUBF{L~-hAPn_Jzs@QL@d1#MF-{tTlW0*bi)~aa80C*0A;_FdK&!&_qA2RJHc7z=4QCUF -zRRmltg)&4(e@l=*hbC%qKRanDW27Cci!5vB`13X$2(GN@z*f%Wio2Gig%8<}){U6s -z%3SH@izhlBdTT-(`DT>9C_3}t3`x#?_zNepO+6UP+##0ifI`BsLr#s`oF_JD0AT9@ -zQB5W+n!9tNlqNheC-uOpwi`a;IOicnsN6( -zZARg|PV -z%hUf3U%NpA?&sc$2HRfsnY;a>YIKH}Qd+o%2Xs;PRkbmqn#ri&?gxIVm2kIS-P?H8 -z7*&T)&!m&U`;A8XCBF_JMCk!1=iqOEHzZKZac7C8w>7LOoreM$L5(0wqja^aQQeaT -z!lDpYQ|=rZ!1H~ZVxWAhQFTT^cJRVZEV@3gJmro_A1`k3u#S4`YWh!PcFU`*CWn>b -zTOHb -ztpd($T_ek&!<)iXE3_iSO#t&H3yx)H!N+1w0d{IPoU^jm)F9h<3&i9Q-DbJm$6(IE -zMCzJ+F5CEN^D{4zx9aEo+LgXorx&WqCQ2vV{eekRhW=U|Y@NXMCe@NN4_!Is0}D6C -zRL%i?J*F|LX~gMD7+tqQpf;B=*Fl)CFOVz97&HHS|wNpe*Fj4rHv-vveb&V%1g(y;Woyg63f&A -zEhi7_s)`A+&9Njd9k8$}Na;xcx7)uwT){>QIkLEkE85@rxZums#)~jOi@;hu<*!P21GOU=SHS+F8Yzol7&O9_~ -zJYjI^6f*t;af8b5K|!t3XdC?(#7$u0mhcU6pMOK#_)y;vH_11|J#q8@K-`PAU3j}? -zCR?_37++3A=2t$CH_ls9n!-{KYu*xlXVBlPe?{&%415o6x-k07mBq|BnaQBNJBe^0 -zt`V>B16ivMvk`L0Q6SJPZZDqqzaQBwUV=CrhT{O&D}RJQ5a}0t6jyXHr$RYIsWz(Q -z&9~DQb({LXBl$K6dUJ`^=q)P_CeVRZXS%|*xacx>XD0xQ0v9;DsuU(Eea0d8>mSI` -zRF+zs*>aSYQgyE+ -z$}O3MO8vfmLO*PL6>m|5F(_JNYO51Og|aJxaZHKBAuXDa%1B>q@3a6OvgtNu_kY0I -z=cE&G(ULV|?Jac$cd!tC{uTV=@OaG7=0M9)B^D@-71%XqG@XZSBrrh)bB -zxJ;!#hR2$(@L$9i(5vtJV<-8YO;5NK590O~x@>jV6dc+0YM-Jh{#@YCfts2y+V8h{ -zy3CxF#+^)Sq_4%<=N+>7WmRj$J^*&vouR!}uuC=7%JF9BAjthtMHYn?hgpA -z7fS&fxZ!1Z&3N(p@&51;yC#Q3!!=AFid89h&=%h4bHL*YUt{j=*2)FmHXBrL2t -zR$AQ&{Cc;Wc5f4E2^~s7wk#)&$qyXq*#~u!YuqNn4OTPw_lUUV4^W~lGn&m_+1)S_ -zmi%y6$#ou#=pf-zH9T+7APw&Ri8#8jrdW4!U&qx(uQ0&4`UDb>`{^=i=cOFE!Ax>_ -z!HtssP%yurKqECwOU7Ty`P7sLd-eh#v#vsaA?}4KWUpHXhvtzyKry{uS#DmGe;{t( -z7}qpsruMAbR*Y=nh#1$6x+p(6JBJ_AsU^18{?uVI|3KUT02^BsAgc-A6LGz`v-tl9 -z;yho~byrL2ZY -z&iWb)v>bSjxJdXY&!%5L182)4Ycjl_;HVjI3>CBD0H}^K)Y!p|=)S*6{I8U3A3S|X -zHEed1oa(DSvxdyw2FCq<|D?q@C(C{82JzIUje$$QM&vuUwNYsH6^5Aw8RDdHRVg_32_UaAWTp1R_t1rW -z+P>^*N@fj<(C3c4T1wby(cW-7Eo=`vq&iL(?vHq;{EbQ<;EpR##jZ;z -zXp%Xh-<$EFg@$qyAmg1CaC=>8*z?`|MYqItC-qu&fYda6ym$8*0D6G_{9aM3#*nT{ -z*B6#(b|W#ES~gpm~Cy|k+c2H6RDh%ZAjH9v*(qBwhBV{h@Y*%d%M- -zlQC_JQfTeo^Q{fqA>Wol`o{oG3o^^E&uSu%y -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 -zDiZ;ZlLQWv`2%D3X<YNC)o -zyUs|5qPzQq7Jist&itM^%0y;}10Lvm1CTY63Fx=)RH3Nd)^j6!K#!)$Do|IDBAn5% -z$paCO=PfJfFMtf8d{HmA%j%71VLHqRejqJcsI42780MQtYQO{!o53TERf%@#lQIL~ -zhPH1RFhHgC1hiSt&mML7g`3Jg7XrHY)(Ft`gW~g0iR4>}T?R_F%U%hKk%_~O?96=- -z-dYS$Zdt@wl*uBBd}y@4Wr&iWGuU?)S+($^QOaZ=vO};lShqTyDg$v1?~>En?1s@{o&yzOT_Wrs&9BzQbwg9 -z`g3!k%LETFL5&g-vxjTv!{kKas=l<1nj$@xxV}sPeM3dxG5y&eNxP(n -za5$vYyI;IEg-Zv0wiip&M!wfyhT|?{du4Zn1SH7h8qt%7!8_9LlXmw*X3a+5SOtkD -z9^x1lK@f@aB3F2&TpZ7I{3Xt50Tbz3Yg7U0KuQ1+GFoYYs!9*^5mOtMN0uQfr7nSB -zJs(>^Hf}L#nfmzI3Z{X#q;Z-uvEmV^1h^()ri$^E%8N6|d;Sb%NWwB>mAn}b)WIeZ -zQRE5c9t(jVWC7rnip}^#bQr-kTln(vXKmZfOf?vNBaFtt{i{EyCo$4IJ+g?Hn}zm6 -z8%mq|$>P^p2v6w2X4;4P(C}rX{ljo0-tk2G<&CTVdG^yJ_>E(aQAZz#ET(JYGgMeC -z)quGKpH(ZN>Ykt(j5Xp$RL7IP0RzaEL$4mB%20^1q)&K@65KAdL_R>xu^ -zlpNkTYKd^m{C0gf+p}jB3Tq4M_N*q3P3|G0lz0bBqFPL44;^aH0HK-ELs1TyO29_n -z58Ws>n7${YeCzg{O_dnpRhHNSwVe0k#z;C4hq7R=GTDkF+&|UxR3|Xv0bp*TLzwKc -z7>b)PsaI+K^2<++E-+ -zh$zRgdngp+n|xjx=^D@T=w1nP(MDJEOT`vJ%)XH7ET+T!!BLGME>hH3g>hdDr_=yNy0PA$bV>THY|!j(GuB%iTm%}>kmg#V -zxD9L)e)CE$fK@36NEZ`myC906?W`Qzj_LK!ABxl6$f>Z(8gHi$Xc?CIvpQ2;vK5SU -zl&@n!t$H`vor+%TOEUa~FOQtYHdnMi-p;NLcOH&DF9?UOma?nw&Zc&Ix4IrNJ`XIC -zn>8rET0raS4!hLUBs&3Z7Ffr<~~)g -zDg55+`s{4;ta&LvB|2l>$AQ0vQyGpNAOGBB1qCx*yn~O$SmSFR)gv$6qRzhtCtN37 -zG6of8^ox(|*hbhO;=I2TZ0d03RVSsBtDV%fyL$N%Jt_xPJWum$+{a=A_4-Xcr>X&B -z^2nre(+x5iY1OYs>c-Z{6^!K7>tIb^Cnz*8B -z?J4GxP{e%4nyw|VooK^?_aB{e3@JIv={Y&{2$<%keMe8CREB$|G4sl$n6q_n}7C%465<8UjU-eZvaFGASjr_@xdDWUZ=gywtS90syv4 -zA|?0f$`b6H1Al6$q=-Qq0Af54^IGEhG6^XwzpI|Ufr_ci?VZ1)`A%(vWRl&&R79d% -zJ7`9(*INNGA?5NCPL3m5oPv1Th)syr^%N4kUm4cHt8eHcTkEIy#4h$yq1Xwrut5GDd -z;OhO*kr@&dY=r$fzKEDlC-qSm$&;&FI>@ObY)XN;Hc#U+P4$*PhSb-S*DHDhl1v%yI|6HQc;h750i6%{L~Bh)!rsJnoevt-N|ms -z2^1O(n3m?uwFm?oVdxl#Xe-yb+@vn04 -zaCAZ}AEldSs){%xry`fV#4-KP+G@OgY>6(pdAyXCbnk~+e4-)`V)yG4Y)^mJHi09W -zl;4**8tM4m_y%}lNzC4WZ5jFPd&*#VOTQ< -zkSZ=>^Y!Tjvo>3L;^lge?=w=5)f!&Sj}jKuHhWJ#XW7`cZb%_7V_%PRe7e!P@Pk-w -ziQXAv-JQ4*UGG<4DLAd=r5;?7&hnQ-+h)zPRW|A8$|h$fBX2$%6dnuuVau^J*R?4a -zZPZ;4^`D#}mLYYorR7c*dv1KcA(pyCaqkqerCYsE%3m;6?doOPDktx^6)Kj-Vzl2L -z`aaRS94B=t%5^ql0$BEf5wZq=*eeUHz4#{+g&&?v+AIG|o`}8IO>OorC{OrTi?psZ -z!UQvB;O+#ayLx{haK|4G4=G*Kb;YT~dNg=oVY7iO#U+Vsg$DN{%bi3I{-lH({5{2o -z>=IsqyOy`Guc4~xU3(;<{hcT4K(bMN1^D{jR2egXX)+?%8Zpb6ec8>v?Kx|$YrX=B -zIpMsEpw)Q@jZU|DGiD6WF1OgieVw~lVH{HVxa3k>M?q6wlY$z<;9m$TPD`9OMa -z*ccF8HoWj7pe3wP*>djcA38mHubSPxE -zs`}Gs&XtRwyld=or#1o7kg_)L^WZ?LPNx%@Rh4Gt&ZaBb(czXC48mwNX|5JNF9^T`Js>W?cTex(+G)2Smz47ER-FB -z+>Z-r3ONqj=SZIz|9l_v5g?8FlVyyYRKEIu1jlxQpl_#v@H9+BswWd_ER -ze)4N`4}Hf<7%0U!QiV0tBi}!KkimS*h0G9`@~F;TtLAqeD`g|eJgC4$WNAK+*2a9| -z(N$K-cnwR)YLg)krqXc$7wUCj>V$O>c?XOG*#i!IWRe_%iMR1K2S=QC -zRK(&e@hV!=W{sfxp|sJ&nFK=Y5qdH<$_o#0T{-Zu8XspH~Mv%rMWN@Z_&s -z@E3X`cdYn)nvcmML|rjnDO;^vfxAY?7p_X|{~vF6!ITBSv<;i??hfgaPU-FjX({PO -z5G16frMtTXq(M>|q@+{2yOi|32o>XgYQFjY#O^seyT=|-C>w4}4(8AZlzJ+}#;z1j -zz8t_*fa}+>!&}Ir2Zck^l}h8WZ>c>(HmknU8h@vCxV5PJq8x3myEi2X(?@mCZtD18 -zPu}ne&Qwd=VhUl2ka+@S!~y6a%?LX5su_Hw=JO|%7wpb2%Wdh-5+!Js=mWsO_6FSd -z_W13`c;Vd0ftEp$h)R&a9U;SN?Onb2JSWD!P;n~Oai5`uX$NdzRVdRwGa4qVl9fbC -zcHckEweyY0&(KoQNXw)0K|(uv-UPbN@4NQ+DZ{(S%xfL&fllnu9LUpo;dir$Ba(-y -zt*7Vq4rk4uJPIl#*zjTrn9VjV4>0`&a;-z?CBd1U%DSa9Qisb=fG}BvTrVmIms?T&8bn9puI-ryOPsG7^Eb -z3F&v2$q%rU@w88JC1SiCB;oPfo=n0A9=NCInY{cma+1%$SW4`jpQc~e2xcI?(V^BP -z-ooXpeRg-`qdCT%no5sGpgWm}_ -z!0xgtW1*;C27_o5>p-06rxT&w{QC5nWY^(0-WP~`1)Lk0X^1t+$lK4TUMJHkVY&vC3U4pxPgQTd@PxRX?x#rfGQXdn9@+S*4qk&C!a4LurL;LzMg3LUieB -zl}rqWlcC$zsRYTFX$zq4PRDX7xJ2WY>&-(`^%2MKJD8_5v(+56scSE_F=gtkzkE5h -zGBiCOXgVALVNuh(Jd1U^sO~E(2V#EJDylurw;}bN)7$BQfS_K|>r#msx_Q2>)mS?) -z5iG);aC)}H(&UP;SaP;51&xrgag@Nfaldw!j=p4thJaFu_#44+SZu)X)n)X6-~jK_ -zKAk#A@P63yZH#d=~GweH6ZcmAdxn>DXjF5W&u3 -z@9(qR>pj-^lgQ*>)<)EgmA1An*8*l1FFwmvym~joUqSqNvrMk~b3i*khySa6XS$%! -z!z~JHd~#F`>p<|*jzltbeTovgu&_vLKPQ)uf9uG~IaFVUea=z}8_ -ztHKhmKGuX6NaWdMd%bpot{=Ov8B1KQS2Ko*P1)(2KOqa4Fnb;8YYb1V;vvsY91TC> -zp@kIgVvMx%dyhxBrhJC7WSQM;(Bn<)1AkgZyf={%bu209*)8lwn-%$KtGN| -zUOZkIl_(D&n*StdA_U^2#;aMcjF}Lhv}^>@M)TU(d`IX75#Ni)9Nnqh)SpK`n-J6SwY?vlU?=%Dz92PSzcp5bI2hd=O6_9MStuevH%cLOaK -za;AG%sz{DMLa_+-cZ^BNQ}bqKW0``Fq>WnR!>4dLNTko?j@9fEUgu7VoHbhf0HrN+iXW@#}XQjDwBzi2^7Q6lc3S0c4_bYGyrdG8sNg07ArDiynEDACmLf{ -z*K4P9jJase-&y9<9KcRuHJIRe$<2neK#-(iF|mQB&L2m0R^Uznr@aY6Nm6_X{SMdv -zsddbkb{Z!n2X@Ip_8q72$5)(hWWa~>Wpg$Wuw4?25z>|-yky8tx6#vW?DvA@-%E6F -z@!CV6MjJa1H;iux^PK66kO&(TKM{QWNNqbl0uA&N#3^$o=h@lQgTg^JZ^;aMmUs*W -z#ATvHAk_B0c(@_kuZYeja{VYwZO^SH)tq2TUxMn>zvdjpAfAczmw(e3iCL7Z3)|#Q -zsH&sFRR#pc{iShnNT{-D&dOa?(B_QX`ewJM1Q@f{0sD)L4&nPz1u8?+9SFOrE6VvJw^4(j-r#JpXMD+ -zngIM7Zr>rIzi*dnahL2zC0$_o{wV7YJG6YIRaI?Ub67bGOSNw;YiQ4lt@vuz=krC- -zJto$KRX1@c0+d_ChU!*3Y#E%5EAfsn1j;z$z?e|MRVL;vdl3SW!25wS&7$JJ8IBz5 -zJ?&_w;2S;kNDa|RkhOCyIWh<&Vp9WbVJ?L9*y^gsE>H -z*VC*`4$I65vD7*6{j#;BcePZRRs&&TB+mV1%`mH&7WKsFgc#!(S>~R1C6JvXT^>8# -z+h+$x!^J-Kw7WJdSVA;G=)4q~?9NQg*(;A%ZRZuj?AX9rybDj<(%Y9*ABVre42?Um -zXCx%3S|n&GK^HxdMUh;$(0)h-%Bc$X0z?;-h -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 -z#T31^77;NE5=xWd@(2{PB%Q@^c -zl)%74qe0JSY~na#H#pLUKgI13$CvE|t-73zeJrQL?-*p{lqm8{s#<~uUkuV7oF#4N -zc%8v*lvW!A>i3-1NbU88s~L{^iXWU1>`Bb%tCGfd$_Y8*t>-4h{^rna*DfMH^8D&F -zP9gFu89Jlz4oL)2W8_RsC)JwPiz51@F=|FYLvIlbf=F0E=1^F0BkFuSBRg%@a -z_67`q<0Pqn%wthcv|ys?+D+^chEF{3PdN79b?=A@d?M|ON7i(MX9wtJPTkh#>@xc*|eKrRbxP3YjWO5GTz|XkS-cDzWqqu -zo8c=rcs8wo=`EhEyO%Wwz_WYq@NA$MCZ{_*dwwu9kU8-Oo?TgehiCKO;@N?tK2t|9 -zMF2c|=MK-NhhHC0E4DQ&*_fwtxxuqF?(uAh?TyDoTtHGcc=o2UFqTSxjxXG|8bQ80 -zJi8fyXLG3j#IsH9>F{sy?0^FRo*mzq#Sg%_K7>CfVMfc(&Coo}D!Vz_UjI -zcy=`a&-VU-XBR=1{=l=*ap7D5cs3QiB^w5d4s4de4?NoxfM+YE;{{{^@N5Kc*r-`@ -zgy(7)>!jt0eJQ<0MFiQy~nft>g2!N$-J#qoGX||vKJD$BBmhkZ&&(^)ivnPPWghKD|Y@d5PTebHV&vw7Tvx#o; -zY)4oCp1s(y-0DHL3Ba>sf8g0@GaT7>c=q-0csAiLJX;TdXD_2tH78}A|AuFO`XV&} -zz_T~*@obplUwF2>ni=ICo=s?hO&I~ev(NAFZ1X!jd!H>h8O%}O7SGP(8Hobm*-=@1{Tc((5^JUbXo -zUaU;;4$p=k_=#r&sd>fXyj$uyOiw>Zi-GRA!L!o=cy^`LEuKxgxLU^Va*JoTaNpqB -zu4OlPw*N0Y+Yf+egUj9G*)UiDJX^x)7oKf#i)ZiN;n~M+f>dY#JR5s)`!_tBS8-1G -z0iIn0z_VlS@NBwUJR3INoDqO$`@lcIv*}G-Z}4p8@@5uK30DA~&GG}!F1p3D!?Z}0 -z6>jkCqB}fW_8!kZ2H@FwT&C0EJ`eG1^i?7zEnjaq|KIRz)ao5iQA;4Y8$4TWkhAi2 -zr~_PyZJqjkqa)W30)@!`{3M?{nhi6j`yq#-{y}`3DcbtCU -z*(n=TSGRa}&qF-B27qTnrT)aT(cl4ic3}T4o=y4y&mIHd*>Ue5;Mq`A<7>;ec(w+? -zJ)Z5U=nq$le1~U~R!LSU+~C<={{hdgzrnM^yl?RA<3I51NMLE+oO?Wb^C6yX;P3#? -zPQyK(`vcGR2?pTVG9M#lZt!gQmjFDw_Xf{qxWTi>ivf5x-@ozfp&L9Kve85K2cC`f -zC7t29L0*BBAF)CP0MAxv@i4OLd7WCZ3Ba@cf8g2tb6(+XWF$9uwt}P`0MD+t!?O+d -zg-HQ;HnQ$5o(*Xih99{YJ)&}hXNT3cle;txFA8-QoSq5ccc7Ula5&rZ0-vyr{;@N5WSP!&p) -znj1Vj_Xf{4yv4KW0eCh{=|AyoMqU7(T{z1Ue~V{d-r?EAKk@9%8$3G@fM=WD;MrEf -zqeZuPHjB>{{zo1!dVeZZuy00+mMEN{Tt8T1K`;wCI7;+X8?G%$t|9Z*Zc#|HUQw+S^Wl! -zf8g1o|HQNH@A2$3lY2b7?FXK1^EaL?%6Nxo_y5APhyI0UFZ{r>YyO32f4af5{d#70 -zC3_#>*&l!4*=cupcK;3yF#Yd%w%G$b8)|_CRk{5Eo{i9xdxK{~;rXgQrGMkukw5Tk$rn!z(jMU18~{A~1oju6Evn{b2LBV!2Di@q3(ppWhO+z}&tCZ# -zo()X)0M9KC33`Q#5go9h>zjZ#_Y*yYQc)WR?E1JAYr -z;MoR0@$4{^7x@qI>9>a0uk2{S(h7 -z#r_k|{>a?p{kV_@^A9{*;sKsbR05iLi)W_}KE$)R@9^yGJ3M>koss2x5xvw{cm`UQ -zZ|xK_pi700srAYJsvsLF^43j3WM`+gTFy_u@eanOs@nxx_HuPK7LT2DX4$;bReic? -z>A{pD&}(4C`iXWBW3(Jq&m$KK+&S2}2Nf6+d><-?kvomYPEOFW?6izZb%&@1jBXw{ -z1Ql2%~Dr6p(SVz%JbqhvL3VMI3L -z`OuSP-!$bL(Smj-RAVvxWIEB1xkVfIN3;2c)GRbc=9Ood>OXqaYCx^Ldpw+t{^b)) -zOsS@y&G1(3`#j2YD-x;Q2>hfHayB~PWdYCAeYhwo2X2=0P3c%;`XeG6B;_CgpNNrkkCzQK{t90sen+P -zm4KQ6fqulJ>yL=oGt?i2b>)*&wBXLyrZUjDV90#EB|0+qeH(SRF|j5%VYB)wW_299 -zOHWMtgv$_R>!=y0tyELaoQsPd)~A(*5NaReggB6=axDuplM)9S%i`PN**gA??bso# -z!>T+vXpJT&0{nU!*XB&xt2pn?O0@EwpGkWTft^tRnFd@sh3U4I<_A;h!Rb6X -zAh6kMHuDgqf((Pm+u1Lcq$|86qw;U>$<)$Nn*F@}iZ)7!O9bCqnM3DEl}6cYNn2uM -zXA({f795T_7P1F~zI`%kI-*JGOBZQkhWKg(CD -zSjjuaJ(=5pNbuMA5cX0P&N$FfgKQhTD;>YBF|u+^yo4Ns5?NEM^jJeoP$3ze>0nYK -z1Y-^;f0-tjN!gCr;2BkHAg?<7lRMJUwwwHiXAVYOm2#Phw!nr(uLXPqx69A3Y_p_glc1-PtGE$=pe- -z<_7vtx;#U_xKHW#+6BVTcyc#X>9wn;*gKpqt@^*()K1L7tuJ=fV5bDE4 -z*Szu6$;H{$1>uK2wqoP!!q!U`TNZ+RvE~>4=$JWEC2Uu(U!u==6Jg`yRj{!%qFo_jE_V`$cPkh)^PET6kJ$RHtl5Q8&!w04-k<|yVBv0Hbu!q -zS)jO)#r?8t*)FPaEY+z9V{spabt2`w*_pHif+VdPzU*BfzT-G7C3^j;0oKWR9`)z8v39_li5H -ztDXL=O*-TDjXbkE2l?}`pO73LD6nQ+(0gyuWJez>wlI1`;})b~?z)+UjKt2fCxiG~ -zGVPssnNq<9gl`TfxJO192}JU-jd@#gd21X)cY;i<;g)&vzZ0wzePV0v{26kcdb}V+|a@N1TpV;gc7f__pG0IoE6^M+|IUwE@NSbYt+$b{{X=n$E{Mp;Os^;{S&$jI -z?&)(ktnZ^)=@1x9(G=k_sXGzyCbrNZ3At-vP_Rg7o8}{IQq3RytSRclo_n;$xyWG? -z>ts^`v0`b?6iQi0h;)6qBza=aOl!02j^{*<3&H7)ecD)uFOk;nvmVF^ht>AA@eEJg -zOYzgyzC|GVetAonDi6b(5j|yXH20xUtP4Z*=q|qE4+r4xSKkg;x0G0NDRXS+^oW7R -z4uYbkU$l!hc8)0d>#paFT18pH&ZebMr{x70r$1{deXgoZnrEvs`*m}G>uc#jzu$6_ -zPPu|{^E~h4nmWDc5L3O=Wa#M8lsDJVqUzn!@1hg?-&h2U*{e3l|pN`WeqVOPzeiiOvmTh#jk -z`fIlHDB@Vg%9jKMBnzT}OnB5fp9pTo*dRI)db%TPO%52CX%$*w&BH>krsOdpOHp!< -zNi&P2b9jq!zf`C4_Hn1-kFmn}qDI(kRb8YL$87R&_H#*k?R!?*z`KSd -z3@*VeHRB;Cw&@D?Mf&KeyF7OMrgN9nHjg9|eGYk*v#B@VNtvhj1~RBe5Kl_!+py+- -zu^yCCuvZTl!l@Ec_Zist=NF57C2SM1sF`pfR+D_fCPBl6keWvx$Lqv`E&<%sAoD&E -z``f^HY+ZcgCS+Dk^kVL-+V}=02?QWM0(5cMa!oJ|{1_kV`T^(Wir3WyrX=K12z7FC1MiTZ -zJ+(y`)G-LyYnjy8vGq9>oy+l&C>k72s0udkI?v*pR2&sk5sV@$%&l%#eheRnc|7%! -zhuw6)&lqkUB^GPAxqiaE;*tU{Wl}&9c%1oo)FfQYRhqQiX6`yGQ8;OhtDO#h8|BEB -z?FCENi5t0qal)%`DFtm-c+>ZaK=T&s1ioM`NufO+(rdGUoZFxD(n>2;4@jj-yfrov -zrX&lV1BKpj@t8N>L&N_g{qxcxKl~ -z@OT>23Z2jzDt&4-w+>83AsxwDCB<11(l -zJi71G(r0R8#9P(9qDxV+GSWTZJa0X!?2NkJr70pg29*0)98$gw9(tdO=CtmV!UrRG -ztfj^}HoX9o9Fae678^6{)I^O!0jK~I!&jZ|JE;g$un&EkQ}5fBR#!&lH%YR^X9zl#7Y`ppxjWN?7iCjGr^qKE4TRIeI%)mfSZbd) -z8zT}ONL!9uZz0y1WLO#|qorHm@gWv|lkrGE5!Sk(PC}v)l=zj8pFErNa<#hQt9>a|?sDBK3t}#c#}c -zuOhxMcTdqK0k7yw_bevyaW@qa*q{6KFtw>5D(G_$%RqgUV@#@#VNd9f9F}-EwFSVGN9k0x@r3L`UEY!6B>;_ZL|_fZboKVHyd9U1ISBmddlWlfhe6G1+J3p -ztVJR3D=VhEE%llQwk^(lFBB;qF8vD~qtx~eoZOK?FQzAbtWBTt8iW;flti4$g*Xyj -zLc-Oi`BJ%XeFIIgl9W*4Vp`vOjae-U9B~Fy&E@KBIa78agc!Tdf5FhiS8Nx{&aJQu -z*TQbM5w~+nz{K_K@r1nMI8}v{1kIXmSjk&O`nuuU)12I5kGJ6pe!3|bcCgvYPg6o?88l3Mm<=U>4`!e -z>qgSVqr~-{NKL27X`dsl*a|&{`!g@`o8?pH*Zvb)5@44Z8XkQ84Rp4PN?uPi+vG7Y -zh{vzekLpvDu(o!v%3|j0N!oJR~3@ -zXqL;G6NXh{ul19wVGOgRpvYd_Nf*jMGoG=$B&PqCpgoG+==W}{oH@ETDJ<%1QRb`r -z`4~~gFDWZunc+^pTWJTOx6A71*i@09=9;C*Cg#M2kjv3 -z2CQcEh&h{3p;8=XSqaqZz-7bei+z}$&%S+i&mtthtUv*lGS+Q!LPt953L=? -zu0sB7Ng-Jc(_W*bRle;xTae@xQyI71BC8;y_dZlP4bIiXa+t+vd0ciS$jD<40|*_- -z)E-{AwDBqb;JsKt2!(hT^);9S9;<~@{^#vy>VjMJem*C!>rQWvl*w6@A>+GtE -za+wi6e2rK=B>$%sFIn1C@0ydVWiO?$H~jXFF-NvV@fEvY -zL^R#@Nx;~UqU6DcY*$%9q7101m)&wSj!(S;aMm?QOJd%~^qub8Xa$cczidc-dYWMv -zfYWM}@dPU>I~psWMojr6!+k+5DMmHW#s9mpJdXIui` -zjEq<;jkiqS*2z4G{Ix>c`19rruGHMw`>dbB>o{C6FrqdQPlHmX!9vPj@(!^0<93$T -zkz0CeP}(4re#y(%eK|ws!PGG?w1zcNV$TRJtP{K{Z(--P3S&Gn?7dzIY7h|Q(FQ5K -zUKW9BFnKW(YK3Dv2!Q^=ZP#X9B(a(gm;D;BS2oCpYfiksrn|YsNLg5Qv$Ma@j=U_A -zpE-VGPyKCsIk)T}$@2|RQMgc8jVIEJ(1NWNH1JFMz#jJ%<+cKiBei(9ur6gg4V16Z -zFWn}<$>hj=`ccXPX5M4lK$;nOb3YOCp0wjWg*Y@8S6UY5Yd)|679Tb9)=y^e+Eh}= -zk4S78Mze0De~-0NPBd6P%PMXau?3&!?)D*>-xt4Ab(})&Ii0e)C+!F%4w`W;<&3*T -zhMjA?6!xhVW@641$Z!)*5jsY%F&sWn50B~BGP4B>nzySy%#@_@{5c*^)ZUIzVpH%l -zo)-)f&9c7;S$p=xQjCVU3rvrP^BOvno|C)YUrzbpuV`8Dw(zVG%`8O!yRq -zJTstFLwQ6$T-St&TPq+`(4yl+Hn%7@>FAmkr4jT+gVtNGcRn_YS#&YJl`oD-{D_dDq}t+YbZEE^yX-0hWNOM4o}iDEYW4b_ivsTq9hX1jr#vsSHBUgT0|ud{5v)kB$plONZC7 -zRpLxwyh7+D(Zomk@B$Fp&R)`D0?AeLqEqHmfy@gEs9?0JeBaT9hyLEe8VM#Lf#xp} -z=ZYCo++qN74Y8Sibzb*zexvqmn>1aRcN=SUdLmb*iLzK|I@ox5mj*7ks_Ym$ZCEQZ -z`bqY2$Dtmmj1~`Ke0^+;KGJh~H(w@kf33E#@{ -zmm~Cr_|9c(UM)FtM*Dp*090AUa-CF0<%C6U)tAiyer -z>IO{4QSF`AtY@8WlJJQ=00XoZX;(4_VxtHW-NO}G00X_z?Fq@GHBrPtpi|jpk3G^0 -zZK)_S267LLc9hSXH3FumZ%dxNK3kdSl2w!F)98J%vEhw#`f46@+~)OWZVg -zGv_erTsnF9(fO0Dx)@d}E;nJZC_`N-Au%c6ZUH0ettT83c6~7I4UDDLh33yb*@bbl -z*(*llTXa&8lfC!!zB0r`JXq0DEsEmjdTXNgT;`QG#|NeL65jX|%X;FRbIv;>Hx)e%eOHxa=0+AdGMA)9js$|-#d=l7e|HaZ}qfT$3_#Xa!ri`X^*VEp;zrUKe2avDW&7Sh9V}Np3tNKX~dl -zZ4j;X=(z4!uge%-7irsw2iS!fk+3>7h|y4vPW@A+AD*Q1yr+|-OT?MB7ksHQ#tURd -zLpMxYMAFDtr)A?w5=JywdbrI+WF%uRGUC0k>htsDQEj!pEbZ2tK@9|e9yw3Bczc>0 -z6Bn)PjpF0fiE?T3T!KBv -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 -zZqPI0kMI|@k%ax9F?@6)KCNuFC -z%JwQgN>Cqv?8aOwKHW6CCgH4NogoJEF5XYUzJk#5F%qXu9 -zx;{{@kTLg{cgml?Pm@Yu?AHzTKKv=VbFUzkx~lOD -z_Q$of{e&0i0x9p0aYfEOZ9i;0B?M<;suRlqPVLvD@Xvapwd)nIj&NQ9eF_T#!Hka5 -z7c>;9nWPR=2bt?9{5_IJ{P=mybMg9m-*0aZjSMsh>%Ya;ASu!M$x7P -z#`s4W*^v)Sf-BYKTl1K$OEX?d=l4ogy&6r57w#Dleq?(7#+?@5d5=??D@8?ayJib( -z4*DU5{b%GME#Ko=(s^okMsB_i>wD40-wQb2HjgftvnD;hi`ZK!Gw71~w<8&Lb9!5h -z4Gn6yg}-OZ7UhT5ac9IfHcX7c|;7OVqm_Ntrqu`m6nCh`Q`yCoA}Kwa^%|vjBc&qkmE2cji!ezRTvNbu;z6+^NLAw7xdmu@*k2 -z|MI0Zbf@J$e+rveo*574VTV{gQ9KP;*I>fIDJD(X=IfK&(2)+3S-``%Ao}a^X!9)P -zRp~Q*{fh@qaxu7_^H0;PhrIR2gv^pk_sOjT-)DVv>Yvw`FuOap=n>+iyN(MyJgIcQ -zCM2m-J>6}R_amvgfTIMS?$d@L%UgD -zY93aPFXwdMBfj%vFD}R8-kUv;6x=oC-L1^m`Xg_9=?Hj8n%i(Ns`iX1y$gOL{yW(m -zbG#MC9f4_iwW-0vl1Nt#{!0(d=WqA*FH48F&qBd{Bca}qH%EngbP=9>R@g_LBKg{} -ztsfhOH<2-p7Mk#Jis}sPrm%6@n{$(`z;CDx=n$@D@yShhBsPygbM%lXzCIzP);iFb -zRa$|ppdFjfK4)+FCp|+QNbJwb7>U9J<+h7dqv1U?&k%KEK8IYZNj6vI1-A`ChB-jn -z$7_#O?u~ZJhYJulT`M5PBqrvX7Ac6c4G-D%6oxCPCc6#kcLzK%(A%aY-WJtvE`hCV -zzp~6H3!c0~`(UV&xt$&6*u^G=!>S)+D{jjWOUrWuEH&0&K=6kq?!vAf_X=c -z0NK*kE=)7#H|RYTcK|0OB*r~_O5oEusyNnn&6cYUh|ZQ;&1u96ObpIprHah1lxI7; -zQQ+KT9LDMrX3mv7GR(eIaGvU%MOt7<*7lZ967XOAmN4)hH5nS=L=3f1oVi6S?r(;8 -z-)X-?lKk1bvRClY^ajLE?F61$y~%c#aw8YS54rrg=e|SC;iYnV044V7d$_S)gD(a? -zrjH0w=nl+ADl)?P@`#^w2}|GmCGobe;}7AsPcB7RX|JLy0!#W+BMFo<7`gl^2BLsqy`^@>9eC -z)U4Pye{Be?^^7%TMw^fANDE^6N(|z=`9692f@9S`8JyApqTu_zxdoHf68l+a6C-~> -z8aIE*j>XR(jg|WCN=lF99T~hbW>VC-C@P*l1h#M|<0oC99F-#hiOBQ?=CRYgFdj8?K;ftb)W%Xs_r0=>+R<& -zk4^Co^>FD)=vSWpj!e?To%yK8+Z092bxS%u`igV?ab0`m=Xo|2x;Zq&?wH*la;KX7 -zv6C6O>dH41rI%Zf$s+&87TyAn?Ul&e-zK2y@;5^=$ph>WkYqTOwgK#*_KiRSSkjTs -z;F;m6RG|hk1Vtyo|B{;AWJP3OzkTdTRB~x`)O-xqHsr&F8(}znXs&OD*_*C6}V*b+7Nw^jvK8*!X0Frp;wC|6Tu* -z(0iM~9E^Ov*;PS0%*~Jhcq7B(J(u1jiF}tdF975R+paA~bAc}v1qWvW5IR3G8Cl%K)Eiet@lg3bre^2+*!V!%t -zKdXQYp@M-8&3zsS6?-Y{tWd~2%H8Yk!pw`mzgu%6VZox1)xAQd^L>T1kCWFMdN}vl -zhG?mx9=d7G((LKIJJU58@ROLx-`_v3!|>AH#qVVvaeylUzH -z>bZpv$GRyXy~ys?_3zts4m80N-rom@E2DRBc_Z|IVS`YBw;&Qh+v({k((|fby5?98& -zMw{r&=$L7d1~gqSP6Z&r4PmNHH>?UdiPzjDqiHZSa!=Ojk8?_UU0GS-nAXpnCF45W -zyJoGE6h~HJw~<9$3?~x^%dK(L@ET3$X5I~~r+MLRR#AYhH*aAt}+dSNFYOU1CG3dT{wgXy!PPl0t$MJp~+ -z+lQ3KZhxbCg{kl62zxNv+^zi+L*<_gj~rB5L<8RKD9ExU7BwUjxYPcdHra%OUVy_S -zmEiU9x+}drP<-cN%};6;e8ta?3ts@8R56{yaM*B*`Zk6s(~&HK8IGaJ85+8g&u$8Q -z(V8^Z`a=O2!_+lrcJRU~?i#SnO*Q)CwzyYEhNW9mHXF_0wS;|o6aqPIjck(4BESiXAT|=j;N9AOar^A#At4(wf$l4pmKA;$ -z@Lv9XkHptu`cb{lCgk1%DzxKt!19PH2wzE0Z}&j7Om_YQ`g{8!E-UmQmGuJ>VP;D@D|j>!bFZW;1$sf>)1% -z!Gwhmt|_YV5ucJ0KvJ)upJ4$SNj3=1I@g$wQ9+Sc~wGOu;Y -z0dU>h;Cf`NQbdA%!z;Jbp#Ed>~$q -zegmU?3T@`kRgHLAdRj&lRNWv@c(9MFA5$jZUVljgxFrhKM16tW0sLIjAd7_~5(fOp -zF_|1@Rqq(ML0&NWACcXk7a(C#inYLe-s1`#qOuIJE?WVLK-%XP--h;cCpsh#Yqwn? -zDFR%QGwRGyKbKhQlNNMR?hASl^njsKNI_)VFf{^~VGd8~{8yiLoec+MOGknFr7hGR -zxeQJf(fKv(kL1j^_=`LmK4^9OyvsTi0=g|mBAw4>)~tRd%epYC{2pCe;C4F2Nky^7 -zk}#@)P|5T(BZ-ia)O}8ON-g7_$h_*3CHXssPK8B?;IW;!Z -z6QLK}jH&#Vbuii(=0O0y29ic(J8c{m)8RWRMDykGz4oqrJ|2`{cTQ=H7Ps7jP65-Bo_DkhzE^#=Z)fJu_vta+w=R-l -z5`w3e+kMwXn-90v@z|3ewsqo7Vb*rnMljyjKPliJ)(`W}9s>Tj=d$r&g}!))g1eZePzSE5ezh# -zCis=0v-MU2Iu`C95e%8Gr0GTup;RLnn&}lc9Bb`kiRqN&zZqhIV*`>JDDz;otFEDG -z@LC)-xI66!wR53gZfa4VA!MuQ%YP@x3!2s5P3ag{20r`nvcv*{+OR_>QL0mb`+)8 -z&05T`!BpduNv#!vX+D)U^S^P&IgV=ebo)q}LiwY)_^ti=n?^WCYUman2`qDL@nY>y -z!Mf1B$r?s^3^I362HB4YpTelp(*%t3{rDEjA{$gWe}<4?iniE=<<1 -zLcUlzcMX0$mN4C!h8;2-oI^^l{A+OmXU!KhWp*9NsL-2Hg>wqx={?$c1VEs%~ -zIAQr~6Vje00|e%n9xLmOXS`rcATt5Da0-^~Pmx|*JXxH5^`y4;tmJ4-$$4K4oQE{Z -z!5>b~2GB8#U^9&jH}OJ%Dg(eaMFV2Kc{;&R$%K~!7G#x|g7tm7%9^sYv1*DxcNzec -z@ebgK@LPRrjY*F+`l7+ZLhZXsOE@NHz}CH(1m_CI|$2wlG -z@~6huyyh-XDrLD*DpUgoEL>5S$4<6^=PRQA1A%An;0zUSRkd`MZWg8p%)T4xlwe(K -zT-(|5ZrUvYeA2*TN~z;w6A{xLt(p~*oKWS@)Kc|zqa0yNy0w1k*(hyntOgUJkHl6D -z>#;SY>|+5Q?1Nd`?`u9r6Is4$FkYDQ3giY-{8SQNk`@4r2=)e=DZJHd<+AOkUY-0u -zR$l>s1G21OsNe_AbM<8z1aks)Kb^qy$__4*^9OgzDi`yEfkGXWdb$aw#Xxlz?yFwt -z9p26)aq)iwXIJnDT;Ja>9xbFJ@gx!tpI$>(+~A6g4fXBq_)k)*(})fLa7ZKoVtayM -zFOMV2QH37r*oUwe#wWiRhK -z4eZJvSm;ie{FvqPx%Wml=v+B=zlnM>^YIsKj!<&FJ2wLv2-3WT^Xg}+tdt}xfv!%r -z-UPDRRrga1z&YqXf%!XYdP*_WRK;k&W?w+WfmP9xHtuuzV?k&cQQzuaM7P0p*TF-L -zF1d_COSi`DGCbGL_vsSqJCl2!TYCPm4pzNicAazk0k}+w`t^9fKl%EMy@6TPsUqL> -zyck^-dzYEBEC(;_zus{k2?MSu>lQDzH>FD3wc+$^(Fo@|htKR#B2U}V1!G_igwjE$ -z!w@$oVMQ)-hxOTs?hutGV))$aQ(&%(WlPsR)p`j;#2WYPq3V!lnHc!;H8earQ@yVz) -z|50SbfY)d7=Dq$6>k)yZ@O*GWDhbdf1#}C;iaZ|tITa+e3q=rYU_+|TAYP1TWM&Jx -z-hHKJ_JQQvP_!gcYRP&#@{pd1rJsQXLyZnz!*wQh>8K-CJ3a3+y2C^mP4hG_q`l#h -z9!GeCfwVqdqti_T!|T#|k@&&4w7>ButHbY!L>#j}?Y(rz7lj?L0;~R^DNhucKsCgm -zaH)nm4qhBbnUma$UF@a08J`OR09Z&^&ZDDN{T8gSOcJt@)J_3CeFr!C48qN%TrhIeOw -z?<`m*TvJj?x(YMT)(c?G -zTCAJ&hWYZI-pR8~(aKNAYjr0`_t$SRS;bcXZ4FPBnJ~WzuFZrt)9PZi&&7qCI^xj5 -zaMuIBJF5?o{G#@)uOsi(;AmVUtk4xd`S3%D&)zo$LZ;FE(VK5YCEzhv4#T -z@~5=qM@9ugPM{f)m8;276mE;NajS3(i>IimP@(miH?sf}+$p+778G+Jle#`p{R{LU -z&~7&cSo5(KyppKWsV(`h0wGM7jf4r6!J`NT%a*o!eW!6Lk&9!^BSQTIN52ck#2$&a -z))t623UP!JPTKk`1x?Nr1T@&2Sv6i( -z{1Q@;7if8F7Nr_ogeAR?{$z#@yr6|=&3|*cM1{oZO+H>YhIqv4`FuZ%7jv*yKi?w9 -zhtS$!A~u>Dcz{?Den-uA4z81y0{<&-JEi^~@;31Q%G+i5O<1MZ+MyH4MR}=^LVB<8 -zho!{!?^i!LU7;YTou%T*ly{ZQ(if6t(}!DXW7D%CvC<(vW-sa3M@tVgt|rs|yRd)c -z?eW}ygbQWJ@_MUlbA#PkG!4zkG#D#itg2R -z+|)4PuB+ymZ#}4g)IFi!)4^Ow17Ud~1*zzpWLpe0@wS`J& -z0k6s=Yg1K4AVW6AV$m!LXOBmcQBw^A=0z=UDG^#KrU+`_%lZ~2gul;)c8Q-bz%Ljj -zv@A0MPdqrxA}mfSa#Mkf3TnXdakQ8`jlk{}1-ZuCTu8L9=kJ7_@15??@2?r(F};e3 -zPL=%6n(yatj)00(kawsbh1=m3ooH{cIG!|QUT2wp$Dfxl8&Ao9(wTCU?gd@bRz-;{SxK=uPY+mQ>3|T-(Z2#falFZ0y?_Fv9 -z@U74kFo2mHsa2NT2u2?Qw6AdiHh|3AF_txjnhG}!#z!=MP;dd9mU!oTh4DbZ5>!sS; -z`^G&?9|pN;z{%!{fvU`~P~|{5%*FmHOJ+J|GV;|iEvcdNkGUMI3D-OloF3eCxM~F< -zpuvtx1A{l#EdHOhefAPU?;z@-xT6#5e;~@Y$$?^rSO=PjtTU*UTnX#dYTe~r(u((G -z%V>*e6$|HnYg}SO9RfQXW3^FJNzx*PDyt_(#k2(*?I-;x+zUOKgg**5i5IsbBj|t+ -z0vk>Ul=8m{H+|bk^CqZw8c!BY$NoPGH?qtt&t694JBm=?en+#h{OUgnx4FdRo1<~} -zH@>f8zZfz0uRGNh67UWJC?A#5<#0!Z0iiqLEGeEJ!Xn*uCCvM)QL=UVAmY*hc7CQA -zFPHkN;2AA-Eo3nesS>eHdY~TT6w` -zIY~<)=@FD(x(QgkSfW4BpV3ceQRrcwUd;OeVn$*v>v1M#j3~De4$KM^n>6`U*ij|t -z{kikwT?gCO1iJmw1tt8DTZYmlW-fM9a}v8O0e5K%510P@+|Cq_#|S4~0;PC32v8V8 -z)CAB=V8o8X@rE+!;ZL@jz<7;O%7JcVjU-X{Q^&eytQxe%mK=!1dH_wcbj1>*TxmeN -zO|iEWd(8(`XQhlG#$d9$#kMyg`h}f+Uw|2L#}$@P#u7{jgziM&+>&l}WVm=k6(pFg -zzl`RGw{j&vc0~EYa!M~}b~J-&sLsQ -z2u69vg*T;zhPJL?Vk&gs$?vnlOIJGVN)uubMior7x%L||p@M8wS5@c8^tU-m7-+~J -z+BS3k1~AlUB_o+onKfULpY{@?uZz@*1t%2!%n_6$a4p07SXREEh_b=~jOM6EEVi@Y -zV#fEmTmZ=-Yj1J;5zjLvuT-JFWSUi5FECeRu7qFBNYUsO>E#%B=Gj7qmV@KjuXznIXVW -z)3r|l0CA;yT<>RH&nJgIF~>;88hc@EtXJsP<&cU8R?YF33?ZcbxImcr6#oWh1ob_f -zqT9HL$5oH(v^k5{ti8qh?!37*WbmjvoUl|-{t(>V{}9|=|0TG=+T<5fGkxq` -zDwF{JA-HuwP79vm%6|y%w#A+1pt~z%@rpihOrs5AOOFX94F2cnVDedP7E<^fL;58z -zpI-bmOwB`9VvHYxJ94@6`dl|t`9B1AO{(hXe+X{8e+X`fe+g~~N88|nkwaiV7E2*Q -zOkZJ-ulde9R8f>Q_Vj-V?wtRZ;FhZ}E+IqyA-LDc<-Ck55LJE%Zet|M#w^m;e+cdh -z#}ek}t3gGg#+`}fAA)=Chu~iAN3~d54q(1r4aIeitW-vzW{Zz;KHCFfytcxn2>Btn -z_gf#q*B(ps{w28MSfesPTzUT?xPjexloQ7h=26g%G{0xnSU-WV)*rJVg>eNo-8oR6 -z1}|20L?}pHz42J&Nlf+cea6517lOKDJNz8VTxaocd>_wc`TmOFD{IPArPnv;{^^@~ -zxoNbPn+GGh_H28-B*n<>`ZcLG?mcPP>bMDw231C&;eaPfb<{fmW~#qqOs)>K2~W&V -z)i)8jY+^Dti{K_d1j2OBPL?QXTy&Yy%n_H*Mc-`iq>M1su05}L7V!ETfP{Z^k|D7o@>-MqmnLMToS39 -znxRhq4ullU#*ZDEwOrg}cEN7p{ty1G2>2iCECK;yO1;Y!D+=Y11KYu>0vhTAeJCmqX|W51^g#PUmTpblQwKGMROan;?+)d -zf|b51C=!p-x?@gRw6@-0?G7Y$s<-mMsqOn3vxm)Ng3Z{Tnh8Zw8*Pe7Lu5`Aw3Xv9 -zl~zJH#>o>XuH06}BsSkRQNm%P2au}odq7njA6fHCuQ)g=!zdwO!oC8%HkI1;5b{V2 -zt63`XGGwto&wI#H^jAv56q&+am)*D`kON;qAdJRadl$@AwnwbnXtL4uv4AukS#Py9 -z&)$F8H$lQ{nN`qYq|Gjx^u@i5TGDUXM1V`qF)8UnSM6#f_Y_c6q$P7H^(x%Z87756 -z*t@`Q+-LNmuB~SiIV)Gs&fTYg?aD3`m>EQFg)W0m0SOwzkJO(;aStr$r8)jO&M -zEr+HnMTATwo_VYe-B}1=dR&cc+Y3Y4tguKo%v)%J!=Qt>vD{76AWVT%L2-1Lc4KlH -zv(_woXz3C?5>HVtZ}&zYkZ>k0z>R5XPb_h_$dl|=?6A;%H~DRR9?0sW9IpjK>_%2Y -z9}z8goUvV`8}rO{U|5ZFrc6Kt5_ -z;svGsre+2rx*<{si&Y9;GT7F9`mJwj{;;=D`-KC>wmAnUHa-w%d&XFr*>6c;u?Rk) -zhTN6SS|!G5wQOsUrZ-0E@F>dRfZ4yKv5Oh?_Bbee$k8|)@ytFoPbm0eMUSDn-x={x -zjU_CNmj@cozQ?D2sa%Ji2!Hq?vN*lIQLA@y7qWoKfoY5}w{Xt}=b=q%DkOsM;>Cej -zS1Uvo^2-W0T~fekZ3bySSnYGeX!bAVzF`IfiH#CiVBuZ>K}``}ZLqx8X4Z8INsNoi -zsrbSK<_m|xbP@Xlj%+5K@LJ6`JOtlF>$8(H-kclO9^=yilrPR9?nJbmi;Sm -zgWL!c{s`Q4djAOAO(1r0h>1kWi*o-6-1=9h2aHe)mwjGfu<~Z|>HB!8C>^%_XT}%Y -zByB@&&+Tl%=hom2K%Mln$zrA`3oh_+UN*_fF%WrB^&Wms=o<&(9japAkUsZ+9hplJ -zRe{DweaJg-WO}!RL5F-Uj5NFjV9g@$Ys_lfGLP?V{2+pxBE-rQjKyvBtKwXxlGIgS -z&bhB7p0QRb;JT|2tIT#T>?`Zc;FKd!gHmE-xse+2h!%6j*YRRs$TI%{xTXIAxIxi> -z0B*?|G|NMRc&T;e$8Gp3@DK6)E3xi-;W?fkfID%kF|}%Wr39M&`JxNS%IFsez9}>Z -zUa7B=%Yx0B&gZlfjH1@~KDt*}xs2VT<`tYdD6Dy-D~39nojiRp5>{Hu`IVA9Ackj> -zFFY1TQ;!mx-xQgk@P7bqh4*(W=m=ykQ2JTb)*JpbJB`#_7tPO6$FxBXgzE$Y>AcYf -z0O5eH&~VL8G%^aS -zwW#pj3JAK-5cK%g=qt9`PrD@BGtjY&_c%^FsvMeB!V?WTc$7j(i!8 -z;MbvUkxTC*b7gR58QE-q0Pa%5_G7s0{{q}w{{guBckYD$1-KPoUfT`!e*kXQ-wOg|uN1&((d6w&_x+(qqdMDhqU6Lt~W0=$^mQZ6nL -zoh;x-_vU70DbxU>G!0|_0l3Sjilx+rS`JWz_+6guGG@@1Sq-uCxPAccL1r=JDs}ra -zo-Kn!a#DHr-XIP>)vHo^kJ%$`uD>{cW#DmT{3TmNBeeJ-PqCf|!5!?yrx2Jzh7Eo1 -zd)YXgc)2ASn6J)xvm&YyBD5%CeWu6r?x^+#H%`3)j+#KSl&Hs}%*w1%*un>7$UPoG -zfi2ET%FSfWT+HI={I4kENZCCsCctl@MEP1N7i;7L6O5_@C|!pMg^Qg6Qveq2%8`FNb-IiFHkW3t!&B^Mp}r7Oqo53 -zM)z+NGE_ -zu#l+oLk-hqHiWrvH%`l9uord(-=1L$BmV#WZHor~|M9n9^;q~tg&t_TLFU+-dqfVb -zF@W|BtbhD%<&eQmJjtQ|!`~JZWEm&u23^MI|Bt^-m$jreujAwPz9?0nVAd{Mr4fyd -zVjAOq-Tuh@jcbp9&=ZclIPhP8dy1mhA84GMW~QbB0`>pqZ#yA?2>t%?w`Z4r{Ov80 -zqBiZ{)-LO4xwtBo2p-`U(5R@b)Upyz<3wg@+cEI;tliO1|N7g5JNXl9?mE&|z7)`H -zTXCl}iuoH)o)~&*jPY!LHYE3CADD$?OK=^-o>-Oj3}CJEORI5wixK|ux1aK2nPC8c -zZI{H^|Ko2brZ(e~F~Lx5=ilse7{dcZABlnVyUamSo7h0vspa^RwpcL?j(n2#k;9PmAXU)2^D#= -zy-d30O9W2{z^sfrfsp&HR&yI(stUK?Nri=5xK{^-VA(UJJ-I8ih@-DA_8@;C%cMEF*3Ougd@dqrC0=o^S*cn&(SNAv=XNiCOZ4!so^%16f)qIq= -zs#95L{9IhhzgwldX)g6Th%p&DzxmWWzofcl10g1%5|}2 -zWaN|j&~;BqQG52oNR{GR9z*oHwV6XBM%&KvJce+iqZZXy@A`esoaCx;zn1Tz&qR`R -zjG0t9_LPkoh?xT$RMQWm81p9nLJg^C*!MN9s5&lbb7BnsQ7f1%n^ouxztvWM#tL%L -zXZVdBjC`!5L0r+uuqR*v5N3!0X#`xa5Xd3lEycP;HqsP0+y!5f9|$6O5XoH#5yEhL -z9#zxsedkJ~30dSO*!4-6a=0L7f26Z_&#&iWI!5V&drrVrbT#E@a{}DZu4pHecm|`H -z!&G5l>fc^S-^!isDqVzy_N+{kC>gF=(fQcW7f5-ICxuKm1 -z#$t8nk>>UwdTUOy&|QY{V7uzi@L|J+0?^of)m>Du$$3E>I~NPusuwiX+K?^ApiJGF -zY!MMimqO~c31|C~jMYY%IPlB01txjgrQ5&x+DAw5`~LD>`m9qF^lZIHi5@es%Wh)? -zXLOex4>*oQReDRW!sTcco+`o1!N##<{xZn#_{3azREf@H)u>2%4|wHsvDGzO?K7*d -z9*$SS%%yKQCw+2lXm3z)xxErSV;kXPfSMuW& -zbHSGhLbg0K7aVTu`Pn<~`~mC~@C#OzJY_u?A`0UD`oL)QX13?>%xHQP&EQxV{W@Is -z5DZ8zoUfA}pVd$4TEbL3kHl!+4WXjQM)`ontqw;5L9jz)Or#-=Znndp{XD@{fg2@A -z{h0X;LVv4RX&k{z2vDoy9nBj$&D{U}qqu#Ft%^&z#Uy1{CKwY1vPE>bN)HH;cj9meNaC|EH-~&(-FInXnSU<3NEBjks -zsBt~mtJaWRschkwfgL~GCBYNAhG)#JrJ+AkoePLnjf*kLk_I7iZZ@j<9&DUs_W*?u -zF*@T@^j+y`;!-Vev@nB#16RIf0{qAfRM~gbZ3u#%Tf*BW%wD(xZmd|R=@WXR9O(xj -za=DfKW_SO}t<`OXInso#MnOpDDHTIQg*!pXY7gH3undM`<~}{O7MqFu_Yb^?MW@38Jt}Dj|9fV* -zvJAn+RiR(5#meZdt;4r+Aq~~hfuj)6yqky$4x-P(%%aDg(|zq&0icAzusly6g1JOR^@m@aTZSuh -zr!M7ov}mD_c&F{qaBom+8>pG2GP~PQ${^cfVWW};Ai&m66$qAc3TSJSv0!;#SzT(lS?Qp$L+;;|#G6K`~a45}| -zO8-`=9b<+y79o!f`@~CGwC8n_QwFCx1+ug|2e4<0YN{idgvbt@N#%SEj*18sY>oIy2F -znP3xM8r5k6pQHlb#VT&Yc@P_;3}KbOzEZKgePO@jS;vCiR2@XAQ5{(KTXD|eIj|~j&c3xztZ5&~`2^39w^8{9(|P)&+;$lPMW!e?)@3!M(NNCvTT6X3_*hBs -zz)~O8&JkwyZkA%r8zO}Wqnm+5KqH`7Tq(0j@%s&%E9*!&0T9aYoWm$sE2#irN#ESa -zwz3%S6W8-9<+^ODzB~WPek08a;|Rud;qAm%PZ1eDqvC`?A9n41&k^@hl<~fYTJgX7 -zwyrYGfAwvoAAP&3!vW;$(>BU -zy8urzp8_(SV2_W%M5U#;8>Fmq=QVU@G-UU%(n;sqr_(zf9$|~Ew_AK?ql3R -z=McCOj6FSpr9QK~-+vute>NbSY#l1;*%Rb6QqQpPq`X#Oe)R3!|LWV&|JAppaD!=S -z?you7p(55;{-bXb^`KXUu184&WG-Q3U%|rs=-V%bk#=X*!F1t@(Y=h;*6|XxXEu2# -z$MqZorh#qZROu71CrK0G&~b%{BQKEa&5ZE1Yg&qRktur0@sI1UEpj7uizmsfkVoES -zlF18PsSARss0Q^ULiMSGyOy#&JNfYi$MuMXEY_in;JJH<{xM|NVY?(FEzHj|_I&3y -z398!cUIm=`Z@Y8KB$w|`CJ|ecy}mvl@@xxipMEGU3LnaJYz=H=1Ax0D4Fc4sk||KK -zep0SXl6F=SMMif -z>B->`iJe#~RE_C3M!rnXGXz$PQq?_HSdbbYNTO)-5D^XqarBYi?Tm$~_PeFoW=1Mi -z&$c8$7u9uP$Kn7`by>k^_^aVrKPOuz;tr2ivfEGSyCJ=#p}B`r7oyO`X_H8;#VY6(y5^y<)ZJZ%t+r^1#9uQP9@`@C%|(2m!LKee?e3H*5Isqlzz?`9G%0jf18-%d1=d)|pC -zJ=p2dtcqlxdxg@*T|YG~wjsaJzw(+1lJkBsQai%(kA-+-G7x~7&@+WG#IZ9om@((I -z>g3)K4j7b{RibRb18Z4vH@}?3BQyY#i=9Ld^`2TWGKGga!EuvMpw5duD*rrgaX{xu -z7D&JDH%?_x5ke=PvAjN6OdI))q>H(3vUP;J+qI0teS$h31S-<4=h7cB%hNQadt;=k -z>bS@=rZ9wN>neDfDK})CoKU8Jm{*?kFKqPrN)1o){Bprw`-sV!pA}YpPawjcHY#``YmHU -zdeYeahWb`@lZ|O@ny%p9`nmz>gmRcKH(YO${EYy8y=LA4xN7W}=_f|fZeohGUHc)q -zSu1+tHn;QB!kFXAO+%oSiAWw>k2yxnDz6C}N~*0R44y{c;D`KJf8bTp2(A`JaMCQx -z$VE9MWIs;(sV6PWHO5Z4KSRc#{40sF>Mt(aqs)nc=Jt+pzTBiE*O)p)RAB9KD{iS3 -z06ICOy;2L>5ga^qqE%i?JfZd9um;!^m+DSmx1XW^^4oN3xu099nba5V%_B$1(E6;o -zE3AAR#}Qd;29U(D<7q^EIaujBF#P4vb{^hr`fB5aI^ndtmFSrUhMu$iXK|Dqc -z)UwSve#mI4uK0x<%4}Z*>KZ+6HynAj7APr9i`+ON$M*MTxdM?lT7rbn9TF2`|SvFg4IuapaY9)6Zzya -z%N!>eq+aE~M{B@;D|6P#T&>*edLwb#m|{FoQJQ6+B{;;VL;MpdTYo79B0@1^S0*6w -zF)%e%b+?KxdPn3Sw~->QQ@__A9k{|+5=%ud`whJjhh;TkMR$?OjpmQIWB_0;cMi>& -zkR$Ex?5Gt!-RYOh?6w9Kx_&C_AC*Nq3x?8_nU#MZ$4=D4WN4ZUPPG@_Ew4Sosq!9) -z{N_>Z6a(gy3knw4S2KRpZV5C%tYRv3MKQ#_y+|P3I1@97d-KCsK6SYRy2CR1X57PS -zaTHCb=w^Zc1hk=VBkJcycjEag8`2CdoHuyeUO7DY(TcLFmA>Ku@x+90-z$NpKUZzJTsgb8Tzdo -z^%0WGmBay_gPs6`+O(&GhJ0aKJ94z_pzfz1IG%UBe?>h1{xKl8VEx-~)ijQn=ENIt -zuFCpwRV(2gjyhoDyU;^4wYsMzz{2=T1GzTn&V_oX{B)N-1 -zqriMs+L^rHF=ECO1WeiNmA@X2(4=u*Tx*9@LS=4sFYZh;Jh@Dobm$sx?Y#G)*m|H) -zUilG8=v)B|ybA;YBz__u17gXu8h>Jk;p;Q&K* -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 -zOO^{C`44nnw`L->gN{)+908VTO{ad3dcCu -z&RXzAn(zo0F$0wNwXp4kbq1D|y1(3VKgS9IRb!ajSWk$5GU01>mP|s#iV^M|IWbus -z^dCDUA6{^qOF@bMi*M%^)(i>i2Z6i7{_=hWVJshXDI+KnL8GO -zS%t1amOR#bInU;WQ8LAuC~cWt|8IO7K@$r*<@Z1M_Ql)mr+K{mk9Z%F^OOvs`bw}> -z`2WDSul~WeKf)1eFSv!ofAi{_S~3OUo#3M59P(K)IkVt%l#P<){6tnh%!w%_v=tDg645|i+> -zy37fOZ;?w+^>Dy<0}ZC1+FzUL2Q9n5U$C&hZ{tmuUkwq6?5qYj -z5Br1kTxC~GEQ-Gs4ni_IaS=~Hkek+^uEpTknmM+G@fjLcf?yQ6*!}s13F~EV@Q=N{ -zqW<6PZ7b1auXO9Jt}}o5nV!!LFB$?dFy82Bt;npPf7#pI@mIxXNdLv&=2>^e%n4{c -zFkIn}Kw%H~)~mo)k87aJ_GB?mATd~J^)gS`8@m)o-A%bvIRilofmL-&#hPu#78UCG -zK&6}qe>@G`j)@kw*XA2y<$fE|9LyARna&efh!Exeke3&fW5~JbSi6yPB$u}S?nXL# -z=6a$|N!mW!*YI_Va)b{7= -zwdnQs5oj~Jh@0M}f|QjAdfg9@j0I-=f|>-yl|k43s30|)*3Uo -zLyTQgMPY`@?YAEX9uz6_qjMR%CeCDnYrCc;e(#I>B|t`}xnK9w{oVGdBWG%=o}>n< -z$HT)e|Jd7tRW^TqppiJ@cO@g~O`K$m8YOphTcwtU25GW5kZ

8zY8LkU~?~d6F!8TR4m*z(hd9!Rm0H -z^&HLgU;coc2@LD|8ic})!KL4XMZgOQ3j!0)Z`@o8FshjpMm-#ey -z!7wH!$Mdo_f+h@xoUTDmBLpBUc0yhlkwN-_bOkOFEr^Uf`C?ev8xjdDTyhg&I-yv| -z(gj3r`=XPn*g`CjTRbZ8=#Q`P#ouA%wG^@*EmePKtxZVPWD-Lv$}9NIArtq>q0{Z@ -zhvO&*er%F7$Vk-evk&w9i~Civ^J27^#p<=8-@WhKuDj^)PIpy_g(jBvF54wLKkBV4 -zAatJFefxOBM+LT>Z}2t#2*%7mH>X=HeTf67b1V5#w?LlKDuET{PJ3!Wxwfe8{;fWP -z6LC>cE1plK&Ksjh9n9RyRfNSSh-TBMCn?byUWVZrHT5_+BKX;g4bkh%kZ37tMwSJv -zHXt=MylKplfFB1DLd0!ynCzfKdOg2!8dCWTZxejcrW3%=4`I4}@@nF`Up5`-*9LPj -zYya#@h-DJ6NudzX`Ed?9*dRniTV$3?_3dKLH^f&tHXntgXh0L;a0&#E>T@y2$ZIVC -zo&9H)cBX^~G7NE6zYQ|vgkkE7h`~&HP4#j{(3P5F7KIaD6N{H^R!4^;CXSsK@3Y7_ -zhuVx<#I=m8f~fequ4#M0e!Jvxpo&qU)~ogfWYSw;Wr~5-?aPZJ+(*R;1lP7Vm}ulWF~8G7 -zd|WjfMxxblHz~XDeYb|2LQzy1y7-kee@Ov;`Pzt}IWPN;jD|8v5kDJq``GDW<_l#o -zssuaR449-kU%%kdJ32G==XdgLi%nBz{qVu3t0mD3HDL&em0YMgjI}M&NAyHy4&wY9 -zqu*Ik-SU!*uP2dhaUhdz7P*_Z!!?09)IfY&D$t%4eAKQ~U%>wXg?O8k$mv?ZS5bn` -zw!;Zs+Hhre)s-Jdre#JmkCFoxjkG=SWTG_yE94iZ6-Qiy6dRuMV~8{1$-?V~f^M#O -zh@K;(Cv>lktNpr?_JyXLcyjxsPI(2+4g|f$el9-TYYQ3!u}q+pKPb{!Lk14ompD*H -zI}BPfdC{|Pm_fP6$dn$xH2I6|-}ZI@_y4uGnY_f>aL|^zyaXzo)8HzrFTwO#C#l!7 -z>xW<~4!oT(`d_%LZr(Zzf@#?ZYuX-vzcKTyNqO|;c^7C~8GpM^hj$x1751;q>bS}e -zlenF{%INX;*IC^OHQ__cX10`KHYH)R;U5iFHfeY -z`}HiHOrG`mnSTL~F55BpqV6`XSO$LjdlR0jI|&Es6rH70UTi$&mox9M2KLl_ejEWO -z&o=54ZzlN+`rZZoUfv%jcbJW?t|oWtjXs5ljEKDNZq{1R@qNBJ1c5xC`&Zd#(Aj-e -zJWT;&3!tQ5&Y>-1Q|DJ_W&I%2K15_qCj7_Wyy+<)eqy(Lh=l1K;AsVMu=9+s!sm?( -zHi-)>n{Q-8mso(kVVoggJ>~UXR}hpsPn_KjzPB$sfR@)op324U5(F%t=B7If782rY -zVjycpYF&#FJI+FR8Yp@;%(S>vUUd+{ok&$X-6F=me0YM+CQ|*A*lw1BHV^ -zME}}OQ70U=3QtmkN4(b{1}uh9#2fdE@+uryf#9f~?;$5MSHA4v^UdKaG{0N!O-jA{ -z25`O;BOOo-ud_F9vQe|n~r$9}!PL($o3Lo4u>(`vI0RE`~uXqoe|WgmN7b-jb% -z`Qn#JKz+Hh5H?&Lq3^74S8hdwm5*qmq-j%A_PPTb+pm$NzOBtvv)!1oQQ -zJ^`#{9G#%$+KSk5iwCVXsm%4$d4_K_{Wp4>-2UFdZy6?qie6)cV2COY?kk?Mi4EDN -z_$|N*j&UTRu3r@YnC}vMXJpPR5j|P7!j+MZ0pjOSG*4v;FQ4=$#ZwH4#2U;wv0ZIR -zQVotKp><2x?sTh6j4L^k|>AJIE -z>T%WZ`KgUsNWDDsEV)dlRV5-C&8JNR@*{IW#qcBLjDi73Gu_7BS~PIPsw;^N4x -z0K-hI7Zz7?Joc8O&4Z@4tomZt7GOxU%{mX{?B_s(Zd>RbVgp75ZY`z6)uwngi2`*b -zRH5%TpCQlX+hYGkmV-L(%()sjhsBlZgJ>n;foMf{bhbf6fR|2=z+UW{WlO!oH(ZB= -zB5Me*aYVLuZt}K$YuH2@5OjG3Y^{{8h_or$!nN(4*S0P+Xwd$7oXjy34ch&480EG) -zmK2;3xP=oP_f;Vfo0yuK=f=$^ctA_hi1&>Vh)^iHd(jv-3#)0zv;x@G7n&V6O -zW2$^@(iMoJeeVCxZ?j9Aq5l_t8z>x+NZ4&6WtrW`nE_*;Lk004{Py?lkqRH%PHdPz -zjYMLaLRHcFM%8UYjE_P2Ew!5MpP4I+vv4^GC&mT4!k<6;d!ZaDQjl(#V917;aZI{V -z-_{NLBZ^}1p{gxC>vH^`v;W0!+y9&2zHmh~vTTe$n?1ll+b -zU<*@@E*aM8j26i%o7uiz+_l`KMo4-)B&8n+@1)=D0Gzse6hqwjAQY4to$_@ZR-T*& -z^vk~9Sp5;!VNdq^$sE3bL`QeOHJ@+CKhn>PVqzW2-mgft$D^m%go1y8jW8NuWLG>P -zc1)5Z5t{(a+mAQ%&ft#WBlPuquij(zY54O9qgn~C5Gr@-hFZ|`pFiosN{j-f7r=EU -zchPa8)xr+{5|Q-%b13@riynC%#x!n;crk$j6HIdcjB$78tUm%6jscc&>=|YxNQV*z -zW8*Z!uxaAnv>)}aw5ERc2bz-GF)DItzj3$XCMLJkohAwdHQ;SzVI_Xk*3X(2=dm8A1u2XJWqI_9`&%ub{q -zrQK^8%h1f9oDLE{tQfX9)u$XI#QFjwXzL@^u8%C50=G6ITlM*T{nLfMY6D%9fCf1S -z%wHSI&;f#>16WtbDz7e7^RGS(pSQwaW}XLyWtT61Lx`Qkp(R^(KYJaT^m1z>DX+df -zoX$04DUGCi`;Sa5k9*&1^B=Cz$_P*Yb{H=A$R9!S!s&mJbxtb#^F6$Z8%@v7F=hKN -z_yxwPouyd;)>Yy{`?h01n -zY51J(ckNSm{=pt<`WpK(++OcfcC^**zZth|W&7S41?;@7*&wgu7pA9v+$GV@moNO= -z#W?Nk{3A`<=L -zgkWOg+ex}Zx55oOX)o;#2hps`DXu*41Qvhy2bSOR!8eT12lzFWQQBjUhk;m;K1k$QIN)5T9&kw4y= -z7r#rrq4}4UXTz?O=59TW8$wbFLGqvlklrt+q91iAHwcFy!h6Ul)Bw);i1ua7KkI~< -zV~rGv0);hj{QTC_L5zg(K8t+ro7+@{v@j+fq9*$U;rdQ#(#sw|Xu~l>&6Yvd#tEn6 -z_zR3!eS?i7@pQK6loytnP!Ll@ZjRco0kw9d#U6HZWya}m^UUc1$1hGdbr3T>7$X#@ -zQC0X4aISk!XM&>kO~fxqTo(J{nj8U!Nf}2$+`UZxRzw%*m@fig@5h{?m9n^5T3Hql -z_YD5!3^A)7lDNCf&(oYn@e}L9~-heoPK0r*QE+GksUvgS*$9ytK@~lnDPCgLwyV -zSoL6kc5fzrviSBv%Qmjq!uJfPDc-s!z%w>t&GP95IQy|mbGs5LN8*5-ipq7A_} -zY3)F}H9KZ(RfHKsH;6wa4B+?ZAo3&v@+O^ksJ4+G`YW}D6K8uY?)r3B+nCBTu_E*p -zJPv!$S1OqNcz6RtJ9)(Qp|)8x03f(C{Sq4N?kPg#1_pn{Gk2$FEGpwmwk*v)bLKPn -z6?4GVm5BJV5JC@S&>px7@l^waCqz=Mn|X+E@YM1SEw5UyzzYV7e)I`WivWX>B|2rB -zk{n!a;B{L0Z73kO$WT+Pz?Rkv1>?w~4}4Ap^01D(p*kh@a~`LC6gS}z`tbmxb_-cU -zb|Qc9@zc%iqdzC8s!^6JCymI>=G5T^J4)yXsZ6({li?&+V&lZuVSMJfX|TDlaF2Tv -z#W4`&j}!rZOc1^f&9j*gUksRJQGnQizVByKE7vqz0lL24{>paoaD5x}{3EY8b{c8} -zp@N0^m+uy{)*OqDr&0<7^w|##z9^R_e2o1MzQMUkGxe2nu%Z4?gfh>V=vO=j)>eG8 -z4>HKP_eW_x*uby0bL64s_rFj^>*~z_;fIgH06r}c?$-8=6kDa?ifY2bo5Ni2gC}PH -zZ9^d=){al-ZdHHf^J69zz6BiO`k;L&rL%qR;O>Z8YuOL|FTf2pMIpAXN36BQTzwwD{` -z(V^1IPm@A62fq1;7%!Wbp3x?E468;4eGIYdM1gPN9}`cEN|#432JMn5mv2YqJ@`6V -zpLdCDtsoD-(*izCc0O(H&KUaRwo6o39<@Ff`esNByCb3N0VH2b3Se{GH`kI@drZM4 -zJEM=+cKCc>k=4rI>+pxASIUY!^RM&%XydN3?^CS_R!5WH2iY)pT);#as#6kkUh=B&G4rzfAZ;w18 -zq8`?2{TzFdpf`KcYAf1nOUtIRY?G5sD -z0s=#7?x21B@XQ%-1IUj}&}2g#$R9I1VCpZ?fig=$*o-E9$7h&0Q;OW$hoTfr0lPtP -zDc`G!i$I@I40kce`A(T&Q!Vn$pfY?2R>th5-w -zEr&;L>3)Wh9NX;(!%<_e}#@;=M!fWa5_hwbJad1e8ay);rNhV4IH{H(==N{Bf -zoOJ{E$)5D8jjP~R`&sB3y!lxWrRx+k+M%a(6nDzM7?C! -zU3clJH5UDcv^P1PYkiN@bke}lf2g~_0*tI?Lo>sF=vp8gIXDh>id{bDN5D_Aa!)s0 -zbl4vcY(plR)12YoVbnvBP)NoEZslYgE@=}!gj{#L7scN5Akx=hT=s4cCDtSmos~%4 -zNkc^#@@u!N;9aPYJ2OFUCh~`M-*yRLkaNEjZ>eV)(jWFkVe;#=liaXcgwm=v3ornJ -zN_MgaUV@meXi{X?usqN<06_r7HooPkTXhOWlAmi@s*)>ntkOUQ@vW6AO@63RGF+c# -zBj5w?dP*n7mc^fW<)KM1bR2wqg`F}j=dkatyU0(VY~3QVXlGPGqLqJwEVyWr6Bl1- -z$iO{&-X)-h;ZV3 -zxi*MW@0G~b?~g6dYupOBjL)CQAY~+HoEK_3?5*D4%b|C6u@Oys@&&u7m7|AFb#E*3 -z%%}&lXwy<3um_+Z%O+Gm&}ik5!1K(fhD7zoN3T(K&0toP4+fQ~IaGc);yz!9CycPV -z#@Mv%gC(IIOSzq4#|AQ8(IpT1>`)fKENjM?{ic0AL$jbQk7PK~3?%J^!eEex$#G?G -zf~v(Gn>7pjUjlc>0aH-Z?nM4vSFp^3<%>aA@SDo_VhI1m1niTA{N@!6y4-d9Dx#$7 -za__@AUMAXA4#0>ZY!z2xY%WNuFO&j^EVDrn!`5odurR^JlbfM -zeoJFv_N`g@+Rfr -z2Us0KW=TBb&RIdm@}lF3U?d+}?k^}(CdM9SClNp;mTiOHG)@mE2IjPX2&nY+G_vxB -zEblMxFvVnSj?*XH$(_ORs2nI1>wJPXqTo(oPRn}1F@Q{D-a0(x0-=|e)7&?TP4I*a -z-&VuVimtlOp|J@!TPna%u5w`I3|1)6X`i3hD7SgRsIKhO%(l^@IH6PcEQ<={)_*#z -ziK-U%^BN1pFQDz0Fw+ifPEy*vQAkGRA#}d$|3#1Ls@91g}zPz9%o3h}$ -zgjxn9uOjh~A#r|**dwarc1)U^P>mY@-ErmjJ$nqFr^bj)f@T+;xJN=8xcw>(QH}S< -zFwFEV;kK=(n?@$Vbl8K7UPjZ&91I=X-fo61W9XEQ-HWx -zV}z|M;JDx9<@L8$RtWq0k4ZHVY>uvp#uY_j*o<*%_D?!(XyOk)TOx2cJYIMWn*S|u -z&-@R8J2m1(=>Gf1p)>?4r4g|mf0IYdcSsE76%?lRJH*hl$Z@sDis0psO2#B%Uj -zo<9e>lFu?@M^@I~QAY>GCROz9SH?>u;>V1`{PA+(jvI#>%JkMUDV|p{_($Nb{||wC -z=3fFg(=Vr>#l5rt6u9I6EpR)jhy16&eenMfxcmMkaCiMLfjbT+%ADQWPbc#ATE#ug -z?&#hST&7`p^!j6^W9x>&i4_?G$&=g+IFJ)Lxh)sT1$@Y3>_ -z`&0XH!o-5u;sFk`P&rW?MtbtyS3k20?~qEPg|P&?#yWS3sBwft;dN#9NF#g5chaZ| -z)b(UOF3)WLL=!af@HO*78n=?EAe5FI5+$}h{s+L_O!E)GeJ1uVfSd4t0NlNg)qY## -z{{px#{{?WT6#PYy4j4I7|0}?)D)#>$;QnlCsrTr!*ZO||+~xYFonZ=HvZcxl=bdUX -zd!DU=L6xt~U8Sx4(zNPJW%5i@rW`y;kEdss2pb!TAjQyY;8X$I*ER;HQzyfjszW3R -zS_=#qZ!nqJj))1k$hRWA3J%_M(gyf!>Wdx6c`z -z=zr2%K5Wfq(sntVPUl4em*0SAi3*u_iPgc#p{J)MrsJcOvB=|$AQ5CU_LI%fkNlWb -z?Sj`T)~C9?1)XF^c!Lvz*PqlG>TyZw1zKO5pfsoMfmSp(+;sFF#|8Z+n@d;BGI#Sq -zEBAi%LJBzny=tSQY4tX5;6pQz%t?^zB5C5XMP7_1{!J^xe6riWxxbtU0R` -z0^(yk5eg7AD`sC6@KwX0#Djow+G -z7KAif@XmhO-1vFtx)uf5(bcQjZmr9gwd*JBTw)cebPXL{{eV4cPZx`e`!kpxt`LE$ -zCl@biOEk30UF?*+D2veN&GG}`qFAX++gH1nWLUas=Q^eieWt0QFMn;#rwjALpVV@N -z2)&Fne{&ad)j7^Mo6gBCzrO;2H1z>&HRrabw{CyuoBVznI#5dk2)ikAED6*t3e}*+ -zje~B;63T!22_+~QUGa8vhejE+O&RP3l?@C0^=bKw(it-8S3?_G4Ni$#Vk3Jyn1j(% -zHG4pGpU%Q*kveDgv)SvEA|i_-B2-#z=t}N=v1ex;xwe&!@WEAP(g>FC32B`}kHQ#= -z7Xqxq(F)TJ7F0f;{-f+?zU|G0#*Bf8FIowL$<)WM=cVm^M-CSpcT&=zE3QO-Z -z(f4qhPgom~TQ{ZL?`(CbI#?&UYeSuBhx8wa|;wQdm0TMo)M*%Z*EXPrdN;!#Jfgx0V)Gxk} -z*1Xam)F5USy9EV{R$q>m3aw!Lt~ZQtzX$5T@av0X$a$@J|1t`(tuIK6N!;>aZW?ex -zKkRs;zM27J*R4DCNf;5*OH9&*8#v2!dr?{P1Nlz_i!ts2h2Zsi!Xl$wc?Io!9 -zEav&Q{4;mRGZF6nD%zj9VBoZw$Njl>gieBeNe>467W0}H_7R8XxD?ittC{oh&udzf -zYy9kUwkNNG$GN#EeR-FvfTSnxw^I;CncnPE-!l0@-Y_TVok#M{E|oO0AcV5%^3a$4 -z`Iu205}4%(#&T!0=7)ZiAf?-i@#zDdieY46OMXaqjL{e@1!b_}#tkCmKCp$kdYHzE -zShaUS-w0Bxm_njEneEgR!&DI -z<>-u8H=Zs+kcVeZCA4@zy&thEDHe+MfLuZlLH8!o==ctAKk04?cl9THY!Dt<8}yH% -z`!wx>wCUbXZ7f&-#e6d?wrm55I^w3XkcF#HPP~y@l}Lo)oz~(anmyDBzA*?_wi>hC -z2g3V@!Zwi;hJFD`KBspfwui{+L{%S2tUWSyR??hqh{=Wp>_1qPW^Ph}d9!EKMq_wPyp9eIt6$WY#5>lwtDz$p-V&uGrM{B(lmGmv2p -z60`e(xDBr5@o-gEMlvooyqVd2wEIEBwicLYnz1AxFH-~Fye=O`QW~P;j8-paK}o5k -zC;Z%b%VJIL-;wH71ig_bKV;rr&tulGn3N)eE#N(8FOPgJzwcAcRvGa;;6oB<8mkXA -ze6hYp1d1QUFZDNgasgNjHaSi~%$x3%Plx>r5pD)^OXp++cbd#Ku*&pv%n&Rk>c%gA -zuNwr)sm)D>71qB)-!7td_&@!P+mecfU!AI1PJYl`s{5hYvA=p@u{GcC6))});|Z0G -z`f&C%=jyVXcn_J8qlp2P!C!TY4OQ0Lt{IMTF`^y!oPnk5jwtl-he6v*`8ve*=1-R@ -zdw25MXf0tw)Qaa%lguMCmh6nh8=^fc`OLd$5N=O -zl4sLCIg(t8Zioj@eS1M5KT)wzUqtu#l+1Sh-k#Qg?smGcqr4HQ48?aObvw?r2C`1b -z@M}pi;zgZ2>^=p?Rlk=a37_BL$xkhGhN}5=mf7pezXvjuFl^PV;c|@xCmPpRG7vnq -z^VLyfC*z7@MEx!wPhq4VDQ7@qhkQ*6dnP=1nfVy8%1 -zM?P~a>b{%CE^dP9s|C#H>{TDg+oy4VVbhv_-WfC$dTX>|Valmra%8CTrNuYTF7X)*0lrCDTkx7b9-ToCMtUm?$}Uv(pvp_PGE?e_TQGD6Q+;*=!!ye -zw^u*T?8e$}YC^3Fb;7paNXFdAlF!`LMnED}SQPkoNW2!PzuTC}qO%1N779^W`Ap?Z -z$Hgs|7R=1n`a)l9MR@BK;?4b3)N=yWcUBqt)7>pf=gwmR^1WvX)qmMz83A}2b2T2*lBPj;(q&#q6uS0>AD -zflcNMnf>%5Qitv`>{RTMtQF5c9oS1MDuiNPS2)>JbVwE~6V5q)ouS7=m7uex$?@@% -za6z2L@YfMJr6$WYbnu`_MSp5AbS-uaR?1I%dBeG=wZXM!`h?H`6p8sCyXd{09m<;1w49 -zZ@sH~{A+NU?qmtW1zr8$ZFmn+En#j%qriW8)E_x@G!X7vd-_L_ja9* -z#*F&J5HLhh)eR~K5ESoEDc%kuW$nLpA&!aSySd4By*sm?;#+>cMEYc;N+d^_aLd|X -zU@TQSK2CcNfJh10h -zQt?V}rBr-21VVG&Z+0E8ZEA>FbjruYP9$}dDA!jOD0^S#wY!{R>Q&!4zhgN0IBR8R -zjV0&{SMRky=$q$b?kcS43#E-V -zRnH)E=7cWx&F=hGCxzH2#;_D^=@5MEa)b0+KQF{6RO!zB+gDCu#BcJ}+$nN3(Ga`< -z&t>yQ(M4I4zekiy`q}ou3p}8t78!uv{rPSSi$X#ROHYnj_=EO~JJ%Td6;qsu4mH|e -zx&GLNowhOO7HgW0o{=)a+Qu2DfH)`tyQFy@$z}KTABfrz=*QG|TJ7$+WyKkj9)O* -zJLT^L4NJS#lQ2FBAAWx*klD?wXHZ>D!1Vm%aFtV8_@imId~PT?tnqy;x+z;9vZBX! -z%`BWkBb;YPMZ+_qj8l6q8*feU_m}uY3+{D}t=K-{z0>Rowdg1WO+UNlJWvS02$byJ -z!bR7z__?PmO*bTS0Pq|e7d&7SjY9ebCOvqX*C&wpYk%%Q|CXE~w~2bo0+PH=@LGIK -z=NZxK9Z0Y?4d6LUb=i+#N2$5W9|<{BUExRG7jXY@DolQjSe@KbXmo*!hN6sml2 -z@my+*5&bY7W-NE)r6LtJI0}gY^M;WSCv;n>c99yn!DcR4V!bC -z*6HAKV&v}+CxvRF;BmZ70q~OkohQ&*?R=OllyJaZ$siV6FjWOd?$pvamgMN$11Oow-y1d;8Vd3xnOUPOt=?ZzbokQf;N1H> -zPTTiuRssYVROddIHmOrf?xrX}jkkm7M0?I(ry1{=N@S^N%~bhy>C*n1Gd~WubG7A$ -z9(}iT_w>wO=N)kLI>;C|bd#uv-Woz3lT+zJfJ_U?OoBNgQ~|Z%zgeC`Z3)^xNq%la -z#BKf|PdmX}jL=ExUyju2ac~N%kF;0A70Ws>RX8JlohUQv7yp?j9$Y~l#m`*+D-Y+h -zs~RqRsBX3xDsBkDep`7>*g0qJ$pxDQ`8U#f*nWM$)`@h_ai|=;Ev35WGQDI6E^%Jk -zh3vM-fj={MUk}87b9(rTTxX&Vf>wgBs5mGtMfJ>}bt!?@>JY+t_Bn)n@S6#QEv1+L -z{CZ6dE*=DOQ$~R$(Wtp@bX<$U5*IX^JG2zGBNM#7))7)yIIg>Taq6Q8fv*?I;nvZ^ -z_C&<`uRm+_(Ac|Pnrj%m#Squ!Y9<(FifOUK`OsDF#UTpM?~ -zM&tH!iha5-%k)8LH@}NuyN2A=-FQpFLfttM6^r)D`o^2P*nG8AAJ)diUsYkMyPH!s6(3XiB;mDNCcE-S@1+@Mx4JjDfFagFIVD -zfzbi7g%i%=;mU3V;**n--Fk)m4(JfELlQ2(*ARn_Ol1u(1f4fRalxn8A^=O1K;Vkh -zG*21dmv7!0LW_IOYV^}dTJ=|hFljJ?ju#KVVzpjpe0#Wh@wuDGBvZN2`I&z{ -zpAxqyIfG(qIQb&3FJlJU&Z?v3yW;Pl6CbIGNz%uSYBetmNz9aWE4fv=ZAk=nQqY+z -z(4cMhj3Ivh7K*EpN$hX@+?%!NoANQOPCj_awQKu72K96->y>Z;$xpfpuq4Y&HA%XS -z>~{02It%`@WZs5|FoX4571@{8d(&A`<6P?w@}zrU-Ce9E!z}6l7W6aY<&<%Y;T~Mv1gsS@=vfQ8xPqd!^GUq*~C4S48@y2llC@5A5Hwj -z*15N&{w%2PGZ%$6fc4Rjiqxp2N5sCT$cN%m3V}8V?5pHht*{kJYgZzfe?DoX4>!76 -z$y_MvhypC)B9Uv7y=O11u -zK*zB`aj;zb{hKTmqnTFX5_%LUiICLHJkGTfLF$gWyuLSbOV5StQodBNt7P80_N-i! -zd27?@F3x?#L22Ocw?Dk7!p0KQ`n^K*Sj@B5xdSO;-`J)ZmEB1zAX~pJ9f0;jQ-Eg2 -zF744IVW>Lr!wq6_lVX2l@gRq$IX{#KNEP;zvdLsgN&gHIcZ+)SoEAE_WgT5EdoC4$d>aEbAj}Bvz+~J}_bqTrBR$GgcCd<&8lv6J;YXgOy5DPoMLXSH -z1k*NQsZneTJL62!=+4}lqK(9F+TV;+2G{o9&v5Op%U=AlNyJQQ_mZn9Z~^$*^d(zp -zlxdise6zj_Jy2XRp-y%ZXKUR2M0V$y;HkEdg{8&li4cvEFfY_iZKLG9Csa}RurZ4V -z12Ar)P@^7UOV#0cyfOy>F3p}N@iS0iwTidoD*;sxWi>ub3+@|>>W9`7umwTi`k{3k -z_<_hZv4qi+9gS^wJ)iWxJA%;7w5DfB(xR9}y`Hq6;HIMBc(sI7X~Xy!8kIv(Wx%dC -z91V~$BH&Ial+?x55@JDfv`R92Xzh*k(&y)T$o`NLC_)P+lK{%0-ZIcC_nUB24wVbq -z&`)z5be>xVkQ8$x`++=p4TY(6{`(WoAlDaMST(F%BBSJImu(n=IozP|Bwav5O*?(E -zS0t(Vc&Id;ZI$yMbypVf!@3Avr#kv(lq=Z3<*N=NHdchXU(3}!q^k}v=N)!uzZk{6gtm*l51qX4S)oI>OvUHez|2Om9)KV7Gp#T8w;)xo)Eg13!<$7yJboI^^2sh7`@z4Q&l)cUhR$ -z^jL0(y;zZ4K{uoRZR7{bM%aXVpYQaF|=HOcEv-FP=%>d -zc^MCjp)h%yO{SHkJTjtYl1t_^E&3v`j$1_uAEY|z?us7_YFcmWc7nD%cfm7?V!ZW{ -zeS8<*8ovq!qpYos$~rSIL64>Hqx^mKKo*t&iN!=IDfNvuj>w&NHi56)XHncron)~# -zc@IkktSiEQOq75?jeRJtuyJ)q;97AeM_GA+A^xokxx9q_#~#*casD7%#k(BO4VN!* -zzc(Z35k@TAKJIB2P;t+B5!T3mKt#8z@-}l(XAAd0p2QE%95%sl{Z_O)WC7U`?ty-( -z;2@*gG$z-DM)hR3fD+_kV{r`&)@Dfh!7A1@L`{P@8aJQ_>;b-Q5z> -z-3`*+ok}Pj(%l`>A>G~GNJ*EJf}|ks=K+l6TJPS+_x*}{X70Jp5l2k(A*llsv#0mz -zo9q>DV89$sL!%K(i2F!4Tamu~y7d@D3Y(!&&0c+uK0T+z_`Yfp+Pm^wn#{^dQUq{7K#&~;X*6({=VtHSQP3bfc*rZN -z7FWatZ~>!hWFLDSXGm#{)UKuYUA=`PO_>u1FUc=3-2pEG>1iRR7K;_K7ZNcyl9yj3 -zgmtSI%PR_6R|tH?dYiy4x@vPLAF)57ue^U?&>ea>=hvb)L;~u9P==5-SYDF8*zJH~ -z;+zd3Vo2u7gjYcMBl%S+HgnWThSg@86iW|zs71U;FGMv%@pPG2bpKNU9GXaD=t#cO -zgv)LvtDrC1st8EP_LxuJp6eFijB>%r@z@hGYV^x`v}k#aYrq*+qndsaKH1n_4Z+Pa -zHTg_zA!mQM>AJ^emTW_Mh?_htSwmt_Ne(uYy|457fPFr;!*pohWfW0oqF@?>Dt05R -zSGO*Q2{ETI6k#s2ZJZ|E#mkd`4O|EHRl+>4BwpRc$w-ST^%HB_rs=MJGw}%bOKMKj -zbn6!od$84WEvoGY7)8%1v=$F@q^8{C;l{` -z3OFD+SYmzi-qthvh%=vKWa|eVj))vT$6D+W`AlDtj8pj<%G`2qme^*tq`y@I=_Tq|XA~93LQA($udjPS_qgs3Ex}&swW@*Nl+b>~v#4<@Ei@wN=vI|5W -zS?MI{D2@_r17;1G&d62Bek*!*!T88TxpYy`zD%P7JO>mx*{ZG~B(Bj}KmxUDGyY52 -zE2&vQE?39aPyP;_%~G}y)9)*`v&9{r)VmDYV-pFkl{=wU8J*PU$Wdc_tSl=hhEI){ -zBz6sRTu-&~%Bh^MF&E5hKz9uQ?XFSi*V0LqrfmyAFEyx#U(f&GWTvPn^pQ&Xm>VCj -znBo~d_*SRK#Tk5@v`dS-#0$4;KUX3Z#w1x**bxN#>%0{wKXzRfiEq+347@>U5(h6_ -z_+pBC_jOIOU5FQQC9|e`?)pa}`iG3ugRKI3Fk6Ex&vFl}>W^7pwn>?4ZVzwLTIS}X -z&r~Nm$9}h*w_fApZ3zn7RzzjlWR2|*@FNV -zyM^v4V#RBs4y~CWs*9P7SlzH3b29z+!NVV_U)A($3|Gt~3rE1O+3IkLV8J~9_}t_> -zF9z5Mz2U4bjM8vh5?C(G={U#~&h-gVXP|*HwsIF%%c~9EwH2RP5IZFpB7~^kSfZkFUu9CY;~VhgYEMhwPg2TW^D0s2)i1p^lu_pB;P$bQikyo2-qhFsW%%o=)5NqFSV0Ur -zSk)}gr?&TzlhCP7qo}&_ub1)U%l6S_{noxTf6(#hm8iAOzL1>4qIVR89PV)0+i6xd -zLRw#Jw$oRi`pRj*cn((_Ye}pSu)D*ew840JoIVxGlU8ePWcYl*M=sNIQK{cC1Yy~S -zxwRIiS0ls8>0)!o-+fL^C&}^*-yL8Eww^ILSx{Ke1DzI!#n9t$#sA|h(y8salT+ph6c7--R|B+*RC72+pW2Ov^f -zxR;?^L2Jc-PCbLV6V=$g3sL;cgJfqwb<%^9swlu?BA4mgE|fcygVgTZRE9rgDw!m* -zB+-f{zk&lm1@gn5a=H%W}lnSj%W+oC_ -z+s+~sbVS`s@)Lw}sv{LO5P{2Av_5?}IfHUD!rw^g`LdOaBQ#OeFHDVd%aZ1ej>KLr -zn2R_3z{T`KTREiHR0$=ME>z*FO0BfU+-f&PLhlKyKQWMg6>op}DK(5DRZK!27m?Qi -z9>Usyp$`$~$pUiI0$LkS+7n@>(>9Pt@WQsuM}Z}9ewzA7m9yP~}_ -z6}g7U{I<5pRl=Chi!<ydN%jQ|(W$1QE7Sa4$PuYoGa_o|c!f`F>eS@cyHZoC2ECZ}S|GJa%y^lHOd-(%M9 -zCM{nZ%0tlSs)NV3`{Tx)#yx5}+5r-rAkdQtVjOTeEO{%2s$?$hqg>g-WBMR< -zKGV4n6P>`#l!Phd3F~2k>~l4^OwFYB>f?N~xQ2aXyzbD8JjH&C$n&20eA-6U5IH4D -zVr9q|BZyp5U^)_UDqJY;fm7pcOHAU`G;ij`0 -zEN_(@SDO9sp-|D9Z5K>@;}!Bs$DtDd1|zKnVG5;6Xx=zs^hcxYE?b0Kc6Tx9 -zwb&R9&isfxu?T)?o$wy3%aNr+hWk#lc#!ZL9;>erjrodpJY9P&pWIqA<*6c#5cguL -z!UyQpkoNHviIAVlAJ~%l@V9yWU_MZ0E1=4ESTP_5nL7!MmvQbD^UPaTmdM)9pN){J -z46s#@|0F-t6FO0$H_rmNuRg_@eEea5ocr?qiJZ@th;Fr_S;rb*MMIN8e7J?dg&$1( -zY(~)C6C8~}nX>rQ@gVEqF(Ul*97LEsL2-Rtsa -z$R?8?*;W#NCp~=Ij2Pi-`4RzcGU2v(=(FfS_A^pc0W)^7T@Ucy+I7md`3`wwOJA>&iyJtuGQ(j&im!Rr6Y3 -zb{|bU(4D)zlNE9!ELQq-Hg;Y!VIrDDHyCwD&fJhP{rT0~yuwE79&?1H?3qq8SCOpT -zPtQp>2T$lnzq2lnyQk)%V3CikpUbo$;z3b3-+`VRpBOxI7IVr(E>*6JJW1@6 -zMu^J!nbeO~|8*!wcn`H&0p37B^b7n5F+^|GkL-P=?sFN|HK>k?D}Djg4fX@8&y-yf -z)Hp@y3_c8I}^nHR<-@eP0t5Y -zSSTt5<*gQB%ZU$S?~J8Y2~`ou6LKBnJKfBwWuBDD+O^`ECZ~M4aemB3C -z=j5sO%L6Mn+e@7!i9Ab_FkLtPu0a>fIqo8*MKDjkvuV9&PIPc1<2%N4PLh~oXo)jr -z_zt9FVS*PH+m{PJ95i{X8Qsa1ImoALZZR*GxD~;&i1De45-7O0$VaUyM(HcnPY@vl -z7=>_p+B3T;Z1` -zL-vMM2Hw0My^ibe;^W^h=B_a2BkDUa)x983mewk3_Vavi*D)MLDSK0g&ttNt-a)wf -zm9Jy$02g6|rRqrwDRpAHJy_)EKBIB{D=G>SguLS9b+CA7N#1g|u@meAkAa#Z+El|N -z-l{uc=B)6P1^wWSv9b|Cn1V%;^VlAUHAL;|?`l$Y@Sl|}nKeKs&ht<)JkvQ?Mz@G? -z((uTLFRaSGH+763t+~OtB>z%E^~1((1X4XCHd?&qFFb$4K{j{llOD -zeSkG>cX&3)>vEfd)`Q^K;kpMlX>vE3y{Xb^zSL2Q`W9Q8ci|*i!3KHU+nm{i*8!0z -zLvJpE+)K!adIKQXDQA3QfEX=Mm(v}_bR3JFkxhgrUpi4x7!4j2+&!1=HSE;gx461H -z!HOP$!4n{wu6-(hE4yL*E?r*@#5Zzj(fLe~IJbfYv6l3vQ?clVozT+-Y}cFEL38Z@ -znu`88vH1-#8_aZNusvI -zcUSs;u-%aSDi}u1o39J!UO0d_sm%A@HnbA4aVytXS+i-NEewS-?-|cg=%`TqfS)Ix -zM+xLM%+%4OgPN1X%~_wx+KPUiHuh3d=GX$7lTys`MBTeL0=i*prJo$*g3xX`VtB<% -z#Fom4w`|PdE)zUV<72C_GrRY5WtI(7qJu`%p0K=fRuI}Wv@@aC{&b=KclPp+tB&6}>q9K;W)r^fBx>5L -z9@oOJP1holPQ1`}F;~r_qN86zzTUZ`)9vCwX8ONSNGNQaPp^*8Rf<*R8yTa_=gKu! -zagG#?r9(v+v4D(r4(MR$iH$D#E-%P@KI^zA8S64OXd0Zk-{E%Q4cN`A3qI1+r2`=q -zw++wLe-d6c?WOcIrrUtX1h#y5Mn4ejBP8v+9(j0Kn-a!yMVHK~U4L82x6g*#tgjpJ -z{TBz6ayj%n@1SYS=*5jH22mXk9nvZE1KAxMTo+)TTwPwyHnj4jSfWZWtQYABqU&?f -zkfzVC!@;KPcU*5)(fRGDVLMhIQ?S5Fc1Bo5L+PgBygduAXB6DtGF -zm*I(1p5S_jbodR@k#h4Zqg>%C)p5%N3^;`rkSJ_#>NhkuOx}9JEB>(ITE{RMwu!PP -zCJDn?L=es(eei0Ny{23nTc6{inn<=a(P{9GEV*rR!v#v6sPfe_&$2L3_@pK<(8xBv -zDbvNru3USAH}bh^4S|S|(7MAEie*_>kGUn`U)a5HDDutfAU!RHu~ocq(H(a^q{k?a -z$}5P=E*~!f95{BW=uG -z#=-;g!rTJ=`7Pt_lNs)|^O70iU0*cI93ghnqSl(?ky@WytLPc_+Apo5y&qtGd8MWh -zA0{;i!A5+Y=8w#plNf#WBH12fWNFFSC7t@6(-A*&e5td+XZaf01m%#`vTY|82xQfX -zyO{4azOTrW%t0$eFD$98@aqfuHw|m@FQ#tXOjxB7sBkz@kAmN)oqi?o0iDy}iz*e7 -zATDE@nyVjEQgwdGY0{)-NKP{?dD1NWjho4f`sPa^nrds$gnL}45F;pr;+7*Bq~UqS -zLI3HiFt28{FU3ni$;np;3k2SvI$RDLmu@?P%bbvN?1H#x!l2IH-^CQ4G;b1v#wkJ) -zau0L_rb|;1of@(QS;n%mad75l5RfqvV99EOQSbqbw0CK7`zuhdoTuOVJ$r*38pgQN -zR$vcGCvP#oNhQyb+E+%{f_$`8t*g{xwu<+uiQ-zLvf&zOSN+Sv(74NHqW}`925Asc -zEI9EVp6aL{H7v9~TEcg^;wB -z?y&P12^8uqo!!kAyuCg<@Jef7q1U)0qm5ERht)&UDz0CF5&xLur8l22|9##c}+Gy2P1DV>q1La*~&m12&m%+o>Cu -zv~G*$Bjjh4v`(}_O_&a`Z)2SO`DO7gZ>OHbDHMPrTuVNa)hS52#yE`rh-H?5+HAwm -zhD|{&#yc*GAU9DwBKA}}?FA9LC21}@t~%yQfM0TotpuSqny -zx}H`>fxocSJqb6v@+X$c(tUaMeZkN^t3VGwKV59RuuDYwskNtKjzE{~j)*JKSi4`N -zw`WweYSGWM+RtbAP1FZ}(Ts=mm7W1-ZG7!Gh|5kw%`(?JjufO+^NoGCfvwRRJU^;8 -zpKwvY5J~{eF>$Ucf-oE``hwNqW~>E!bp{6~$f^009+$V`4q38`nimx9f@%Eyq8)fm -z81E@(1;ATFas$rJN|J>2kvi}?eb=tYA_W3OgY`#r&(Y6Tml1Qof?5NLEuO6!Yr>NW -zyHcg%KvZYUd_NI|&{DZNI|?)A7sA=ZKQ+FN-z73{o}-f1mjkH{A>W#H@I$$bBy7L? -zGF%y%94B!yiVI4DMH7&ylDs(HrgmsoSZH3UP>1KwgAh@X7$UajTfuAXtjK~##uF_N -zgx>vLKy;MZ1@0zyuB1p=K9F-^{rL4h{$AtUGXcz*Q=;TCtt{-6PPlm+S}P@;Gh}?p -z7$16he0l^isN|wjy-CR!t7lZl;Xb1}IyaUHep{8v)y0Qd&-nzYlM(UGD3@H{GG100 -z=E6^zh;-9fsbTjlqzid{oh)5_UcQEjb!9L5+DDAJH3Ur%cls>T?2&+ff!ewGb;SouvQ1EOT;t^Ywv^jI0STBrE>%MO^>9masnvxMTFNJD?d8EvPzcQq6e;Zs%y>tX)BEyG)8^|m3y_n0&Uc7a89}x#+3Z&$KAMylua7e-QTyfz -zkx0;B*BZ+x+e*qnIph*FL|M7biFrLR?h~S5%|t -zSc)_?x}t6UN61t^%C}M1Ftg1C3WJ3mGSRp=bckcjKuP&^Xa=>5Da-^<$8OvwGu+Lb49O$=*F63=1ve0@jSW`3y -z4tRJq_4v%ZxGloBxrV>dj7dJpY+I;LWALP|9pRN8J7zh4qoq;FiW*YHDY -zA%Oh0MB7j!?L*~ReMc=q2^4smmiT4u-a-j8#7V`;maM|rk{Yel=JG`Br%-;`iwUDK -zCJ0D2NWKXdA9JG9LKc1q@-Bf@MgODlEy{jP#BL8$?9;OebDs*+v#2L1OD|^EF=)&+ -zF-YT0NZSNI1hcCge662}<{Eki2j>h;>Fx4dm3TfH>0RHgbFg2&9%O6ku5hyxg;PO6 -zQdH)|Mc|WZvl>K$A#axSH_OI?M=CB&rp}hnQr?^(e}%yYTxz-spUQ+ -zy5JNGFkcO9IrNRh4|x|OZh@YuDXi|O1bBWn+C`}}`MM-Fp+6*eEn_lwSH!T1eKHKO -zM`VUb2DeL!y>;=|7C>ABt@p^4(tNCW1eRX-be$f0x+fZvAP)p1kXduddvzA-NisQd -zJ^x}9!=5wHP`&XKpA=boNv|axF%jyW0t>xyu`&4Ah^(Og9EHaD%VSM**M2+aJAG%& -zZcqO8aiY^jWc9%h!de#CF;7N7YMks>^*f7g%M3c6-t;+Y`j5WWDbgw*E70EyxV&wU -zkq(Bgm2osBd`a^oM?Jhj!ZKrFKDUZJdbL*$`9!+2W``^t)oBHfB00waXP`*g%*M=L -zZ{)`d1Vs&0BV$vf57yFP(8#4vZT%%Sk3+=BwpKqF$rwqh*`UswKQ9-7!g|fX5jaeM -zQ)RI5a?QUi_({puV0{*PRsnZV{y?TS5l2OgX;$#s5qt+B&R!fBPeB=3nMO(d+9d3b -z^^IUTo4Aah$jn5T>q2T4R*%Y&3F -zr7K(<#f9S9@qO$v9KrXsbili1DLiSY+N=e7XzQ*Q3ThQ}Z#q8wPvB%yNjCjKVmsR;@Llb$ig -zXR$pT-H6#CU`2w)!gUl-q)VyT5Q3m( -z{UiG?(w`%uTO}3GhunmgQ~8_PXrHo$aTc3=Ua&5vPY@>;zKDZZ-S}k6mq5nRtgqAu -z3LP_(t$(Y#--Q^TH%YlhQH6=mH|wWd9z7DpByBbGDXSGUbXBGG15*N;pedM)YgCxo -zkQ4fPMt9puq2CT`s}+B;vZ4vsf;){%C|FVcvG>;yFH^X*Q4{!j*-|$8RbycXE%A20X3S(+ReVx8I*weDKL7Pj*oK7zyfqtZ)1F3-DR?QufEr_Q_1di4P -z;QP&3wyNXROXKG4$jVWu(9Wl~ktr-V`MsZ1GGo}8M%jZT>DKWaEA;h@gYGCOV{+He -zU-aD$1id{Fyh`!YMHEc&Ktf*3U%YyPPgBDoWJX2nAdj(|(bSpf#$F2^gt!C4`xOH3 -z+GyXSi$E}TosN|H(svPj_nV4$*G;ci2A{z2C;K$&AgT!wsl|9cQCn&o73#J!NAFa~ -zNePi{j8SYF&L82HLB+BtB=HJD2zezS3_8$gQE<5AI#|9W{H>4++y)gjbA2(K$g=Sl -z*E7B+f|^5KAst+dlO8_%5b+XZRTUmJ@PPQc)97aOryZ*isqEoR3QFPl-dFZuz#G+O=;o}Mch>1x>a75zneGq|e -zh-w3$n0k;oNjJzwws^&pcC>R&PQL4XOJtst? -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 -zEwjS%9l*^tGwlqgo!G)d`IxJ%|8O%>_fPG -zZS9VGH(M(Mzpxhn3w-5jIYcdL7QoH!0lL|9KsTEdE7;I~2VXgZwqxe$>&fZ(3>QPH -z>PVb0fSdgc;AWS8Jro4E*;g^9BdkegL{E48AKdJG;p7K5`;GOEs>w=-I>625`WP-# -z4t)l6v&{i+cDCml(9NzE=mWUfA^HF^M(`g;x|JCpu;AYPO -z-0VT(Nsw(=6iBnK6B2j~%gv6n=2Cu}>b|?DOAlcHFj3 -z%e|W|4sf&AF+Q6s0^ID`zufF{fScX@+szLC>1Lb!WEGRiW?y>1O8v-0Z=!fE7RAf4JE_KsUSNA8xkT -zA8s}*#-DCB*`u4SK_CNkv&#W)Ho||q*^)pvTjbu&?!9-jWy%?4)})Tb~aHBp2XjUjW=}l?OK)zI~rAapznB -z;AY1f&4Dfq5{$+@&oW^42+$8A0JzzT05?18PdB^PYN9|t^|zZX0&uhY0dDp_?s>?A -zn{5kpvtI(-Y!{%Lo&4L)CcowZ2e{e7MT0;$+XaG+9^hufF1W}7-0U5In+@()kFB9c -zKzHwEze5ez1iIO205|)|8AIpd+m-AYK7gC8Vm4y)%gyEre8PoRL;ms6&Bl9lv*9yq -zkk%X>6M$~EvZC3>5+Db-*%KwP;Q%+g@zKr30=U`EzuoLC -z@BIfi+wb1Z9^l)>0J_@hN-1%JBPssJ~eP20r&-p%Iyhnr1)?`GSw+5dF2zx;Bu?H=6hSx?ZN51s$* -zW&={if4bQ!|8TRhAKYvzfSc|A;ARin2Z;dO?4&>3Y@z>`n;iylv(v}q?%izd-)=U= -zgPU!J3O@YkX1CwF*%1?Nlyu%SD|CbRZuX}pQ<(=hoAtrXCbS2-*`bV}lE2;T%LZAk -zUv4(ry_@X@bh8(MZg#Y)VM<}Qg%I2r<<+x)6F&pxY;1v(6}|7 -z05@CJ;^+@I+i??1n#>^^{-4PEX*sSI$lN&j%Oq0a$s_5j2iM}((8-E6Z*H``JfQ8c=rcst{#o1O9C -zW=8?s?BEACJL+#YI|ATlAG&2O`ymxRxY@Y~N59-`5mIm}%-E7lbOf(vlHHe1_qb-E+7;E!Oh0C2N&f4SMnzufFCJS?Pi5V@ajHvAuM_S%D+ZSvq| -z3w;UZgtGd>%_fiXe{{1W9^7p6Oh5f-oIl-c&j&Yq66j`A-n-cy05`kur<>gibhEpD -zx!ET`H(MQj`_au_jI3q(?Pk}~5bFTk?5%hZu6s9|;Fp^n0(7%)0B*J<(9Kprbp3~$ -z4OzV*O|*_&H1N~SK74Sq4?zXy45;F8sD}{2bBy781+@ovfo?YJgPSd)5NC%Ca(wS* -zhXLK}IFzgZcC&B);b!9j+-y82*1AVGd--DRwPCkh!2np7jKhob=LSGGd+Nc>mU(cq -zZ~t(!yFbi4xY@V>{_ii?|%cGmE1aPx+HCe_0ZgvO2%^m`}*~ZU~ -zhJL!)O26D}`QL6f;!iib3Fu~b^1XO)v-2L^Y(xW~n{5tsvx^D9;c&R%RV37@U;wBS -zr#H_J^Yw-Eb!Xf4v0$dKzT5=i0^x@06ul|k-)=VQFE?BCmzxdu_R-CzW&pU^rN7+l -z01OvzfSa90JV|co8Xg8NyJK-R2dZ@MW-9>Q?67+`8zl9oo1OEwn>~cg%?#K3Zd_tM -zVD&FIo0DPhUv9Q9z|EEdL-*(gy4i34actVcI{ -z6X0e~eHsF~*)UrF?Pk*v1Kn(ipKi7-P8HD2Mzeuz%Y6OlW*6PN*_{t=HtnOEy?*az -zoBwvRYq`*YZnn&Sy4kNyy2;G(-7%MHPWT{MMaF#z5EU&S-E5)GD)N83*%okPERf?h -z%qKuMy9eARuiMOvNH+jBUht^ff}-?DsIGHOTXT#d@`aMxH#|_-yj9Rak-yyRhG1-N -z-;ZC#CAmK^-MiT%O_D|TZZ_=Z%f{bscG07o{fsl}^3lx}xOcO??%iye{od?E6vg{oBokJ-BzXOMbc8-2gW`{=ePqkw4t* -z0-&2s@3KeKX|VO^X3ziaW{-?LVSaG4rT%uav;K6m*Zy*|pGE=P?67+`o9d^V-3xTH -zuYqoMgbwZ3Ki%wb+TnXQ8zK*s{@%@A69>airg?C)nUDOY|8}!29^7o>pKf-?|8}!$ -z0dBSf(9Mp?@wWb_n_c|iX2SyA?C{@icC={7zuoNOKiq5vv_IYKY@nOn_tVYJx_7e$ -zieeDq?;hN2*Z*>}wf}Il`Tyx=-UGVX -z4i9cN;q!Yp+wsB8evMN4Z#P?kwz~%CW)D2N*|9)3dl2Ym7yrx6p7_(vHu%HM-u=_f -zj;cfCe{{2_^N5yzy4eknZZ^g0PdD51FE<-87F+h-%|`v@W_Lci*%XHUp1<7e;aE}S -zM>l)t!Of<+ce9!QbhEqv;bx-&-0ZCX;byD;|!^-)^=6z|EHW -zUv72?(9MQA?)v}O-2TAvkm{t&CdJfW@}<#|8%pne!1BZKsP(&Z#R4B -z-p$?sy4gg?nWApQSS|tw05@A7;AWdWy4lGOZZ^*!Zg!T>vS2^J%^rDlvyFeb*)+f0 -z?0{cx_LOJ-Pd7XEZ#SD*>Cw%G2)lQ)sUO^IOMsiL@^3f$9O!1#{pDtBaCN*)UI4n; -zTYtFOcqDy4-E7xiZg%j!n|%&+v%}fHWG0dU-0Z>MZg%`1ZZ<^Ey_;P)5Ox0OW~=-! -zH@gGiW@r1J{B*Mccc^~5*|raE_NV`Fv%fyN*@OS>W~cpjv(+BmY?PTl-E7T&xYby&}s?ugo{i -zIzPs}zMp?x{c--|Zq>@4@{a$t`-9SFPp`tDiAYeYnzZpD!<`1Pyn@0bE>gz)?-qRr#VcLK&aN|n@6N;=F{juXSJ -zBRhy6U*f>bh7AZt!b+Y|T}8fAN##jsJX(IAKKfQ@+durXsY4+}Tmmh3v9>4T@Jy1- -z$9I{0$imKKN|_a=f_1a7BLy{s=i;O85Ek-e5$SS}*jXsBT^0p|5H{bo5N+u+gdy$8 -zXZp8#B9pt49j2PlWWF^pxefR8yK#kpGIup4lEVY^UTCbB}J81`5xj> -zn?Ng3GGnpn3vQqp=w=^CbGsjZmvO3V$`p<2X24x-{s{+LUOC@m6b~(z7)i}EtWe%-Odl} -z-U{YSbk}Z1uR1EnzPYssaKIl&U+Rv@%e3L;(>N6UOwUj~S_(z3P3oOY=ImGzqT}4i -z6)bH}Xc;-!DerDq+ZKsV`iieUmq?;KlAm5&pbMT@6WT%G%kk`Y1k5@k&e3utMNx2j -zU4Lt{3+e+#Xz3Gm`Di1^$(ol}gONtgGmX^V^MXz_di9{<=E8R63WDQWizy&`fEKAz#DVTa)&B+vMMWLso>HZaWwy5}FG -ztXjF0yJNKfG1_pbT6D~`W;g3Cmj)s))AyhVmJF(#=8E<{NYn#oX^Ly83;&z9vN5r| -zXVn -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 -z^ -zuf!@zv!$*~LQZMdC>eAU*BCW&>RKzNty`J1ps6>f$Aep&ps_Jl-l1ITbfqu#fPdnS -zNokDy0ewzX_DN+f=_HdCNR%n!;T!O5VLNW&7Fnuw-1?l*`O@~ps1QiUsaU!q*lN;^ -zQ@7e8zZ(y785lEv*3uJ_^x5v4`m^#Aw@e0ss*U)DD5n@NowEIC{ndw8R2$Cd-%#!T -z)1(Uks*Mfv3)S}C_*66Z2dcgMcT_vsNl^Ybs_j21gyr_XQEkgtRGXd(fNBd7{{z)7 -zc}2BzYDW9Dbc+5Xs$GnS`|TI1?f(a=4W;q_0@coZLA6z~Ur}wsX#lDX7Twdok@;_^ -zHjL>jsx6h5k0x6EcT~IZ71e$d4?wl)0jPHQ3#uK$`oB@_kcB@{?dXa>Q0?5w|A}hf -z{vFj$`xjK({T0>T{a>hd_zS9ywD51J_6-2l7JostXKeo~s!jKQq1w!^sJ88&sJ0C9 -zZ&Z8Y@b9R$QuDu~+Cu+CwH^N(sy%>_{z3RJR2vTB71fT|i~YY*?VaDK_Rhbe+Lte= -zc3p10N&o*wwJ%>#ZJB>XwOxOs+RXnzwX1)l+V%fHwP{O( -zXn}72H>yoa^mkMnb;BQkYOB}&4b>JBkevDx)#h)bqW_I*PyQEF`=i$7UYp|oiE3j` -z{2kS9_&-qX7^uK^zff%)jMD!PsP@_`s=e?Rs;w;Zf@(YdS5&(ifNJY~|BY&&KC@ie -zNJo)K{$#wNzCU&%Bx9i|vA8qH_#md*X-A9boz-4f?Jy7d9M;N4)vE -zz;211h!4?sG)xuez>z9;ru=t&!RvcqA5%Ri%W!6VGNC6`F9}ACmNV9L-q0rrBC}mr -zB3txjH((`sX%KY#&?Oi81r2UWei%1t`~a${?eBZE%LV)c1T5$dyJiUGB>!YH4Wkmy -z7IR>QisYgM6+9EdFx6UGjH*6$=1)k)|&HG#+h -zH%lD4AZ`l9Mwl5o2|xlk@Mdg44iZ%23F505habPm8z_c87|um9pY&WG3!r<2ZJ~Ct -z((}-QwQ+lYiwz~W-0&FW3xbNZ);yGh{yqwiO$ip3;);9+Porh5A`*pNFy;02Dc8t1 -z(nXlG;8HV!8x-Q&u4^(^jow;wAK -z{6;$Y7Ij}2O9Cs<>7J(|{PjTH@V%18$l^Cb>$MMxJj{}PmOB&`=F9bo=3;dxADT`1 -zCX7FT^p@oh&n^qPTHSjbWx|xXTC`(!~TWu -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 -zIhnwpKtK1G^2e3tDFtb?7 -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 -zK8}y__i`utDVX5o}}+@D=LO3l=f(VDhsM -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 -zl+`<*TzWgM|HghYp|srjF>f;^K{S@j -zY}U>-(;G#y=t_x9bqCq*ZbUnbN8=4_gIst+F(SuByKfy?Y%0B)yWBU%x8ENXdEb^% -zCnB+Vc1n;-6Q(0Aym!t(GjiTnZHuMzLPTq|Wy1~Z-Up^>J`ri7hqR4&c#{zBy==bU -zep}-dT)!rrxjm*>GPJCXI8yMQxGX$pHc-*J&uV8GLMX)U&J^1 -zleWMo7lAJdqKaomne~WV*gc)e`F&BagwlirLuIUS3%3|L5_*42@iO!}nG10I8?~pv -zDR{6_j)CJfiX=F`D!m)Vg9MJH~!^ULULIaA{@~m*`-T-Sa)FKz*DM?smcy+Dc~Bn<(Skx1GWp -zG-{RL}5)dCyZGG*?SK~*;L_)f<|Esn|w30IEbu -z@zXJ}qiW=yx~G3LX44ifgRVI0!825XDL=AKh@~}Z27!|}it|~}5?%?bZs3EyagUzo -z^X;O|a4OIE34%+*)<C -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

O(r*P@Yp?y^TOZf7ti6E06s>$Lpz8O>uc8Lk< -zCDhR)LTHO_G6>Q{X9H_o2ip*dGGYUkpuYa~xaGDuRu;@vnCVG48d-Dl1<5O-`0cEa -z@0Ih2W8CzN*m-O&loWJ0+PMwiRs)mMF*fLhnwAur+(qPqu0XAZFRx)FHtkmUed59s -z_h()oTy9_RCcZy^C7}3O8~wf_yu{KyW}&fPn|%RW7iu#NJ*8e{;Niv!7ueX}$eD1> -zWj2hQ!1r_+j>68wm4L?$0h3!AZ1_^Bc6y&>GpH+p>Y2uRp9~<<#Shxz3^<@CBvE^< -zW3X-AtveJLp={tNP5_WAP7w+4)O{BonD&-eSLWZSw`Dp;~k+(Yt?> -zrRtFJ)K1}3Xgh3l(mQ3@*Tt4X4`qFA1Ft6iFyfyk#3StqO>%4B@+K3nT)w+?GxC6ZKACFsA-$0{Jj()w{Pnm4va&p{9AzE{} -zJbI(7)vcDP3^lA3ElcD~Q_;ERWm!A~fAvgl__CjLK&s}2ok>gEREa1lG?)reB -z{}?e9fozH9y+I0~y2Z#Fr(I?y6^D4;T48uzw=J1f&yZyyuW+ud2* -z+RCGWP}zoXR)ca959%%0otU_4osFVhGYox(MHW$xy*sn}G?LSD=2ua!5wcgKnu#S< -zxC=~}2P#vwSLFUcioI;=>Z&9|!yd>{B5ZOIv2X`S$Xn4%y}0P^0sG}2HIkph!r9*~ -z0)TArnq9bL9UNit^{@vTE1jkR?GgFh#8Rk%f5)QCK1y7ErA?=Nmzx;VKMD|$ekQx> -zBJy#T3(8cUaGr2!%o^ZDZn^9}9Xwcu1TuoYC;igBzs>Hk?z>&nqnt69m*e1@Mw<}9 -zi-sXmDB>no}!Nm2*KXiNgpHcT?ebaYUegm$>_rWo<#gf!>J=RfqenJYO@l*(UvJ6C#6@zkMZK1wrXk=9 -zLai{azr$1Cza1MHf%VU;Y>y&IDcRwce6qpc)O=00p9=&t5W3>veBXB-1?~X0cO*NO -z9nNDFdY4tBp{-J#6-Ye{`A!u{2j$G{@*3$uDj)`K7HdHm%LRqVJM*3K%bZVn*!%8Z -zdixn$9v(>#rVToqtlaXCH -z-DXa-LLF^ldMb>*4}Y>9Je%M!~Erk~7i>Ze<&BcE3OdqEXx -z4TTGCpB%*m)Vy||T}1WLmz@*M9im<@`*xgf+dkw1J1XgKGfN~P$dGB8Lxg*u-mtNJ -zd`t@S0S;oI3o-v24_KdgO4}@PGP`Ahq6y}vx6*mJ9lNEXGU@ylkXR`+thb@eQA4;F -zfY!NAC8%IBDYW2#^CRw)bMsQX9IeHxo=Q0#1{I?2!)VwMc5TwF`am->g(s1J*=i;e?|pE{@{KhWDYgd -z+2=Uh@r83_P6{iV0TgVjUv*cE<;rM|v~mPG12{rQv! -z3)_aU)NJcM>m3GIp~$ne?^G)gxNMlV%w72GIQhQ-a -zw_)!mLI9i>awTs^4cVM2GsoJcD~xqu+{oiv+*2MyVtt%nK>jDI!g -z{*)MWH8b+HljNFg6@vpO07bz -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 -zOmXYZ$ZgOB+LPCMp26b0W$rsPHDtf~HR -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 -z(s&7vISxfx)ZCz%% -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 -z5Y|M5XM^A6imi~awo)N0Rx_zGU -z5>S9sTNvP!-3ZD24bW?HO+Z7I8i1fs9vFOy0B}yWu -z4eLm~H^>B`Pw-*vre1j>&hrtyGEc8%u;( -ze|E^rNLHlBSF6B>O9Ot%qg>G4`D|orYEWT&V)cGw0zj;_nzTaUZk_jI)%D9}T7DY3 -z+;pn;27v;DJX-v(tZU6qi8JVgZdtfmGy_Ew2{TYA_!m48y!GA?Mud)O;hU)JkGlD~ -zMHFQq5oHT%!I2CBsZczOr|P_$^F(DydDZxeB$LMxN#@Yen`k3b$U~QTQ~LE+9MmH% -zDP`0R8G|Y42$}I>dIN9}Tx|qCdFHUh%2=jvIe3a?(KYci#amZa`0D~xwTx6LpD#eD -zz{{q_N?2|!OYzFjOPka4m_;9zhf;#FHQ}8Q8NAt6dExjc(i)CKqICqLP3J|TjeX7y -zMw!204Hn{D6bhE7yX~b@X)t5OLcpIyMzGmuauS_em!(`)#i9LHCY -zVXR%_ns1MWvpq24DC9V8Qm@=LI=XKCxFuXJT?E`U_MJaOr8Cm<)OQR%HZj7DGXpNp -z+w*O8J$PN;DR}0J(TZE|w)WI~NAXbp9`LT*j=I=-&y|V4+Mj#PcTFO~Div9e`*u4N%Udly*WFfZXVY -zwq3Jh9^cky`cO0Df3CTh+-2aL6)SJ`amjpRqN{S2@WP>Er&YKZrM@)dy~xSF^Wfs5 -z4#vgw65ZPFNXNXWtWGJv7kw%J2UxGiIg_h|QmrNH;)gX1i9z)T8m&d`6VJla`Q0Tx -z`**kXj|Tpnt9K_nua2y3N`*akHa2U?8(QeLM*$Ldt4Gg?r&h=A$nN~^{AJHojqG%7 -zYhdk&oQ+p&)6&xXQmKtsMtH6GqPsiW{$RjjtF;_g_~Xs(_nY?br7)2n8K`)P)(QEu -zSSuKB-Ima>G9@>ONw{kKtmC?0zi!oNUquwD6D`pKkznzQ32k0RULfT;12oVyzY|cW -zh*T&a*0nplhK=Y~fXN4B<_Enj7BJ-R)H6$}pk%jAiWiOGr%~s8+GUPy-@b!jI9s`} -z)Sd2%6EEuP?++=Y36e(+Y?rw8MzLmf=}@2j>Y;=|_@z5vdvCySEp0rbJ7pRoJ?wVzr1(xCh7doPBOfX5D>rl5Bj -zm~-@)bcVf4d>h-V{QRvxvp7@0ioHVViNE+`gLvXC -z3+{3zZuf30ArPlvMOvN7+Wp|PwVJd23tv1q-x{QkP_8%!4E+iCti|kol*OZduRyW0 -z7WkyFi($LHzIVm2Qh8p}g3d~oyXQC<$8(*ECnLhmN(jMK&4AxioGyCw7+zsHfb93H -z)E7XSFK)iY;^1MnOa@A~4+YLuZS7~{8?|Eq;}XCJWl~$JMbh)JcWxPcE8j0=gZYBw -ztaA>#RpOIHVM9h>+x~1LM>E&C9SE9 -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 -z?pKFydCzJ(qTNq0HIkYTwpUgG -z?HPMB4=LrB2)Sm07%v;b4cbKI%5GswY>0yc@h;NOG?KlCt+MVK^9@&m@yf03vmJKY -z%|$a46@rI~dFJNUl+^OTp+>T8i2{sEVX5Qc9kSkHX}XRF++z(QNyAofiWFYYm7cu4 -zwC#r&h_7p(O?@+=?R(R+9$7o8bTYDF0+=CV@s4-ji9>@cNg`H7A!LC0#-h_GK{O%e -zCzaK})s=OwqOv%GHEu>kJLZ*j -zYUgO5uZ}o$J*V-Ej;~i&GA7 -zWN*=4r}aTNO@*(XTqUxht|U|mR}`8^+a_Z&nPu|t-k&^>Dt^B+U_R25PfB}3dtGHr -zM^X>oCf}K4<$~7kna_Mt19<==BeMaU{KCG3nv5V|n!i0&Kf4L6`DQa*K%r;;g!2Ak?Jcc9Mb@tx1d<(v__DOx!8Yk>=acqw$7NmQD -z?9<;;MXyX<4pWfnybEaurQxC*La<(e;0$!U=p|=WLL0Anmv!tD2|-3jRlCXWLkB%! -z`03VYH?#?nwc2^Of6m$IWxCUgRx@j((hsdcQa36Oi%SnAtD{!;goiQ%P<#`yVL)ED -zC;N{N(PtnQPNcV|K5jnQoh%J%i$6$G$>QZ6PiKlgzUzE8EhC~>)uFTe3 -zat?fX@H95rc6@PjD>Ps7(il#hudW@oY-Pg7{ZrGB2SmFRFC#?abo4@IvF^&rF6~kw -zB1CPr1WNaZhNuUtUu&^BZ^VLmW$V^6Z125ot=`GkWMw(wQJC%nL+RdY>2G -zXaUu6x?MzZuAynY=%%->60$%P55{4AYz5}*$N5)rDL6#wIPkR7VcO1LBjz?5!gbSf -z?;~4HJaIJk-g-~foHJ)hEi;jQOwVX^*TC|CvRg2@_6q5#b>MOark1j0eNTNU*ZN3h -z3j)YSZd-_kQ3y1`Rf_kO(nb=Ft^x{!9EHrj?ION*(X!oB!qlW2i?HJL9P)BOD!}wy -zP0Wg|%L4}T0?mAl{<`4uxGJ^4&}SLS9`EU|kfOvS7%na;V4g8UKMr -zu7mIy-CJ1G@63i`2n!@hEzAcP{QhCVW!*(xpAnhAuxtb2hG2!h&P=N;EErqA$GWu_ -zKoj%xdI%NF&cqhkAEP0Rbdw3vX@37GiL~_6k3fS2&o#2e<#TvC{s7}{R9IDlJ~1c- -z?%^t0{-@>MRc&jyaACq0GB{7-1g!*M#0df%oT$QyPdH*OUX_%Prh>qWBFijpIK#y5 -zJX^JgV_g9i%)m-aR+{)CM3*YO-B2E7?3P^qF^N_{0YH1v;xeBoMug}*sq*j9)|Kys -zWQnk0Ea$>)sGt}(J!CL)n%qUMr9iXhOZ&0vQT*iDNPAwKJNgLOJp{mkC3aejaSEFT -zamvdcS$}{zgqKc)8xZd!IKEP1C$%OH`ED}pVpk=eMuF4dq%ZHd>uioV>gS21Kj=^3RfcC4Z+=DwCx^W)9e?P^& -z9$#oxqBVk0@?k?yfh+11JDL#xSv{9L20_;!^ -z_pk|&O)8kPoo3EHjG8d!4U5sSMTyWmgmjnU*|<0sC--jB4)UAgSr%s8XrtZb9Oz^_ -zf#Kd&rA+M9Q4rLqE}cw#pi5D!vRIp!gV-`zjcElz1V$59A4;^xidA2!G!DgqYv^m! -z=yNE>1$y_50Urzuf>Um*<}ud})RPc)$@|m`8=)~#$X0hAM<4n#H%Xs$oNvvY=ti=C -z+&{_5Zot;A1dD^i7=^kH-KeJjbq3 -z?*UiIsf8H091D`%r_-Y#a)Iaf? -z4~KYaqEEfvL>ZGr8!KJv{9zSN{^6^&4XNfd -zGzW_AC>AU??^IB#;Z}3jb_j9!zPmLf-_6QyCzUNW8jzw0P0EAbsK;o_f2%p7n!!N} -zwpO>>$epuBdI3w~)83h>Mrdhxd4Tl&RQe6+NB>UBp}orihP*{%7V;hIb=bucu5%}@ -z2#X5Lela+egT~N{MroE=-^}ioDShUSj -z%3K2z)__|fNIHw!(es?F$6QW}bwAcEhmG( -z#W#Fr2|Gs>*+E}cRW*>2#6sT6 -zATvi96cETp{>HP1S~>4Mo{TH=+UZaZZNnx+D3}kg*S*5p0VTw -z4D`@GDtulQ$h(5bO7RIT#g}Rlo|F`F?A!R>{)|n&kNkNM-ciBFPH+83rI4CFQnk6o -zu({bFsx#S{Gi#MVGa-li?3n)C&9dPz=qas$YuIC9IjQpv^C<&q6-_=-*%E&9DG~{9 -z5~e+O`#zv3d3iY=WMZfZ6K8A1K0;<+$}B$PeD6WS;n1kB>b5(Z6fGT>d_6|ek*HYT -z8gfT~qB$itZ0#4@(7E%NeU%h*;zw)|`5mq@DB5TOO_;3YPF!>=*7!E1Faz(>ZV_)g -z-3!6uaayyz8I^}OoWi*`Tkn`T;e*H)f_sm&%BZ3{ -z)aEw@D_Yr?R-4OY+PAl|w}{>&$PZ<1)_(`hp!F?(25(}en#T`kX>}74?Unw@H0cZA -zyQ`E{KAd&=qS^*At{HIuqe^aN`Pf{X;b_WL%^m7A7MI?#M>koEkR$}6tqB1neKzBxPGsV0Ip8}O%Q3H^L#~A|hICj4 -zwi6+l^!^4OO9=O!Lvh2MEz1M0@xyV#O|l4tq)27T$2upNoi`k}-{0$blc(QhdfmU2 -zVYg*D8#QK(XEjuK;WTd8Izyy14n~A3<)qwYC71l6&_vvodIaM-89CFB;QKnyS>~Be -zL*KjXpuI(}FnEfoZI$9{3qE-E*trQ+@V@3?0E9C)Y|}6eJ|gX7w-dfx!+*5ui9jaI*x_RJSc#EmI7| -zJo8`mVDSB7-sAL|@uZIuwakGp$S8%bQT7``WH)l^t90uJoJsd6o_5l37-G3?)sk5> -z-_*ReOQspw$?+p9AxFA71V<(T{$XhMyRvb_+CfCBrwl$D+I^VMhW4^Bz|dAt%|tnh -z&zKEeUk8Q01$Wib2pG7;yVA`1qPG5_Kl;hgcHL_!lm{5vF$KX907Dz#z;|f&C>#_$ -z8QRP19#{V?5+s-vhmbrlB -zo(%09B`_>D**t$RfT8XC!_aPe{|(}e%3p@IokKC+v!Pvh3^25VTMPvNhIWip{R@Dh -zeX{%0&}NfCN_Huq0M -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 -zSEBhsZxJNo -zKhR{V(>%hpHBr{Pt9?d0&XC?J&I$5fcgZ_}SS}gCipRCTt=zk{Wz)kuje`{@(Z~?? -zowz!yx9Gyj}{f+k)I9V!fqn -zKvhNMQZfrn=<_;~)W8kpqaiz04#>P9R+XWY!U$(|R5YTuZEIio#s -z&4ZvSK2U#e#-#>w$EtjsSk+DGtyB@lY?uOM;9WqzSvgYyiX-Ezab5pRUfzZUw{Wv? -zy9^yuxNpd`@e~HQr{i4l`VW#PILXy@5fZ^;{%m&PC+@yCVkn!#Fk?;@K~D2sB+cjX -z&>QP-oFbU6(NJp>Db|Qlm3_wWhhj$SR&fjWlZLM$+jnld{5pWy-WHX_DqKKwkdxlFdzh>b?WMsO;Q$_}v}PNH -z7I7Zzf@JsPL#=LtYb~DZSHm=ibTy%T*U_{E;;%bqR)+cUn6RIFqZ(HWlm$(99n+Nj -z*EO-O5`90guwi8gZmb{b -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 -z42Y%l{-415sQ$N{o@r=8Y5UMc!sWdV~ -z3n3SM0z+%Q-5SeI%q6h_0+=?Fcv|JpvkxNWOA&#&A6sN`?I0nPH#Su#W;y}{C)zBx -zY%`mcCuvqNkmWNFRyt4}K*x2CDMx>_S#x*vlJ;u_R$Zc(PQ?S&gq!4UBxhJ$uVc!! -zA&wSIk*#V-YP&;+6Eeil+>{eYOvo(BA{$sE{-EfAu&3TQ@5!{2r*QR(w(mVvyaN|8 -zQv*kPaY(+B#j7n+4g?-+{mGKFP?bFuRa{cQo1GeZq+B&{28AwJYvXR|r8c=PkVf^+ -zXR)XCJPyYkov{wQk*=qPnFE^>gW)CyL@5EFIW3`(1N|h)7-orY#vK|J1;U0K56I&h -zP-Rz^<{UGahPq5-c!8JCh!|Kpl{?Hn580X=jBIZfa03Oj9y6g|b?!J+tYNCUR@`6b -zVnS%vc55Dl5l6M|1Dj2?#qy|R$exnmTRh#u#}egcR}9HG06~&E6pUX>UD%Y(||;IdEV -zR8&+>ww~(UqCl#tJCMBMpVH&H`&l1*FD`1%;ktz%t#}nUqVQP0Ms-^ -z_O$imumn7K+d1;L$uR*~SdxAh1#2aT%56i2HIdBE8En&s0Z<#{x1J}mwW+$>Ij_1B -zO@5@3hc3xhV!BqyH(_}Fq<(j5jk8&x)1Z8}5~~dNlHTHX$fU=yrFb3LjjCtGY`M}K -zvYYuo6MWl#1g*b#+y>h58>^po$RKH|JJYL^R2)$uAbib1FXtc4y@w96 -zrrP)g6DnjkLT@N5aZeWKo|3_rC7IBHLkIJ4o_PXJ`!V%BVUJ+QaDtJTdqM4ibA5_G -z;r-%2PamM=bU4nKLlnp%MeRzT`nFUxsubT=IlxVWXe6R?i{=a!m;b`DL%}8qkxD3` -z2GUgkjV`naNP~@%E(}4DpI(D(ov>G0Yhy{Ah^9*Wd}_h?AT8xT_1~P5=(Rv~f=VP3 -z+0))x29JW2x#sD}O!GCyq|oJYlary!;$pHgAMy5oeE->>tZ|95Q4fTD64U23Rq%R& -zT%VH9r~e5*XZSx{$j;h+<1Dl68=hT?d6-=eRYdL&?L3LIuxjZM -zF1hHM>U#q>tpHU}m1hM7o(*T#TJY@+-=;(15E95qOq?8Q0wbY}e!Woa79k1jza)fp_7jnEVmmyXXa{KISd%KRNI7e~MEr?x>T24gMDIaf; -z_Azp|7<7jAcFUfk8IX;*F7OnhwyO^xx8=@abXNE3qmL$7EJ?$g0;VPR -z{BV54|Bb&%DXq8o^A{I~6Z&nD{W8#|um(vQiU}oeMM+*qA{l)QjfouwC()A)E=I~n -zKRr%bU-AYmOn(}fm#xz7x>l52h*uPPadDh+m-Be*)U4IGCoksK8iAaD4IYcZFyJXXi0byC_Ih~=PDM7B<1PA+cJT=7W@OqVOi7#VDsZS -zW3PX9mE4if91Lr}9tg>cP?9q*?T`WRm7h9DuJz|UtaH?`kZD>G=4d&B87yrvF0w0nhtDKjW2P7|_h -zp&B5q$k$AVzNU-4uZQO~3b-_?+~k%1^*x -zmW!dibSj-Q>2fmzXreHsg+=a08&%&xr#J1Ju%2a1RH0ieR32Z!Z+Cn~StVRP<|mV`Y_xkb|fvXf=)!qd!a%ubu9Oo6-P! -z9OZC}$E;rX0p7SqzbUW%bQI=H9~M~dQw2U3RQ%1WN?KNBaRn~pzOWM^pCmaLeM9e! -zzHI=FaH}({-*}EN*4N9)D>O+>f05fAX|DykRB`4Q12K=q9R1uX>Po-=idoYz-*VNc -z2i=-SAvz^MW+n#&qX&q>{nZQkh_-;lf*(!s2f#3_k4Jjwu8uRScIZ99y~ov02oTZWDF$n100k5OY0fV^ZNG_`O(s+( -z5zrl?hbC@1Kz(vhRX)&`^Cc;1jS2WJ<)w-8rZJ0x$EN94*Q^3t4KD{DD49mgS;`ph -zQI07nNLx$nR&@Nf4%#2oRv45zR(k>D{a@>7*BO0wzMsP|>Gg`^B{oj&l-rMrxwlG( -zqx%NBjR@g^W&~Xp9J78T?x6`hULbgIMpq*{?K3c4w5EN3Q)qZ{Bo{rxRrVykC3}PZwO)JEW0Pz{%n(Bu26n -z$(Djza#gU#iP9L{B)Xdh-Nmwl5K*kUJ5EKyMv}r?X+R=y)_casgDOTJ_-Hr!*|sV? -zeK1>~`rz7vzbfM3^EFo7;97t?GdbQu%_W+YdK)UMAX|fBEP{(w(yHc7F;T7tyN4IR -z8gU+mAL>rsctw4$AW5`sB1yAA@iAx_VGQO}4g1ufSkFCnPt+(G3`&CrdC12aIZz!` -z&JCGw%0VgYdm3ExlTlz -zIBSK}Rd)fdnB^1g%~#?CRPKB*`rmw;i}?Lq(7+U#4pv+H>h%|#Y@@Q@z&uc_GcYUq -z%ayrd_1~dZJ>1t%LL&M0J7#b1FAMwY@0RiP=i%)lr?1zaKbO~^#f86r?KAw#*RF53 -zR{wDeWMM6Tl`d~DdB4>8?e%XTIlTSc|FJ?Gaenz7CV02E*P%Z3^7GpgUEh{;XkUMx -z-@apczpml+=k@LN?fYYVTc_*W-^1JIr?_m -z$AA5ObNyiRSJg(+7vvle@{ga86AK((4_MakqO&Dx5`;5so$-j><1#;BG)bf -zq?y4<#$NDn=DvN`#@TnL3(%E=t_otEMHALq4U9A@}u^}mk-9X9`rf1W;O834>{0iGI$DF#W -z-XxP&>ITe~w7Nq`0~8GECjQ=|(;PRC0xq2<8n{z^NK6ZYG=c#KV=yHxzuw;VM -zkk6Hm$aEwo(aYYp=>_9^Yorij9-MeiV3?D-Zpk2l)CH26=`@HnSW=n=e6TnlBAt3eYSGmLoD(;#3 -z2B>489P9HIiYt)mPM5j#%1`O#Vnu|WSUckvS&NjZ0{tZNU?Brg5+NVklN!mtv-(21 -zJEA3o!^mUAug)e--F&BJabCnDvxzLc=yZ##kHSitn%eD_1P(GqQwLbz4u;jT8Juo_ -zsW9|tEurzsJ#-4fbaH4Ax@W0JOlM6$+L?wSRlUiAW|eE}zFs9xbovQ)Y@(Ww7;X^6 -z%iyFIU-UMkd~LnTD05^#4r|+O;3Wub^wGaGP1IdrMh2`pcOgJ&ffrh1}o@9k#AsO -zM0107!1tEaizFIc8p8ihmQk>eRGS|Ickx2#Yx4k;iRx`=s+w7~a@OG= -zs^he>&$8N;(Z=v_`;V@Zea!qC*PTh>BU(YD^9+X>iX)}BpALjcwc6mJudNTd*`}8qr6{K$l -zCTRP&dz{a27vb&5{oC~~*~L~<`g;hLZX&CUcb_nMagCkg67{|Tb-6dgN0=ng&8oKN -zxB~Wq1{as@J3o;NWwo;fp|)e4A@SShcy?kFU%v%+^!+^&9>`b*@4kqXAheA!rj`OU -z3}G93xW2PC3nk7`L-3DBUO_1@nPHyYf6~K(DrrV5gKY}Lz?_H+SsD3}U^SGF(m&Os -zh~cqzR~2{5+$b4VXL2_lxy;8O`}#qSvGk6j{J^x`^sBc484=wh(SJ{8qZ7o@9V&N0 -zH*^PG+64Ip`GT1V@#Mwe$t})I1KopDLZbG`5hU_5i!j7j-a`CG`LfZe4?g@$+=wPm -zIJ!%smfZ}2e)|qn8V$4Gk8ZNRZuQ;DEv!*I3$IuF!9qG0@vx9f7UN1xX?&nYY -zFn(%Z!BkeiyZ{I7#SJYxi#cwZIo*|iOZ -zE1VwTg?o5@eTf=LbL8zQc$lAQSCM$#N&VVd)L~x6ZIFk2Tnog*+#_e@f?rf69T^b7XJd2@unF -zwlBV%>Jc9|pZ3jWls;4C&qXZHg=^g&aoPG>IdKOEf%+T@9Z*|qhJmA`#M1i_e6`)h -zL|7;7fu=4Zu2-LSzhsZqA!3uOnz#lPHCUFCBghx_u&HcqDIJHofg -ze>5JCP|ADRJv^2&fojE<&`(}|B=hA{af0QNJ^%K5NjE1CWPlK+O{pym(<#P-X7F)) -zW5^y4roS;D*~MU;{#W{G_4w;A|KmStW&AqhqdxB61QVWDRbO0^cd%vs@F)lm3FU4F -z1uHNa%HOHEP^ -z=HigcmPkjv3Vo2r0$wn)+08VipP}!hz5|kz0+jVUrZ<>5ojFS2XT+_oBI(<>XiY_; -zTw&u4OQdV=RL;D~QOIsSg}rHFih6Ry3?b -ziP#1O)sE3}Jp8ivz02Q_Fpy1e=ksE)Su2X+1k$RZRQ|6+;&yvqfIvJlN(hvMTd*6S7VyqnubtM4e0}vmt -zkOWo(5UI0Wg0Dbi>%Wv3DP5i%1>{y@ItEP*%gL;p#KuVpq0hgge0aam>XgDrC -zdeU^a#U~JZPSs1baC4r{J4v?V&(eaO@o9@^Lovl~xFXd4Ai9khHY5NyGEnekd^myv -z38H?2=UjUHvenkfGN1}vTG7{jqlvG&2g&x?KNpq}kR5bnr2Y4@cNb7FS#(a5EDP(D -z5;}-)vKtiR{XE^!?!M{Pr^3U)f()i|if;N63^An}V?B|AXa4xmM#6u;E&Cn*EMGkP -zu@pN#_(s1TQ}7~6K7zd3en<%v%)5y)S?J--=cdM?}Xz}^;lPjx!h$;Md0%FoY(pw)3R}Fzbf2L_i -z0Okd^I{clE|4)`omeBU$&dsL>)xpB@^-}ufhgX7{+csI?;GXrZ+b8vWZ?gd+AKT3? -zF>D?M`?t%dMN!-Rw_n(=blHp?(XH*DbkcuLCth|BcU~nx{?bSSus7M^v#P{R97(^A?e5n$wEl;yPPeWX#Yc>@ -z+N01fKDWEY<+YsRfixAI?=dxXnfTn-ue|qp_C9LX&Q%)AU+$0KsU6vRkkH01^7WG< -zUvbrqDJ(r{TbVn@TSGAEpvq@78Y#K}K?nI*y$@CiE_)*}W&Uk>6U3}@kTro9GkWx6 -zexgAPYXqU1ew#qJ!jN!yy&v8wMk_wXH0~aKD@yzrgWpPk>x|+y5B0k89W^6dvwkDcXQH@Wl=tA$Vb5yTe0_ -z^U&{n7kJ8V{2^WgzO;nB)9))Nn3Ha>$b%?cykK(*gLlgxqP}p0Fcl94xND8ShF*fV -z>(n>sp3p0k&<6)E@k%V$E#BTvcDf009lH+u@8(Wde(Q*i(CI#>N;->JpXZ+^Qj5P! -zuW0pJxq)a+qt#`fZlx26fnX5%;PYA*ZQa)+Zyh=jS}%eHu*91sSz;&OEuE=rM$=3G -zZ3~I&;@x7AS9AB?mMOqYyW|o;q{DDJHdCzC@H|p^+hk={Of1i`iyLw_GXMJgJX;xT -z5?WV>JpA3dRS9RJ`lj_Lf;Q2l2l4tUx7Am{`WQ_b2%XUq_hm65j&QPl%2xahPg -z+RY0vazqSTPvE?mmb8{NM|d~({4_}q%K?#b^q`wzEXT*$MZUy%TPfg};$R3}gRN}H -zE3`-@y^>C#)6i94$z_n$%t4|bc9f0rEO(!@gLeq2 -zNT4}&4$p?-;aYhXa^38x8OO)~w#De0XIZV^uE=TKT*5>gWxWj9u!`yu!z+BLLInu$ -zV=j3mqqgm-8T)4*hcXF>C0Da#EyA(`W}mehmSRUs>3B>J9Ua}Xxk -zBZ;zm!|Zr$brJ-UOU|OwN$zFQ4jA2s{8?m*=ipYPvTu=%WWNSHbLONfbl%H#J#EgQ -z>p9IeJia7=hxO4!irykdPhSYHve(iKc9MuAWjl*w$WE04gjbQ{h*Xb -zQDPWyB%>b=x=P-H2L~SwxV|FqzB-|QqEySkZXdEVj*%Tqqb(Nzlt49_O1~KVb9h(r -z9M;<|4n?r4QZp8ga9;Ec*=s<*vV_16HzR7$oPoR^8;EQVP9-+%+!UEW -zm?`P1`%W0TR0U85T=@v48;DbH7T?7ycn@_piqkjk_>vC&*>_Ea(y30S>JFKDnrAkg -zEbfBY|K3cxVr(7mw%mV -ziV*5a#HT~w$rsjBpoOR3hm}{AmMj1PkDe-aX;DpkiOf)g)YO>PIFOu9*uIe) -zpTsxu1;)kFw6|ejcdXURjGA -z0qf1yUpqxh?&K85dZl#*6sTc8qgnm|z7W~T70F3>3M>H9nR2$<&h!IwYfHTDjbR!- -zp8ME_%c8&P_Je~CURq=Uh;>fnq^@a_NEE#FtZQy;|B_!;%Q0^eq?&_C95YZ*1?}CY -z?4XEH;2hAJMp=m;^qM~G%INsyt6CoLv(Zb_&JB+~?~5!empYck(O+w19eI55NYRuH -zzd<9vhS&~lL~LmOgl`Q~892*iU?#_4{QUaQKRjL5cco&`vcRUs^tr>GTXV#vf=!Ke -z6k}O3x;|J7AZ6ErdeZXv6<;>(N-)Rd4o8kYw6a#=IN7+#lbIpIC$^pQ|lNo(1v|LnRCxS3}wmp0Yr&eQzr(BivmFi%`UKAL93S -z|2~K1)QMd1jF@42)IcL^BUTod>wG01aOM*AH$1hG$Z1e4QLl^gX_Q{looMy!WkL4m1d>hl3~9K^Q9@Sx;Wc;x{6FnP*K>#fc33JVe8h|!o-cKrQe`jdDLTSQ}S&gn? -z&u>-}Y`%kwSxDd^i@hb+mIk0xoyq*}>uTLNI16Stc&X{yFLJtjI5mQ0O5Cv7oLJOWochP; -zkhf43^5^c27Hne-9%-Z5BZmU2eh11(hqwJew+b05L)nMZOjy~4M1#R;w47KT-=vuI -z+5Hp9yaRrTbh5awWxs3}2RJOTRWJTWPYE9m0h}Dl~y6Z3PDC9Mh3G5Ud+2!#uYb3&x -z)ctBL&KBTQFUrwCf0ldq{*obcdbt|-FsPz}{1VdYc1x -zg{H3g7pem+ekU;WEvf=%(yyJm7Idm9(i -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 -zqC{2}FhaDn_iO;<-o#|RaHh4%vBD&hwE`=_H9 -zFw<3gNPz6~qWNCFHQ#JQhwgJUOcG)stL=C~X!v3mbT{QNww)+l8QARNfF2q18P;Fg -zmYO6;$R)utsf -z^|xDZ8bx9xmRM%E(2ow?iX)@|-r_RrjU2rZVecTywI!H*%SO)yQkz+vsO(sqOft*E -z?whJX4V%%0=3@8iN-EqihtGkrge=Z<0z&KwEL!eS;@aqJkRQ($iRauiBePO -zBXBJTBYT@UR<1>jeT8VK6VF-$)8o<|s<=D_zXt8+X)OafLkp`%a68$KNc{*Z_}N;M -z!3IeuWsUR*$hua^0>yCxM#@kneaoeJO}aum63wh>o)^fo?elUax$&CB)@!07!yYSh -z8zh8#S`I41L=3CUb@!DIn;56k2^^*GOg)qSUr60#>$p-M>E5K%g|>y<5CCzS-Omil -zR^2#hP7T|(!7AyX*lx=aV2<_f@(k<6h=R{uu#y=*AAooO@?aB`mm~p+C$B-QCl=je -z@zPpi!QQn%A*MES4p-Wr0s&^{4ryYOBLNgTGeBK%fq>h@UM$P2i5gOplcEpr&9j-TOtW9pa>`a97G6Qge>%x1PEF>%XskbG5WRNO9*ii7BhC -zqP(eWiC%u$hZTMSJx}yFUfCkjNosb?1(I_P1PScIdTauT_O%Su!r%*4IIjIcw!O(0 -z`GyZ)rdL!Ox=jL$A7=Hoy#m4>qDi?6X@ZMQT>sK@u5^Sm$Yf%dPP -zYEX#p`Z!7eNzA~e`IPntHWd0iZUZ`^>OsqJeCyYZ`=}>-_y-+FV6VviD7aOei(sU37%oJ -zLMyX|2Ev0VCWx8GF`7~$J#Ck?O^yt=10xH!EemW{=M1`r8H7`#rS?D@d$=R!_2>0g -z%Y@p(;n6Ew7qIcFt|?+hao|s#!bXfWc`g=z9>~&-67p_ni5=vhD#=U2lahchDNHO> -zJVA;y -zU9Vg-qw3KnNI<=pp)ay+)LfqyDuPDivhEW)Gz&Bln36Zwy()0&HSH@+;F`5~FkQJc -z3BWZ%{B%}NKZjT|{lG2cgy&tF#|0@EbiBEb2Jt4XHj>As{@bpaw<z~~dk5qZXo-}&p3Bh$rR*3a4 -zvLval$qQ+YW9Qaq;5LpR)-oPy;9{FS3!2d{3^vusM} -z_BTNxEKY^FBh%@7E$9>9Sa7EQP0fJma(c?kA@Umk?{;bTuMcv&Simx3I`wH)RM$-byhg09m`NLafEk -ztXWNEiMKsTXY -zKIt~fmYxcK4JrsL5WKPs#0s@>=;Tih4Z4*01iMGepV;?EB -z1X%w(Ea?kp8A@6us;6m^?I+R(e@)rCWdckLc#~3BSV>Dia&u2!?F_m*38O}+zQRx* -z1>uA5G^d4EPyGW%$$-SDFKLZ?hcvr_sn<5hvf*Gm|V) -zt&BQY2E8QN_0MuYJSO4+M&!)p^hvLHc{ElVJV|6F(%e!{7o1vB38zjY~MkVDFJYTvHo4Z-Zj3RY=ke@*U)JasPW^b0*wC#k| -zN#A*ha*K2Cd2j00*$id|c%h>!kIY!6%N{uS(lV*+5rM3JelPv0`8_MDE4C?^Xv<9I9oISdIW2|slH!&h>%z-xTFdRhU5m;0%5m! -ziFt;I})TG8i+rUx4k)<7u5vWMuH+W -zxiG?<(ZO*XA@pjlMsgB4q*YPw{ZPBZj}Yp1q&tt)+>?+_CD~1S*>Zh#>_EF`zZG8_ -zjUhOg4di&7`(pn*~QJGBD{Z5n9EnZZ{7qtW{QT7%`pRsjsOB`(|%=1Oi^pj5sNa_K&X!{Mc~>7U%@Qf=ewySW?>+ -zPrOANvc$n7^)yws@F6jnL33BbtyhO>u7lcZae|3PZ&rGH&r}yIWQT8yW-=$S%l64xE8vwgQ@PEr7<-#UWgk -zi)l{E6eCJX+bp%Oo#%YL?0`hKjzww;z5G>6f(V{{nlXq#N+EBNl8vE|JAwa8s$8*r -zvf+tCm9?l*Wz>mlH}NE<6&Ee8%L_FEnTO7#o^9`q_{qJ7l5*EtJm8(lHZ?Y+dB@Ck -znQOh)sUO7H`gMj6-u3KtKe6oM9RV(AcLsJ1Sx{j*`!0}dp+f_dl(!b>sw9{5n5Z}s -zP-L(&{}Kn##t}~#KQ;!++QgS -zjJe&FjnNkHDw~U!Z6}#3iV9UPgZLI$e_%y@mB@7M*sia~IQc4IFH`tAnXPQh3sQDA!&m!`YynCi4G-ae{WA=DD%777Y*nV%Bpm^9ANz}?(I -z+W6AlgT)ZIKp_%;gDhw3c`?oXAuZ%~L%g?!0^K&>!}vs_iTp^ntr%_uP}AjP*ddE( -zuS4+;YhVdIws{9EwMaDH+0qVrkHY{k;8u)8M6=*B&)$4#^IfaDz#FCIho0MJ-` -zL?B$-IEST-O}5+R>tFfpYV`4Oeu?u5!-a2#E$IW&+Nw;U*_S8$nN(Mnh{JfQPf0;c -zuR!w{h?Y7f->m~smg-PpQtFi5gI^zP|NsANo6FX+TVA%k?B!Zmu4UV{-Ii^;@Uy3Kj^YWX>!g&6V@kf -ze2rH?@jT4k8;aGx6K}V9D9O}K7Gvh`(gRjQUp!fs(Qg2i2_N^C$#)%6ZcXHdc@SVD`77$y8Jt6LJtnfFX7fPm-L4?mSV`MnJqFEqEJ$S~ -zld`LCD<(;!)o&sod-zqm(|VjEHUi5$>_u0K<3L)`)6CX4Ka)weoKUG5mr1E|?84N{ -zHY$%fS{3q;D=r>p!%Vuy!=^~)_~&^^<>oypRYnw&e;d|=uDXm2J+|pYDTUH>?Ro8# -zI8xo0v+W?qsej$yZ%%Lhpf_4?Ei+7rKK#vOFhuNgzagzcdr`l>t0{yD9m*KCGJnDh -zcyI{Mx{n0|-F9)6!|P9q6)moBtKPui3zbQ?*Z%N0E3EtReW7;a8?x;7{pF-h( -zHEeNuk)hpam=>-1C_uHRuu@nf)3d~!mlw)RE -zf&~xwt3^=-3WM$<%JFaF2Dqn6!9jGVUjW{6_**FddI#UiQqUkCzXot9$H$x>+xg1 -z3h#qo%)o60s=UQ>4SW5A<;H9C!qsa{bIk&hKy1dDgGj2PF;IJ?E^F4Zy=Tdb|l^Z%<4km=kn&jS&Qxygoawt-TW-cBqKGY9No%EnhFW -zmWWE8>p{bkj`ReHjQ@ -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 -z$4q-EVqnjRCfDjD%e%7()ta@A -z^hJA?Z;|Yn6A0TA<0Zh}CLFRUk4iKbxbk-N*ymrrsrF2F^U9wBJm+p@^J7z)E8oX9 -z=e5gc-PU&kTki{#AjN6cj>r++Gx%{&Tcco71)>y_XoJ1p7tHqxofq5dL^3?vBkP}r -zcFSsF%kK^{rLG+#?XxH9wuOhCPeXfs(!y(R2rd -z0cK};qhw#j2&s}V;my}d#;0^w?L=o}s-@p-E%dro68kAVY)?QW7x~1CtSTdwyPdl5 -z;3I7Q#DpZ=bb1O*4&ZoT60{Nz-&AeMl%!i{UkLRNu&@WD!ReYf1(_x*`53ew=Sr=*Ait2; -z=EHGdC)Pj~vH}~$kYoSKZ0se7W<8?aNC@=ad~RQ4u5){OvV-i?Hu$@HehnDW9dXs% -zrl1DvnX6WaE1hQp9yA1^@YLJx5+Uh!H-;MXm6E}46Go6i!S3iv@m+7dx~-WPZH%Tr -z=5^~N;JPBCepHbmkD3cDrhxt)4Y1VL=b0aOaa%2BLMgtG#a2o25P@M715q%h7_fZu -zRkDX -zr>k|}Z!NMO#s$7R^~=ylYA0$#$V^>OyK2|cA^V7)*r_Ru8df&u7!VLrt`2DL -z^Kzua;LlkN@g;PEqZ`9hneMTur+DE;3n);vLSvo1n=@*^bi*enwgU9+yvdzvG$q|n -zn4|!G+tNAV%Ee;3wo>v%-<|>J+j%z9ie58lC`oAOQ1dzY^^{#|pX`_KEwr{r6g#+(b -z9)^(yTVpd??RZ1j;!XdE4=0>Zqb;c!Hy#lnBMQ*Bzcfqw&1V%-NG|j1nye@GBIG&* -zNo5{+tApvQ6O1SxHgze!?Jw>`!B$URS;EnTz6+BA=-Y2vBVP3F_osMCFZ%X~PR^BO -z2Up5HwecB1-_~m|0&*NEghJ|KDuQj_>9gCfmGr+7G&CB4J0d$&c -z574)5j%Z`lTySxtNqu&`T?OMuT+5oKs<>yorvm2z`nG65Y2S;!4M9Of*zr@}#*kz- -zjdbhU2k6^uoYQXJ0RVkl>_y)$2k6_M=A@!qbF)GAFrr@c?L6^gC1YTsTfVAh#9;MN -zwFL!QbFOv}Q_GGe4a7o|B-K!TydI!2UKD49=&+Ny4l%f(X5$fd69!pyO2G`JNgI;U -z^TD+8H%-fzU)l`E)Zc_$g25Sc;x0vrqXl!f0z?Z5daVU%{sy!_3|Tr<``! -zU4W%P+=jf@bTQT;X{aLvr8?en{*SAFWOy$cpJ(Ps*N07sD5R8|q{ZWSYMJcB!2D -zoqCi521n?_u!J9;xc=*7_%h7<`d6Wyb=Q?oYg~ewx->KlF%+y+qB72YEjzmk<4IO< -z-8{y0SBhnYG`!E?E&1=7^GfAmcUW%(YbCu%r4gV+;gNzKLMYDHt$>64J%r<235^IJ -zn4uxRd}Kg07v3htico1A>=)ECWG}|Ag#gKH$}>dPUnV(@$d`eZEx}m^=`w4I#-#z% -zUunAP+G0z=AedYvu6+}=G31yScxQ{wez$jB0}0o)?_wBiJ;=dm5x9t2f75si15At) -z+e0`Gr)RK{QGe%OFv8AQQ(_h$1nB^XUF@Y^BV`{dXClJwd>?E!4+r?$d4)meIYfKD -zeZ}S&2^Ao=0Dl`YR*WQ{(g^B?atO^bw+`G1minV6C^pg{?d2eGNga>`5Uo+|LFkI; -zs!SHam8;Qjf7_?3q4-nIJ;Y8J6ugf)kUa*QF)wK>)E60mzb#T~?S5GT@V7fv7(Lqb -zKnOa($l#1}a;sS@T))ELw>TU;UnvrF+r1z8SY}}8C~D@qwTL*qBYp0|BXB+D_AShX -zYmI5;9eyFqmt;@>cCi5cx~$AnE#y+0>`(~yv*TVJld54t7qpPkaK+Th=*-i2+(@X@ -ztMt<|y|4*LzWFen0}a+fL{V5YVl8tybrD+kkA2a&c(CK9<>3u-_CdsXD*@EB{nMGa -zu;28fDvMoFWn)V^4%fk7zfDbEf2f|Y`jR=V8cDcF9_6R5_pr+v_8M#n0B|c2sgH3w -z=~Agq;>JRNpGU^C>Srp5{6#5>Rc$!eqccV7ZsV`RqTr@*8Im|;>2KR=loo5g1E&Kszuo`UreS%%b -zpq&tk`;++mZ#k$*{li*r+PdjFeY0oHT69x#6HA7jV)F_YL-H1GDW -z;GOE_w<=8VC}*(~(LLyY-?pltf+Ws2*GR9)z$qr -zH&^Z7mT`^!v|T&2R2JvC3<9lw(J*^_M01kB)#CAd6#2Mxa2AQS%pgGu^h<^p#P@HeJ={d)E&SA0MBvR(to(uRpkOKiyw{xcTV$K0|kfk07Gq -zt&WGg>Q%STN}G(VdieAGN5?z8Ggbe$v+pUn+qVhI<*<^lA6sN$SjJ1g5ff9@`&!4f -zDhRe|_O2ibH-0D|fr&H*p}={5PDJ0R -zYY4LC-Fy*bv!?R*_Qc)(O&M)Z^9TVh?x3L%v(MM!HWwYLMX()=1o}v|9qpo -zp*sJOb7xxniae=SfnBdYcLqNhCsR+3{p{vd+F9cVG@Ct^iT1R8A$<*-JY+Zy%D|v6 -z5MIaw^sT11ana8!a^5R?$=hi`y0~XI-%D=^*tSG&AHIyc9ttlF+*?yd~GRP5Cz?YuNWwD)>l3S>^j&U-KjNkEVQTfauG6nY|M6eKLRS2Wb! -zeEPK5Q`!vX -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 -zzZ%*;c_O{@c)2 -z_-$yHYyu4J#RsjvABMJ^#V@vb-4DAfEp;wAr?OSpL({CjSgDw3Xzf-xt0Z+S$?n -zFtn)^e;V3KYX6^xw#ol8wD0~jv6$#p>6qMXxsc{XqUej+J$t#4DCmN -zq1}u>`R|4{Xv&_5dz!;P4ed*qUxs$|zZ=@!s=(NP8rt_T|1`AQmH*4o&Q$nmXiIlB -zWHRj!8UPG!1f733v`I-{3~iec@=v>Vx}Zhsis -z9n191e;e8}KMidzKhTBWhIY)0p^fJH!_Zy~Da-kXp?%2qVrUlt3~i?`Dx_jBhW6gy -zhITFoz|gk*)6o9%TGfaBI>uH4c@V$xbW|WcFZ4!b|Jvf-U1lfNP+T7 -zKMify|1`8uxlJyA8rlN?WoWDYVQA|i{L|28VmgXvzxZKjLv{Qzw6g(*wjBS9p?y(z -z-u8!~EnQ9;{bFd7h=JSvFtqi=LCBK!J$@M4yhmxg)&F5=SN}A$U49$dr -z+H7GjhBgj(>^}`{Q=uP*HqncrP5!r`T_hqw@Lz_u-=Bsy{XY!tDS)BPBJ$hNcKBgv -zR{;#|r=Nzl!Ro&l+D!R>8``w!|1h*I;Qz(YMs56IXrp@mFtqFbGPDU(|IN@&crmoI -zssM&|Au_R1>>u*Eb9ke;y+{F1p`jMv+$2yi=7JaVDoF2kh -zWY=1j^V#1Ib5X(A5WS|rLG%*a!lsb<;v2>@guRz_!>mY_J~D|Cnu4^^bGadLoOo_2 -zV;MFCBF?DlmM(m}lcjbMFdIchJC-W{4 -zY%o@+R$>}&QU65l9oEtpMl?}h0$l?n22&6Lx7uMyLmk!IiuBGllx}C)%)Vr(Kn%n8 -z$0a$&eJsOvq$Ed0YWK>JV)@FXU-Z%+jg+D-Y<2G$LnGgwxCI+5jm{mMW2Inw!os-MP9NQE#7x^yZ0P5&o`0uS|(w;wo#pQJ&vmNc6pv_1(?7JFO8 -zH8=C8aXj*afk1TN@K$eXK3_z1cd?&eEw@ZsG7!2PBx_$o8JOUD{&l9YuF{qV1Z9IlxVaRyc@eUN8tPqC> -zY?%-YYY41qF4QX@Pxim{>L%%yo91 -z7#L%^*!w}eL`o$h=G;OGU2NLoVdk1)YZB>>V}3zjAnLi6AK+pBYkwBKoo}zoYu)m0&_p1b7Zw!56@W$x_UMy -zg+JC?9B$HJmD~;)UV1cwA&xYJFSf|bMw>X6D*af^?HN?Tx(XPb2B+O7BgLhqRhu{w -zU!m~m7NCsp7ITdz8o&|m0sZffzx4kHy -z!)Z^4vW@$#u2w|;C?>?7;66Dob#XGV%AtgXp1@d<@f>ic8GrAtFLr|!ZO%bXwx+P9 -zp#jGmPl-rmx5e?!^&EO3g}q%pRvel=xwjhRjaIF8vtF_BXywiBZ4p6+GN1 -zq+%Z&xjCFGpN)F|x4n~@Vh&TEh8Sjyebv;Fpf1TlM!xHj)i?(dX6SaJ5K)o3O?JDs -zePOJhO`+0pCZllqyi-><^g&DIAzP|jZ?Dz5@}}9m7FZzMgUXwFK;}>tIya^+%(WD3T4q-q#cil-@#EZ$y1^Ek19q~mtNNeL2%QKm4E7)5sm60Y;|%b -z7tAEkRHY$A_S)~-g8qixpZ-}0f+~1)I7mHmpIFGNwz`RgayIkW3J%TjqV1ga`Aq#tD&E$F$8H?hf%>{75VU -zushGLE->ZR2rl0!Nqv^Jdh#+PR<5FoR=Cmk+ty2ROP;`AJgP;Z#*g~)Y9?kk>Wsj? -zhwTHs{nutA?h@prd1#u|nGH2*D04P*;j%tBme* -z?^aYyp)ajBW-Wk(@Eq*s`QAgp9w}qD7Z#Ndu`YM11_RF`&ksH&)h)+-c-4N`C`(Bv&eCn6@**B@jj67$xi -zD7lz$ijen~NI?+ZoqIRhUm3=uQ(>J+!i0(o4rz5URviqMXYcEdMAHi{uzb;0AcpMn -zL3)c905Xm3GVYpH$Z>~YT?Mz#j0q^iQ*q%fFFwo_Mgk=)_a@>;i~_J=1to*NtzeEoSFI)V{Udt(huiAm#_Mmv -zNAF}N=j!P*a__QlW@&3!%OJP4NqmyD9PBD!bR1?lZKZ4o%p=8nWaHnnw%wrfIO{|yrB!^?}Y2nh8h7n`8xH0OK7}O8-Rq?_2xo#>ER|2IL!RxFWqof(nrFj-6YPnRZzyV -z+Batl12*v_2mR~sakQ?RzJ)M@QKTW1S}>bNdt-x2O%(u7$f;Eoygh0_rrSGWOnJ5l -z+Kl}i95E-^p?jwPQc}hfg?sn@HYS)@r)xCjKhP)KMof|v?V+HxU@GbpIf$bgcA;c#2KWB22WeBl^Fk~PSNM+8UpLQu9=3tF3Gw9%3$1YjoI6Gkz7ry -zbR}E0w`NgkU%m1pu{>g11LkKbk?Bbh6IS=Sas7pUm6hM8^~NTf4U$g9*>q6KrA@ae -zVU|UY5HsyPH2@hnFA^#2d(JJDw0AzSsrbO;LeEVvMqwXBIRnRn{hx%#yPE -zrYcdxW;CLvJfgFoex+g$Q!+;hus(l&W*XSYIF4eD_q06s41p-RkC@N3ZKp4QT#Gc>Y#M7ophh}4y!vY)Lr -z8ElYuQtn8PfD~+%EKnRL0HzFL(zjfi*Q7VJBkRlp=XrrV+deO+^mZwRVQ5zBNBCyn -zHc(u7CxXzo*x9nN!1yk8v(+BR@ng2{)d|f74 -zv@c;$TG$My)>N94OIPi6ncJs7fPD*zOFE0XGRT!ThE!lxvD>*%c8{4x2|+FX2-WXX -zln&5pu-GL)J-dM3n99RUADV529GJk$AAQDCJIq!hGP$0M2^Fe{o+z(Lc)ccGGVHN3 -zw?R(0r{$pXOa!#bTz6mju!(Uhpukc3&Qvw&|Ao|AwvH=xlkQDAU1(d#5CI*hss7BM -zY}JjE=G3-r8?2HJO76BCf$UiCF59qPj6nF@1uL21^8vgE@DDaYc}WtGcqSVxdt%Wo -zFfXk?7F1pflw)c$=WwO{DG+UT?vN%zITB5wGXp#Z7YMseY|OG;zDYufmkwj2 -zbyf2r&=KyC%=Abp8*0i1)4gA`^dU~V3VNkfBDLtqitB0YzyAB$H&@HygDiJmlgP5# -zD$1V9mgu#ZeOTca(DOuN7jpb^(|3+;aq41<7s@`20J+p23YHeyEnWFzi%9oAfV>*K -zv9YOpcug;H3LWUaGWP^Bq$cp;Qw*k>&kuMcTSJT7W&=jR(a@tM@B6Zsrl4#_N9OAD -zsTp&fgA`kp2k1e)^$EG65RFeFK7MIj`3xhHZtPSHoQ&hV -zc@1cv@o6bJAqTK5U_zXQet}si<_suE!>j=wQo0jSRj}<>T5PWZRpp -zk#G3$WqO6Uq4gxN5@J?w+bh8BA*PhOkS4^~#Pu)zX4NlXr(Mq;LF5r+JI|ZM9_R(T -zsWyf9u8*Sxs>BRznonteV0WR<<2Il@svfi^$G3jnxQ}|$2bmV+N~W|~uaxGde|TE9 -z2WbQm_dVVG_Tr4so+aitbRD+R=nELjhu6_ev(4K%QCG7~HW?uQW|mf-0| -ztGY6UXox(BWrDDI9HS{H($jXS+vLb_J1`P)+p<7?bYXBH6bJs)FKonElk{To=YcHUC?W5L{@6kOsgmp@JShqIlJdkt -zB^0C>v>@W-*vKN~PEWs0kB1)OO&%#Pq#!_jhy#(*ZE1Qy+qwQj%2GpZhK!UxoQ8TumIM$PqUp)P2gF6%y_U9&((fhl}*-K#Q}UemtP1g=@X2h){H -zlh9lvL{Vq;^mDv5(+}K2PORRgd0dc!LHnEgXb^SM(j!S->c8!(d8=aM+oFaj%t3Ux -z>INAY4LjkvzTmTz%`i3N6iwT<2O_lryvTy28d^bT=l`9}Sp%7fBWQAD& -zB1@9$n!J$yICgG*25#dBVlCsL1}?VQv!KbXqq%HX*0>}{8Mkw!sFqD8QCwJ&7+Kef -zxX2=-98W4Sh0Nkc<9U?$W#9*^` -zMJAa%zBt=WEWx-?>7#7o@zwiwWGM{$2mxeBa>QxVw7iwucpEr1%H9^V?6T^^zTWn! -zWwR5M!{St!J2IWV*Mg?;jn!xR-_#PAE~lrw9AdvcD}_gqWihFUru?n4yNF>XLcr-h -zfY*Bj6MkpGmXzN3`oe7IhP!pzdkL}jO;;nLHG7=0cngc{byJ3rrh;EyP;v -z%$gNgmU!Efbe?Ae!tiT1CZ`)}db5U6GL3edQt-Gy(4(GL+6uvR;A(-RO9|Xiqn0&W -z?3d+f3UK1RqFg>4iEe^zn*D&t#Z_Ld!ux@_a -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 -zdp!D22bC#i!;Gxv;3iiSzWle -z$P+HO_R>dJ)B!{?j0Ca) -z(Wt>3DCZ@UZjuObhbPJ~kW_hLFnkeJxv^-{J!^{G0QFnGC4Db{oAdgm0c_QGU -z03N|2h6nUV?n$go#$G1HsucuRpandjF(4vi%^Ft`6SWTbX+qj~#eM6YVRcYSo|JDo -zVO-cJnTANDyq-sR-FJ`CCxE6oc;AR#z5=O-CmYc8q-Bt4n{P -zu~@XI!K=7sc!dGIwGhi&U8+knzL^y)VYdw7rq^?0Qc+ypMuyyFJJ@4QY+3@yg9h#4 -z5h#c1c=k}T^7IGsK*(M&nx+?+3gB|2-eZHc=@Ll#t$Y_BLkgtzRIPWYYO=alv1lDg -z)@UNwH8KCu!sEU&Nobn2K;VnoLoxc&^|4#`88crMG%fSx{uLEd;|eC)E?#B68a5la -zPj~O3YI&4|%y~!QXBc^&CGKKYdU=Cu~t)?{{ou>uD;h7IOc#G~K -ziaoXGq!*(o8gUq`0rUCj9qn=E@(bO}p-u*Hw+FyDBm8>k{`twP?JF`OyfX_>GWAcs~E`%cE5 -zZM_;;AVq8y!P0Xb2}N~g$Yjq>inQ1b5K6wnC_^O|GWM17spykcw*!SlF=5SiLe(T5 -zZoUhk{u9O&YHBH>ZO2ilRYq_}%lSwPJ)?HclZpo(SSI#lz0Q)3O`()->(x1{HC!d&VX -zWGB@^APdjQWgNwC_wJR_P9Dyhl`%0LWI@MoUv05vI>j39K%ib*!{RK@xjO4{b3{J% -zt|*Nct{R4-WE-geFp;VegxP&$@H4IYef|3W$8NKY>ly!QLqAO%jYp~xFdSmWg`xJV -zP5*@wO3qDS&j^wS0*Nk~5Vh -zbM(-xPIlK|Q^uVsQbe~=@evIIPXv--X|B!wtwvi$%G>3J2&D0ftT;UUs-LO-@jyEvuv~_-2cYKAcQx2ixSl$ -z!4fi6j_jr{+Xng9?5={C@w&O&&|SpcefT7!$)*v~)rLh+90LJc=!>z1MwxcFZq}49 -z4`)FWj6Ax0!$mcZ{db@|)|-ltIYyTm$2<<@vAL_MZ9IxnGv0iHPrY&NY~2^r-WbSz -zI{w|!ftzDAteGwV2vAX4C}T*D@Kru$#pniaJT*?WNWonMp -zt6w7f#xhYBK9=HqeWP0sEc#9C(BhJee?vFFsA_0}nwu%^Ti_*{WYUyrf$R!{ZVyTvTexDETF-%fM7>%HBc -z)fdp)+1QJf+L+a7YWkqtpB7z#R#{Jj1U@xJ%b@)d;Qm#se_~GrC8MSUDt`mtT)Pz@ -z)Heh8`LodfeJYYIOcETL|Bh8bSpeGs6!x#~Kf)%t5RWQOkEI~aMm8?Tx&yB}rN^Nl -z?>F=mh85fqmBWB|$Iy@gMU00volIsnS9@y{vU|%RmXDFOVp*5rxWpyj2xzutj?j9m -zg^xAN->aY~zJgD_JEeXOzjED@KQGC4o#NQmSkTJzeQX -z-)`umS(Qy|eJE&pMx&?Q?{>jeGUb~ESju;cAXTTNZ{*_bFO;qR4qm<8lDm6gcUO1Wu$$dqV3X4Tt- -zQuWpx5*S}yT1-wQ+tjx=ET24RaNOt&UBeUk;xWx2hBMq?vKIaoR2_TW -z_*K#0PV=GGBNM=GH8xKF2F7c{PrTXUYrJXVOT1Jgvf#2S`*v$<5=krsynZD`=gt(RBr+T@Wy)s!206!iTd_h;XRB%d>1sM{v*N&e- -zQ$t?EYx{3-+f}L3rBgyi6^XMf<)udgh<_j5XQa;1ZELioK@-LlE~)x;%_(|Zt{%B^ -zBqpoIo(VEG*oslgOT21&NvW9SGo?b5GXV3H2zi!NL@DZ1T6sRGg_5b`gu)b&@IRmiFAog8tg2nQofZx**vg -zyJjInB~B3kwjOpW>eM;gqlYg5HaO}-A6h^?xJOg9+|gx$xGvZD&Rsp_LDAUxn&QEfGKX)#g&;B2TKg#^Kzj>>>c=iQ1q>}RexFxS5aOCc!) -zuLl_vn2MWJ6L`ilRL?>t#XP*$rZ{;?G!KLdiTOS2)FU)>%&F~U;;pWbkLeSL5VmYB -z4^7QiFHVc2<$s$gAImyq^>&$P?DDdXNx7is5l~jn47nv#3c@b2mJm%I<&m*E_LBk+ -z9nbSeLWnyTMveJ)Y=utA)N8g&(`me46^av+4Fl}q{$s!P&m@dhALZVeSb+J#m|lfM -zE_Jc)4*J2o0WU-9H0|qp@Q{IR0e}5W^8hoiuhcP1$QJO9#FTcz3OqK;zmV+Sp_uSA -z5UM7I3uGcB^thxx$467%LSj)}SrkL>k<}lv25scb1`8B-PGvSqIMo)ZpTs8smEfA} -zw=%RPc%M>>$q0b1C!VnI%e;|kCOOgI(k`b1_7bRaLyO|Am;c@_XKRn=vE -zdSZ=gIn^z>vi~>cHgMfB`TY6w(^f4IXjs?X#hZZlg)=h8LWv9sx{ -zaB3-h49=dk0h)eo=GTCKF(#RDHX@!UtH+#2p|VhM)zy@!&vZ6|{ZlL`ujP%i^CWlE -zp+o9+JQQ4WVslV|eG#`c1Woo -z)uGZ9M%vS;j6=r5)6Q5=RE|LC$FaApQ0gSz?k#&ZhqzBKoCdxt%oa0a8uRGvQL)WR -zf*jhoxDSYrQIa*ej(o9#2S=jl#iR33f}h_nBxq7=>iU3XijFX`ac=f)Vt--l_uky -zg3cUA)q?x%4%t?ZaU;vNyjTE7j#MH?;tJAR=RQiENH&RqoGG|mQ-xj%R2?j((tM+N -zYFmA9jlug=LnnkwT(w!|WzEO!VAM*68Z`oQ`bRK4Yg!s}O`ecrkjpp$JFEeW!^}=d -zz<>W-jpNI`syc$~5<9jFs{%Af!K4#I+##{48#!Mt%Q9$;m7q84juc#L*E?7ql^5Oi -z81WvJ{L~s2261E&-gDdz`bV_Rbl;JYtz?UrP}x>LGK3TkMAt^IEf~<<82hIw+PIZ7 -z?nK#SQ}-WW2a`)B%yZV@quYtG6BSHMt$7na!(6gA`L?OtI@zY6*@WQib?^)nJ_E_n -zg#lD$S~l+9iFL6}>$NM20L^Am-Cw$x=SsXr$Y&5IVRl8`AZsC#sd{F$G--e&1^~?f -zPU%zGhXFhr@*`ww+;UIY>2{e^mTu_=1tXvmFecE74mQ&gCy%GN)&;58icnx_7t?7Q -ztrcROL)Oju;0|i8;XMa$L0F<$zr<2G`JFd9kn@ALrA$C1wBjxSF$tt^);Tq`!~X56 -zIh7F=xMyBe!7a+Z&g;ol!$1}T_)L#5Q^G1{`pgOtL25Fd+JB=v -zS!u&wpJ*K|wv>Ihl;b2QBC?@R^ef{=~{@DC`>-Z?+#(?z&Iwa$0K7@ -z^x{M7QU*D_I1ogW%aRy~*?X)X%kQT7?GnPPP9H{SvTw-r037JT&J(}SGall^G0?8* -z+#o3mzys_zk}H@UcX6PMI{46r1{UsISpl9Gy*Xr^oT<6O8ujoXdJ@6t;v%r|0j;`B -z>BLy}-oT@iMmA`Z9Wl?i^D;j53}AV3!`_VYbdzb^`oRm*K0V7NHn+4lH{7}=d99e- -z^ugI4e3tZ6{&>z%L_pZcB1j}ba5_g7`Cy0wA{d2dzmh9rRIQ4v6h8=LDp@Uw1k1}m -zEQgXX6RKwk;zSbJ48a3wvq7Gn!NP1f9(w~VG=wR#q#q!AxB#tcNkXws=tcGRsFc#p -zaB2!X-jptuGe@81W$OM%=S^f#EFFT-2)y8R_Wdi=*oApd-p=HqWMi4rmpgUe0yhQcn -zGo<`W6*W_evR&6 -z35Yyj>-)Xf$PI#04d%it0~cRq)A50vYAyTLVi62$2frj7;j4*lpKsMbadj%tk)Nc~ -zlzVCt?}9zDEB(y8bGPn?D#0bisHgs@!HHUx;RxEQ68vUra$&vY{e%Pbzb2Dvt}gI) -zS1kP$f7@~bPaMdSbRc21kag4s;FLxeXHj7Xj*Lv{hBen51;$6i&wgbm;i(iRc6KBb -z1@amf`AqB&!~ksH3GH^E2jQh5)fE%PiE*uZOob5@8^?Xh`iL!ySDOy3HHGu^FO`ZF -zR#z4h{Zuws__#I_Pc)W}Fk~!L!j+;|_QunFLNg`MFFWf0EHyab)2|L)PoqavVWKGr -z*2gGbAmKN2Buo(N-$7kKKHd71sieRN5DWz+S`1+Qx1%=1{RinxQYR&2AX;MbSxDXn -z_X+q8q(uAmb%a6KbMq!jVv4-$G0eBIPm_L8fQQx_PtV;)7@OM49B6=Ob4_~g6w({r -zhjlh_^xS3gqFVAnl*aT05rM+h(ovAe=vBi|gGxo_T6F(pTFtDd2@aq4NQ!V*RyF8oJtzm{g9z5B@a@wX!F~h9j92KB#aAAKp!`}QErEUk -z&IXs9-1*W>O@IKRd11-%wE5VxgKjhPq67n5>ONX3v=A~I7g5^GjS?u~&(ULE^lmWAZLnX{|xpglgp -zDTx*wG$`A6$X@Pg#Y&l^bB~5u)+9cNwL69W>MdG-R$hNh9atEm%!6UapmFOL|141_ -zR|++|InrhCswDM0GG9-0}D9DvCMq!j^y&(ciUe2GL)x1!K1B?MmaWZh;o*o -zjFi=WCHvkDd|8vYs8|MGrV{U9-8+jvRTRUjU4(-+8=BP4z4<2oR|eo?mRvmay7aEd -zXAOi9>}EXygfkBxx39&hvZt1G=22Y^0$6})?0wr -zPRI2mx`9|v+`Km1CEkg$Dzudg>+Bh-HA9H40VGGGH&j+Z+i!L>N&CvQ -z{F9hl@BiKkpVzhwo68bQ3{BxpPjHPF0w3=heN0&{M(Y6p_?Vn1f~PQnf8|0zMDdao -z2)E0z=S13GZOZ6-<;e^xL!z>~R`3Tr9d(6VeNrx;Ahb*`rylRNjF?9f_oUvz*`sDm -z!b&iXCdAsl0vMuAWnpmj`srj2!%mf`-n8$0!h--=@`(u6Om3pZkYy4fXn6XAURUuP -zCR{nP+IUON|4gIud|FcL7q!Q -z#u!uDnD_rkyD)`+kxR^z^V**N;<(mfb!UM6>!`kFwR<&&AOJSLya_^o+iFQ^tQ+oR -z`sHs^Lv)`r41o@ffe>_Dpe2YnDvu9AjWBTU$lUfy;*(>|o8mpP45^D~G{;vXddLHb -z?JLS~4F#kwv)0PIAYY;C#2l*C`)lgS9C?|A2|BnFz`UL65Uk9HTzY=$nnA-agW%2c -zq18E4ztdGr=ufD7!-VNGT8A(tV43znZZe>@W-CzA5t{;{s*!|J#SIlZ_ToZw8b2{$ -zV4}=c85M=c`(p< -z{^H_s8<(*JY9HyOQ&@&`MFP`EIgyRAFzJid0RQ>cS#Y94o6C+6Ya_Rzu -zQNNIAq&q9{W0WzX_PDd-5G5lXOWk)~PREu90Z}q8B+6t1(9BgvWL8HR^?Gh=NP9Ow -zxcP(GT@3r}gh7MQYlmYcr-#PUGDLHj0c>Xjzrll5^jh73q3J7&Y2h~Wd%2vsZax`P -zFLuw5Zd!6=s6~=9>#$S)iDEUHMMCq$%HAkz6M0^26%of2Omj}cIWgzeVbUq;r-7t8 -zwpMnA`^ETwRTq^xF?we9aLu+vfDyqC;YNsb?yhV^V*Y(S#yIAr)z*oPvg1m0fWhce -zVK}d$AsUux2X5Gsn6N^gd<}X5?o?;<6nT_01m{J<3=|Q(oB{Yl1-&wd>C}aUHEZ$W -zJX*q>Wk0BxCXZA@5i5M*ZnU9O!%etX*iHG4uGV-AV6ynXsnq;qvmj{^PwF_u&JUk;}M`fQ6~XrYv3$ANV9~244)yg)&toAR9vUHChw0nYif>G!hX%b -z>XzuKQ`vz^!c9^&k_{~`-Z7@!5c>?q$Wk>#wtYj#CS-`8smj}t7?4?zMQX50_Jgbj -z!kT*JBq-xf(!#|v+CKMG{tm>%3=JIZ#g6$x2Cp0RDMa(Z?#;wk==60Uk#uo2G$2^h7z{TtAj%H_6>4#X -z9Q7wbMn8#vGwcwo$PhN1cz_|7z%9GhG@F`1JJcjr!w$QALd3w*srF&o^AN4c!btXJ -z22oHz=`peU73YjY#u}!osb%|XTv-Uk+Ga^zF|tuDd(UE1X}&x%88WBD`Ien-=3|L+ -zvkN+A9DpD}9TLW`sV>Y=2^zf+Zj9%ayYrgse}+f-CxPNtqJ;37e)p*vu`vOlu~&f+qf+$Lt4mLD>q -zKQ8-vJJ6&fyJA8b85a{=A~>KXFtV>(JM?qFgSVL@pN)n6U1|>+rtk!4S&biFZ+=_bhR~!#$E+V-d^!HihLHkA0N?pNwL- -zgK2KjNqw>>nd3VrcMis&Jgw=HDxqRi_@w;Bx+!oYa1r9gmXx>f_;%0`iHDY -z@`l%63G*Wdk*x>rYjS$o3~AeYXrOVbg41G`7Bj_@mZ6u~zP<>!spGr{pz3iXo9-!%TY@9KLFa%kAdI>Uh!d_^ujVW;= -znhLG+sRiSMw2=Q4gL8hO*96rGDiKd)U-!l$cqF83YaWk8J71z_3RNCAISHyPTTGJa -zBl!N0^Ply}5|=0w^?=(aK7ATfMY3lw_Hp%Q^*^!bbpQK~RIT+7K0_5eT$)5-09n%0 -zCos%&utZLaW{$vK5hr6N|2f<==M-)3KmSWV8Doh=lWhjm-g;t=k|b0NTI!d?1fF=x -zMa>Q2P&T&rOP^A)Hp;g%--Osv)`8br_51)JHuWWVhP6eogy;5SZa#nofgsyG)m&`M -zfz@SnppS2f57oY`T}>G(mzb{G5RCCshQDWPdQcf@#5>?g>?darunOwB--4-h^b!NL -z=cdBw0lO8kmub_wfk&F@J=B?vW=?|~A0|Lu$m-MdQ<|W%IFVJO|@@Ab-$M -z=!RINYIxdGY=_xpQANc55YiJr3!}C^LYI=R$-i9BG}fYY^JoE4qq$)7=`$8hW%+Gt -z>sv){dN`Q@c=3<{HgZ7nEbjTUu-j&@q083tJsw6%VNs4!(U}dB=p=au -zYYaDn&uurG*I%^_r`RN9%Qvc8>u=G4td#{9n*$YYPAXl7m4#Y`4rlzl$$V6oj|TmX -zh*rc<8-=q4)J^w{8K4k_TXPpK2*8`#)q2sja5>d=v@DU)g+l)3`8M$96vDf8> -zZp4J2m6J*2HKXcD3csYC18+F?Oh5XD?`k#U32zByEE7`*frTo;NzCRL4dS3xy~%^; -z40_YuYBBMLbxnt6OvOIgsHgTarUyZN9Kgt%+g6wQEa#C3tR5V>7__+WbP -zQ>WSKpt&t~N~1HoR~~IN^ -zQ1xVF!dxs9a3KfjKFfrwEP(-ygsGt(6TAK3nvZ~MAVx*)=#=%&oUYs#LjiIXvqBRA -zxJ}u2@DE6Epi6%T2hrth*V0xc^rNAdkx(29ukI2db|~t}5;xG-SvaP#11(*m6KH#2 -z7J*pSNAnbW?(ozR!m6-DMwh1dw#HUMexs9geD9C%vBI6eputH9NbvI+%T4XK -z#nKEKr5@wa#gkEMs>vdH>rIhBITFZ|lxQ@u;Yz&Ls-LIuqz#2R=U}~w -zX?3iuk`l5J^#rZPL45Rw>EqSo-EdRtKo5f~ZrL%57qWmi;?W;Uaz7rKIn##)%KNnd -zxeGG>wyR25RwShb4&y$v5+O&D7>s*E>xI6p2d8kYJ*+>tj?mWE%F4?GN==NB(;Z>2 -zIl7c|W*Y-Bk6BawR4eLA&woL$X_#&)Yt({!&Akxq5+F^J1A~FYxuTmgPR!b&_Jr*{ -zF1|v5hz4mfC^Q53nE0n{e&K2JP0Z|Ss%7E<-JyVJN~b-fD0`LY1BW@kvaXtA0=`R0 -zYNGsTNDASxs+z?GtUz7E%fSI9(ugTa8NxlvG6f%LZGqi_ir>~i2!uilz4FLvEr22b -zOBwAtqc_sGQ|L#%UfFnwjZ-`2_M>3ztvceUzJYrqLWQ6jz@3GSSq}*<0t=w -zgnHgV6$|2$vdURuK!I-?8Kd#{xu}tK%)y -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 -z*0UE?3+pv)dLZj*Ua5r4Z9XaMsA6R_Z -zV{YDwq6gMxUr2Jc%=-gDtf4C`8Q47pMCe=d54%!j{q?V+?b8OQJlB&SZRcPV6af4` -z_|di!qU;$$TaR}ejb%Y3|1!YcA}Wbebt^!f>Kq|WebQao&o&d56ji?zQ9z;m^LuXl -zgv?hE!bwo)?t$;(HGM&3L7_I+c?UF&TAvMNex)!d#(|X4GUJDwL>-&0=$IUqT+N+_ -ztqP=4rLdtNR~3PX%R`421Ty*{MuMj1bn6K>E4Y97n3EBf^JWpOg2z?;y}|U{1EhfazbuYdUs+HQj%6pq@Bq -z&us8%q_+n2xR8U}1%dNLF}RuEu&aH1y-%d73l5|C^96BQ6TQ^NSLD4un}```Q()-% -zI;qk#znBH1Ko(~=C;*$)1|~tMQf>l -z$Qe#TJwqhK<_;`wL944ctrtj*J-+ul!}C{jL17d^cI4$>>r?sS$)*jnetNwu`&y%; -zk+|%rjjImt≶5A;ozN*2Vu(82d{(MJH?Z>3Djw7;<4`{O(Qt{V+tU`^PSu&e3u9 -zW92&8ySIIPE;0VvA{~n(KKOg{c(JJB`e&2zO0&yh{it`wMIRHs%VBr-1t4bqacbyg -z1&mNb@BeLX`L|8Nox(_>e&uZCMUm%RupE`KJA$_!g($$93>Yrw>P|cAfFiDBcg+12 -zfUEO&=($2yGwFbIRQp=_3^RXo``|%G;a(^UX67}6=eJzi&86lJ6u*xuJM1}4)-mI& -z+8eqdLKZ_N&*EJS)wVg}3K -zw%^ccE8OE*{V~|^pUi^uMB(hg`tp79YI?>ET)%e^Z%OuL-_?sbrsDT~d{~9pPmkAJ -z$Ru$(6bx=q4FkNu^`t85O+MTN9EP_wFiblBbu1VLezXb;)%9Elz-^BG`)vw?=+TH~ -z*N^*V67hocyPk%%d09@tE@msUFx+XK?5I6`gomnp5jYSc<`*K{afk`xsH# -zwnMlnIs)XSb<|LlabVN!0=GrwELHj}BqZIFJna(0!GV%@8>}NvKjPcNrJT0UO-$0t -zCtp=N7rl1&aTc|0WVcka#k+P(s0Cr+qDirRL3^a1BhvTIi$3P%{xY#0!4A4sa~Oo` -z@J|w5WAbg9!X|q$XxZPkFtP1>`^NF=PM_i_G@~;8U(k^Y#$5Q>)GIcg1eGgxIIOf3 -zX_o6F@Z8;50e=2@e()U7wIiYgKlZiN!yL-#i#Qkqg;qtOo_?vD7u81cu@pF=9M(~v -zO&Lg%T$O?+ZnFV;t{cSHC*XnHgOIhtjs|k(I)EI~-?%>hStSw2fnQuW3xAl>4i9aq -z4NZR6rnt*FF_AS0>vJ3(>y#uJU{lSxpLsWsag4*987;*-4Ta11=sDH=t|sCmx8-)6 -zgkE>OWlyui5nJs^uA-X?(wL;~28*f>xc#GsANpl63sDlhGXO!g^$yiVl{YzUz)Epx -zaqp3Hkn%_Q$R)C}O*fgLPRuWte89j|$j7k(#(WMq@e<7Mn-XhNeQ5i3!V -z5G_94-)5LunW0A(AZD6BGou^4XZ^lsVvZ<+US^#&gdQgEuEW7K%?0m`DaR1)3DZtG -zvNR)?RSBs~DA9^X+-u2KiDK(L#_oJ=JnJCRbe78JWUozDyuWTlf9P1~U6JTj9^1zy -znhZhnxLs7xH|~zR@;EH#f{E_E4@Q%gzj)ahCimxe7+BGs1tdsl4iYJ$(Jbl)*6xTm -z#k;ZV!?CPGG?jn!EcI*~tmI*v)XETkw0_zU%}=S+yHQlaKth?7tmX`HDSa -z4OCH_n`g8QP(a2JCrUP@RI+eT$@6%SiR8CqAtCYhdWMM}hPg~+ -z^J%biGJE*)rG{`n+`p?m5iad8BS^H=E3soVjE(e3MY*@lxfnmfuh{oYv_*`%vl}_A -zW`qpl;g8xOPtkGR?W5(0rqO(s{V3t|t3asm>wH5O;LWNzV{Q-rt%Z1SN6xd|eWfFQ -zYzNES&($bZ-68_{|FCw?I!O;~BrUy0C$x8ZjFzpH?78Ky2S!adf1T|B07Z4ajYY`O -z%&Yiyg8Q^DaubojI-oxC4hjZ;Y5dW3ER>jCU&t3pE`ssQPzrK8xI>8Tx4uOQHj-mQ -zU7fIXIZpCG&bI@Xr}`9f#VcOOFqR5$k65=ye57ri#*%y|9NFO{kW%F4l$E$^X)`ZYq3R4@uNNWr6`}q -zq}gk9ahu7}z>`v-wA~$#RXoUk137X24to9=08Ic!RQR8P;dpGSinbSfRd*#%Do2Df)#A}%L~nUQcXLE!nQOfC0;ig4{HPqZ=vwg+JixKT4exYXL~3x*@x-8|dWIf7J@P=~t4F~@f6 -zXkvOPwRUZ*Qb^8nl -z(SSwPU0f!WhfjHH*|uMQ{WHXZ-(O!z<7hJU -zUBDK_72n}v>5=>FDhOzq<@=JbR*ji2$8EyuL4TFG9^oqrFoZauBL`g|?X6`(bvY`Z -zP7fj&bhpQGOh#;!WF*f<=+LqS_>D1x`=TQ#5FPp%(Wb}AZclO}aSSrdm^=j5UK5+9kq_8>*Gk#&mVS0%7*#_okTA)+lT&qF$QuqEA;!(o0_shiy5cJ&D5Ag#P -z8hSlvN300cmUO`ja){u9EMGDhgUT-7Y~ku^GAO!T>FwoG?jNv-MmD4@MD>fWH~j0^ -zhkok^NC({>yx1p-9*Iq4P^**v-PRZ+N|r4AJ;jm|(OpE^=H7O%-VXurhdb?K+k~He -z*eID99I!XDHT%4%_XM5)jfWkm?j3NG|IifJYqQsr2k4XM;yP1%6WT=%w%WX-uSn@) -z!A9Iq99m73$Qc55SSs2Y2MH}lguci+_4B49mX>zYqQuUunH8IbFh=TE7JboUEebD_ -z2YO4!+}y?1saMK^G1DjptO4aLgJs|DO=6&Uj+;10|1JeSQ?tzK`|Qf{A(5@Y&OOMH -zX~;qz?@~2!1H^T?3aW=&LkzOY;zbs&PsxRvi%MEydt+|5a0v3Sip>X8AQ;$my~O0) -z5GV{7WukD}a!_KlqL=?L^BmEug?)AUtb+UCgh+0jeT^3e(wqdbEbQ|OAvKDGPZGWE -zv!QCZ6JHuRD$rYIj&V}6`n7?8FWd#%lH}6e0M;2d!M(8faV+{^xR^yEntggx3S8h_ -zSy#yP1=`nmkhZB8|~nqeXj#j?0%re8xh06FN7du5M5VApl3yfZ9w0vm+!NC9 -zyIB#B7?Um68%91V2kkw5Q6o@f`**2~jh*aPeuBcyVhbm5ZwRG{s*rp#82hoTj_^ot -zEo=UD&MeUQdaTRDo7@>4aRu!VMZa(%fXH8a@a;tPatKe5>ps5rb@%LqEEZwI0|wn9bEE-YEcl`fXW)p*sZ4C+P`=n4KG3lYN?V! -ze~J`J0&t5$a;TwyK{jLwtM0hch<)N;{6 -zR4$ocq16d(rhi{788+?GIk`PoCW(JB`#ZW!G_?>uc$t(n -z4j6b>*y_@wdV}4FiX6RnPOT5_T}Mm_O#G$BHB|K41s9_lm_Zs<6a0S9+y|orj4!PQ -z0h)zr!-==A!FL;MJ7Qb%%fJke{X -zI5|-51=dw;yl?xe3L@5o1 -z6=TP-O -zh+ht%1_?gilZI~^$fAYchBg>Ay8xz6auD>j~ -z7Z}2*hx)h(Z?1oLmhA3cL>RL2jcf*XzXCK%GwmB3VCKhl7}~M2ag-)y#~?oiQ#W;F -ztV9-v3cBj^cnrrgj^A=+z^n*oDxP*WB27G&IEoNegl^rSng}~G#-dOT>R=1zvEBiZ -zjm{FzaBrD)ke(8DhsDRN(@aAPxat@s9U$MR-9Tl*H8GdHt%b&M5*i=>QY*mDnFPU4 -zT&Glm?|%_jZ(!f{&Opf$vE)@oreCPo#L#aq(q=fTbkC**Aqa9hKoxOG<;b -z&#up5Cn_zKc6XVxZC$L%MN-}S=*Mj2Y7D-2({-QiEoLC2Vio8svrx>tTCERAIaI~( -zHvFqJxW<<*?9|=XCfN^>g-_3>*yuj*H8A%y0Wy0+RyGy-BjS2v#2JyF5!h+0)Xp3^ -z#5Qxa=D>zq-lrFFBjP@1FS4ll5h&&cX21It)7}!9u;1NLw(i_L{zC18;==mjiJ{W+ -z)Fe4Jf`}tf_UX;M-<(^-YTU^=CP5Z@Q2?>jD$!HWVYi=n&qjr^t#^0~G(R6x~AUs*b>SBUX(lw4m -zRBL+!70q7Zg9|CX{biVe@st0T)DVgD>TgU%$D9LNpGV^_al^lVe*`5RwUdp?m;O3y -zruXe(OCqGgag*I8b-W|6!Fau3;gDT?cGAW&<>!LNJDUAr66Nak{{eyd3i*kcw0^Dp -zLzo~}Lc<}ha$)eR0gul`k+n;-J6A(wgfwZjI2whY<;S#conqF!PPg6GlAR=(*Z8;y9RBeNr&Oyh^UV?y}s03z;5!`7|ao!LAkVvC_%;oh~zh=x-EP)1ovvsFuhm -z-!)(8o?^al1g^_R0sejcGZ}=_G^d<~@(2o3;H~d~?hzi2y -zDZFuE4-F_6`?6bSxX+smb3YG=?z&)enve4=Xa@c(b!qq7`R!6b24!1ev`3`^T{IN7 -z8?a{H!%!7oOI$Mq9p>2Xl&JOR?Gf#Sm*PSk!L00NI+eXJ__h|a5}&MMfq2cFt;)Os -z@QE>O3ofj>1Ggjz!qZf_4s%P -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 -z5Cb9VnHAg{XPnGyC3#&76CKBq39g -z1%qLJ%CA+)771gElvFtvMNk7Mn}HT|b}t|V5b6G#gH|q>BPq4bxWR@I`El_+ikYDT -za=q_LLnSK5VYsBS`2og)CA@=1!(>Ds&J_P8{%x;uD%0}-t -zex~uH0X_5!&`n=RG&_;=G#!H9+-n -zuE&Yk$FAF;Y=*kzsidG4!L|f@Tf;A04|VD^K*9UJyYT*4hsFZ2HtkO^NTS{`wF);9 -zj%jHaDh;h4J{%(3B9y;-VpEA2*B&HRkzps{uF;fiqmZYe|M0{HEc8HdM29#4#4aZ| -zdHDcWnV_jO5QU}1sPSOPkx<%HNgZRXy=T;sxX>`0M*~o@1Mp*Rp6G%NQ10<@QaQ&_IIs~!}9sSFsQ1uS>odSB($dG8BD%qjPO -zH1|EMzZiOLyEnZDq*y2fn+`7=Qw%3nhAK3A&g$j9>VGtMN`T`3Xl~BHZ3+?n8MwAl -zLgmj-UD!!fUT~&4jgC8^42SiV+vnf!iN}urqq&Fvqq%`I$o|pXacV^-CcQfay7j;* -zQwvfFO&3h>;luHV|3`C&E+vJM;~PW`^G=h=67Z>5tAfK5CjCcqvm*JpY-fadD!%=E -zBM!;)LeIZM7+muPZ-;Zm0~n54AEca!jwf`pikM7j34SY68@xg>5!qsTQIXw8Ou(Vs -zmYzNc|Se%ZRql -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 -z?-LF|n5KDv)Ol6ZUhaX( -z($)uR`#O`LQdF9Ng`3c*<6t*x(9)(36UV`4%Y2!30|ZA(Z3+*m7eoLyPFH<&YmRk{~ -z%bMlE$dE5WV+#CO7ZtdkrdegaY%a}T$YLhZylzfb8^ErQM#{dAkKqF;vnsQCh -zNPsl^B*Ap&(?*%pJh^QOw!I)_q+<==D^dSR9A*W0VVRz#ChsRk)9{~n8LECMQ696Nr8l)brLEM6R#2IsQiaf*M&OnK$&^Y_<3`C6 -z{U>u@XB4le$OC--$=m}4|733e5%s1cU9$gVZZh@u5?<#AVxhJVQqNa+&AP>5PB_J& -zpX-^0+_5KGqS>UkARv_hatL=G;wBn|KazbBjt05FPU-bsBYEGDSxR>5f5G%$zNvTI -zhW-!c<}jhxR?7)oPk%Q4T1|?^(Hd9F6XVF;BQ3kSMdU?01bCKi``8m05YIYw2Nx%6Ab2k|${0DPqZ)W}v=GGYu -z6!yK%X8Z?pFE1eGH*Ns^e_(D*{K!Vu<_fW&M|UjCy>`QZ@3u2lwsNX6CvGERpq2ZW -zR5{lniQd`Da;A{dYzgVL>v%K2+`Yp)aL`{Yoan%SEdoDzYtXj0-!yr=^iZmWK|w9reOn#zLQ;YtB!_; -z)!_{o^Z~2Cf{j4E2=p`N$WxA@I=8lz-xgT@)`b)obm$L>w2=4FeE7_JSNs_VYtW6I -zKi}>5w0nzUY-z-1Wx_Fey4t)?%E0wv(odOh2MKft95wFXdII@=u+Rw=; -zyR1jfta3>6>I(I#i{9@%TGxQOyn|k -z2FUZoK`Df&T(_hlxJJI!oUy~y8rt|rgX7f*mZXsVRW~@M8vFo7j{jut+kY~*9@mjW -zcI9L#x&pH7TIxi~QmG$->4@hwPI_fH%h@?>Abi}% -zTm;4(;tjJJy@#tlaUHA|WfTHREbg2@0ja7orRW$BAMiEByJjk+)$S?IZb))ST`!)D#!n1T=b7@t4RXCHZvf^!Er5=hn*`O`qnyBIHPIYiL9%9I&Y>+2{S -zepe~wWY}UEDZUbp`TWfcZQN)UV4%P-a$;)JB+r!9wTlSxZ*b_JN}!PcY{Ta>-_z=M -z=qvLJ%w6Tg$sjj_^^`nOb037}^Qitmnmdv$;Xj&NjfCvEql|k@vc`e9vBKUq+W2Ms -z192pf!^R?nJQ}i{1(H0+CT2+y1vOB{QT9a_djsXSV&#Y#xX>!ArPiC<`UG2Yu$7~8 -zLcDJtexK!2R1Madh{56iXznzidU+S>lc!;v)(7==w86_UlKuiV1Rg4P?_9>RhPm?>H -zHX4pB8YjPO)SKKZNr&Kw|AS61le>itr#=^&{g`SYJ9cKs&t*)@NHqTP;@9`NF-b0J -zMqqyfASll>H}f&oJx&`EO28Gsx?mahyI00)DGm?HvQ%M>?|oT27gS=zI-;HwsP(FE -zE+R7%Llv;3A`1C(#kTXsQuTtO2Qt -z1ou9tbsQgsj*d0F3!%1WoE#H7X$0-#`=!ivfZ$c$WUjwM4gme5xo3cn-Dq1!lY6Ev_voc#DtJU6aJC>E_|- -z9zopCVfOVSGt;ZT(}S%ps*mTADK2wVW>CbJgWj(Y<~3#=EjjGG8UtfL+BWNf^5n|jzg -zQBwEBb{Wehnu0I@dZ6!YW!Ey& -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 -z)0%Z)uEh}8QM7cs# -zGS}g;@`|_U_rBrHNFo8nA05pbnH~IS?78H|6@OK74jF;t!%)E-3525Y+_#y~Qhi-Y -z@iTJHl2Uxqd*&GH!uuvHx0{*fj)6KOJdybC+Nj!U@;XCx#$ykWvOyJ=FM5r{I*iXo -z#~F0+J9yjBsW$6r!KzG>M59^7kUN**`^v=_wIji|g&@01ndflqPb}Ve%<%w$)QR=L -z+lckuP;T_nyy5avAnS?5L*$Xd(_`6+oIjz;nU;3`1H7&Fj_u6 -z?p>eH=#?JY>}G~wky_oqC2QUs&qf+l;1CJP&7)Fy+g+edOk614L+sfP)% -z93tWad-^YFUAVM9r2Lk5>|AAj1i30IJnaMw1yJ{VwgF?rzu4ZLOpIIQ_dRMWdf0$! -zC&R<8n?}?7ELFCD)37L<1Ur(^4CGIHm;D~;S%P| -ze?6=QLoLZl^Gq%=Lh5iNk;OW+|3M;9mCd4B%HlhZob`t>gyv`TmC;-5MCcgTML``^ -znnY59+2!#F689JLg%*B+Cy1)^F;tLAXN!xFla_F0?(R^C@D|rlNoWu0aof-OgI4-2 -zTCwmvL=VPHk9?|p)jhI2AxSHomZOQQyUp<*%^@#`+{j~3KyE?rg;y8+O!DxT5!_Fj -zLVpIWrrEa6ELm1PAFYc8yTV>vDwrfg$_Rod=sCAxWyhS>>wpvfeZ7`W0z{Q|FsZQd -z338w2{s(dkPSD`>)S${?iQkC{Q&wAbKWq}NT6*l>$vnO8I3yKi{SV}(ltrNbAIMGG -zwlt2CeX#BFAIL3f3K0U- -zy2$0_^OFzVV+c~6@A4{6yq -zOzA27@(7U!^%~#Hz}sq@x~-W4nc?q&46(u+g3xA-y7-OA2D@)(s<5EouF%c23I=&mT?7>!SQeIu~*|)1p|JSILo_==7<01aSy!KLbV_> -zejI7hvQ(!`LuKCRu0|)Rq>#k*6x~YiY=^0}FA*;D=2OSrd$@s+eAH9T4m@Sn?L$~$ -z^Z5UWO`5Z-i?+mc%wP4IEpsWhX>y7e*l&fj}&`}*l|TV-2feR|wMHUj0s-|78uPoTs2flZG3|9RXDc2+(M -zNq7CnWzp3WIw9>5nPKQ@iss6C;~4*%Ni-UgTQgQ`Po6F@IVjTfPST~c{UFmIbkEo7 -z+#lFqoRHotR+thpSNU2roJ1LqwyUTT{t;*KTG?Q5L1#7ZX5CX4eccTp`n_>AbT*wB -z$Qf1XxN46)h5dZgv-_p>_F>D{^aEKK(*Z>`=jcP&pX`xE5G?)FI) -zBI6wWxDC7!7cz3uyJ|qygxzNDkualCXk1fQ)viNY=2Mdn%lYbMUf-$fi9r>ktx#$Q> -zNxmCZOE;;upAD~nn9)}2R!T^%4z4p2zmLe3DB|k8!l|oxQ74IJohP|e&*b%_C5{co -zy(%KWPPOKktkXG-=@eBy49-V(got02^s{#T$5lqC`s!n?*RzUrat>y?4T5ssS9s^2 -zuMO3^FytZwOn+cNmAwAOohz98upJ&ouZmblP19lqyyX!vgkMAeWH|i5_CIpOn--P|2 -z-!3P10KluOiJf}jhe$q<%;$DOWF+AdY+3N6bSk{Z{&%ibyDd15Wy+U+f-|0PMa -zaoWVy`6FEBkUm_)GL!tMx5Wv$B7j9zM<1pf)U18g6k#9XY2uc+{ld@W$A(hwD1xj -zWg(O)-lR{Q|8qtLL7;ZAhn@_meBKchm=#oPezV$}kbd)fc6bsc8O_1Gx|P^CEDlF` -z-sluEdOLn4CitYe*$#zN`eP8MWqY`Ks1|<6O4ZA({3OrX*)hIqb>%m9q$-53dJ|NF)n1_$^|pEUMrAz0HPxf9y|*zm;{R01^oXw -zO6++=YcK7fp?9Dn26Sv4P3f3Y -zD=D3ODqz8{72879T)_?6t)VIN_NfzC{aVK8c1IrzWwS&>m3XYVi@K~ilzA;Pl{Iq4 -zD%Lw*FRSMr!_Er~#m+hTLK8tubJN6UJA~gnWdq!(;aDe;nnSow`TPgzA?_uWQSkl> -znWek$@2Xa5Z}Vw$XTRUq0;-u@KW*;#V75l0Mp=YKP9h@kPNGQkCE^s;!HrVUj8`hGXfUG5wkXWHHcI~>2;}c0w#scqGzg3C`(rE7CtM&C-kP((E1j;T -z&@Fb7ct2YWQd2s3(Ql!3YJ`6)b*$~US2^999~C=NfX9Rd-#8CHa_>?{`$h3eGE)Zj -zi|vOWt+c{C;Bs%V0b3eE4h@H#tN#6RJ!s-j+x%1LI|#%|R4)Vt%2Y%q)M$ixPXbUCmO -zW9bD{xfB@iju&VB(tEW42;=$s!q=d^5%hb6wzmTbDDoTZ_HOUmIFn_zHJ+NEQhkpy79(>zg`9f&8WE9z}^{CX)pS#C&Q8q@45|N -zShDQcz+!xgaKOE{^)C!&VGbZI+MSpv`HZFKt$6i1gvXJdfFvj8cWKC= -z-JOK@BW&?Ma*wj5yBhgEp%xt_M|A{oJz}*yMM3?8CZ7*}-^Kv9SY)P8o%?IDO_&=j -z;sA3BDq_wf8`wzi7yB>tjJNYHY~6eckCm@{ox}w1513`Yh^)rRGsiwW?5~!6PA~4a -z-fqy}Q_WeN_n3m7c6tOq&M{HCy(LTL6FF^H+T8Cm!5@JC&vTpq$8%e(*S4N-&~+{i -z>+69&$lLxHx9FpJmLdDseDd7qvD>=@cb -z}vLa_Ny`sDBBTq1(ooSPYi!i3mIk -znMNze6MH(TOFQVeN95!Ww81|T;RY-Iy%%q#C8FND3GacN(f`y7%>7&wZjd(}e9O8Q -zN8^y=yg{=uU^b@XD^${Mlh6Fs#zZqjqx$xNC-OOz7`0I%{>HR=(errzlic^ipo{6o -zy&%Ra1>tK;g72`vP1`-Yq;C`XGungIz7aQf4=|~qJeW7o_+?sX9Uke{7VBx-K3_eR -zpR1Ou8qvn+MOhEG{rrXFdTsLUhAa~P3fZlf%ed>exKQypJyCx}z)qzNYE5qJsZl65 -zcDG99R-po0-*i^?=|X2thXD8=wiIE;q3Uu*sZ0q{K+-W%3|sS>YxevGTj>0D$h6$= -z@eI)g>#5uWlCA4p|MNvzUv5_Y>XZ}1Ro_pd`^SWD_7&h>GT+<~v^jcw&!E793^64{ -zVt)DJBuL*pSPOs^yN1lc6h3P)^CW#4YNfaFkJJA5H#Pvq_yg4x+SM1nUbLnsB5`&n -z^uqO3v8Q?_7Mm87*H?5BB>5|n7O)e%=^|`Q7XK#kVCr)W!l=q*_;stbF;t+cRcbBy -zxtQ4xS7vCOQExfXZTOX)Pi9K)`=lWp482PRgl`6i`!l9piE#b7as8o(>lcB#bl#g7 -zy5p8Y)VN$8?>Cu>7*f<>lL438k@K$XFEKQ)5NAlVQhV(JVf#G8XpA39<6CCx3LjRX -z<4+2E;J(+`omaux%ocRXSj5#Qi0ct1fslAen#4C1I5|wX-`Z6$uj=pL=Yq1!V~Rn}aeLH}C8r_6 -ze2Vsu(D5mtU;XezD8+(AHVC8tl78eD*wF&=?+xiw-!Toa5 -ze9{2i-oFtQuc>0Z18b_tn{v|q35AA7&%7)>S1j;}z5%NqXUfe7 -zfYlPj0V3*CJRL|@&xs?{-nKG^=yn^|R69XG?^e1h&5`U?;B+$PZmKD>3RU+7i8ygH -zAspeKo2AJe2n-YH`7NLAo@x2?Gbya0M41Uz3MK$VLk-ir+6y|^*gAytXrkohGfCGn -zK02B?hXsFogwa$deV#8Yzeo~g{Redef}Rm8l4g5iPgmZ%ust?(WB$7Grw2ivuG~QU -zey9r~egJL{QdVBO;493*zCi)HO!#0#S10~OVH{hYpnu@aj-$Ux7Q=PfppHlZ>z_=V -zI{G=#q1Gp47y8TAx;C6+jOn_n2WgzLqI3yMy*`G#TeSJ3an- -zXxJD6#;n)9o$yA7wbX(Gv#%-HXBXB16X3~0mL96NB4oZdiCV85!w@p}WrfOj*^xqtG?vhsS1$;VG{L)h1;n+dxgI$f_>|FgQI5WVM3 -zM?P1W#ZJ+3bf**dZ;f|0TiPTD@JM -z5@v=k9br#!ifAMw_bK$xJKm7i8%cwg{2&^#lw7NkVmM6?f|87^gH_NED-J*tdH31= -z+fbR#6p}uJ^Ib*GK-vHByIqG0QEU#}l*d&XkAwgxA@j3b?{>jQ_1HHm`&)8nE4Q2YGbEvlyKW{>MN9GH;SvSmV4eHQ%a>cJc6tRna|-*o=2RK4}#xUUAB~gDxnh= -z-}$_s|M{=Mpt~4Sexo!MXD+BUu0nRDB>nvZ;%1zDSpW5n#2Y;iP8mcu!@P2tDVXfv -zVJ1GUH~XSnUC^;tP`&`D*HTKUGd%Tf9?Exv)O1CwL+x+&2JkY^!X*)Tk@j> -zB1b=PGJ*vt+q+3bXvQy=YlCSGUdZGF{@U?D#;-uD`2LLy)<&x!c&1~--)dZ0{?xi- -z;ac|;C=M_yr;nWLUS~eFZWnqvF5-JS7X%1kn-ESuxC-YMhZO4UARVadl-c -zz3J)?34hcq-DM2E3#(jIea@tU5rosre!by{Fy&;}@8J^lXI=v?az+regXyA09?)ZF -zTcFNoQkF}1I)5YlCD0BayIII{?3t}K2 -zTcvU$heiapK%#Sqn6CdIZ_1bT6(w-0|9j#O`ii;-Qv**`mrt%cOTA=Zh`6K-cri}! -zlvb`!c5S_=ut146BXU(u!h|oDF*w)TRv`eorKKA7=h=gsdZTJ2b@`g>_z|&6RL0?w -zIX0k}(EoJ*+WVFV)QK~x{Q8Tefg#(@t9^lcrd<=Gx-rr0yTYGMczej%3vD$^eX?GO -zsWQpmN4lGT$Jryap_u%Y8=XKFcva&@Q~P}-6o+xRX`wln=Mu3aj)tV@0221?V*wE% -zhou0x7HxthK{@)O65Ye2IwVlPU#|tM1%(8)Oehj1@=vI12nhJ3V{#UuPLbSc;Y~jL -z2uPnrx07!OzS_46pz3+HorsUJ9sk7Ll7n+|N14|AeI?i71@7;$5B`I@9A-^vpTf0A -z0WIA8aY&Bj_RHml*O+3_LNQu{u2dcmO~+T1dNsGgF(FiE(%@mM^_2}YCD@O|&>zlX -zupYsUauPTq=(`zZU)wBm|M-Sgd}|eoOdn1oL4;#pUA-z1=8n -z6ztL4Mlud-`^5=bFYF%Aas=;>>K+5)rJ`q;plW5InRA7%hZB?9n41+S)V2HY9hiam -znz`?a?sF5un6Li^-)`oDwgy2<(bjC%|KqyV{^Pp! -z)#FG-Rh&Ezx>&8#Oe@w7l3+CgzWOU$KuS8xgd)mhx2*GkW -zpETJ -z9(q(f9$MXhP3&FRk-zKlI*-;fjdw{J-uokq$GAeHwn2c2F=jRU)P#0_T6BW2GELb7 -zH)>Lw`1iHmwfC=H&j^rHAoM4KBW%`%a<7JLn&6gQf1Q%vNhp{zunmJ_Ev7o4jW -z*M(tQOcGp)t1z|2eiLpsdv%VO7ECLH)Vn*O8VQQcH+e&-v!{%XBzZ{qWsn`L>{#^V -z=a3Ul=Qgx=0&~0=vJ@l6p2NZ0=H1#dQrV=4jFB((R}5uJ4IhhH@JRf8w)3D2^_e}3 -zG6IjrHA;P<1$cP=k8%QbBBQ;3&4SsEPW;{c+o*vJWL;42;F)sp~*AQJ<>G${e7rgu3v@O!I%Noz4L^uPcxv-$(H5XxC*!BmKN8FoswiU -z@ZQ5K;{8y@WIpTyGF8I)=@+X<@x$H;z1e?WH(pG@_erHTxbpwJ?%c{&p>*teRKyhY -z-})N9L`O#~%3cBG0Z0tf7SMtQGOnLqcXsr(!g^q{t+j$nHa@0p;V0Asx|b=NJRO

;kLyXV}XdEns!j3MtBfRuWb`{(Xlzx?kW6Dbxbt)XYl%kE=xl(C9_Zm -z{2$o;{2;Hn=d9^=HY*-ANM8j6vH`52eHY%e{j>Pqzx_-|WC1Qb&^_fL`%)nP3So{c -z{CcB5@M2LP>ygq`?fh}TAzt%zYt+oA`9_5APS{6MrCRXD{uE0(^ZBmdVM$6peQ?W~ -z5i$PPAB8a~LZ!-|u}sTIs|^WzXZlWo24n&&%!L^)f1pupuk2`~GDXwi2if>zltS_& -zk6)gun8-oV{x}O*6QI6=7QW2mY76)AAmptOz6apY=fy -zEQ3hOd&JC%+h1nn*Vi9;j%&3iSIA1854mhO&%1Ddz6f)eQ%#?o5g4=d=b?sguo9jl6yeub;s^%CD|w1?fmNU -zkrjALp$ZV%&nil4cS45xM7xjo6FCp~S@jRZqTNn{kL>fhv-9?GkCz~*Q@f)})&-y{ -zFVA%>c8RUz#XB3#oyMBt{7U^LcWPVTdN;9Pbv{hEH*J)xa90|mb)cK6Jle_)dnD(P -zNf|hsVulq2sE^{f`4{t^&qcs!M-U!uJW@qwnADm$>MDm%HWt)Na;41K%P6VvO2@X$OikocMN+A=do1HJ8yK?9@ -z=9nMllo1n{q+T)(4ezjbZl7v*q*JmY@P}t?!NBxa-TfuAWq3kUWr4m>D!39Ht7An* -zJ1$$%X(N6Dq>_Ho-Wl=YLPVI$W-Sql+YZXWd#n#ZasNjcUx#Ry6S0S*Mgd<>=D`|k -zj(Ovl@V88du2_wnDmUs_ze;w#JENy0wx@7SPa|I;RX-b>uTDRuwM0i>bIjd?@zQpb -z-h>;={*nx|g?YY;bdIX3(*zTh#aD(w9Bjvo5ELRWM+pCP!D_zm!wA*_)yT~q=v54( -zAO0!`?TP`8BGPb%C~e%J!rmNytH~Lr2av1v%zv`o)g01wLll}*eU0+MMup0`+e!bi -z-7G}b2&kCw-YJa;;5qSqeYE?2qJyW8+v}AS3r4_F=EQocnQ>-aG%A!D6OY#7_ -z{et#NJ^wg8u*;PvpiM7SJ>EL~=TGKRIrE3y25H7_XRMH1%E{XISQ=f`YPf%Nc^v%0 -z%H#F?S`=#a&()`DKaF -zC0UzVNvG)fBO*ah7b6<lAY*_f}5WGG_z)`pEcR}|NA)m*JTbRQFK*Y>-~ -zWFj{sW_7AoUb64t2;$gsFF5s%NiA#R&@egCtfFtTh$ET+x?F$nIptqCsB_{X@*Hym -zWRk>M8h@&$wuI7PrlSvy>?_CJiHe~N%^^)b*?wg%+$q9XCN(52J^1{r5gNBz&cXsE -za>hZEo`75<{VM~_qtr4i=QiCLmEP7ic?*h5@RG}(&_ -z(?4xj8bazcukRx}>3=n#JIRB}gBzt-!}dHc9TJ04hoGss3d}C8tYHUd>4=3oHL~ik -zkC54aLpvw%u|v8K7MhwT1=||()swdnm0xW}e1-`MGZ7_$r|6}X)Gn__fUcjC-;s8f -zaHp0xv^LA1XqK(+Zfa<3PKRY?s)KBvEIXWaQiTQS`LUP%guJLIHDS36>$%Thr(Vg( -zU=_ls=9H-Gx->UX%`C+!W^)j@&cos_wxL7)0};o -zE)P%s#B(b^T0B9`rWfIbSEki3Sn>FP+7L#Dp4ECRSvo{Oe;=uify#5jYsE}GWdH|f -z%1U+nlU75<<6+|Kx()=^Sl-~Gz7mgvtW!UYfAZ|eK}O~hKm(Zv*kki$;%t1>Zyl$; -zLIt;3$?=@ZRFMuzje#p1q?71YlfW_!Cs=cU{GdI -zCo41gwUu+)3Apz;9M|$6J7t*j-^~%=M~Gs@+NK`^yNJ6PxWruU;@gD~bEJQq@< -z0^b8d-Dm^ddr*Wg0NboCGXn-+cedNW&IPLB|7u~OSFU^CvERtsg~uQdwv7X&`dLSf -zz{6@CfL@l2e$kivt7RmgXT@jk3AFmFo -z;D>XSf>!Cc-H5aQbbzOi*O{Od;!L){7lr7`2hqO|sL@9L>tJ`|4`&xHw^u~_DbbyX -z`raSp&X+Nfi7F`zlt3`>SC_erg`Yu!?;YME5UlYEj9Pp@e|=avYy{tn?)F+Pl%?LL -zREq#7q-It;hi_YqZrkMRi1#b@+rw6^F -zgWAU=ODhkv=a#>> -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 -z;6$`5b{z1eR)*yJgs^c -z12-%|L4(>{;0$aD+s3<@8uXEfH7t$LT1DwVy!d?nE(ee@FK230xq6GHytcwCh@RQ- -z3omiGUoWGLdZ}b5HnAyLx+s;B^GQ^8(1$sBQCBba{oHD5n)9BBo?$tXfn<&cI -ziDw27u$Or>{Z>;7%jO)TwF3Q;_$boD6n7&BRYL!vR$?&<&w@pl<6CUZbm9=~kVf3$ -z{#=FAsqpkGHGt~PRo2@tjsYCI&+dT0>#{nck-z>0(Ap;aV&FXfv&VT^;k|W+Nc}Nh -z3gBJZ#qv2n*|PWhY6kd?VWmXd8A3l$0sRX>#-h_OQ9#~1--S|T^7%*cc(X{6mzlOg -zO5jqYWudLrH(LMIi>M3GjLIuqse2Tgy{};`{^I4kVci1FL|E+lPTk6 -z)C~AmboV??w_kqS%|^lYqY=x`wGoR`E0S_o1}~2)azI0$3DX{E*_r>=T?3ka*|ClZ -zY-OYc#+C^en8L^+O{%MGg -zS99?9DE|*}K#sqVxP5y11?=$StTAh2(N7Qls^&H&_uK{0#I%EAlPx+#iJO67Hf`c} -zjs{E#=pL4_u|{q`Zgl5fUIObT#Ge(6_{8a*Q`qrVuTMx>N^qq}x+ic;7|{v7<>Xss -z;TAD#DXI?Jt(B*>htt!?_ppE@do-8R8W5eRsuUu@Aj%fB;ut=zQ!Ads9;5!w`-^7W -zRMvPsYtKLh!&Q^+bZYFJ9;`L)rm@FR%iE{N!|H$Hgec>RYJ#FU?OY;le29}yB{C0U -zrnU@;5MTjNVAf^~WDZ?@3!pGKN|wEYWNeWCfKMaujtzV;Za-5w53urvNv~uhFhgF% -zX>j%yOLZi}<|D$9z&(EvoGzfLPX%^q$m=5#9_Ywcx%J9o`)rRo^i|NaA8%Cjgm*b+P8Gtch9wdTVMxzny{ -z(G)p3crRx>Q%gJ?mJGADjRwnQ=AG&t13e@XTdQ~QQ6MDVUpLh{&t0+TklJ35-n$)p -zC(2h6n{m%~Nju!m7SYM-0ycZ0==)Z=Le10C5853&%QX^{wtpKV;|19kvZ%90H#%Mn -zWDQl8nWP0PA#rKEaK!1;DwRr7_&UuGrN^{b{<;PegyEN6O`DE|o8%jO8h)k7C=rpq`b+s_P1J08OGJMhmxuT -zVfbn55Nc$QD}~X5r%1A_`4n1qqd~J=fu!J9BcVnm -zaPn;pfEe|XEs{j5Vng3w4wKlaZXPc3Ph8GJ&fUOaNI5BB6x>o7hg2e#JTSe4XRo8U -zBk=~@FmLzi$x#>zgx5fV+gJ8WXj`8ylT7NSYNUR^Nv;L0MS{XLuIjhjIA3%qMY4{O -zUN^;>aw<;^sBD9d@knz?66jVy6-(%>TgXoqUNIHtn6!H64d`KoHh`p9ql7wo_`%vG -z20sOS|Jf|0Mj -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 -zF`5pl9|s-b9!h2&s@yc>0wcsfbp^Ifi) -zrO^rnOCV|ytIL<|rI|z@tW8iiF>I38%OSO=hUkDSfF6#pw3J%wwL~~&X|G0ki_M}U -zYvh>FLvLc=`kUva+=mo@^ckD5_L62}fH@V^{APFb0%kS$DcGvE#s(pw6PwTd_uo%3 -zDy(4Sp=UYX2e4nhF}f}cvrb>t4pp6@aVL?c&(g|1>XZq7zcW`tMMdr}KzYFu3M7T% -z<352^1ew#g564>D?iAZ7UbTQ;tmuZVMW=?{3J~+#0&DJZS=}^H0md0mypSPVM{@pE@_7(Jt~LiNQQLho}x<^4mGqKADOHu*K~fydU%MvD;Zli7jK( -zJ#A(xADW5&_G_j8_f5AaaJ5squonZaps(ylerq)bu~u4`B)dD4-;u4y(dgaGI&j^u -zaH$^W77Ut=Lws@^QxV%EuFF0hj7OMr6C20`m^}hq9CUX6`%=mk=U2=Wcw%%uZ$WP)jxKC#fUyRP; -z+poWVTlC@Rhst@v?!3=lw&_>4p{j(0}kc^-f#_+vKGQ=A7=#5l=g~oWKcSj{O>DL~EM@=XJ$$0HJ*M-JC)HRe -zGyiy*TQ-URGcYCK@e6m+{hQd3wxuYd6t`h0{V@b_3-=}T>i_qZyv>V2v;RzlLIU! -zT8k`zXFM9jc7sxyi{stfoNLR8x#cPz(EAdF4<4(uS_-8ap&dC%ILgl6);IltjM{iW -zds*zGQ5jVrWLbkN>Rfi95y(|V-r?AC`6Lw4QH*S&ZI?7*xB+6fYHLR43{uX^P!p#H -zq>1rkPu%so{^As?B%z8Di#(tzZ_X;0MJ|lP>Tou4n!KHpM?q1n^-4`0C_R1Kqqbi{ -zcOxNmg7|U%Sb{eL1k})co?3CAKKbFf2=EJz`_OOH*;QzKd-u2v=%DtP)0Vrq0@K?8 -z)IL>m9TIu)-(JFpPg!U8l_M36+d^$ak$KZzFcBpm`FV^LYbn<_-L&MSL2%XchpvV` -zZP8qqYuo@1>gEA(!Yp7;0U#o<$Eyannm?@4&^KfBTuU4XI{Op$Y-mn^L7^@%<6;o5QW($M_tl6HCdDo~PzRTkD_HzmRxhmvQxU}38dnqx -z>F|nM9~V4N3z1gbzeO -zPEx-i6W;$f)3@DZ?I1;9$uT#y5sc*luF4suKwy4l$^P4~-}E^&&7~L{oF~(}U>U*| -z5a=$P(#zHQ=$r24#A?RPKbGtZje)Mh-pXp+a!`s)0O@0XX-)+UBrXX0G-D^5&vv-@#_N2#fnTwydm2IAQk=y;E0htcghP$jE!{wOsN872 -zhc;l^oVN5j0!+(R4jqvW{C554r*T;g9R$hJe6RzJx{){Z)~zo%qvAwV0Kx|3 -zNTcLKj5rLQLn#M5rOqopD5q{G={43lWtt@>ssm}-Jq(G|d{N!5Eyu%!k+I$b}Ce?w0RppFmSbRY5WdbMwZo4!l;u$3_W}C -znMrtR{r}_F&4|BrH^?`htTE79G6cO4(j|f1B3rrT?r==yJOMkbnM+LOHg))A!?rW{ -zHR0~Pa=osDrZdiXg8HC3bB#ZMW}_^1Mik59AE@$m*5@6p2Fg`p4oa4#=A-ux}slgOclGJ2P`4Yn91vk2_wC2 -zfC?t7K`9YV;R)VnsC^QXV9wqA$IAp%3SDgSM)pf~f^-KczV=BousC*di3)yMqfX>c -zn5Lv;um^ybLE!`_g^vkIB9BYr+KPN?Rj6f{Q;F0 -z0G$D@(!*%25V=iMLu~s?3MOA7tHH{UMEQ``jN5lHKUTTEmr$-e -z$3T7%Gio+}u`Q0J?;)^>TGF)J`zTA>B$_LL``@yYiG;-#8*PeY@e>t(ak*wE5~TG8{n($KQIifL}Mu^ -zyoTO4A+S>@3ilxzMl*~UA-#;`nS=^YoZ2ySSh9MgR=c?i_W>YXg02I)blPh}JI2I2ZgG0Tj>Lmjh-LKz(W`H9? -z_7$R(>Gzg&heIf?p=aCV>f1579VO&QceNVo=@^}1=&uS -z%|%1si(PLV<)f2j^Svb=&q?H{5DAk~SUEIbdk1)19eOEnNbJ3fXP#|iH#a#0MhBEe -zjWZemVG@devI5$f9=r=~<`BtFBU?WaVTC-3vnujAjUTgPN?uLq!6*EYO)QKmwMxja -zgBbY))Uc-tZUHmXRyDDG8Lo4Qk0U}*9=VAey#RM -z9FEH&7E@Ikrzc2f5DcbPg+hzf&~I8U+v_uCfWn{IxebQSCqu&Jk0WlmuGC4qGMy`x -zFg?B;(KG_3f2%i+$v__vp8(|On@U4^3wuJ#OUz9-4b4CW)A_Jr**?umzE27G9(Dg& -zpy@w*aqc7ACsu5C)!yAjNCqb{g{`5^bWBVKy40cQ-?y094QupbuE!P$w^(ezF}yR> -zdYP_84Yn0Wv^r;s5(N9?_#WJ7{#j3R+}9W99;lCZeNlXrmaJabwD0V!5C%`SQQ -zqacP$DZOWtdO!WpD3uL5`jX>K8Zzji=P2q+dvfeQwD0f0#^#EscEcG@xixNON8dh5 -z9PdpIo(1r>fjx=86N@+!((%?0@))K?cJrLg14^DcQk?4bVY`)6jaS*S(1jVSA_k0- -z7Nh!&gNsRfT_+Duj2pa6wUXU*?${vO^^qIWi0^-CEZ~P%$eE -zV>yc9TPsT95Us3WKvIdCjqX=GUyheRO571&q9cY9u(5DK`h=uw%fyBGB8WR*;)Z_u -z-G~|xtb#M9^Wh=PYXUBF0I9$phyy+Cd4Zni=0lP|qsX%ugXdEU&H`-eW+~9BFcNJ{ -z=Ad5OWlJXRoQNyyhfVhI0z8T#S%k+0{EZ}H^5n|rLoAUGOxi%+48TT3HC}c2%Wdq_ -zdGbdFzqSz!5uV;%OQDAHp1!~=RNEcgC61vnv%Dh2%p?$K0<9P7J*zS5RplYDcJ80P -zp?wgyA)bXjkSx0ed5dh(*&md|9ufp>1=vZ9&9s>#N+*hxih*1NwDsITs81$ioynMW -z4&&IUdiJ11a#0jn-K?VYqQo=ypukEMJ{>yy -zr`?IzjKN9$ahj&k8<=4W)gnj9!#4y$i6XgCz)*wUrF_7FwIYrQrOm+yk$-NjzD*al -z64A*nLL0GA6ZYn1KWgtc#LEPv?A@KZJe&!^UUNnz%e0$xJxOp4gf4Anwq=atnIoM2 -z=XP8yX)HN{3VK)l8}%}*Q>FpA`12cRxh4sxv7x=yG@cTFx3v!z-XuUP;B(~S*)d2~ -z>Nj-cMV}MwNrFxr9BC|`RNe%SsZ*w;?&}4-_L6?YUHXHzfP9Uex7RV)- -zymkUCX&GQ|Cc?$;UdMk-BNC82g%-rdLMs&;!fAu8vTeB0)RwVnJx3#M_lOZ_v?i>Q -zA<=imRDvAw(VXN=QRh)&LNCuY?iaHCah83^@UnpBP)B -z+x0w34(|(&Nkt5q^-|_RrA-!nk+M&%J;dA4rt@NoiNTC^g&|(oxyA;leAEgyfo+2X -zR+dx`+@=Ys)=yYIP$ehaFXd1N1T^gdjvqBq#|E5?4wr=?8kc{{L)Dcgf;nFDa+Q-J -zDcyMHAP?~zsdHSfUw67+S3}=o~i|J(v7( -zH@ChmO4_W_U*%0@?VRw=bw+_2qe`IQRRSY@3rEpk0!=lR;0`t#Qm`rvG~i{R4Jhee -zHufV>v5aX9U!ccal8JLGhsLiTb%e^goujE=FnVZp?IiSND~trHg-v2_JfKRmjPTOcORuqj6<`q880rc3N0?gX;$bEvrqi5g*C`uu?#;?MPG{t7O8$P$V2iST$BR^l`4pW^SjW14hFfxn-P -z0-LY+n5v31Wb!$5;=?R~631(&>Zluz*UH+E+hB`>aVsfz(NEKMHR(iUdnCdyQYYh; -z@=JN@Mg)MpjySawYPrn9rKl5-ob0 -z2{EIyA7bBJGj@a@k6TER9)w%ORTA^W$4;*4xzm;Ei8uvZs!EFgEh75IV2P912InHCrW^KdOXiaWJ1{l79EXM_w -zQu09Y0?lc{F9vGFrBt#BT2Kqn=_EKRnk9fHG!Q+r+Lgq#p_QPW5KA=e-6-@c+7~{dU_NMZXd~zbBy!v= -z`H*xi`eFfogWT^Eu-C7CWh)H}MmhReaz%;#M+@G4xV3Pdam%HbJIR(`M_DUZjR+*J -zYZEc5jZJ&@!dZY)vp}_F#xp>m!i~g39h%oXr*3GGwY7a1)2IyMTx9;}o=)?YLPpLL -zj5E8ae;jE>zL}bG+9zpvj2SIul?QpE#<@Mz#J!|uB$8RNPJ|0xD -zB{S?N33-MC3J?W8Mkkp;+7lDoAcBIncP>KgVL+2>fQ)C4>%kQajG@oPTqA0?a)IK- -zqNgx$*$K}AgRCly$!#KklLMFxl00UKjm2v%e6q9L*@{V*R9KV)64x}Wvn^HLsN%=g -zqi2(z3iwScNUWUo9?wK4 -z&z)rDhO853kg7_Xfo=n3%Z8)UIwNL7jQ{AR&Q)XV39TVFAU}O}f*OnWN!otI-eDonpd*Gq#RfT$F~?aYNJ5c~K3uB|Pi|_E;hTQ7NW{0QgG{ -z#2(ZwmxPeo$qx6{sD33tgt5Jvp}YX3wqeukLojkm;5Z*6AhB>axLSf&e!}L}ZqByozhR1$CM? -z5PK8ngC4T+w`s1U#$YSR$xyJBr;*|eU|Y!U6P-2DeTA`l$YBr{jcC*}>gJHsGo$_s -ztvm^>aC_S$Tv9QW23ZN@G3cQWlWapnr9QGsjdrEzjjc`5GUVKHfeXDRSinrDiCOjZ -z{79u_T8vuRijWaD0zm490^w*!awnLyag6>LBFvk_4?E}Y7W}Kfsa1blu3~3uP|59! -zH{q3@e;a^T+hU^sq1=4rkh#ZdkS50@T;-YnXuTFUhZ|Ao<|!kFF_967*)*~Ku=k3yYBqg+yXm+#hf}0gH -z4Vv|VtPlaBy-|;8-M8S&>)*!nNiurbdCSo>zG-U7b*?vz5?VHYfJ{Zc1B{fzvyiVD -zGcbk0^4zn?s867t>qM=gy|l?w;{r1yf0fITJZ(YGl!Sk-(?istYH|{7InIV_5LXKa -z3Z=E)ncf{po4jfp;+Q&ums=}l|8$n(aJlqzC2_LPjw0O{^aOlr9fl=CNrdF&9Bxji -z1G)#XlC#za(O*g@9;|wd*ZJ+Y>tJ$pq%#G?vc>$x;*zEE%`2a&gsJ)-P|NpeXso8V -z&k=PSv+X(p*kB -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

    K;y4yqD|-?I+>;zNr_t|w5!5eM=_T5 -z5R_I<3;KxsXF5-8&g}#fd+njU1azboO -z)DA0!aeQneJyK#gA~lO3btJn?nXxPFt|Y)A`+z7NK3v4&w|~6%@!e+O0bpOYgj!Qu -zMG%R&M2&>wcnllj0tK&)V2>)smZ}UxkSJ6Y=KwzG0TQs(ZoSlR`;4mRqu=CZ_69H! -zBWildQy4(^jfE$Gd}Kld+rl=fyf{s&M3!=!5{zM-YG#s_bU)Mly;{(r|NK<061^}9 -zNmid%YdbAg<&k5XxA)wtK4tSqN0bioB*Ls((vSIJgj=6s1a5OC;DnoTTvDfsQ-T2N -zB(ldDgA7)*LzGi-P||{dlgjT*{%ow5;I-7x)b;vP$!Bw7oRtC!(A?T(?QLj++c1*% -zw;(npFZyW*-6AV(#DE@!zc}Yh4=0dN1a-(DXGM*r)=1Y|HQ60LVi{wX1m$nQC`Dsd -zZ$BEq`KTCGgpeA{G;!X3vo=M)HWGOh8nvh+h&z9Qe=g*qL6I6$e2)|3mljUu{gnIz -z@q20dJKTX89JFA}U;Vhzk9yGul@`=WGC7h?^fNxpmQ7`)T=I?I(@QW8n1%iwuLSG5 -zBFG(Z)DNgh4_f7c(VA1>fqtRcH!3HnQ60wpx_Z)(U=kD(w?eM+_9BXMLaY~6q7=x` -z9VqjhyR1EIXWl{Y0c11V=!l_Pm`)GG8b?mwTG>#0I6ZqEQ5}ZkRiucH@0FkkG1l-Y -z^_m1-#VL-G^6s*FargsNuinl_Bn3(bb9)_5K9fRYRI}5}DpDEr^!xO9=tq31HK!%2XWfyqK2r=HS5#Yx;^ -ztmJ<;!$tDkYX?NH>DXujOYA<&m0%4@mkoS}M*3v~hUEvkkd)e!1`#C~00lipLoHq5 -zQlxZje=lezRh3mamMQNG3@8cd2y=T;nnTP|$6<=&WIR>MiWT=Q5&N~6M+U6In*uHf -zo@e9WlaSWiCx|ohV#Ni7zx(S`Bl(RDJ@OaI4vQq#DjsU!=B{`aG`X#7+5#!6yf%AY -zDIyb*xADTNM6@eWgRBVjf2~Ym$FoL8()Mp-M8JT+Dp>8OkV-=MIf0OYV-|WTrfCiX -zqQ4-{BUY%Tu=xqoA9{3XNuqUi$zYZA*;0(MOT-7h;i80Lf8tP~BfPTHw7Qkscw0C% -z$`%1n>(PPdd%GP|#|9A^Hjt3kaAeA{7e)XPTpN}i?J4oq*BmLYr`T_=N>TMHS25X^ -zQi33rPHj*pvkgk=Ua;ufF0<~bp^r$!hr8{4t{B5%dK$ADA7&&f%k0%Dqf>vYBNP`B -zfV!Pqh;0{~r|Wz>Qp56vcvE>&ZhZsHJJTrHM#o-^4E7R*GV)5UaPGuS>OIY)k6GV< -zip31taa}*H4F}d)Ir_udWu~0wni0-)<1fl2ZyFar-zD&w&fdEJDEDG(*P!Ay!+j66 -zZ@Qi6k#sCZiVtoPjDVNzj$!TLVmh%vb7~L!?XD1ne0D7px(BdMIV(^B0K&0yXGlB* -zt_>L@oH2mm^QzRq*f#(Qub859J_vAxpswf#{ELptb{pr*k%j*>RI-gWh=Uw3%>?YO -z!T$*So5W`ib=da2$&O1JP{Y5b;@q+Tc2%T!E`6^3NavoSTFGH$Fa>{%>t3`HP -zxWp?=nB1spvC2MWU$Hz61O -znm7-GmT>i_enFui&EZJWH1<}LL)(xPAjyZlyN)!*K(o^I1l|&$^r8LX2vzWg^6^h? -zf)lIAOzTt0cz=!#7VzbeW5wL#2XEN8jJrT4lw`D|GNRV52zrwnns`d~;{F -zH}=edw3eyA2`wF+c%X(xxKE -zk_Y@qp>AiobBG3G_VFx -zt~W11iYc#d9NfaOQo+>C=lq9dpELMLB5`p4fzEYQX}eVw6B6mH$-~L<-%LdKL0JcC$zeE0)qFw`%qc%G~(v@B_%ND3xiuvyw -z5POGX3KH+0J`C+~5&GB6i-e*(12hmx>?I5jdVXN*61&=_Zj*eMWEy9chUKcnE+l`+lVbi9WAVgku$bdEV%TZ?y$)-&#m!M`anc!+dz>TDeeI!pldjW6vV+HHA74o8jJnS1Ha3e>s|Z^W@$XOV{l1o%v8zc5uQr> -zX3vB4pdN!-Oc6g2a89u(Gv-j&EblB1cTTg~{U;3M&H1}#%sdrxvGOHxnh%QkK|Oj; -z2k|P8mTt-z^8mL|av(e7%I}a^;=n0cJ{_Z^==$8E8zPbjR2RgN180Di9r6wNt#fFj -zNDf9H1Ir~!W@bsr%@TSj8_`bN0i;>ySfp6#o%955 -zUjcwMK`|R`kPAe_6J3^pb57HSiyAq!t2ai{JbT3NlOl5*GDJm8&)n;IkO -zC9rcHb!b5Meh_2p*9rc-ZOj5MV%fWrg%MPwqa3I(Wd%-3<4`}MP1K%tDnY6WOkdfi -z!)p03`#PV2`)*Nb9tZU?7NlIC0*B$SOF<3l(2FUGv9P$9FDqT?_qrS22mV7(lcjU -zRI9H}92uH6dJCP7B(KCMF|4Mw%bJnyb_B?1h~C7POeJlAH4Fjq(0+~=G-GwOR?!_& -zVm`&eK^izdNY^D0-HJ~qDFb*dzR0>4dP~YDAW$- -zPL+sx?%pM&xnN=-sBI}FZ6%kJtvc_aLg&Fqcy^Ga%_+BU`zRX+f#U>TRBxn50D9jP -zjeDE7aqhe>rnNtmh1_mP^cL$rg}7qys9d)>FZ~>DAb`;MY8{?k$D<9^S6BmE=yT_{ -zK)avibob;zAr=fU;EwTS>&}H`0feHBlZVoi83q*Z9!asTi#-4|mLCxa*CnAeS)0P! -zmFFM)Ytu!(!9d*-e8O;1PBah5(b|?oDe;p;(<#O8S(iT6GreWQ_6jtQoKY{}w|bHh -zCJhZXrM{k8Iyt-bAn%7#h5?nUwE;BQb+zaAbeu#PUBLD&2)KDL=0vm!{<)q4a#`%s -zsfP`5sYFf^6&C`7_Ll{tK~XiR!@UR3REjaQLzq`p!$SnzMFC>F)pL#JoCPNyxQ~}S -z0{2k -zYh>Olg=x9d!t8g^&Zj05r<*IluHnC -z-v^*1k*KCbH;TtmdrHP|IAIAkq%FN-7%88*_{1P<(+pCsvRu -zBePG8WhlGIW~@@esmNkRXa;Ak(#S3c?bppb8WOo;E>5n!wvwyn(&5vGhm?ya&#a33*Vfgv@(>U{+W10h&1n -zEsurOFtb2%9_ZOi(ImP49y~LBAQf_rgIy+&0X4Ibc;zx$OOsQPZV`A}t6cgAh -z5tx1UBwD0D}kP>PAs?H~Tv{>D* -zd{(-U?9t5IAx`g#w#3qVtW172Rlw!cQZ@R~^|dpm5?ptxe0hAepiD4tZ{vrYkayXy -zhRp`<)5Cj^kDN6jGf4zsl+Xp-XZrUQiBU`GzG=^*ina%m!h${(v@<0}&(M@D-0!Cf -zHdy!{_CMW-+WTLQ$%}lto1C>2X-mH^9*{7Q!;YBRZBT%eE(;ars^PGn)^uLHED%nw -zd?3JEjR=d_Q+rN%bobGNb6LOY^U)_6+f>W1)R}`m6yR=MI7ApTcDi@U`diev#*C=g -zrGD3nMbQ^4^9P -z%B4j}LfIwEk{&!l->0Yp^+ZGWt6S3AvDK38ekI0lY?PlF1D=F6$}={}$!6wc3Tevto#A -z%7Wb`kV89&AW+$q@WFY~ydzkbM2|eXxk4sB+W@&MDJhUjzQQO2M}&JxTJQcj7Ivs` -z;Z1HJLDeK5jvp0J{|VzQPjd^8A@~$TiBTQTd%Px92DDEl{K=ZqkL~@>7%|1KqDd@! -z#d%uO119ca`}X^4)pwyiM4eiRGY=(I=9xQ1)+`835 -zrY(^o>P96-Gzh$sK`WN#Jsuxwv}L7yTyB_wGzMdHK!o4)*jtI+0gT+7EGjj$cx9C* -zt-A69LgHyU(D3@4!SiGAx*c1u*chP6F}IMk0SO1M*phfdJxV*36#&rv%}SuLzM_#I -za!zN(Csc~eQA+~L-i&Ot?NBi!{Pl3E!~PE!5HWj;7S-B8|JIqaI&yu@9VXuw4;93W -zxAAUEca?BAb3nnS&i!=WdRfq<>Vm%4y8Ws6yK3gyEULoo8_N{5SX9_Q~coU1oA -zKbCp64aI#N>T~x{Q^$A|rKUZLnc!0&T(i$re)wo0_v!reKnG!tu~ZdwtCAD2wE(g) -zHz`nIX2r5XWf8q_#itU<$Wqn&#&M*?<1WYLA!~2mt>lo?CY(HeaElwU^mZGfm8Ynk -z)8n1l2*Ph;#A|RD&(`%A@)*7BHtKS@N~wGys!Z{OG?7JJlG(CNRHTo!1RrI^4cek8 -zp3jNgbO}b?#!^#iAU9#{A#ges1+E+p%7x0AwQEg*QnfW2)u@1PorX-2n2DQFdcl5Y -zAI+&C>Z9wzFzWzR9g~u2a{!5Dc5v%&t}Y|Ddu9WIWQn>yQAgn|ELU#sujGE;e?P?% -zvN)1wpx?g^r_pwb(=Lz`6w+Laj*Xmyjw9L_hu$PnnV=ZMZGX)?e=R7ru{5bL6et2H -z7FlUSZKV;hgB4G$_&yw~zB+SH^e$h(?h7=8Z0*HL?W}63aFhp7rA1euRWZ{bi4Tbb -z4B9UV?r&|$6I)YBp#~~{-y|^V_6myHW&pqaTIv6NQzRRtsDj1sI28bV`QWp{{x$qZ -z+9VeeGefg034YaP0bX}Xk3&J;U(i!nR(V8Q<1irJ@m-E}oyc-2^F%KG=%Y=j?yaYZ -z9J}boa9+hC6pnyq8_e8I%;Ul^f4T0aXS6Sr(pV=z9j86FjIo`s6t^roN$5Mz$IrnP -zvqO%OK%fI9)mI56c^ci0IhySaMhHI>G`(QZ)9x44qNA34iv&w`d?+W7l|sakOSBIy -zfE6=0wh{3-cEA?$)G1hH;?B~Y$Mh>8&yx7{n`dKUV5bwz`{K}i^m_=Wtc%$^`Knv& -z*?*u3)mHUS&}HAB;Eu)xCo=2@*t2SoJxsvt%la_>laIMT!)49oNo3QxU9tl$4n~wu -z&j}C~ASl$=sBM;jcMBiRI3ddbhp{-TCV)V(knDKh=cBpKU6!HvyX>spu)wAJao`yg -z9}|@>xTV!-Gn~N0Xd5bUZ-Dh92^Ma|t1kWU8LJKOv%xjxfH|ZnW}){>7ORAH5LX%J -z%{k>k-L~jcG^Z%05gfKB!8(4iSxdK73#~GkqX%Q~KTTiF$FSnqoRb2UCpjlQ}DE-6rjC -zNv>u~AfR&$g9B}X(M;5RRR)&?ZPKFU@0wAlELV%kPIL(+d15X%cO-tX%)jg4C>B)7 -z8y2E1`BhPgi`GMJMkZ;A8b=z^cg0V9vBmH6O$%QVr5Xcs*x4eZcAwtz9G0>j>6VaHrMPD@f0BWpR}fZLf<^f-w17tlij -zmSWEY6`Ma3Gb@KFQF8T)hz<#){FuOb9~sJ76l;2<8R7 -z_CwE28hrB^1G>a@p3}fQSfWNrs&RN02T287&(Jrc3sXNPl3DbYwjkRd0BR=AbDQQp -z4eS#0ksCT1$of8$pfgRq$fc-ev4*s+7q0Yi34%GDjRBbo**P(q&V{?q0HYVO+9tw& -zS)ZyI*MkfSDm5|n -z1U~0FG@q4hie-51O>y&rzgcy$pxfeAryi-HV@}PJK`3E7&`x#`A@!c>Q%m#BOVHwI -z`9I8*k98Z0e!FZmPI=kHq+HPF8K@+j4Y?ImD#9*}=zl%yvrwPvCj}lBdho9Wp`@_R -zdOKX9Q!+UfH7$%!|ajuABq8O)XWA;6p!siyd4)#>L-Z_F0%(@u-|glH+Gzy7Vam0Z{EOl -z*^?#+gJ_lDP!=8i_Q^P-SuxOsyh7)ElR(9@;rHUKjlsm00GMsl=?|kIP^#kkT^vVU@YaT(XoM;Mv27^y>he!sH4QD%e -zgKTc%-sV_h{<3x~Srr;f95+2%i3m;iCa8EJOO0zuJnjZKOjK{WkSOGir$TKqQV0Fm -ztPU)ue#H*!rnwqbeNxRYY_QdmY% -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 -zLO$0S6=Q5^v!nZ3bn?|0W_8|e>KDuqT2VT+ -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 -zOg0jVW9ijhGQAh`p -zCn>*?OFF*y?c@A>?svQg&ymF@#}(P`HJZ;3EVOB^Vv$Hvo({Y%6R2xJO@JJhMO^?k -zpPv)<`g^S8j(p~zU%T~0NM3}JoOx;c1b`>K6Jg_nYiXI}!k4unX(EUps4ryItk<49 -z1G}sioc;)Z|C}IuK4ZD5Bh(FVkE{yJ+l^@bC#zG6s1q9X}A$*eWj=CGq;<5?+@`g+qWt` -z0fSjChW65db&jjc&GcZ9!ju*kISw|(zJudk!CG&y2~W10C)hN=U)*isnR$=g&VnqY -zgp(g_Eb$}oaI;DW#rtf>=)B6ArXiqc6a=tf;vfyEPDD@c6%bfYOrk0$d -zx8C##lre#ztV+5IT*hNzCqh0+axnUa -z&KrGu4~F4ZXIS5Ojxg5O$;m6^N==%P+Z}1I1-cY?<`@GpkHv`n)GF#qzyFF^(=gpq -z)u;m#n@1r!B|y?92Lz)Bh{FBV3;Bq)fW(3yQRN;;8;E}<0fG1LNi^y@Sy}05tbhEJBWZ&c`FRso-f{gIi!aEmv}5hd|6bT|Z22+_m|T&K9Bli6mdH -z!y?S*yM)P~Za;g#FszR!@8IC`fB5TydX?A)IgWpw$p7}f{rCUjpUgn3I2h~YxCwUf -zU#M-Yc@8x?@R%#R`lcdR)CWn~$a1MIhoLsvBb=Eu4DR&%uRs2Q+aTUR5ssNyB7nCb -zYD37OIb=vfPVB=FgBZ3w>5MR+&y&qs6{iGz*+#=8Uz-GYa<{RlKvAF%p`B4!$B&(C -zD6R{_Qe0y##7jSY0AAQJ$X%}9tWT^AsqA$9RwKyMJyt7isco))c?kwAI$IFZTFBYR -zU7+4>%Y%W|tv(^l{OVru!@R9Umg9nh02uKX@52I|_!e#UL>}yWc-v_7*epXi*ZU!I -z)|G0W?+o|wJsmW$pu|MGLrf+`ut$xa>G(svkCfh%7S2j5lf&) -zRuOt`Qvr2MgT?YtI>~|y9t;XN*y>kStkik~K}~bxqw}-l^vAh@QEJF!pfch5UEt12 -z>I!I&$UW*pUn^QXa!A^R$X>zb&6T{h;!db8Z0Q=iewUD$9KB3J>xKMl^&$@cj@cfJ -zfO#xLLdD#GRMvlSZ(eDWNi>nM9wD>R6~{q82>mM~ -z`-#W1xX*q@yc2Z_0>GZd&ri}`OS##%-&t-|>rC`ibrsh$6h{5C%5^D?8xlWzUg|<} -zix>R(ZZy}bXKLC+y`OD8KB*9=_a@;6yMOE(THnsOK<|lH$~1$-lsSDfRiria(s2Dw -z8OA7Z=tF%4Ti9XE!@5MF>vDOK<&|aB*a@bAaIXYp9bY<NSkaF!8-=6lJMpq9aY;n}?jh-b -zme?ZUTVjbII#p~(0U7Z}k2dkwUz50MUf$>6swxlNTvWW3Uiq1(km1$_08M=W$s!MqwDbIdOeQpaI&VhA^v6MdD@!IJ8#Uc9Q0t6OEj~TWf%2w2pS}COR -zL5GUkM!A=?tX3|p8aK*NU;MgsY(~Xk1|Y06sy#9mzi1t#;OAv4M4B4O%}T -ztQeyAoi>kCe;Hct}jWONPj#-km*!Z7T=QT2Q0U -zv=9g|9;P+HD3QVSQ|^Z;MuSuSY4(EZ>+==Vsa*oqacUm7W?|F3pksiTqiMW40G{ge -z9W@=b=70O0JQ#8trb(9~uEwHZ!cDbxJWW)8mb -zw2hI=QV5hj3aEopYc%fEwsB)I`ySKRg|0(wxE`WS&J_k_Z*JT)8E|Awhk)1GaRg=W -zcK2L5@h%pG&$7c85qXd9H5BngR~4~~{>N%gz_FoH|T=2n#RS~KS)tFWh#m=*Os -zh73Ue(;4>Ah`yrRHlgk4Yy)B>C2o<>t_o`%#aPxuPzpU1Mfwww|4ipeX#WZB_u4~y -z3Fyev?qwW_20C+bzeiLyH7zhh8{6$monE4J7@L6VsvTA;@}%LBGci&^G>URd5L1#} -zrfk-gc3BeSkqtr=8y~Ks@!LP%`}l5?`~a*kTSC66ts)jhT%xISkH@efE@0#-`)oa{ -zVnV9QUT6d -zzES!?jp{J&*VU7Tgs-6XxD|GlR~b>1lXAUiAEiKs?m%hk+-2=yJM#{D4`9UEOq4!s -zw=kU^h?&KaU$(NL_HcUkI-+(A$EzR|9r7!h%2A9pd`i9cNq~Hi!1i5MTz0_l2k37d -z3xG%pWE1B0I-Gnag+|=wtZS$ul|fIxPmjl+)~w4$DiYL(1Q3}7)wjnB6MU-fNixmn -z>2M#)OlN|9ZNq6qn}&Tj5l}K-S?J@uR62k;iCc`7{O@M4OP+gem*_Pe8%FML$4#N)mtvcVSf$$(=7dq^%qo`>GxlaSWiCx|ohVg&^&_t&RJ -z#vB{Hjhp_NptqHQiL!fZ{veC -znZQ?~23e845~kPd*&-w9)5jR8Jh_6|B$fp#0Qd={3LLl4M=_Cg7!dseQ6sTIErlIX -znEsGZ?kCaL%{ARrnrO>0$}SNfc#^9UhJB0Eiy0%9ou<{TGP$@eoEl{xDp-#Th|;(F -zFy*CRuiPYspqpbg?62TU~qH*1hV;sYn~*+Uz) -zy&SURn#b$#UsH8&Spd5!XuXs^*EG_(=crb4SXqo7L&KcDAJ1ly6Bn+r$+OJ5`IDBh -z8>48&56c$<@!tN4BWLr+nzjYC{A8yb^`|jBEH;+NXe*WECnSHNRWBCFg530Kk2jrX -z{C#B~b8y-Ej1o;T8_2wtpnOT5NB$=uJ7ZtD3Mk;{9XL=Jwzvto==a2V7_@|||4IuA -z1>OZk(ljQQHjJ&Zlw=?F>AX*vCoMAH6S!6Y{f7dG1zI!MN4fn|pYg*gvD05GMu`#~ -zEa1E$pNhH1FPz9&ku{N#6x>0@nP_;8?h~)C(#NS-kdxv2vfgGznzVuYpe(&;(^mE*)J9wc%}yN2XV9`GZDx}W*Yp^C6# -zd|vUhhLf{eUq4@|i?eyGjgch^Z`;$K#lI;MWnT#ZXmW4HWbb70rxNxmj@at8JRP88 -z1OGyR-~!UEM^4a*x|Kz|9nKFKfTcwF_k3neaXEa$fwUlvqhp9Q{)pP&-};Ji2nN~; -z7jFcu+xkZqj2LG^AD))?-6g}Az@+FBb1o=gj>}Y>0VUP|>Va}S`VdVtebkb6M*2vhL{M66f9unN0t1q? -z@+h}b$m?ZGjqS$L6q};Z;=u>OyLv{BDPGtv@=!?P52NkaGWqhqmj|GBkcLa2Aj@4)mOjw#5DfVvsl<04A1n->X1 -zcLr#nzu4bEdbj-oIx(@UeHoO$Nz|LpCXewEFV&+E?o!TYxI*JN>~qc`DW%~Oy=%B4 -z2l^Y3%;MU`G-@R1$=kYSrLUeb4})wx4q*JI3Sk&qS@XFdo -z?5Q+qVMUCb(jCeJ+gMxg0)L%E;A=s}mWbkr%R%b9LNRBEKS2b6$r;TQ@h -zm^h?vh#5m;vA=oXPq}l+nIvJB#$!7MSF6T+mOLJzth8^oA7mNz7}R2l_=SLTidC7h -zgt}&VXK}c*7c$!Pf`PPjQ;rFyq)K&XkPnH|d{Ds;BHeqwh&S0e6549-z%r|Pc62z`R{MBtbtd0}E -zdEPR(4D?hC-*a!zuemMAav6MBJ%EU!iF?y?%+H665{H%NUVUA6_Q`2h*@5`GrC7zvCl9*!yAtd(lcjVRI6{=F(5rgtaGNWZ -zFG5Z``KsR%Ij&a})90ef`0StcgfM!by;+UVbiEGea$GK3G5WqF=2pAynsF@_M- -zw$zfklFP|ao%hh7^I$YVJILAQoZI&)%E3Y4IDsD38|e{n-w#Ff-sf$cFJ2eZ+8@e7 -zZZ{-)i*3pwt{6ND=WWh#KZiRQDRe+v33k@;=tJ`z*1#6}eDPPH-7gxyd-kA^7X}z` -z$5gd-=fbi8Lea;m@ZhOoK=JOG6$LLb0YKyQVI)P!DsZwkmA9+!KZxF@t9*hD_DS#w -z!$n2YGH^$0TN9?l7dfeOil11QKGw5Hn~=0oK|8n(VsTbALKcqK+qBs%13BhPYIgCy9zHX+QhU -zg7KqB9_t}I5-75AjG-OEvZ@*$BH*qH5Zk?-TQuh^IPt)JycT3Jd%SzP=d6XaUm0e} -zLWMXJ@$zrQ4b1eg_b_Wta314$sscxyZ5J_JiB=>mwnRW@_q~g6y$0I}9Hu;U*pc7) -z>GZZrqUIAZ)ti#=K}Vj7@3Ts0o66qoU|l -zV7O)%Ij#uM6=tn@tA5sJL=#6GhDmHjX5EJmg1;eP02b}$PJIuO&eJ)ug47rTekNN(*+mXxlM_xw -z7BfOKIBSz9dpY61Zay^K)CCtjdut_E&834uoCsHvv%NcG%Y*S;*p61BCj4`78-*KI -z_$5)LjWae%13(nRqRkTn>}Z~``4uUPvn@}Kg%^pvYhlwE+fe0Zw?0ezxAYL2hyq?ag -z$K|3pI|l#9R$qD)S8C-GofU?=&P_-i4#$*YAgaQ#t(z6jVAGRnYt{?TnO8mQ*0d0B -zirZiC3O3y~`Z$wUd@C#1!fqMDO`k_e&XoNHJth|aM=^m(xxDOeKmYhHV?3U(1O^^2 -ze-ICZ>c!+DB&7jvvyXOUuNfr$t$jB?hnz_3M_qjWM~l^Ire^f3>uYC(DtPW(`SSQ`L78CQKE^M(A)j(y4Vw+zr-%2TFF9L6W?WVR -z7-$T0+4ol@Mm;43r@bgN+8M}V1-dI}XG)A-peb8;JWf@8u<$?Z|9TL$_rDyI7y0xw -zIfEsBw)MQL#&D*NR2e7poL~Gk~;3emqF9 -zQRs9cjlp{--UDtb!S(a?4L}O+ejSZ%wXE`|q#zb6kMd{{VpsMEv!n;lkW!91P){^; -zzq%!@9a}9_-2-E0+qO08*tVTiY}>YN+fK!{ZQFJQ6&n@Xs@Tc*uC@2Q=bS$=$LM3W -zr?<6jjs59y6M1+UIEYv_uvd&S$`f7(j*9kA?WQ*_{8 -zczQ^i47+s7-?I7*E@E;_FVB?iwX5`p!RR8qDs>^fz+5fCP@^ewRzI2$g5AJ2m|bir -ztCWdR^X&pQQ^7DL$#;xW0`CW -zPpY~)Ml;|~*z_$?*!T$|KLU88u*N78$~mwh=WL$8h>X{%X$mfaAD0Rqy3zNzLRZn0 -z#yrm;i=8J~{r&J}w#!W&o~2@fxR=I8jGacN|0AcgrHH!{14PY}fS&aC%hNR#>P%bN -ziW(BVbKyz7aDOdo(K;>0YUd+6Hk49s-NQl3*55+P*j9|*R|P!KkmMZBEemjg#kw6r -zf&Q=X-ea?E=`R~$7DgwhY9BD!a7`zps!#jH??siraN=mt_$}-PDZa-v~{nb -z>8ex*y(v~U1D25iuPPGD_U~{y0HN8MdKFEUBQ0r%PtW6o0BM7{r>4|u(`2nvkiN29xbM1yV9Bh%YWO=lG#g;dGf_L%gpxo2~;R7PAn608SYQ> -zIUGfN^nflYZlGmuW!0HmV6wEeLAouyesTS0S_rUl6!L%~gb5wZx}MFQHK6a83pAOr -zeaXzguMhql@eL~ZZee-FAeUNhc4tI-y!lbi2a3oEuwHC}cO7D!%faW^((%wj!BD}* -zu?azIn%D?@JYVaP=RM8Ga#%Jo7_G~{e}tfJPDE>#W~#!@CA@&tbX>u;)mY$@UJ}#? -z3Oqc($N$lfi2GV%)*oAbpAJBd$uP+vkrL_I0NkVx5{pgWM -zu@*b|kA4Idhx(?l^d2lI?`8NSD?#_F4xM%mtu$W{krY9iZb>K%Mq_S&No33e(2o;K -zR2lzUKbB56Gch$RLc{nwS@OT}g{Z8I*yi~z@liYxS_-$SeIlMV&pbo_Klbr{>BlRjlZpXt -zQU?>(8yv$@H0&(eA~tu1bx6Lgj+hw-O@xdyn+^zYuNfAeC4P)~5ka+bzo}(12Hp2U -zjPT=tx2$`S4COegOXsWbDAwc$114t_xSBJ1R@RnVR_EsF?)E;#9(s+Q%@5 -z&Ii{{>tAvuYsT>QW-;+20lyb>{T(9?H|KlJ1o!E(LM#R$aQlGsYz -zq@2mqCy}PkF5$*hnQSBHS;GT=t*n?)#TYq`oY7>­w_DLtn>Orrc`;|?rJ7b_hx -z!gRHdmxa|M=rHotq6l4%^prUGLKgo7dBV0s4Acfsw_APqOh-u6-9?b&Tr)}kHj -z^<1VRW^hpos#-sag&%A5A5?OXg4<4mr$j(TC^v(?g -zq)`%dgi47oQ9Pj64%hNhAJnqYJTstA`Y=OII~%NP+K14&$&+0iT&Q0AvQf0;q5mO} -zC%2jU;XEZ@w(&M2jyAum6Q@cY+J=M#I5~FB#*)j*;LF` -z<;w68{}qsR{}GU@{|HFdYrv7`8@SPAA%p@kPrX@4%l&qi%%s?CTmb78JoZKr7-cbH -zv#K|lv;9qe%-*Qk%RAE>(w(5t-3a#Cov2Yogks;`yid~nn&L*U;iuc*KtMmKs5F!| -z-#iLk_ScT_*_0!K3`ond(cdEiYW6D+PlA?kC7vx?*aG^X%!3-W;4Bj?nc=YYmNpZ4 -zJ0L7`Uk+2zj0@dZ1&RA~2!qX#>6S)bJuAv}rCml$`v)UPpyC_||1Ceu3GEesGXawC -z%TsPTOR+M;p)e<|+vxGU13UOSWK4_&`Fi$~(dp!rt_YqmRauzKkDHX=IXmJj+aNRy -zjU?+__RXOqa8vYWj|)mvXo{fXGPHj&lD+H=JF`7+d}&3*Fj5Ufjq`}TK`6(QhMTla -zTB1pSFDgT0F4q=d?mWiK(QHhTmDmspNz|wBOWqHDjZYi#mvNYbKEu;z2bxUv&q$%W -zXeFJ`ElmFopw0Z)Lz>eqf19Y{)sIroZqpC}+GN1^sC}Md`oY1*A3hD4-DF9Mydu_T -zCn`YBxP(^U@^g4P>-sC)FyeDh=pul*8(sH&pqmKE#tJ{=e1d8h(G3`*B*zXK*FY;@ -z_0qxw*d<>c|K<^`n7mH4Pty%>sY52L>a&+s+TD~F9wft=ol5od969pr?b%5W-WT=< -zciv2kw!_Nfzb>)pRmVV9g^~0YAFn^9txnS_!`D0(vz=Whnu2Zo>+<;BOp^0v&-G9? -z&V91fEUpsRgLQ=F;Y|5PqIEn3!tZ=6&NZPi_nU4zWvUqu9aJn?rnVdb75$X2qiP>h -zyaMpxiU~B~NQc8geZ99%(8_;Ynp`SgJ|O(9l!TYeI;fS5?@Z>*qacW6gL7cm3drjzt*7pS+p;Vbm~$ggIA^>{7oxiCuCRGj -zlZ^GOBu- -zB>;#}R-UYvb~)o8{77l7df!5DWYHvp@DF}$P~ifvr2;!?5b-S_!Wo;cmn}ZLAdL{@ -z9Yeh^mcQ_0pBN5S;<{m*qvg0o6`oGCgl?vxhaDFp;i?*0vpEAQg&W9fURpT9f@rWC -zV<^-=NZS~{!(aEoM@tI$Ign9E)Aff|9*tcAx&arZfvs?G*}lve*N(_oN0@E#tpu_a -z=vGOj&*@Q_QLBSEtdW$p_F{kMmmR(O2+b@wt0`Q5=U`D%)I&pxzN7E=S>0=(r@(^+RA -zVtsY12EtlQ3JKS66mxEPQ)85aR8FrgnOZbgeW{+;G50VLVm0q{sCDN^|VHk -z2ow4`O3`)mDb#U`u)1U8;;UVHU_0QbzmS@0Epqx -zpi%ypYla^VUomrHrr}}?3V0RRx@-f{b-ze=fPYuHZoR095ky?!StOxy#-l;pR>EJx -zB^J#5Yhni?2$ORa$tQ?-=ofTKQDMd&eWSUjO}~_+u_D@up3UDWh@VxAh*#WcV_O;AaJvR4 -z*Zdf47IfXBXS9+6`DTe~@gblyD{XfgrIT#jDX(J#YxVWs%@d;!8#7&M@Q4KHky2$5 -z5SUZ^ct_HGv>H|T&$x?>QkS{d96GvHB=h+z9%sL3$ntqwfR9`dXmRxVhb=M%J5>e} -zTZ>7>zmD{e4thsvO45qZ@&Al6m6@&BAflzGyH0z%I~S8nii>XK-u#+n(@+$fw(ckY -ziRf)*seOmz*(_ZYeAS`-Rk?t*a!*3ZdX1$<)Ave$s{wvsl8QIMsa18q4Eh7Cdrcu= -zpLVGJov<3&TmdsFE4!4I->lH0 -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 -zLmlIR#4oa44xt_n#w>F;( -zhHEQl)Q~v8(xdEg=_#G -z4r56s)*A~4f^t`UTPnoeRIU>>g5-RN+msmhn#_+_Mk-j%DDs3G#_Z<~AnCT)VskpU -zDqa7?tH3^_bKKJR(xCHuc-xBfnDj;BZ(i@gb!zj&TVo8pY1lW=2&t|a8(_m?egUOH -zC7&+?|HMaO{mGOQXTudO-5ODLYIt!7XO;`wEze5}CU)NG?9;cjt_kR0$8Je?sT6Bgy)AZbcdVZ+X@DHR4{?@t*d>yin#XSmdP%u*z4Uq3Ua`BYB5nG -zw^aTT>Aim^zSDAsy?(4STucS5diZo`LW}O{?PbT(qfO`4fdqDTpttrIi}&6C!S)HY -zlP=R-x{-?~<0;;pjeVkERz#|hhQ^qW1zbD8Tuk+6KY>jlShi?o5YVP>vEu=SjlR|2 -zFEm9Zm6;>$bfhGt%Cyi$mpcIU#0o=Jmn7H%ZG$_~(k`NiBfvTje;JTgmkd6Uj>-B> -ze{4Zc7vKe3v2u>{{koG+^|LH%TI!=X_LJA_qAezP(IU0EB~OkGbs5bwNVJSv9CAq- -zBw^sLpOauH%rThOrTlW8LLle2`%NVh{ppkbM!erj0=HY7;Iq=P*;rebLd5X4Gch## -z5#HFbiKC6VdU~ng{XpN(sA0>Y`~G -z;E9vi5w_k-qa)u?t;gtyP>!2Wv$6$Maq?8s-_PyQRXa$qMnjK-xZ0)dO7U^kF&g(%1Z@w -z35W|DP`;VSX3ZCgjN=E -z9oKR?uN?a+ys`sqj~B%#)-sHV4(* -zPQ1XB5hgQr5;8JQ!0gP7A*_Ee`@%5Df3EYyInUVC6{7LnONF^B;YcQIn$9_7wjJgL -z*MX(m5$6sAgc05~Cp1b0CK@jwNnNzRZo#dNJR-2Ct9aDzg{POS0KoHkFg;@ -zJ!3NTQ-%P<=C2?2f*}&FTiJ?v=lj%ttdFrralzPPW^@xkcsUqRKyQfSJubZ>k@ksy -zw0nbf0uwy_1ynD14Xllb7%%@+lx>#;D;dTQE`Ox84E0vOpv*)J$0DxqMq8G{6c1hh -z(X&s?csMhOiNPG0a&x(58wnnUNJae>uRfPQ+{D@$NEO|zk@QT^C%5FmcJ8Rh(Q2e$L-rUR6T>P6>`cmQI2 -zT$N!Q&pLymuY?hg3Xm6@_z-P(qcxy+;b^a43kAp+gsR68D4 -zbMs&9xU8K@jS`<^i{Y7jBVjLVEL+rQYY>06DZf{p6)X5iXz-tj -z6}u9o0}LS-&n*J1-vo7KLGL-xNs4dn(OAWF=}t)7Vlo67`oEgnZ!rZc)~UZeW&5G7 -zvWFKD?f%@U_g*x*8sZ7rEik+g={pUWqBb-`;L2|J<*izq)2=q&1!(q`@N+lWhXU!)n|+@BO1?_s+57 -zoD(%)Ir>+(WvI1&SvOp_jF#XUt1OGAK|&N>d*6a=TK>Frlz65CJso?w5F6;MPVXWw%L+n}0xd4HG5+pqB$bhbr|!(VD& -zdHq^nTp84kMA^y{bAA?zBzMC`o_ZPuhkQD-Wm(;GR3G)XZ5yjP -zg;lM&VS5*8M6}jM!ZljGt31$OZ8mQ$HGxqm01C~`Y__};nA^Ietu{pdU`-o6?cAv% -z@{dWNw_09S&VV!V95gSQ28n|!WQR5f384J(;7C{-esr30@6ze@6>i2JgI<-*u;`3d#{-%`?P82VD -z*t)tO%y=)MtZ@j0cxit01r)NQz%n&Tnqnf=8}EW^1ijG|T?;lqv}mo@_nBt%9P&wf -zVO0oM>a^FRn?{@w|?)FNYbFr9$%!}<62SssSE-tyIo;xHT -zODyWZLrJu6Wf< -ztII0q34C!4iU>nbSU;H-l)}_wW*Sj5IOqPj{Fo4|QOeHYdoE<%6y$39I|L(fS;?k_ -zC;J2U%ZN*2C7-zr)>E75LplV0vrNfGU3S>dI~cU>44lVg4F}r6$fhR9QBFf5eKtds -z`Y3gC!^pHSywCOSvEc?7y+nsIEG842maPV?V?p#)MT@}cu}E9O1EYpH>a78cn&5C< -zD}N+i(`WAR9 -zm!MAV=~E~K6a(i*{i$ZLUUvTw-VI -zgvwqWzW=0igBjBqozgxuov`zl@jO&DP@2n+ZaHZ7UpW}fKl8?)IqJ}D+h-#S81G+_ -z6c-bE-z>Q*M#YOHuM~5R<^*T!HjBm+U76g!5_{AcFF2VjALi1Kr2$hs0iJv>bkrm4 -zNpV4}q5g#%As3M!{@JQ3x4_p)dryG#0(^i=f*ys(JDi4Ep~jb}eQZ5v*K`p{vJObu -zOz#1bzPW4aPJK0d-4^LoPYDY?*)B&rkhhIIsv(ewMCPx@BQnlVq$b(Us?&9!zPG6d -zb}8BZ4~4G?!wDTY1F`(;#Kfc5Np5>BtdY!GaUlcSX%z$-qU99OAx480j@l5nrgGE< -zT5DnOxOn1pLCdlRQkSQQE~N$-R%WOfBcXh>xkSt7hqIV!|Ky&p5zmuEo#=g} -zHS94zbq2o$bD1H?i6MwR -zLt%P0R$NsDaF;Ga^amJ54qJ^(rE&|^*N#JLuVM-OEHhTY9EKH)VGfqA5eHH7a5<^m -zyQTu|kyx234jwG}8(0fkw4GAHPG)7*P`5fS9Rx-kFi1Ovo?bd9r<_%S7FOoPa3k!S -zl5@JHWkdkClk<&|l;6kiLvwrkAQ`WPeLKDEhR3^cbUgVZOKX;moG?36ro@Sh;wGHH -zgG;Aj;r}4#}W1@vrH={d&q-{=M1D`&X0^U&Rl8g4^E?x@7?p3kKB&Iy! -zeymbtrJG_ACwth}Z*`x04tNo%7<|O;&QfHPTv;=f@c2DGmyL#PdW6w)vqtr>4mqr{ -zm~5j@9G0X&%LHB&jyJdz%oG|HYM#5_9=FDyH_`DY0~?GXqD;ol=t&EJ->4EiZ^pN9 -zp6$JSHS;;U@kdolA9&7HyisSe(oE(y4D%xgPF1`?95i*SqKjGB6l&78yD$MlfWi^w> -z{Rl@Or6ermn!>Izp8_uo<;q5>E6vJBZctU=7c6tm6*z*ynfqWiWdqZ5W)&hA^>9Ws -zMo%01QpJ^LkRzIwdfzWZCFRBotK5MI)mfujB|R5ye3WE>95ct0c1Y{Z8+RfoMcAn* -z2ZyFg*nQK5u9-vVG_5t_tohBJt5J@gZqtHug(=ix{x-~Cq8E*$hDoIA&VI5G;A-WnID)@1$o3U=6_{-|fS}$>z^-}fBc7+nrjv+|fOyy@3?H@z@VRlT$ -z;c4715AqxErYVn)k`Y+y9M5!%aZb({g#vvTlKSy;lt7VDsk?=6|FFQxid3u7Izyx1 -zDt*anxOp2V{m70J`xDKfCrG~BA~~CUQN)+6@EhW%)rmqxgdH|?=*=^t(zgBIj~fol -z{PZaD)1howSdfwTQ7P=M=`78mp7IYBM`&DZwIUK*+Zaj0U7y3T6u*RKe(ueFf`E)% -z&`{W^b*GHG4J{TV~SM*QRh|JFoPa-1lhbzaK4HIF4YjgZ;X_mkB*4-d;?d -z0o0uFqX9Rq>~kMP_VUv#t*g;bt3T>ECATFZW*fuXs&1aE5BVF5y$HPX%k*XZa8OLP -z9$+dY4ATv7B``m`nK6cwwkPY>ipmqQJwR_nUf}!An9}@P?H -zWpmskKJJr|TYYby6xefH{Fsb&V)Rs^+G3jT`wM}ovhWcb2B+Ydg}{J)wW)xxS)WMw -zf=q1)j*|H$6IA$%evh@&E!-6{O_qtq9hiWF!AeZAXz47Wm -zBpt}D6WxWHwUd>=o52ayOI+CJI_{_sab9~1vLTRGrEt^qH$e}r4y~|8lX@!V2qMAE -zT*j9>9mYm0u+axAz9mhcIT9lmOVaeGk^7(h;S=UrciD(UB5&H}fwzK$U%O)@4Kt#Z -zZ%{cGCz4?^acR2kSBi8rn%D=!Rbcf}DunQ`0=eG=@v-^178$fIz~;%ZFp6cJwWPNX -zD%+bmI&$*mwH#~wicuShVyiw6jcOwED6o~rrA|UyB=pZUR@EG&6q$AIMKtXH -zFw~_t%5ZzDa;vCWYGDOa&A -z_FRVX78;ppJzr|j0e&o6W(!I532>j4-2sWWxy02UQ?$Rmi3!X9o}BNpnP`HerWm%w -z^hz8yB*=l3MvD?shf$4ISP;wu4t_Qv;MaG@P*zQwctQxr7Gxmw`}%Ab|K7Rr<_)aQ -zy$kNeb_DlB7r3)9U|B38X$xg}$LI3Ki)D{Q-S67jQ-*$4SNFTX+gGV1`>B!vj0ktU -zDJ!Do1LoNguDi;<_|b+|Ysoa+=z!9!_XJ^mWS$OXb2?jVXs0D1_S>`VD8bi()4>@0 -zGQV=c#N-}li#5WbVC9v5Fa+5gt5q54U7}<-1Rg(PGG}Y?Nj2s^XncO3=nPR%VVg_x -zIg_4e)V>WdquRhwfa|DHw)9TiPrhC(0M%^?#3>a}&qBt;oC2#JBa|%?q>S8(6154f -z#^2ykGoSg1e9xV``+|vhssW_a3Ywl+euIKlc6-9i#$oZJU#RGjfu6>3G3lZdkaABb -z*OIwHA;34veB!ZQ!a5|(UA=e7kH2O&08~sj-7k=?0aHQJO-`Z){pc9hW>k^QpiS5^ -zje*~Q6;-Ht5dI3s>)i?%Y&{|J7{lnxnKMpL?FH<8h4_Sy?04rZ9ibjEypZ#=6x;)@ -zLlOX%39@tP!fi6B_9);{8irv}`oR5ginfpYQc{$h6yY;x*bubG8v#>nN$-SVipyTy -zq&ZQCU>SoJ?d^&xf^78hqojkx8a;sz9WY8hFcC`sK&|Qdp(Mx^CU;k1eBY0^ci$Vd -z^kaaIN>N*M0mE>HXfZoX!I7N$pTE -zTEsiDeB(%>mRQ$$9mYLCQI4%jjcDK6Omfa24S&0aiQa487P#{;V+uJJF$%a`~hMk5yUFi<_bdLVKO)_Dh>$(?Gk*FTw$I(IZe1r5!2`XH696}G-{gCPDqd$Rj -zp1BbI@l7!kwl~Mbt`Y@lJOfOQu~txqH&d1(Ru?n0?x>z6x=cwK)v8}Jjm4r6Za6e@Uqz!4O!7OiN3f-BRcRmGHvGtq7JEv -zwQc?V`PKD1I(;C_S#l<3P) -zQK@fq#Y3z;&n0Aq=XsSyG$qd@EE^A9t8fhCim8dh;YpPk63(fhLaAdWW9@xTR7z-`(Oxc -zgz4gD;vnyXV2}vpkP&MqqA=0dOf6eikvY4T+#!DL;y0v{xHcP8ejf+yLQG8~)VC!t&+qP}n -zPRDk#^UgWfT;JNeYS;P=chy~EjJl5Nqyy$lT9hpBWdXB$96}ou0S1 -z!V9heA%p=vAQ;eId1SG8U9rqM3x>&-+< -zk<*WP&B%Fif!`NH?jKh}R;|vPjcHgJ6#HYw@O>(XGjv+=#fyn*K9HP>X*UPatPg@F -zA>}nsb*N8(SYmkoOEs^X9pU3&Ve|QsXBb^Eu!Ar>qfi(fq$`1S#dKhHZ3vWwTSSJI -zBx>wr1QoW%gc+Me8A|20wp(1hZrq{zTbokco{Mx&4hJ5{n73u&~fc4PMe;j4x7$i -z?1@Kfvfy!nTI`X}2E_(jT(9) -z&7RfcEFzjciv*uIuWp>&8y;M{ZrZ(Z+wUJjg%=79?lssQXj*r2r<6;D#8#8_6%6q2 -z&{It(bG&r8Xn05cHtY)(G3N0exPQ~nlyr>J+ui*I84&L@{-`*S&TtLOo_(nJEImEr -zvSk;=DPzr&Lc=uB;{t1i)B%$NFi -zaLyCveFMcO$X)@lzaZQ;)h)Qy$t1)G1MZY3X~%EbHU;wBSnAb-86O%)!WFY?gi0{A -z*LH~`?Q$mY0A-UVsi1+>LLf_cu^18YAWUmaHputq4Kj>6U_&ZPidNPo&f-*7LKvl? -zl5|6nc$*;UXpWhvT@4mQaL?(8)6D&CSYai -z+_;>K+8@(fyCWlYT)!}dnL_T-F=SrX=bpa)A&DPz^Mu*1bv^tRqtA+izqBL^mi8@= -zp?mbCHTBt=TRZ6?o3aI}pmnZ{&YbXAlI_}1P$9opZVIl>H2#Fiz$`PRyh+6!H>v@l -zR1Jluq)Mt&RNxi13*j|HEfIV5RX655%?tpI6I`#%!r**6HJUTBuArRueMZSgGi$_B -zRum|Qm^e5tyCL?pB7`X#Z9VPvQ*`r_6{8Xb0Zi1Vf4Sm#m)+xXI+Gk1a`a9e_{+mx -zu^LuXW%A?^>kt}tGnvF!N9Um3oLq5HCILBJGxm!awv(1vi4`pcKrH9p>>~UoO~Ufv -zra5{aRwD -zWfY?4bm98F(BfHN6IU#|hc#Ld5CESM1HS(Z_Kb#4vP1IVlpqrFu2wV031`!`W4!-^ -zCCHCC1x-wxk3dX&avX2HF3~4CP>C55idC$fiKP4v3VLYR8q!pa-}b;0*iIzK6G-Tf -z78S(dT2}{*bS@6b%_5cn{~nna`B%o>FZe%{8=@z}Aow?l2BhGQI~ZM$iO42Y?YSNv -z63^X8@N&XWh6vouEdAW#X4Bif8|R~ -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 -zKhX9MRbF}7x0uPoqdDGx81fZOau+wWvD9P -zP+WFNUee#;U1~ntgin8f?_M{nEgSWEaYe&eHDQ-en@mIg@rIgS*#=fx -zOvEvbRnrwEY|Iy{u#mv!y&z~wSkpjGFzf(XCsXaa3_79cib`34fwX6+L+nfbpKq~< -zfW;}rFm;}%61K6qu$a%Q-eW*(N>$D?5{13{I0JZ!OOF*Z9^^u%sVKxRCn1Edp9rX8 -zW^s!5Wn~R~dEX2bEnC!E=s&~;QlXB(QG^+eeQ^WXg)Rg!`r*i}R3Kh!i7t?jM=tR4 -z_b%#u!bK*X3BRCGs}pLg*r9IYsJxBTnJs~W$a)FeO^wU^9r(78P+s2Ja7^%w0pq;+ -z(TR?Pkns{>sc(}?p8nitB`9663&h97&^h@FBb4!2YpQE)$tsBdJ8s$cJX`NdJ -z?b(=F$f6ms{BXQViQN8L931Sm&kbH9zK$R6V5U&YKil9V5LK=w=n)koz* -z0qwb?JRG54VJ>G|9HjV9a8}c|9-L` -z8gjPCdtYHz-PCC`f4o_M{83^iR+R{o0*~wS<2CoGIgGuY#ST~Kg{<(8NB4!xlgNxu0#G9Uu_WbN6#SG~TQ6VVEBJC}X)FZq~h5izk1S -z@ficlL&g|?EOc+16E?{|DF6h` -zDJV8H^Iv0ukcU2vgKlAB)`EoU5qndpeD{5#)=1dJJygsB#4e1l%})kP#f-CGMq_7p -zF+FJuvZOQMmwikGok63zSzfNYmrlv1+feUie+zI*b}IYLaVzwH<07L#dk}Yij=R? -zM9dNp`4OF$%g?cvxB*Rmft$}41a(J#(?7M_4*r#RX}-vpl)jRR&SU?!C+&K8v<a{SXRsp2 -zwM!A2+bi6pE&cpuddtGjp#>$-I*UmkfP&?l@_SzwA&Tbx!o`XOQ;@;(JfHf$xxo$! -zzDvV|M_b1s@LfoB$q$6^!$w%@I4FbXk?v#MaSK0FRY8zD)R>){&Q+apen*W!3>rCS -z1?M5HsT5LO`xSFq$1pEBzWJLh1{Wo29Xk)><{I5WrYIdS^NVu6mo-=nfk^4q@KB&* -zIYEBg#18B=$#fppDhB4;-)^|0^XT_dqmReLKYziO5mL-1?OV1$*frYb#HdTk++Fgo -zoh!c~4R0WKZ2e(;D^^4eK4YDX92n*02y--;!MUE}f|Cr2gBqRW+{lmvb41i9GpR(( -z>rSxY`3~V#-_2h&_col27{P=XMZbg(K1dwKt6r7{lz!h;5@p@=D+=poaGnZ{|GvY9 -zxjp2-f4$1WNzy^DW+kt(M3ap_7o~<)yhLZaT8Ye|Ztfu16?9d{9l7_7YKq4##L|nf -zmoV4%?`S!ruUn+P%=a{MkvN70GW}9P(Cbje|F()h0$ze%%g5vnHzXvzaepL_J4c(# -zaHxURtVi({7QYb9^NS;SJjDOP13}0uwJV(gu$ORtFHA_=a}Y5CuO1H0q%XtmNSFAy|Forgpk=qo1Z)Vm|h!2 -z=JZ=9;&s?2E{(erSJ3$qtmZ;(;cLlBQs}%U=w0S~$|Vw%vv#tNNU!g%>&L*>lv -zD`9D{roGXj@NNu4`Zv1)wC$V%yT^^5=A*|7sBNYTV6>Na -zbv7S9L(7k!N2v_RUqH&iAfgY}8Y`2lSY09W^>c}Zf*!@*_r=ft7f(e#7^obrn8C4A -zt`2>g+7R9VX<#K#KHJ&df||>%!6xP`G5iS6aDVCGD={xp!}q_Pt5aP!U^tLI;p$u8 -zDACxHCy)HcwSPU?Yh1-du?yrt^yrWu7%I*|&TF;hD%nrG>9h0OFd*%Cz56UFU;b2B -zG0}up#$6q<(Kbv@NM9jt4Rc;%sWy;bk>D2JK}L5z>87g0142XjVJ{EN7ICXlh7nKn -z>gA4EkO&i_SVv8M^lsK2Gl7e2owp&L*W8I`+zgw#I*_aV+M0GkzIhSWM~thL3uw5m -zPMJ`z>nTfpaXR}u>IVfbuEorw4&d5=M35tCGtrn!4crBqb8)^e2*6*4P1tPa=NYXS -z72mf4XZuZ!hD*7U?$$NNJzD;v9Ws_a$ct`Q6#$H3Rv?f*9vW61w>_v`F>P9``Rqj> -z<=n{8CANO*>d2eRI%A;q;5^5brWOEO4hs&0x%yQwyZ= -z#;vi-BYe@W&QQkpWzrMJk!?Y_wjE@)`p2ygo6Q*TN_NxnC*cZr>x5_#^C8-6H?+|ERnGJn9McDhK9g+YEU?8wpU65?63MQ+_ZvezQbK -zun%@4THAf74!x|k#BY}9Wu9rI<M8>I#v>Y2~Izg!{<&>1qu{md?P8}X_E%MZ60Apf!|$ctS-~<^%eeo96)F%+Tw}k@7vlL7gF5E -zMTRvTyQ|x%E=tI%n&yj;OtS7e9rH-8VqvinGmUIN@FuG -zBL#BrEYutpriJw%h+&jRH!XB-H1Uc=H6OKch|-u{_)XU%8}AtpoN{yx$2fyH&V&Zh -z?Ae_MzxHI6u3HC9lFUG;2SUqFwmBmUUm`YH=#8|T^Y%mG+uSLpRnC#?$%z`Hj+?lh -z5!JK2&1t1sQJdtQOCpmsV2_46tUUzuErf);KUt%&_lz%il5tk$zxpvtq(cYVpgGDi -z08i%pIOeg}wDn9#<Yjs*9&gG$3YR1Z;#uti -zU?B<@JNuL)uqwC$;gLr_H=_5J7@{4rZTttpGgCfS=BI^R%J~B-Z&av{R-9NmhxYL^ -zHJw&JlcmxiDCgjIVAzO*{KHztwk(0B=8FUF%T2Nrn(Umm_-xsNri9U%l4rLeq04F0 -z1JdRlhYQLIiBHl)7v1-9_}NEAblzNVB+w9fcac@e(Pj3^te!mf+{OOl!kb__)v~dPyGyF|9yr`*=hS-B?tMLhzidK26 -zV!9CeUqA{ZpZapgT?L`{A5`UrLk_BJREPWHk+oEI+u0PbB2Bs2cNE*U=Z`=HcPK)X -z=K|XZBsk-g`MthbViBe&>F;lrC?PBS*IWX>R*%XFayRli`4FTaWk3 -z4%pBXCwNx_Q>pc{h=p0j(9(>$v!cSAEt9Rkr+bv>ohLv5mkMJ4-zE_ox}C`pbnAyxB1*vI -z8L4<_3$dd|25|iw%MER4Y{ArDO`XaClrS<41vYP8hlWbDWc=Mb^g$t;tf`ZZ -zrL%Xskxqsp(GdM8j}#*!6^yQV3m6$*`d0{CO;Ru6uqIf!h1j#$#f$+zu}|hhjrdGI -z1+Td@jv8_f|PSk&yUHoyn)v3a#;YmVFo!fes$-!)m6<5H@W -z!m!jD4&K7!lR6CTm+iQCj!c0!qkhYrg-!e*q#?M`Bc`o$m)u{Ej)f3XHIp2Rz2dz9_^|=MM -zw*K;chEXb;8{b%shb)97kooPrHv7GyJ$Q$flOkF5t%)#RTp%TUA=G&Hv*pmA4R{dF -z2{2#jd#TKUq%`}uS61C*e|@Cx6-SQIWc~VfiPQgbiFW_XC5|kh>i**r`5`$ZKrH|3 -z5+g4Zovq>trs|uXl+=(M|5RTL^IC{ya)oQ(PTVpXYHm{e(zj`i0ZX?>FuY^5Vr4EL -zPs?%R(cu%;Pgq0_F3vbOF-rf;3#?#@vaEm33LT~L?o0p|-q!Qt0#8PO1j&y--g7${ -z8q`oLAm+PxO(7E&qGP9fX?%;wg`~MAS~25LIvtJ#+(_ZEmWHrVfy3ntaxeccmMC2F -z^_$^eERmsv+34DpheX|v27AGwi=4w(u&BZlr>3;!u${D=sy#ZG{LhfE{}a1zL0=k@ -z*#&7`$$<$n8Wvum-yB>303Xo9XFg$qaeN{B!8{$v!;*1)>z{DJDeaprkLG1B&(=8( -zc;%IavlJ1fl>P}r8VGj^!0$mr&`@eRL826g@EdFh-R9)VK`r%D9vs4M{pgs_{No0V -z%UB|csBzbOk?FAFtG)X}JOD8z=;so{Wiu=K$u`jHnK1CFaaQvm`u4`)`T5~#pstk3 -zU+c_eetG+xzx)IW7(llxlIX|u5Zj9NxW`}euvFK$Jn;)*L|Tqmv~F4SxPGdgZAc@6 -zok>dZ{me#?>D-xO+GZ|mNI83cD&{y9gfthJSXd@P2BBr(OKHRccIuVNA0G^}z#|*q -zGCJPBW)CsMqrXNJdDNOx%n6qvP)R`;qruqo7_Dg)Mp%DQ*{s5;TXyRrjuje74|bxR -zu_LBN*w=N=IkMEx1)TG@@ePwkak1TYr~6aNulTB0#D1_)t_j^>fWHeV`nK`;bL2;U -zHx)-s3)PfWcTf?{zf^@e#36)H|M)PnpbP^bH&x9jLe#H-K##6s&}c3-$sf-cqv6y7 -zneF`7qe5EQ2f38}l(kJHU=;gC6Z^+;@%{%*#M1wN(Zn)giqt50`M=U!nBb2oo|87> -z+#Bu0bn+2m8_&AZ#s^Nd_A7Yp<39lLyq*!UHy~=Y)^tD>P|+(n_JSp|Fw_ZZNkg!r -z?CQ89h?=FO!PO`&_#C}@h^=wFYkZ -zNo&|rey5w%ZGQ$PP~Ha!QkV8Yif%8wq%edaW4aK(Wg@oQw@i$+kN>l&)>J+0d`Z;% -zJ2p4Z4jj)4p0F68umud<(u~Z7fR*|ovO3z%8qI4wdDBO_i?d&KPIny6G4BC__&ozi -zH_C7F>0aPI{2fqC=srvIFnxbJL%9ASvKh7hT4$}Zf}i`K*oGpZ5HRo^HQZ_5xpv;3 -zPu4VDl?K1>1!ru{=W8B~ZfvowkzZ|#RAS#CR)efw2L;{jUkotKW}XE|0J$O?G#oM^ -z^tGFgg3e32KC*p+j3{ZI5k#M2FnY$jzUIPf$_siZ{yy#D^jj;?xfbZy^9iNc`2$a! -z()7lmgXsUW_}?}W|GzfT`oA{OVBmk-M2P>`#0lVkY~qUIKQ^(20obUN@g#?Z{4d8b -zuS$QpB@`*iqn`&o>h9oP0x|J(o5S}@c#R-b1T7lDkM++8fa;<>$rl9I@ga6RW4ihW -zXXo0!bp7u)mTiYUy&0IQDiiWiFki{rzAs<%x=&XPISxvzIWP?Udz+EeAw#1U6U#(8 -zQjA63PIyj#frNCP2P`!n-@jlzUZ#O7SGr9NrY7as(%G%s-L^~y!c7g}t+t8X$2Ljc -z2t@$gk&px?<eQ2``^4~C5`*0D@<`1S&Y#J|FMY!aAGGcS(Wo)LZ}F=v6E=b2ew6# -zzqih;dQ2MXou)ui#WfP-F{;`QQURk%&_<}n)5`?*3C?l1t&p;#6AikH!R6k8MKoVTntY#A4YVJj -zvr-|?+9NHQ?j<@f2Cn?C%QAz-Ks?&nlnnnZ!YXw1;Hq}G@}oX8@Y -z2Cbn{LJhuFtJLmFGVyk+ga+R*QQ_EvA25$m-L#32qf8fNR^vAencItc4EmPj(M(@i -z;9bcXQ`R@~djAhh1o|&bBp3OvBRaj{gp7HNTAQ`pizhjneh)KowCSdniY%h}uS|vq -zWN{ntGH9H$;>Q40;FCG2`jHdxM}ZwiOJLixx$RuRsgB?Vd(xQ_1C=mx?MRHU#^W=A -zyjQ^dJox^S>JYTk$g}p;Is0mnN>vih+@|j$YLuekZgAb0<%mKwBF8Jx6CkGNttdu> -zOC^mA{1mnDBO`r?G`O|Jj|O%OnU>GNhq&rL#!g6g%m%M%klC(Nh738;F7J6efkD>q -z{W5DUBRiUFSXBgC$g@?lO(yUXZzV1-80uz(bhuvJSDt)e=6>J`?E(elT}jB%NGI?m -z9wN(#$8ojQ>n6%C1e3|#5H3%>(rPXydO^B*pHK-kw<|kR2bnm<*?YpJ52f0(a3r`6>58x9Jz{N`EwE -z(ty+Zgo4OsuKec>&sG{uQkDdW+rQX -zDVLr%-H`jzrx9XDyeaJ1bQI?{RPemObDb=Gx=s5{6Q67FVt!1g?;E4neHc@cNxtN? -z=-bB(_x}IbM45S{jBlGrh57%ri6=gVyX#|)BE+SdzaV#NflFA^TG)R=(tA=%?91G1 -zUFjbdr~Ltf&TmSixrw2D9w4NvDEb@^17N5kfIl_vUAhk)9ie>Rrnp|ZbR_YRZ<847 -z(K%D?*S7)qoQtZc_s7IN|0EU{Ok}F-|p>=QRS>xp8CwSS--cH~o^u^7| -z*7Jx2UMyCLA!8rvT>l^+s_-7a+)~+1q4`4s!@2U`c~PP+Ly{eCfL4ZG2d(uDgS=Uk -zrWO3LS3mm6at%84D8!4#=^FBb!nVqtxN*V_Y|@kw(q5|eK=0?REu=|6lwtj*7K_u< -zInyA%0-=oq#QPf|uXAwN)ei6g?RJN&ID(#`rD9asYGXEimwiX1{arLK%eF5jKozuQ -zR+O7`VC8pRS6ULeJo$JmlIsRQc_mE$fYg_P7L!$MTsN~q{E&uby++T!zeKnu%@}4rE-*DRs2j;t7U&6|>G3F+(7R -z5ZIJ7s6tCR-yqqf4W4jScmpGFDfW@rFYSR+n5g4zB -z#L)jm;wJl)fkHGfH)fK4#A8>|{}@GNdyAyNa+0HXV>xDXgu-%|Ym50(4mEd_Ya*)e -zrC!q4*gtg`%@b|Mqr93g^BzuKJs<`+)zmhee#e?ELk>UFPpp~x0Rv0}ZtGUnS6w|W -z#~fIjyLzP2Jh0E)q7*l)IGWiu@**l$4P`4Vuy;@?=b`?z>kmx2za1gc(i1Lhr3$`4 -zRmt`Y4kk$|GfuG}?;m@&bO2Uq5dp(@mstwC2TmxGDnO<};%n31dbeQvlu)@zVsSLP -zduX3;vCTyohK+udl%IzJH%dgIk!J%xr&lBlCVPK>pL{p#>{NOT-ZC-?f8 -z|86?7j^NGwvWX2DZ`MEA#K5lBwkT%9_6?7hU(9E%MF;-O4xub`!v`O7F;n@?;GY%c -zmP$NcRwcK>=?)N%iu5mi<|#wyfXX4P^{T-PWkEy;_w_rj?H2ziLHxdDbw-vQ+BwB; -zD&`-sBLxi)y0GNc3H-M4Ae%;0rZ0WsLLH3MjEybg -zsKY}uUn}M|pX6qgp|$&Z?9D9t6^be?UPCOz#J7kZI8 -zVBWNlxr80m?`}EoHc6|XKOa#OPN>1`sZTIhB^+56o^&a1fEQk1m}&R1oqn1% -zEeU`WpzH^r-)u#wJm}Y;1v^C}eaDRM1W@>cjWzvO#A?3aMjaQFORKW0ZEBW?z*%Oi -zMVA`GOC;BHfvbCS_7ffO2j+qLdloAELFZ1&Hk(wS6vV51;`Af;tYa4t0ialqs&T!~ -zi$JnAAytsDzR&x@GjHfr&g9igt!kD-GKo{hk+(CC#3^cQMp4%y-7ZM$T0KbYHYZz& -zQs^U0+c)v(^d&93zq82pcNlnm-Kn5j$;&0drids;B?&&UAn5W>3bES#0`X*DWRe_Q -zt+T&|GN{67rqQ#6u213Y?4KxuYL?k7afn<*f_y!8suFo4cz|L7{|PKchX*J+?0$j| -z3G8(c2f$D6ODPd$q5L%G6rQ3!n~`sB4)lq!=CjKWwT-FQ;jr!Y;lFu>38Of`gh%k) -zI58MhrK_EB9}`aRzU}=v4(r&1*G#tN^)>b1>6 -zF}+U7Syn{}Z=btW$KKtqt(n%#<;;o!`Nxd2?Lcx9_}BAGp~mIR$*IJ#EHoxT7?8Tf -zZu-rDb~c*%1$DA<`DP*uMM -zV*R~~2_wXzVB99H@HA1=P1p+5X_mK?F~xK6Z7FtO=O~RIQm~kjft8N2PU(-FgP#*J -z({9Y6d|S_U<1kKd5v%bEvvHoc(A)zBK})IDKVXgD>Izr1OYcduK -z#!K$!J^|QcO-LJ#FV!yJkSZ5FfpA8c(8i>l&jl*$2J@uIER@~k-*Qzs`=g9;S<79C4GCGY2%E}MBY}0dNoNc!JIQ!Ehi6tbcPWM}Y?a+niFs*&gC-2erxaqUrS%sCm{K()Gl!R(_cxo!&+hF9 -zR}fY1!aNhD|7{eP|EEzrBK|*(qBw>Zia#*QKSuF=g`?$Q7vKK(x>VP?f^Oa*(r}mf -zg!3v6RAl;Z;)-0pX1_)wJT$!h)g+NXs3Dd|)3&(lFX5B=Bc#>Wf1Mw#0d -z%k7oUR}tI0D#mbokB@Nn6(bp~#ccFp?g*28V -zq1Ei##`(j&0p1 -z3MqQG6~($8S)J2|z0)diDo`NTnsdsy_}H)tb2~Bm8^`wd64n4o8O>eNbQ75!Lr=L*r@KQC`q~_|%1*HCTF;6x^mrnLiPe)< -zYBrD}$GgApI>tW#_9bEPqMuIu!R?blgi|aYLv`hu9<(>`mh<9 -zwnk0Tdpq(3E+2W$WNn*=F_tg9w6)Za9J(x&C5a8W?G~8N7*WH?i_6xW|05K!{;!1M -zq0$^VWM{|&ma|xJ$E1?a&bBSd_B-8qB+cm!EIog>d_BIGCn6nDxUu{>_->h@rM>Uq -zFN;|4>xl2R;z?16=4iuPoW*!#Yce%HbXk<$wc20-uC2+(n0TMO%VX058D%!bJceZ5 -zO0WZbUXP&otio{}taM^F^_8t9C)69pj0}jo{xF#=rZsoy@q-hmNsNOw1?ML_BP(2R -zRBfsP50V1{W$g$A>!B@MA+5a;?ov>F98{TzfPBRyHpOmFW80PdI{iP{LsJ;6z2O&w -z7)iRvJE7md=N`f_^|f~}vIy~7J+6K3O@8$PF+a3~ -z#O%7MOBzwR5r3vrM((~gp?Q-v8uGkz5}o0dnj{cpuu1xxio=%d83ZK&u=*9$FHx+xQ{x}>{SI9XiJT(W@Ejm&>#{q#2q!9PIHHud@J -zb-ZCrPgDw^$1@{~C$XaXe-cIaQmw21K@|Hq|2I)oZq32|CW=l4|C1=t-2$3fUkUl%Dpl2oKQ`=7|EXhS(>^{Y{=VO8 -zC%va})QnA$;0LxAiT1V~Ic|T^X2HtJAv@$fNgYs0(DS>cU@C=X7AV@DoOOSdz~VM= -zia}NH%82ojj9i%5=e+iPueW?E6_r8^XPMrEC^-zxJ&uviRs0&(Nar`@o+Dm@pR?A{ -z1l*MTD2y2d+bW*IitF73UUo3;R(0-@RB9q5LBntVl-E?Dq$5Rseg)L9#U+7a0mPP; -zp>>ZoPaV6W`2%x}tNSdhAZ!~=FJD-NXGRrTThE>vx23dBGm2m@v13pA(+4K%@?GL# -z$Lak=DGS+3e?!E3w!5O*fIuDLhnS}dc>=Rj`Z{uG>P9A0I{Cb0pBMCcW&Q4Hc#l6Q -zSM`YAB?Wo0ui~!)YrjK#A%y5t*gCLmk~`#-+fXZsE68oY1%84thk2HYY22~UM1Xqx -z&Hrd?h=vUo4t}|QgCC?JT_=SKK0o4GOkO3g-s(Y`80c8bV#S4SoI+l3??x+m`1##d -zJT5g<7HJR%@wc**Jrmsk3W@rh+nMH9-lS&ThKaz~Q7EfVCJJi&u69IK)Bap5Rupw} -zs1lGW^{Bl>@(YYP0C(Qfw&T>MAO3|K@+ddQy8XnrZJO_VTd5r`@YXmHu9n{@P=l(k -zcR*e1LNtv{S~b;>3>U^M3;c{7LmU~0z_`QUhyZonDv3e=oRij;X}GL*oME~YLn()q -zZy(1g-G#;WbFYUx$O1W6Kmgs0o{t*8z$<*B-ZJKCZgf6Bu?LG`hCi^gBQ!&46kMqy -zAkW8ssENljn1p2fFQHz-XwIZ&2FI$E5 -zlY&2()h={(;|QV~cf|{$lf0u7qiWe{#&QT+Rt}#wEepn2X#0`UN8dClcf^F4Cb?H4 -zM~kc|hg5w`MjQ)mknA-@K8NlUvMBYizy*LL2C!;&*FHEeP9BYFt(4=%29$Ws2rB-K6f3O6ivNuiZ@!TtI=06zn))+t -zG1YNI$_Ak0jKsK%%~JWn<+R&>kmATcND;1fzgtigI1~UY1UUHm#A#ss9NBX}%NMav -zWDHsyToa!hxCytiqx1yv<&hANxwMr)hdV&oQ%#BRiCI{dCnjPt?3Fm??>O~Kk8Iw- -z^c&q#A>4lVAoOIVcd;B-5#&u$n8c+t`Xa9dy5)+F(e*fY_6Y$;72U!@GBG@XtZ5id -z43nvstxY*3W%Zw0_bpxD2PFN?S3~F9g_NRU`C2b -zfnaZdls#lGYvwUfy<=uUs;Y`eEc@SnVNMnv+n*pi?cSyQ4Xq3(?WaVsnIxY*iMvB{ -zA`9XD3t~&dow2G_QhQ!^eEJskP+#b^MC$>_9Ze!@KiGg -zmS0h`#zfrxM~_9}z)?_vDCQ&?TuV7^ -z$-=E-(A;uB?k_}^%bZ*F)1G2ACI>?mV|&pW(FsC6)K=ttRha9gF|DR27WVAR9AnvZ -z*tX711N)r88_q%~q{P)@DhQp+2hY|tLiA{Gp_MK--?{knOFEaH6`$JzJ77>bCnFfl -z`K6)znq`y{@zycf8Ho*_rvn_qtVnfF#~`r4-06*w9KUE-Q$37;G6twK -z2d!Rn?Oa{J06nBq6I|1uFDlN#(p}&n_%wO^6S8D8Z%l!A>UPs-pPvZB4@lwSScxnj -zAjfciy&I)+hPBwQ(&a{f{-}ig{W3mgBUwHc4WI!Vl2AV-z8P~AJafJH*@ZjHEP=VO -zfHAC_GaW43WA`0ijM?hT`mQ%>{N7H_#Y)ko*Z*)$7 -z0XmIHw5sQgTfsI&+r!8_;ZNpLx_H9}ZP$y`9)DON?nf^Nn7xW`hJiejxX@Wlxe@{~z -zhz*H~W*K3V3ePxdO$lBKtd(fxwS00fK~6i3!NaQ{gb0aS%9|Vd9`YYMXY}>>$!K-W -zwjg0Y|I+t4Q>AImPxASxsiRkX{c!T4u3?8!SSZ*?r+qSIL4cL#M6^DiBMq_oCUF8O -z>B0`!Wi{#O9=EylX$O#J&bA|c`??WT*c)7E2g-Mvjxw_KzFv1$Kh)SV?NNf#g$?tB!nEH -z9Jri9;C?hOEz$pcWa*P5q&ly87lUPiYB*ih$+qP}nwr$(ClZtJpf=<1A_dY%P -z^cnpV*0(k0oY!-69WTv^ -z6m%$c%ZII&d;jnHr!|!Q%8GIV%P<~WKEtJH&Ek0m92l}2`|A|e|@bq0)FV2?nn -zGJGA4U#i*TT#coZ!34SXPy_vyqB=Lb%JVq^aM}Od0?OJ-_v6lE>aiU0NU@%?ohr1O4nnuo|2dDJvo7&Y>rQ -zN39S7&u{f{a_Dxz=v{Qpg1=W>7Qb$&YQzO0(w`KL`IDuk)Ro6eRl+q6LTIzm6s4d{ -zn#iEL9JKjH()nJn~qk`I>+0zx5?$>G$pf$|)rHmGJW26_TF&r7V^- -z8taFN9OXX9ITxqv#>94Q9hQhm{A3o#1i3_yu~VtsL5{pRM3c~};o|XBpPx9;Jfv^R -zlR{u5$^B>r-u%*#B2iDslxL(TAXITt;6XUJ-ytEbZI8axA@$<1i_aX6(=@TY5U*mZ -zJ6VS$pBt#F&QyIk__wk4ph9~unQK;S2J+Z=zj!6k+R+<`Qv#XDq}H}R4@k>pTY3$_Xe -zqJfa9hsU!7sJH!iGq-GDzhVc(anv@MsU&7$4MEuMFc%O}LN2w%0-xc5VNgP!Jx3O3 -zJI^)c&~|qMQ$;o()kO -z%1@%B?;GCRiM?1v_UrM}3VF?Kcy;MFHbG*qAQ{gTBDsBsi$zS!fm3xAOgT&o8+vns -z0@|nHS`A5N8FuW{i}110N0FDj%eEVU-HNf6 -zQbqJcwU7qNgZiRIpzsY7f{D|!pwxCXpXlp}H2?alm0%BkxO<5q*>V$@Z*7=8ztULXRuc|Ay^wpH3p+HLo$59_ZfR!OGeAg -zY7t8ZBF4U37-IICyrcy12((j~MQ;WvBHZG3J|6blFJ{o_iiImPIwu}*(eeqM02e~) -z`6k!GIF%+kH>?7+jOQrdb6Yd@oKNcY%JPUXCCB#Yw2X@wVQO)H#HM;%;^Fm-*11|g -zbdr@%XE@JvM3~H=8@+9)rNh*-GS~Fz>PlIQs -zIGRw{#QFSmRMx3Ib^q#yvd7bacyx+?SaFWA?0?6Ka2nX_5H0^3R($$ztVsNW6_x)N -zR)lQ*AFOC(t@WEQuC->itBi&D2P=vKT8slF|HF#j|36q!=D)Dw<_}gJ$L2@!wcc -z{lBnc(bE43E3(_9L*uZcXx*MEvR`2LV*>#=$6<>^W4D?14YD%dD8n)YsgN!gVUlaS -zs8R}pmj+{TdHCX-P;W&NuCIeldn6Q2JGR$?wyR7YGkbQAy6`ga5XG<&`J;Q4A-jUL -z+d0`7-2H1Vo+&&-%)wV^Jfry6*gdy}(f*7J#P8y$b_A8inajtA;`VE^+k(w&$z=?G(0P8J*gk -zg(d~E!18g0OO;G%&182se;y8Bk6^iTUYA1XpAV1$50%td%KJk&^o_FVqx@U-`O6Fd -ztQ0@Jv7C<@YSvUVpE8JG+rQWf+3hEd6vbO+_SNAG3)J%^Zz6*-+PP>Fd1+6jwXlm$C_1%m<8 -z;8E1pxdv5H)(oZ{@NIN=M$Yh#M|~sK?!#M!&7(RGLVS?fBnLP=5e&XgGS8)rD()ka -zxbhR+^>!Ej6dIdla8&Sb)2l8w^5J=~JfY|LLF;#eY^J7??^KNL$tcay#!oBhXIr;z -zSXf;QhxIoO8eK(xOVIR;UMvYBe6pd&sc-KxpH<;?F>%n6j7(dr$P*DDVbtd|^brbv -zGfb!nUVyzK&?q2Dv#3qNYtsTm`o{F1Ww|KD^eG;uB%;DWhIchFo=i5J8o=40OE79* -z^ZPsB>P5*MX9}=fNT4lvgm~fgJJOmb+$Hm!{tM|WIFB)r-IDeg%)sh>Qo()|7ot&& -zzLvNxG7j^CzrTo{;wJHFG3U!nm$nI7O+`M@6xzQcM4xfiJZzpDYVfreq85+Okta%(pwhWoc6K!z; -zIyN%>Go6KW9AdG>!wO$9Wr>pOE>*&htYrK6H5>UO)%owdN>N=tP-mT3*B^4bSND(Y -z$bM~e84ppDG6FGljtpR9tG;gDY?#pp>8-7aDxcb#AHZXJHP5*H%PYQx##DiV^1bgy -zWX-^^yI*R5q@HzgbJP|Exgbv4xMBwx^ -zJ7(adsoH0*V#Ea|kKydf$4vd=9A1^nSEE4F2K3xzs2R^;n;bLKnVV8s+%2^9O%6Nn -z*%C@=PSX=^W{o|t;0gIs?7GFBGw7U@?%D>H&&N+A -z!%yNqphZ%pOpw)XDMBhfx%N@#-cPAXTIaJqOHMDnp&>t<^J9HI<+7z{AvbW-HJ0N_ -zrriCMb<`j5c<(l%iYPk!arWiBbxEe5f)ZO3h|7r#qip0%9YXupFR5Er86Qf2mIy^o -zk)e#ks;~##YPoh+LM$L2V4hS_3MSf)G5FSafDGy#SciPrN(=PKFc6Hr4YF_zGY)kh{Oud -zSh#95g~AStk(;!Q>ucO}-Syom0V?CMr-DS`1I%@-&@s&9R7J;e2cOZKFy`+5{b?vZ18sW6C+(bK#A7v}LK&5% -zBxB1IO@8$Koa$=^u&Fz+NxN9uqDb-`Uw8SrvCQgrk!CH-u8ncJ?NRK+U;)q?yY3h^r{CR#q&5D4n@T>CsQ5mF4ECc?sPDJ2ei02@q7}zhsVnA#ZFEY -zT+{Br29Q)RQ<;dd_}k5FWvXk5K3_1um-IW(L`>-m+KH=yUc|2tCz{Q+^W}UUyA6lb -z{iOOO&4S?eeebG@kjJE;0GBu@nT%Wy!OmsWe-|znYzE6mbw15v&+5f18pZmtQE!;ZF{G}$JJ?I$a{8Lgam*f0bQtSlfeg98MF;+s)9|#M|R0sf# -zZz(5=$Vn_FEUhRgfv -z(AZuZK4q1|5<+M35!7opIR4tC`vVluO{)y|Ti&qAz3 -zW(R2bf$M>arIg@eB)f@KZn@Na+^nGfw-m -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 -z@BI88vuJmunv$%>P40f1LJ@{$ -zhti1io2+?y(n?duSciZJcS5Tsf9e*S_VP)b#=WT@?Zk#v$G6HoLv>UZloWO}GQqu= -z>=g}(M?qw21mR$=a=;i`zOwM=H%AEX2~$256d3|yosytXN(u(V-LWIY9@BRhZAj9T -z0WT{zoz!S@&2&$Y*eQToo2Mdx)bG2D?J(3z(0fjHa&#Rv<9>pP+=D{g3+LTu?6#+a -zeD`Wr%zv13Hfw{MdN7#8D;(drr`KJnN+mmL2qiv>9Cd?mc>^_R+f-&E?kkA6dHC)~x^##S_BlfBH~}&Lgi|J$lxu8}`IO -zi&m52n@5s76xmN^2>!fIdc+@dslkFrQH`f2o;VWd$%Hl*l*M_*D!SpoGk&fIZr{YE -zYzg*QCOO)@$4f%Yad?eSZc|}$LxPq})OklRa3+x9?ov-8)6r(PlpGw~{{X~6$+k!| -z&x02^!85njzQ;{l%F>q{s(m}|l%|Cuw9VkQOsAEd{S*6I&wq}oiMy$ -zWKC(Sy6YQPC$0JhBIqJ{3ppq(KF>%ZYN^>+JcPDh;uw*umddlj^gR)BfMSNrUZ$gO -z_e4r0g~@>}@#+5SJn3>Juq$^03(Tx$1#He}bvftnqCKLs6X0em{-5LFG^R~bu~S6h -z!-zk{4{%uf3hPJ4%PGeTV-n)bjCoR{2F8Aw^9Y&jJk@}`@@^(qA@kdZV9 -zbK>qd`B$UN9Ae6TuD+Q4}I3dnQB8kXRT} -zaNKQ`ww&x0 -zVM5D4>8Q^p3GIn30TD<_N$8NP9vb=B?ofBF}nXSe7 -z2T9bQjT=#21fQ&x=Qvwxgs~1^(Yb+QNn`zt&r$Uxqgd&;LhqH#7CISDoCM$^MSd5i -z^%Ys^J-6oidaWYj&Z+{r#-#FwfeSntIDE!00JH*%a5k($Cw#5+WabLW6g?Hv@*Ecq -zpEo(wIuWcFrnd@a?Z8-vlekw&B`_=ZADt>Ny)QpCGj$+xxz>2Odo|}t(c9u3q;AE@ -zve$-8gIzBUdeX;b(GW2Gy#7d<0|haA|3nFwmwoForyGcH0c)5Fxc;?~j#!9hHi7yh -z9+k1EKj8lM;tE}T(v2Pp^KeVcXh$@WTt$KtLm%1^miS;H;}=f~q_>-}Rh2SltI|OQ -zEmGk@+^QH35)+} -z%@aE1Um}=@!{rbZV>{>Nn4S7KOKNboXf$-!4rL~eW?`nFLuV{R1j0(rxL4VT0mA%j -zsGPv36+w`bu83JlLFBbSoR^PN0tK;m=(XzhX%^9^cj=uh)EL~}$lGYvYL9_ag}9Xv -zZfFCuHw8kVCQGa1Wl>La23Su&k9BHN8tpg2#C0a50+k+`pIV@`OWtNr$=1wq1;80}9l-1>Ln?X(YI>3(yJ0W*{`{>)u-Lc;p# -z)6gi5$~ymi2ytHjo@;IAa|Fd!5tOr3!iZV?g`vkMqaBVj&vbcxsNLj#B(x-AEz*pF -zhJ3aXls_dGT&lc=m#6DZl8&)sOGR|}0X~|?7v<#!2WrK%fqk~7Em0u+*qH>c%sL!i -z#DbdTa{BiWB_G>(_@cMUEz{Ne=iS4F@0z8~arI$u$r%t5T5E@BpHmnLTFJ%@%O@{4 -z=&YVl{2agKzYt83thP%!XucZD(!_j-<44 -zABj@&K7a>cM5MNv|8+{;_KBZkVskpM!r72BZD$Ta;10RLe`RIaIkX%Zhmw1~w -z3^8D9IAm#uHoNkKK0)1`#S-Dzu2k-YUm=i -z=L9*jJEEXygYT*@Eytc+0i+5Ee#Cz?v(b3WSH8{>ZJ{*WIxSctHZ?R&KB~8|F6zh= -z_S~j5Kz{+Cgw>d3?g|iY>|!VBnwl2B${h*F-yme4TlR2^nnK0@;u_M+`^7Pg*ubV> -zlE>Q%S32(!u~-v_q_n72856w~2qoIY0NX8Iz{t-MGWw-5_T_PFVQB84Pc8>3N8R8M -zx*b+0(BKz?+1UMg$7A>U$|hx`Wl -z<}U30H$XIf7c%ag$cLXW+`-1lG#ebDfOpY&4%M4eX_D1si1(s+)&kD9+|p-j$*ds{ -zq^S80Aip>BPOc3w&_dp*oLRdI%M&CGvjLp5zP5gixb(>HHJlUY-*Dl5L)WC?I3$5k -z;*gj3T#%33Xt_+yTj9|Qyt48yLV@N2W+If7<`rennDqvwWN?G%%r_a$nJP{;MB=M!l&kw -zE8{I}Q3u+g@nXLUq6Wqu*402-EPJ^1geBI@vWTw*g8YMTo$ELY&A;qfA9e-oy4 -z2vQpQmEccFP0rV7c_7+tnFOYG&B`wqhL#$9iXfSRYF<}C-MA{SZGz2Kh8pgxMc8#d -zs~}ATemw`|;&m`VOylb=%~4h#*H0jDS)@HRO3DZ&u`o2l4dtC*5StkZzVQU3`L+AK -zkp_}w=gk>feo$zO3@wM1YrAf|zlr{Df6#=*>`wNj&6s3QY8i00u*$NV5C`R87 -zUyeQ_+@nh3AR%8`qv=DE>DTh{Im3E5Z9cK=)X_22x-RuYuht)ORHboF)B>6}W#jP5 -z6+vqeQm6<_dCUdZVOg)n;oJvpC|$&2Oc=+tb;3fw+@5>vln(YmlfZT{s`SavlO)N0 -zP>=|L3v^!oO_gIn7Odt?eksdYIE$esch+91(fXy6WERLH?=lI(`hQ;09!y&$XgmRW -zTpcm6lKu0ilGqOXJ2e}0{EZNMyMoxPq(dKqiQ4=35_o?x&3m2+k>4&wI*DZ -zGO4(b>dKT}DG8}xBz~{4^M)h9ruRLf*5=6k;97Q_juE)vonJsPVtoo$vU2rrtip{R -zX^14MwPB}md^53-SugSZ)@1Wt%+W&>|EVP|0`{4>hqGiQC&iAWNgG^^i*O#5aCL!0 -zDcT*y_2QJ2B&o0oLlcV0W=<7%|I`h;z{Ft;PlW69!IgF3$yn}stwV6MuKtjEOj*9< -z^Z=~gim)vQ>Xu8QSER6ZRKvrb%BM!8YmS%oz8it)M8r6|0c}X%>q=wf1z+ER-l=41 -zy)tWEBd$mj7$R~$HkEYmV>a-d;aQcRt2C#&Jt}j50!bZV_IKsY`kZ!;2io!&@};)T -zIO3*(D=(3YGt%#uW~ZWG-@^|M|2@_^Go?=rJYp+3qJrRPRt8z1#(-O1_9ebR1W$8m%5i6F-thdrZzcuaCXdpm3%gm? -zeM7jod)_JrIhhu8JBkwK0B!V@FZX6+LY_n-5FC~K>q~%Kin?hxPR$%edRAD4DjXI*Tbt+?I4cAzj7jBN%O(7*zp4E=3X9N7S=7}p}1n=rGjRR_V+`p7p&dA -zLaD#HNQgmhHnx3A#ya)wdjs -zS)mDwT7jFD?XPP$I|W{GDZ6ZL{J_tY&Yt>%!!5gS{I_GMtrTvkK>9Eh99ZM|dD+}3 -zwhR@&=qv@}7`x@{ -zKd9z2rPLnnF9+|z!}bM75wFct`OGsc!M1@TWPgQ>K*Yi$8M*}dSR3seS?ayicvqHw -zWgEw4>eNkzlz<}_b-|a7G#ZJx3gVdbuxd*yIgTpcv4GYDmRQY0qI&l3rxjC}$cNan -zMq$h5fdhr-3H$Cxn80dp9GTeTzY4-(yv7Dz*ckfyeH^HUh&34qWT2IW? -zr(cB>1l~)-q{{42%yRWk*LDl5>(~mPI&PUn#$yy~FI*fEUUq_fHdf!l1Z|K0x+x4mu -zx9u?PWod2xt0g*2Bt!*idQ!Iroax*Pj%4o_ti-mdgYSI9S2gf*vEj-&ww_Dk1f#JT -zRA&UXX+wg2S%Hi4v1pjEo;IpBW?LaxzJRb+39 -zYo!Gpr#m#j{imqN`t1;;)mF#nv*}O|Ge>kBX;is7!4;qih%rY#j>>Ji%sYt0JkrB9_1dKgF -z#&{|+>;91ugQ3+yq?Fu;6%GU_oSxnNu|bK6_gp5bu1vmo!J{TOoena4%bIdqrutwM -zNYGAz69g)v%pd-9B1@u&Cje!Lh^LHxSc(nZ_)({D#sP^pzZ4rXo4#{@ASR14 -zp-|(OwuUI5HZb~0m5~#B2;dUdy(IZg+mXw)*XWxL_%K->JG}{ZBrOXSO7Mna!BvQr -zm9&{%b7uLPNM_7hiX2QE&lx|BhS!o?&8}}C^$4E2YV8zHRn3&mA>UzJK`MYE7bVBs -zDS4I^8uHYr8bZ#oUiVpmW|E<13t~lg@c5h{*toySW(!tdvd_fu0g?YhoOl*OgQDX> -z?>Fg=1r2y9g_0h9mQ5W)SXjkd93<=5crf*t6q-gS0IAm-=eEo^0xA$}Mcv1bKTJ)* -z%*@bh+w2&S5UFMgNFDkMJY*Q~#a)vW;V^D815c8>+UnCC>p!34vEdevjLm#dy?*$M -znq9Tuaq(pk{S=v3iHJA$%vuB**{Bo`G*R5;=spW08Xd1Y#%N?h3#7;=$L{PC{rZ8# -zTVGE-eQ3wEDVJs&xp#A7cl=ZcS!|K{ulewk&0!#{lod^;^}*MpoOaa8l5XOz>T|hK -zc)=dad^?2Yh}01Tbl9wiU_%UJilzbP#`_LrRYeILxLm96F9s^sr@o~UoB6`9CF1aW -zX*i!AFi3m|`cx_g_>q?_pK9MxGr?$8E#Os{klnZgz@WM4LGrI065;)R25j5ht@T}h -zf4@(WZqU*L0>Gqt$Xt$tJj4{?ATe=vxiE+OV_4Uv#lZE8UnBV7pa~Ru9;wPX?ly_D -zk7--pxl)rGga?(b&#hP*Pzd5G443vv+tVfXvV%I6OcO5~&M_~SnbJtoU)A*6AJL2q -zS{)zlMO{jLgnl-Mh(?p4+id{VK@p7 -zW&&6Qg*jtXn*JX6bFg>dk_gu9Iig8LktibV3$nMoTL5~^9rm3;|!fUeiLh{n~+Mo_SVkc0m|C~swlGpz9umCpF}UY31wNmnUWYW -z7X&QPjO6C;s)5ZI%iOz*{rTQ13aL|~krqD+QR3{ED(M)-AD9*k -z*Y)4siXK|TAn7tah4jqY}wbia{z8nDdkp=(oRz!pj5DZa*{) -zn-`YcNq(OCcw)uG0z%%POUFx!^6@Des-)Btsu|I)Ov}TXLZgktj>t8?dO|}>3LKJX -zy}ZQ1hPcQNsk@OK?e|t3yh>5NG}Sn!P{VG@ii^I|DN?!H!b_Ab`sa7u$`LUjs$MHW -zw;1O}F(o=1+k7#wk@dres@?1R&jnPGb5fYl -zdTQkF!_AH$fF~u^EULO~6SC53LGX_Oi2b&~NB!Ox_2DfZqb7um1EN_Zki~>%7?Ie+ -z{@$?B`Q=|zpwZ16rvX}+c?5(bJAS4DvlD-S!%Yv1dW#W;QKY{kCrtDk0{MVW#LNNJ -zs-k1_xaL+8PmO^S3_9Q`@;p`=@HBy{6T0f^vo-%D({3fbtYt^8vlg&`!(ce=pSXLq@^NEixZ2+Xo`X -z>h^wrEaHuRO%-8B+U|rg>O~XEW=y@C;!!=b{zmRpo8Q{(TVC9I_b4|5F?vnWE38b| -z!`}-B;iy6nL19dUZPi@oXl9Y&Buf|#Hg`N(#06M+asi;_r-4g2^8)F}$Lea?erX_` -zR&G7bBEeXD;9JEsN4R3}#KVKvO3+sY>+&vn$BKYb_RnY5b7E{LsS#bNlS -zd!fX;Vb&q2MXo;W%ZZt%ZPfA1YwIeqR4GoxqTy#G+_?5qwW -zxZ205iil%1x_mFYJYENej!SE*8o^1@e8uA0!8vP}9T3LROYH~eTUDaA_h -z!E7x!mV~e$UcA^&Fb5nBhP+mu5g4eWd4f>5q3fqnq4BrgZafgS?W#c~Y9CzFBMPoC -zv#$rZJ%ep3`3yAyZst!PP}vNtGbrtR1MG~229&|h5V^FhDllGZ@Ve}iVcszC(NnQF -z5P#lE^KiUkElVWzG^VR|{K5~^rVWkQ5h*qMI_}$Q+Lfy04R3#nzwN$rv|gp(S!o|$ -z_{{?zJm*G&dY`{Os-MrH)>J -za4j1w1)W3L*n39)9uy7W>i`I8Ny5rxJjNVvWXmPVb?S{$t529jG#@RyzBPkTEO4QG -z=(>f6Jk)#-nHLSuiT`U@zC$)5Df1BCy%``kwMB7qgPEX4(ry@Gx25{{<~T5qyO%1}aMXO!o8Wdw_T?nP7tLrhcr-FM;T0OFT4 -zZawYH#n8LvpN-JzK){%$!oM2L56kGN**n$}P+*YNH;vFwBJ=yT|_ -z{HrA4WCFO#C!u{hg;|D?z7Z_xYU?5x64tu5&Q;!Gg!PrL<)vHHX5$y -zT~9gcLoDF^auAtJw@vi@VfR4v?1bq9rr+g2Ba!2m9w+78Cmk$YVIflrv5;Yq-;L-Q -zwuw&njjPgQgs&U&TueFZmJn)yt}EiP6{m4kw -zVx(DK{zVbhq`ru~AL%6lh9Lj^L=k1ShrbX@9ob99G4t*))%si6VcdZ439anM6rP+a -z9DW(KF6FR;&hvtuN4*81jl$!I!HdTe#J3Fl8j&RTszP+wwaAO4^=s9`nvfdDAKoe3 -z@#VsfS?lo%vOFSE&;oGh=sjBY*{FBq?i8nuAt>kt3jIFmr$>LnY8S032Iie<5TR#R -z=g#cI2v_?j7dfh(^Kj}oO -zH@7j`>cB}t;ES1bA3^9{Q=mWSV9*TCDT{%nz1P8%U`^afhikPF-lON`d|Wa0^-eU96A -z=4CFF@}FDx6X^x$-w0jVfUkn!kW8Z2kI_ZUKTSj~js7faUiEGYQ7Po+OA>4RI%GA# -zUd((>R0a;vra94i?JJD$COkXj!l2%N<-~_#v7a)R289;+iOa-rG;6Mne!UU2%6HYO -z@UHRZeEX1zTVfRQ89?ysM?LELPZJTC3c|IpF$bf^G-9i++|->tF=EK8e|p+aO+uH) -zoy -zecTaK+=@#9hhNOaZ(H{F!s(2C_xL7Vs=9N;ZYN6IGLkJ~S0eU{@&s0Hw|-JKfW|HAoY{_G -zNb*xcp{j0gQDOeYitIy|4{_Dw!(-1TfUa_U7Ys;W%tu4{1~7`Ccg}sO&a(4W(U!PL -z<&Sfkkw^05aZDurt!^E`n~al_HRuKQ00G3# -z>?f@OL%k1v(4lF<&n=pw{5(6mhXG7AUqv}* -zT+e>)I*_R&hNCSKmGng@_Ni{`Pd71b6QMROUPNRzC=LR!%sd7gt0*_sFS<4HrzM1g -zr -z)-5;4`6ObFdfG(ps~N7kP*~FY%4wu<*k0O(vmTqFI%w5CkJga-ri -zM49}&f|z8=872Q#6&oRU?b4#x(uPQ;zSYKlw0SeUd>A&M2l*~6i3%}yE?F)$f{}8Z -z)<=|V3)-S$Q(@2lmctyMzkOyh^f`|ZJ91*1VGkvb -zlgwzQ(tflp_gEtDskZma1e+HVIzpMoptSjp4lWgoLnF@zTaX5rrfq@}ON>sSh2~Gr -z-3h?Xw)?GNX(*l*RwDA(%c8$6KIIl$_)+qRR&Xk**9ZQHhOJ85h+X>7K!ZQIGZ+WWcJ^R9Q>*7yC2d7g8Q -zW9&ln{MtsQEdGiX5zuhAcvbOEz20J5PvN)Ul2GssGH0WQhV4E}Hn9&eLMWUdW*x5k -z@KhuzNtzz)LZHvwYeeW0>?yTY0o|fvRrO8RTvj~S7|)e%r7tf~czri`ZJlmBt-&U5 -z>JzLwl6e!4{` -z4`=zR10uy*0vTinT3O#2@J8g)_*v6MeyXVlqK~Oj?kw4G8niqn%KHHGJ}5N-ra<}O -z#K^h{VKhi!vY=2ALjCCa)tTZ%Kc3@v)4h+Gg(P;^$`+n}7EB!{@Aj0h3l(s8=`adK -z?S0S1k?KkjenxghRak0>A+-L8h$i~Guw>}7mBK~rUhRDR_LhhFevv=LV)MK#Fx;QH&Y8XgQ%DOy9|s}6_-qYvh79T8iiXmp+2nhRL9GHeK8F5a-@4k;OE -z#5T-kz$c!Oi9xR7U4Z3fv1zsBbRRR75Jyt*!lyH;io26IF~wL@S5#{QXIv46zLC)NtuM0HcN)r#i}esnSP -zE46ax)r$PFW1T5K{krkSAkl;F$TM-CVf>f~Cl_@*?&36QA`BwuD5K9OA+RdORTu`^cqTL*kj1Tn;yfi2K -zxRPHd*w8$$?Xv^YUfts?HQ7tR!dGK$i~Ca@J4(p`1E?7WkquyY+Dcl=2DD`CKVPnY -z2+RdE-lw5>t%#zpQM$ADDJ -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 -z_gT5Z4>ID!=hFmB{Ql)iQgjS2>>y2 -zkqFHV03H7kBY_Wk!4=FD!(nv?LW1VT7Ho=1b}!>Z5OftH(l|}% -zQ)eez?U+t9X!spUZdWWOkbJWYVB(QJ_+Dn`IE>TUqH-fxgDj~-Y=PoE5OD_`Qkn&r -ztunq>s(XF-+4JaH>lI)|4rZpa{>O~0oL-m6*P?TD>|;^2;M6dZ;2<63N&SQavpY3` -zucWnzGT2dJ@B_j?NQqR^F1`Bep -z_&ScPN*jROyHjMcny0WsO*`P%S90qZ({-Js@!sw?F9yKWWLlqiz7RlEZ=HBjL`v5U -z-|0T_$;&JdUSgslRDDJ?kZ6bB{A4o2@uKPHF{-vE|6cxZY%_w5SG2bG-6i?T1mqru -z0yrPWusaBNjtrq@ohrUN%jl(VG&&iwpbOc5)kh~Rh0i=2=IzU?E -zbjGP@$!IGIZ5B6PYbLKf3|`V2saD^>R5+F*Zu3QcM2*UF4Fhc?w!R9MxJAylkt?!^ -zw+!xL`(vCg`F0(bY;-FcS&gMB3?u79tQub -zZ0hi~@)JZtQv-KvKX)cdQ;8(QG#+&?%{7>cB~-*^(wh-TQ2{vr#dQ(uXDcfz>}Vh@ -zkqcD3BR|E{NXvXABRJ1#$?`&84K}e_Mo(DTeypQ;Hx>|}fDAd{^d -z>3IEGun-nMdJi<(uOe@y7JF@0adU#3Pd?*HASblegsv4=E+C;}yjgwlI7Gv62KjN& -z`?NQ!EG>yobtnGGF)EPzKa8Y5Fo6~fX_k1TEyX{KBs`KyhjFX#(TZ-WbNOFJa?5R0 -z=nyl6gcZTA8W+2&_S|nlkbOTP;j=a1lJwydWEFP -z#5pXhzrt@cokIy)FWMXe%xPo;C5GXvz8y{{@})7z!nmK0_E20sDUMMy>CGse0xR9mb?X8NxZ!?(UU8h_HO1-_9wvuPkx{? -z?C=&cDMicPFDToRh?}wo?wu2n=FkgTbrZCE1HdfgNOL1^6lP9WvH&PM!75X_{=>eK -zZJWiO#r;>Yxffzl%x!HK$F3brl7mspt2R$%6}BJ5t=ibZceimbHpGw~1MHKKE}+El -zKfO|1yGIp@?36FMrK;|-vK~I;7cs!$Bh92-4MsgmeN3rK`AVg6b@H?#GUX{j1A~f> -zt0KIUjV`Vi0FM!m!G4=$6QTq`)R6;}H^z==(nOP`Bzp=-XoK%}{6>*vhvO=Jp@1GB -zR4g@o>=BX~ja@cYtm%M?U<8HwT=eRFASlN0`pz&Q3q8MUjkS0K+SHPzh?HI2TS9F{ -zLATTw$?BYekbX-Cf8Q{a>bq|PToDdN^=`!qKRa*dP{ -z+_O;R<(p0pTT@~Sdk}?&WW*a93P=K-raS}h4*1Vxul$G)+d)?k#Rs;lQn_o8;Zg_2 -zkMy1E1b6Jgok348elv5u!G%M%3G2;0!IKaFkBs%Y&aW{J5+@kHfZeTu#$u>1RK^2D -zamzZ)*CKLHb*kQ!88I0R=2V?zc^X*JgWBpcJ(*}EwY53W))u8d$$!bZ%xZ}%TatcW -zEGmBS?=+dH5|J%kduR<7lf6d=f9J5^*3CU{#A5mB{jhjyPcOD;vuq^}l;rz8F*f8iIvIq1KFpK0?ASPYcl&`qy+dV<6cu}Z+-_RVMnqP -zVm5Uj?loM{lgE;t)I8aG()icQf)n5?sk$Bt#si@{EJPDengpNP3kdOFDs8hMC -z#0V~k%4a-mTCy6`FLSj^mxL^@Y=)BJsK>(}5F3zSiA3@4oC4TsWD2EINr)L+?~@axPLvYWWt;SqIc0?0)syk4ipB!qP+p${T8Far5Gm8V($x{@Z(7G#a83c{K3m(^y#yDS1}u;E|Som_uB -zuZVBFdW}UOnOf{OUYO;vDI=NmA*oxD#xDz)Wi^;7*|HEOk0OTjkLZcSvm(-Qq1=uO -zqSxTN();XrMDKq8N@B8;XA(dznkc?`L*jKX9j+RqzBYJP8U+%=; -zMSc@wp>{Pj+cV4bY?Xu##T?cUCgv -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 -z#=7stG6E*&@_Njh&^f -z&-6>v7?^u^8H~D>YDe|!IqTtyqtQisq?l!e5gxZHQwjK*Mz>?4eR57$wVmft#bc@E -z!nh&f5H(bAm1WQYXPS;V3>3OYn9+FeM7O^c$_y$Oy+xn>Dqw7)cAO(9`S$DFK=m`E -z0qY-tGV{fq=m1aNFaf^eX1mH!V{eST3c+lFlv)e`P~J<*;3H^Pz7jOQqjdZT7EuyM -z;+rB2(s8WMBh*w*Eu5t&(FS`8Nemk^Q`@KD?etm;jQD=cR7Pj#09x7#TJBS^$3b?p -z<>v)W$ZL|bt`?i013CTM$Tip{sC2psQ(Aab>}PDfAszS%nWp1!qPCM~5jD4K^QeP9 -z?Okwt1vW%~R8wFl7)hOlhapAL+b|WJ7t`oms{on*2$ay}+G>9V%A{&#z{cx*`b8*B -zD4hbKOLZLG#?|O1Z=k0PtQnikN9H{)zAq@&cR_6ar#}_}f1x;3+aer?C?v}LN_>VW -zk);VZeS(gBP(R4CGa%l%>~SrWMFNl(lTcPMOpf#sl+2em@R{ -z2kg$V;k>w`#InCB`Cgwhr&!+qm!M?&Uj(JpUxG5}V?<})g#@u -zeH<58!3t(XaQBxE49SG$G8wI-7jcRNrc(g#K`Wfp53o&eRV7C@6JMRw` -z-;+n(ubx*}!b9>nv@B}CI!WnSy_rxUbs%{bcaZIFk4=3B6)wIgop*HJdQjY1blMz) -za~fY5(T;4c_+#Ia8FdDJ2dT(b2U& -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 -z`TVwFna>m&Era=j$y9)t~ik_lYR%b|cnu?Lh6 -zmGDcg5$*OsdrWi?;Odv)gfq`5rjy*KGBS`pBAn+`?bC@}nci}-eT^cB%AP-HGDpOZ -zOpu?jPwH>l@YoI5r2?t=%Kd|Y2%%%&fZ^Un4Gew{q>gI_gY@c81q!4%8pDF8asgqa -z^@9LQ04U$3TF)?>M+3LO6=v2T*qFEF@!I9*8F)*3Qu2oS|B>rj=-0m++iBJw;DNV33uLAzCl=+eWvXnmnEG1fk+#i;b -z5+Z%wtkv$^e`xXH;~;_xS<7mR9YYXbL@7{30Absay`s9?rQs^}~B8n%UOl?i-9uXPKu&Xy#n}p3M -zwO;h -zJ^?l~`aYr@`E{8W%x9zKWo2}G2Zy9;lx0Dr^I;aVcR|C51wAp*Z@yo|Q-RB}m5^nJ -zohSPJ27^RByoD&7(z$LP10yDdoQs}hfLw!@h7LennmpGv%ci{Ul1fGxWw{_>3 -z7L318dwP&hp_C9tiJ;$tfsayeMgCd@+5~IO$a1dV*G>EZS<0yO75+P~2nH%VQs~)(Kj!#FIa= -z(KzL;n+|aY(^$9cyBeNG&~|td -z&ZwRtCnXb;CmwcixFWje`dO06BBQe!xP~VmO2U^;b)Kvd)3qcN@qOZm!N(`<wo*))hujvre4D5ngNN&B_RryBR{ -zX#RYs=BESHnq)HPM83iWZdV2v#^``nQ$+Qa;6lvRD*d2+y0y13QdHW{8k@W)$ke~S -z`P{J!|3_@JH~AJbd*T72+W*7XKeh+5t_$0CY}>Z2j;)TJbc~K|+h)hM -zZQHhOCmrWWk1@wwbFTH??+>VNRa@1y)w!QX3F7I`gDBxC; -zXSM1s`tZ79U%~}y6QEIGQ-YO;h)V}4jdT-a1m+|%%9EdB_A0ehWqJ;Q1%3fc=}gWt -z5>MkdW0@GWqxzX_ZxXLDRJq_T5&uw|2x0IStjz1iJw+%nW(svq1}Ym0uoxi=mP1i# -zwD^eYKgI!|&}30u6Y>xK0gkG=s*s;@58qKIRvjM-lll;cJHCJ>B-<>v;L$9LwR`mem=1`uLG_*3LIlN>3cA=UnBrUDnKB` -z>rE_SSB)XMf1slVd)*9h+K}k)LJLEs<(;TWNPXMkHJVXPlJ=FU1nlV=LHuCp6+odH -zJiXCJ`!NB%3K=4iItUF);K|D;za;Peq2nFPD5O1hf2E+isHQb;7oX`&3ddeINftfi -zg=WBv9Jj2`-aLdBwE}ZQvF_aX1{Tj;8O}!QD}qeR~&Y+g&5Fnu2PP -z20)0{f+qnYNfWZqr&y}ALODD@T~VB;j$6mJ%Vt$>#i#!w6p7On*x%9(a9T#qM<7@G -zxcVcQ$(~0z?W=w&Hjz<8Mw|&H*mwSvu%0KO?Q-BPC9`V?@sJ$Z>l^Cqj?syMOp#XR -zU8flYv^EEd$MD&hd!0NnEK{~YQe?S~(nuMaApdFUc3;apD*wZDhZI&WEe -z!9#f%V=+}9773H`2A7Yzn-3h#XZRkY1qAyt8lTY;+w%@fiTf3Eu%Fdc!^g4-Y{~0j -zZZgYFJIshm6Aw{Mla{#OcTVuy=ahxiUkjv^Gc4;>SwxJZr!E7EGIsa5vTLT8H1N!8 -zBwZ%@cB4A<2XZscDOobx1$lw|Px&Qoc&)ZIU>KO=vTeM^5;K!hma<+~yatB7qyArd -zGRpA9Hm{ajQS3Wa?u0s^z|WgW#|xduYP-(f^3|2ka^^^U_drC5#b2kc+?lcay`b)= -zf7!|Lp7I~~HZ{!$XKrQUZPCFA?t;qxbR4<_eHs|=tKx?8#fwB#OlVSp^_T&q+$28U -z2_Z9NlWSQoCYjLc796mL;FK%&sX%;rrRWD5R&L3p_nw&;zayfoQ#DY;nGzMy@%TyM -zG~ybM5iq2{{8s8iw>YtMaVRsD`v|86s>ZB--~)dmY@W7n{@YzV5mdFE?J?)zzIzrY+{&hLl!--$fiJ -z<3wfx6W)`HGd1Z57>N<@XX@z7C6l-wM& -zD1i6SBkhVELZL>n%Nia2n!YhE-h4`D+|C2J@nJ)v3=d?u5kQ*KlZ8$BxK=;JlQG0e -ztURiz5T9EIIsc1Gax_>~VwXR~sO8uRX3s(qC8i_~LW{Ah;}oN5mXHKiA*T>=eCols -z#CeSQ$EP_Ksx?b=&Pb|Qtyyl&2f5b7Ss+H$ko)kbV{%ObbWYCEg0~eWt!7UjG#v9p -zKo7@<>xc2oa!9#|B=U%?Um?7*Enm2W9We7@3a=w`4dDM; -z%meOucH%3XtOh0d?901n8*Be6R8n`3bX3x;Eg5`klDniUR+@SAdus%XQG}zs0zg2WtGe%Nn!EkQMfnY_{Mi|#YB#e -z^{IX7bEz+P*<3+?h8lE%XvSCY&6h&m8wQgE&I|U``ZtedXJFC>3Im}Qf}+HWecGE^ -zI5c>2V#S)cd_S#U-E}JugAA|M_pV1yWb>SR-wcdA+C*D~O%W-)6z$%+r5m7-Qno`r -zyt%-TZVB#pxnNpoiq6mm7F`+v%USv-0&Uywc9iKJFoyG{R}LL`;P1bQJFLQ38QpUM -z6LF^*@s?Py*)tc2$jP+~5`$t`lM0q -zMimUE8O_J{Y$d&DM0HjWjw8`vO1lu^(8J@yL$aS`l#Mxiq4*g}l|AY$UYRAeluHxd -zkPS1E&CeszCr+o{%Xl~QHydQ{{zpiDpYHlkA!!%i5Dd+D%dszQU~SZFQl4l*g1W%l -z0n6#{osh=e0GY(?O95&C7=ux>)njOuw621f#wI`Ly<<4&S857vw*%!pwKc8-M@EOE -z5U=Dt){+HbRHLIL;=EV|tiW;nJo%%r7?5RiY-7KzW=~iIx~4#{rnJRRM-mLWV~$!# -zePuvwSEq0^d5Y5Pf&+^waz5BZx=(B5&eX~$3JVb&i7V3N!eW0qPUyR#eGV*V`nxFB -z-#9W%Dwe$!*q{O%F}iuO=1e?2ha33VXy9OpnWPt4V%8OvI!&-tYjWNRD9|G@N5wd@ -z;oD#>UBEK$^TZ{^>`OPC!?hwc9BFYoy6PdcAyO4e3M=YJGR+>8RAFXaabdwH%4fp{ -zVZhqv4G#){j5oTzU%JHrvBU<_I#R?Yb;P2?xSO$&M$Ceg@2`M`dI-|PK@JqwmI{!< -zg&wC)g@r+LxD{2b{DNObu7!TPDcc(AnQM$Kw~>Ac6|-Z^!IIaK!a|8yg1fIIpDEo6 -z$Hat(f8oeR0*<%`P-h{ZQ6Q!svBnCO07_2nG|Ts20rZ2vAVN&U5pH+7>F@WBDf;zR -zj$p!ii}AV#u>w(X>RUT1&FC-JAASI5=M+0;O_5}D+PcB8D>Y~|S=h24(#dQ*KAHAM -zAyxW^Biz^KL7jh@NKl|ZCNdiYg6sdci45aqgleQ^q-?Syint%NL&hyE241Tbxi5~O -z*sB$Izm55nb*%=JkWTyKA$=VGOHRNq>7JL^n&Lic4*7GT^Iqnbj*Pm1MO`c? -zBP@s{I)7nQ?1ar9uwSQ~#BIFfn7gab<^*-L?;QK(6=FE3f*3Hxob -zZPG}X|G}SX0Yr(Ez(-{|tX^Q-!>pAPh4aX15DU;Xxa|u~k<9He3~^mo3LgtM-b(gR -z%@X)83Mu)ALK47!QAix~L&i7#bbgo$j0^*E28ryNBbfwWwY=3{fQ&sh3=(L*a-067 -zxY(5*mf9WsBzuxrO5!}9BGkSrmQ{XxEcnBOUrTErurg}D>6bCb7Kc-zH4RV$%&+P$ -z?;=VHw6rBUa7EGFj_t{H;9v3N1pb6AVf+w)v>`+Pc96Z0-(UXU4zhXrv1t9;OZq4$ -zqWD25f|BTFBGmR-u%BBU7Ed*r>%^FSKZ#l@*6TyQi*}zN8l&hrn~Kn|8h4QVP$CM; -zGcOikP_nDV^b_9IQhqvnywMOnDc!{BXQBR!fn?jvLrY>YWFQhF2?lFA>r9;8IM3!P -zmFrQa?oC?2%CLc)NGyg(rgW5_#IF?>5nzB+H81RMdeFhe^;)DB9Nw&!E{37S9^U-g -zzI*#AqUM>qzBDA* -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 -z;=;w%>U0P)7|=hMalhdUGoB0$PB&Ma6oC -z5fsMwqRM@Ba#(z~+Fz70rAjcfLml-r{V;Nch%}Zg01at-SoCkoC>L&tL_S5j2Bccp -zVe0qH#QQJG*dYEz84)DbeXjpd#z=_@hakhior(33Y~yI?pL*y46$~$@@tQrQUke8j -z`su;vYhjArKS?yp`0&6DJs^ZjtR#B1`WS)r=$?M3 -zNK%7+FMO!!;DrW#^sco}i@muc?nU)8Y>AndXlKfp7S+~_rWbPvb&D`Q4L3lUChsLD -zV1Fr`joIwYoX(U8`>~algW0UZl|@JMS`a84Jr-;7^j3y`#9OPQzu=g53osFZY8Iav -zw~S?ox{IE%%Ad@I0{e9Tq+z{R-$5l_Q5K&s^rM}Su+NFw -zjKwg2@EkE^amN3N6f#`19rt$Gc9dk%hdV6sd!MBC2Sv*EWS6lB&=o1YmpEPzby~5LdZ?G -z?VQaAIdI#~2LexOX_C>M<~v=i@qvc>VTb?fkmWJ9y~LJ_-#f5oN;xl=JIAgyKy)QN -zzJ8gF$vj3aI6nU3Q#s`(5w#9&Q -zUdyvpU9^~ALyx%OAPDj0;*P9gga#U%-7)8_LHf*KCO1Qtse;zWfwW5DTBPt>2q2^~ -z`=W?~u&If+;Be)rzETvHaJ;T>zqP3na;&Ci)M{kwKyPDkIENol;P^d6tMl>shr`UO&a+6 -z-${eWeq0iRcXWUnzRASTlTjlz3e`Eph%f0jGDr@s4DAE)k_p?44{c^kcRLZLhS_cp -zc4+O{QX$OPaNXSGN^@nPN|0n#89qW9V9RNtPu0OoH(7HbASBTlpfxq|9G~FI_4iL| -zMr%H~CGZA&t`g)Ah>Uj;ivh|^Brla@svw&TUBJDSl)zPztU8k-+x_!qZ2Vv^P!lRX -zos9rwxidm-g|nEH#I}fIP{NgEJNGt%HB>=sX-?O8%C(0``S$s|##BElXhQ?%U!v#y -zmR#Q!icbt^i@}zAj;tYV-I~lJx#-+YRRW1rr#He=8tYF`OSCdeEM~Pj9&=?JnAZ_? -z??tda)7k@2;?)FA>uwYfB$p}$XPN`~enw7%-yDlPt;!7`DTy>)4=(OGFN{0wwd#;c -z12Z%Hs)$!w$-+2#6Puj2jv{d~$PDfpax|Q{2Sgg3EiHQ#a+~`=IJ-C6seL3}e8Q8s -zUabcSsw?sn7r{t>X7??hsx=gSXEYDUkH1KSFGnm-bHz(VP=!RSBsGDv5Dh#__|@R- -zlJ+{ig9h6CB{1tU2Q2D3a8LUYYsp+OTP7Xx)4CvB2}a#5$sZY7dU^eQkF11AZVShK -zw7~9XR2%Sg`68x+4R&~@)JFpL<$n^!slNy#8)SOhs9Jtk@5)^hzhb13$~$RfFF&w0 -z*lKswr4czF5ZGACAONV_1nDo1COgTuN97@j$H~i?dHNLA`c@}}$68cTpoO4PxU8JG -z;__yzOffexU#{3x7m7Y1`bsEH^;py1LQ|H?1X+|U&M_HiXltAI?sOwpNT*F9On@ZI -z53nD!XeO|j+4hlb?GnBji$Mf&)WMX+(qTY-mcThZ$^1r8Tv8I?SLrA -zQ@>|@2BWr-^0o5gV`7$W826WAi#b$As{KpMND!4B#eic-kw_7C9vE!;sn6+eoHrQ1 -zyXEpPPl@`%DtUZY9>`&Q{^qdTN8^ihWLk7uj6FFwx*9XC8}1)VNvS@wHF#!r8$5a= -z4x_U#IVd~IZnR{1i*JH!6o@&GScaMja17SYC_V5sq)~cNwOJ{Hu+LJU%pyQG>%h^P -z$v8QSVxe>ET}J~tE`ziav=paV7iXt8Jezz5bbJ8$(hbFuS`zRLo;@x|tD|OfvbOhP -zR^lmGKy46XTN)kOa|+?-wL30E%syuJKs_8?4U7W^_!;a%8C^kkZ$YD_5*sN}Ny=2} -zA`+fkRw!j&9lrk7I+I#}cjxp4bF+vRDWfdNIZ3k9&S)k=p01yRA+2-A7hw!D`a>AW -z82=Yx1pE!`_&Bmj=G=L+NiZMfKfH=C;nPrL$zS5x?>*)Hi#eCSn^sX4tlFUud@B -zI2`l|)~*ZPrgH0jR0Rud-0HrDBpB&68m;}+#(als01b8qWlE;@;P=YHs1Xs+uhZ53 -z05kj9%npP%oiLQ*l&_j4g+gd`zbZEa&?GKRUCeR}vf{>Gf?ro!+rpr5TV -zdV}1tRbe5{l|(MCfvA-Ffq>Jzcw=yCbMkzIFcbA+=#wt(gBmWNX^Ih|gOxNTj@aQ5Bnf!}ctXSUg`6JQK$SX3q*DsWLD>{XE%O$K -z@~BJ9sNDmR%UV|a0h`VMX-!?uI>cAra=33+eh((b8ZaYJ?cDKHtYQyn7H@FfB%&6fk2Ls*-u$qyCD^#T;)C`9 -zg3wPRh3}=lX^!3nyY@C3%{_U)mux3{dCX%n9H+~RoIPBrelKLtm9DfKSN&eTBnLC@ -zMMW5?q{dX-^hYs497GL}{ko&@ez -zd+MIC0Z0^npeT&shsa4M0v{>L|n!c)M9+B~;) -zONNd$ZK7Z-MUy_~BM@`r4lP@tGK^*GqX$Y`*7&I&BOR{wXCe_pk2VrGgYRGlX_rv` -zm>f7=l}C)DpM|SKiLGN&!7HbJk0DMbj$R7}iOj#*ZxC}qlVe^*00Rs|bS6}TAx7p{ -z>HvLn)L+}wwb%y6*Q!9sfMl?}oZ){YgIpL-YNUGFQZ<%~di-FUS^>wNAIYXQG* -zZ|T|gOz{GR7!{|~aeX(pJueD7HEJ^4te94>4td!i6hycn1ubmLniXNK*qsiNupu7# -zvO@k{+y}zIomt2gRNS&JXC+wIBB9%s|Jy}uDhY2Ei(rZqvkNQBt`bJw_w@bYW19$G -zwAqQrbnqn~6G10n?JlodU+;=Jsmui<|0xhatB{c4@&gy-B{%p}mMh&`vPp0rLt=|L -zEwRWRmFn0WUFx>jT^LB0uAJDekkQLW%Ys^}T -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 -zu|@Cyxc89~najW3;#2g$-C_>6$mG@Z8QatO -z-)?bL?aM7{Cw;j^|1Y-~<}N&m4D`2K6#L69G60d>{Beu%f81ht^S|99!WJEc{7k2x -z-#=~<=*umF1_w-e5nul078?&^^!~S7EKMS5X8v-EO@F&ZNz%XF;3-Qqg3TqhXHzuY3<+rQmn=>NDylE#0zMX03m8d0@BZgCU3w`1DxFSn?B`nOvoVHj2V$1Ordz9HYI|8a|31=j&Hv>V>*~MUBGUi3MV5cuqS#+UDOFihntB!Nva3!{-T?aTa0r|`Hr4--JmH;VnNL-KT6ej?7FAuD47nf~wDFT;g -ztJM~?1ZYT+12<0+bIQ+miEk(ri^q;&8=P=p^+ciimzo8_ -zhkJ6!YFku-a3;NoGpe*89Dv{ll83Q3d=nQVL&UU>mK{mDsuGWzq$drDMuzrY=&n+C_T?RZb3m -z36Z*xGUZ6Z65e>mc}weJ-_O9^J;|qiYHA{&j=cE!6Y^64{@Ny9Q=#rn(5jed{E%M# -zdWDhnF_Ta{*=(b~*gCN6RG-^}vFq)_W$Q)uLeZ&jXrmXRJ6|GI03yiFw@#TT@NIE` -z24R=lm1#CMW80$_>AJtM_=!R;^-w~jF3Ypt{#=U>@oCWnfk3zerX6^6A0FCH(I3K# -z83cq#$A0={GnOduh1n8UlQ3d6IT1y*4O&9sMCN@hSBTt{jN@%r2y|VCKZ|;od`Jo> -zv@P3kyUQJ-OsoBdzyW&`4uHY`opB`43zUC6IlHUf0tDx7L=k0+HQbPnShNv2y~S=G -zbr`XFpKj=&Ig#tr-n4P(VO4}Fr$(yzg$~K%Jm|6Auq>rSpr=MFd0GkhUBw3lauh$2 -zWxsrOc?+`=fdlR}ol(R`-oU4uHAae;#}MR6J<&xR-1tX4fLbb9?tv2jPz_dtR_r;O -z-0#S0xhTv#3@=72{2-Nx$r|Joi0Nf}@=^YBc}qQ?NdxTk*g$+04t=SGp>tiVm23Dh -z&N!KbSjq0WupLz{i)^OIDZ5daQ_D{<=<-+e^9Unz!>+)J8mQie-@hJQ2k+(Ss`5AC -zh@pAzTjHyNJ>iQ9HhLKi$@qUhR|YiB3O_5V7@`6B_c(3HCVQT -zaTcOH-wRjJY$HWNC*5RK5yfZVdy!9xM8ev}`|kAeR+ehN2ej0anaGlBRKrS)Fbpy% -z(B0GJjqctoiBI|QL!%;LV~!*jim+|tT3N)k*vX7tN_CoV(iLWlh7!n-h;&T#nM?BH -zg7eAM9zUIP#Xlz2>OWvT_f<9XyymT*l%p|9tS%&-b}W<~Cpj6j*QM@ajru~s -zuA$J+sVnd1h)Xs^acLF5I046P3u!QTy4{%cF7*SnY`;8rV0j{TZdT! -zoaia)Y|x`QATTVYrIPrK1a7Q68&cPDr>YZ$eqJv>IgR0WexlK?dWbKSC+`h~fJGT*IyKFBdXvr!*0(7O-i%IsP)-!%Y -z3|9DjoKu_BO#W)1li7T3mDcom>87r?Bt3o<$2MTnHS0xT**u`H&U7PGe$bWN5Ey14 -z2883pMUp51LIwMBY;2Wwzb=R?QbeUUv9iUZ)Yx@36b1eb`~2DqL;Llv?%ubbFApj< -zKi7$VWwflSa5By{=um7J76F)wbkIb(7uD}0H=znxrAbSiU0 -zdcKByFllsrGoVv1+Oj$ywtHV(xU@dwpdBcRL3-(tgLL>ysd( -z83n2SUgK@-H}<`o_p-91ISt@wWhLf{oHSU)ZsYlBz{^t-ZIzh$Rqq5#Y8Cu;Y}x}&3PeV%@21Rqqth5` -zi(ir&>gxv9;%;wp-RUCGLPtTD*CmDmSOb2a6Sw+CVnh~HwV}(>z^GSxn`;4$4bft3 -ziWR@uy{6PkH8X(N0R1u6nxXWyJ_UX0K(sliRn92C3ttm$PBkIVhY;8UPbcntP^*uK -zNZo<`sN%MxOTyTPyCK~StIHP`l|A%6Jn_Zyr*@)6CuI3GDrgKEZ-Z!XBNWaNTGVW$ -z0uQaIc@TKea(7NI=sdJy!=&~s$CUI(^*CR1nfUe{wf{%7acn(7r*@p(>B9-YHytc6M2*}k2u8*Mn$jk?W9^v87tO)R=-~P<@tJRzdkn^={~50D8Y(y -zRe8m#1Z`$T_aTG4pWRaS>)aK46|e}-`o816JEq!l4~gZBQ8CjbVb(3MHLekKF?svk -zjVk3t#5re$BhZ0DCoqM@{;N1QtU=P(E3uFlit9%-qtVvZ=o!4ND7Bg~)5IVJQE&St -z@V;R{(Bz!uVZ0B0ha+>HK(v3f#Qi;pM*&SGotiUXbrNHJ^NZXw-4csAcK)+aPSCw} -zE!a487a#)SdOXkjW09(e5I+SwBOr(#zV#L=Z@9V*L6w*3$XupuLL<}%V{#1*N&bV?&rd^L*b*Mzfgc?(4gd5I-#aLmvItg~=Teh%2>puU*&2fq+iH0HUc+o7$i#cb -zm$0pt`s#Exp}0|?sGvWMCr{Un48(wX%c=+Ytvgy_U5RFL*>1x+i3J-T^EvulVPjmG -z|98RIqYw8S5^d;=6|ynK?Ju(M{51pWaEllFP*ro@wey{zEUmSN_Dmfu1`fRnth?&`yk^42%9vTg0+ycOa}jdei|a$IpC~)!n5s#AZs)a(An1Q(^a3 -zF9fT!un>D>CcW#|%^((|OA6;!&~2;{ML(RRB{C*!YmX^ -zyH6JF*iM -z8@>(r_$)Iag8Tbb&cyfYtX|~Cg`&}7;dj6)V=5Pfp7q3@)D(IetT8K5Ilbd18?Ok; -zZ*gd*H$Dv%TZIX@!2o}>il$xl?MO`#!^c8S;9L?YH%lXPHXD$fLSLU_37T(i^;v41 -zC+@^tup3m>n2{T&OSfPnTEQAgQ3bxOeziF>*CNaUM}Sl4&^Ab>>xUss{i5p|8abV= -z0AzMb%-ej{(wqzWldtj(OEXY0XTg%?L;R|3p2jKpG@`$_-3@e3`dv#D_iQ($_ -zH1v$nsYJs5UG2eiCU&A@&$Cnclqs^hATPq1*lT$7vi*c*HK(NTBqr{s>h@GV-v&JGss*4Otk2*4_J|SQilg1_QMNj!e^HO52l9d(3@2c7 -zDe5aj3;0HyZkd2r=Wjjo26hgZp9w-a|C@CkJ<%>twUOlnVnT~ay_cq~dxWc&eP6(xPe6duFC3XcM`}c -z7Vr}DJlD@&#nvn9*4~-&aDw< -zJl;6IyYX#zt7THvs7ygD1^bL(#(3@}{t2@JaI)veFt^k9l`Q22y;dzi_rk-jjcy1u -zFT;)G%mb{I=$V_^^|I^wK%9GOpko~ry2=a|EJo_KdY0@^BN&x>S)EJueBc{vJhq6| -z$T;3=telPUe3ddRP3Y!MwLt_&g9&-;F`C|^JoFg(7C(5o{pvA_@%-p9vQHoLh!yLy -z+J{b0^@7svcp-(D%f=xX#|;vw^yoUU!e(Xap`dgxgT~ -zk)josa&`2@239#gzc;nMr5i5s6MW`HQS?q^pYQiWVz;YU2glcJKBBl?ViEJtXW){# -zC$(vx6XRX8_XO9Whz6PTi}|h=@4V1j?#`i3hP4ZO(JtgLJ|C*qp6WA^Sl1dAHGZcX -z=ii2*HQ_xZpF`Bg8Y3afyVYE+U{*GtVV-A-KLPB}Bs|8y3xng@J8_h}Y?QkN+CJLl -zWjWbW@N(svElMW`uLW3hrS%Khi%B)eI8U0F{7jtZgd|xem_;wCh%SABIiw8KeBZoa%^2y)l()JQ25No3Ov%Ci49UNvd%fbI8)Z;eL=s(>b4&)ub}j)j -z&^k@q4kuZK__pSmxO-9P*SPSBT`r=#3P^p_O*=hPaW^>aa1g6_Q#(G6o|ejDvFU>N -ziRGv^ffEP!cAoI1!N|Oen3ZC|VtAgG5#EmV&-~UUvo+ijawQoh&tFOgYuRT?6C7*n-Ov^5Qw1Zc# -zqky20ex@J^C%GY$o+ExzSicZHqhDF70nQVW`!(!2Xm61f*`#L3 -z^9~pK0ke2)^D3{+&M^CEO`ChphMv9w|`DDa{v8nN<6h&K>i!|RZM4r7m7H}DtI7h|r*XOfAN&WjVU -z_I!rmLTce%ZEYmjZ^Bb8BNmud!9-#Ll4cE*`3s*wIsjHa*Tdn9W(2qjuJ -z=bEW<+bn*2_u7++g$C5rv$4Lup4?SKshpBZusi7R>W2uUv_Bxin1Df`(pTCK4W9>y -zFj|#|skOeuOLTO3!`07>q>|Q|2Z%5>?mt8r5o8`Bj6q<+GwUG_5ypFLK!h>g8>u!%G$rVLbB7I|&C1uE=FxxF{Go%ojPlEZ}(z}#_gWq8!RXmIEyYjqA-HcZWJad~Ud{a?6F1RKB4g%0$)ByqX -z7q>o!e}~xvGY~2T5fl8rfzYRWC+X^XkmF~umGo}fJ#MyX?G9w?|nwtBUC}i)`-Ny3lr?s -zLY1MJlnY6MVAz0FR7Bk7u#|zTl!J#fGc-XeF{NF`OO&LH2x&FVfvVr*A)d&8u*{w^ -zt+9Fs4^_&V)_kw^3B#t7H+0!<<36f8EU4kaDU|tY4jr+9iJZ{}$@ZJqRg;al5FXZS -zCwPz?R^ujhP(c3|TPk -zFTBIOkY_|X3g%+8Jwx78>eFW_$xfXt -zqnE%1wzP06iK?uwD?4u^ -zbR3%MdA`0RQZHUwX$gv}lURjm5iN+M;akdomtBLoD<6=pvZBY%QaTRXfU7|is%Hb# -zd(Lynz1VHlV+l+CrTDVlv=c_^aRj#XalC{^F3dEZPk5 -z3?CsD1%-BLab3^d7(<4U{75&8F=!*Udg7;mfNIsez-Wr+pz55J{02!XG7x5_jvKil{=LxY*V2^d&K93eUPQm -zM_}t6Fca50Uh26sW~|dO -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 -ziDHp^6#@d9UBo+|%aiJO~S~RGg}X -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 -zor#bbc6J=Cu9*VQJ0 -zI;p{qpqvVix&I^N5t;ucBJPVL4%sNbFV3Iv^a7&!Yv5OhflL&sJwxC`pTS}~a3YN0ogV%rkOdb4;(koOs_}_o+Iv=J -zXheTyRK}3(zI`LqycfjDRbe2oN>{dpR?7l*C-HsSk_0y#J-fXXJoj{NA5%I=x(sp5 -zP)KQc_zYE507ov&3kzMVikC1?1xDSH#av#jq{89bndrN|!d=&k`+tPpQ)3=nyN2u7 -zZfx7OZ8x^<#`ZQHh!HtBlW_x49en*8j3RP=j7PAY?=y@ojf}Mzi3FZ;EZnS;4oB(f0a;)YwK*<2x!gyvFAu -zuxysX&y_(hnYO(UvB~EOQ(^Vo7FSzFn9)h&p9o?gp;GEa9oPf$$kn)nl5VcT^;kjQ -zUk}*j@;Pq%(JRDi62t7F0#V4E3W4$=x}@JgA(tcD$dcG033xxfXwcrLV-mGe^J6s1 -zpv@mLicSZ$bn^RA39c7Soyozxrrzi`Y8*7-$f;!!u%&Li4r$DZ2%N#&!W|0niqykm@Pb7GmkL+rcugoSu`p7dv~r!RpPD^{2x_3g&z%J6i%N2m8Jv?@vSrdO0QPzB+L6 -zI%6pcl2|$&xVkEp^*cyQ!_&K}!IpDwF|_`yv&ii|9MePGx}O>g#pmnyi+4NB9bo;H -zB*(AChX*`Fvmx#>H~Q5y|cr7|N@ -zn;LmmjI%dOFJf~v9d)J|?K2ee8HTsI);I!D6I2Jv8Eg;+d?#B1O|=v*dHRxqxHt-Z -zLW2)PMh0!M4w}VpRe(UrY0E8(9%`}qL4~OHH3RDCb#y|`F0#nwAU%k8(79;b(I-(t -zv7pph)MM@)>CDkozt;J9;W08u*0gAca!qHcGW3wsB;YbNsC~u)4ceZe* -z+C+c`nI^GNRc;(tB`Qdv;G^F41;KqLwB@?mD{mqancJYWV;TS1AJh6I9uKvP*`caW~*MAhp06$hkV<}ViK7}3~Tx;)TcYcM1MRw8O)Gm28G>N*@VBwdAq%@T6FNxS(NUqiC0 -zMp+FPSh2qjQ&`PpnU;oxaJ2afPB(Ob;oRiI0Zq{R1=XNOHA2A6)(`7$7x~Luiy)09 -z7ow_?i8=1I1SrnSq%jX21-Ec?qE8LbhT-^a-$q0nds+4xN!UOu(>F*-j$Yhi&RMp6+pG^CO$9-p* -zA{cSdq~f{=KVoTWJ8D`t)Yq1jRC#K@;L3I;a^y}L$e>JiKyj4W0GRNOYm9B^hdT~m -zf>G#o2HnCe*{5#AU3&g%|CIvF345M8gYb(cH?GbMrnx0d1H)U7W(tTIg_Pxh^;os$ -zSR11W@PsSy{XLkM0XX`UYJYYZ4wp#HwuZgA=APRb>8}A?T4OlHlBkewb1>G^E(9>i -zd$@QbEl3P$2+l0$Cx~;kRAt6J<$QMPbQBNONpm^t2!yVg;H0@V#Cm%>ESi1+`;M`0 -zGn{*HsmU+Dx!a3%eH$qHDbw)rq$YIjplSjWS03BAERsW+)@F|=wT=O6}!+YwYh0O6tX$a4=kX3fU{W!hJ&Kqwds -zc&Qz~RY9|n@XEgf%rC08pfr6^jwtEE3Go&C8Q4k`SzJEzTKXB9a%>G@hZ?`zy!p5^J0*t;7Id&9=dY(VnKKN2recFvkg?hL{(K^kqu|8VWR_EvdL^2Tq|GU-d&J|h -z#a`u;Uz4$sBuqxS67y=v@Tp4(VwzbF1lY?4Y!92Weg-cL_8!iqMV*{yEN8_l# -zj3m5yfQ&#d0h`jf@TjIr?OCG>72=T#y`7W9UQTjMCo$ujH0X3eZ9kuwv`>9?l)AK` -zWE0-3V!N+%S)>F#)D|izSR73XS$?BiwZA%$Hd|GQujb2pRopdpal#_yW2%QGrnK-n -z!5_qeKy8O8bAN8b-|X#0eaD@oD{%WEq=?sMz*g?C20a53R8Oah2~-6VVM)*-qt&4xJLrB_xo53YI8hUGIr)LZ`Rlv -zClplnvcDx|KqOQ34$sw?d}+8oS~FvAxO~N&?A?2H<%?7Lx4j`SorUWmX8XHn!}GdAh>BRW(t)6 -z4fLFuSYah7kq%H9O{&`}r$a11#l4@V7Z3$lb(ks%!OXSE#ZDEn5Swa1`Shfqt5W0R -zM_3#h{vxF7oGm0;4KaZUT)TI&k9WLM>UN_Y4iM!PO<1Swim^7F)YU9T=!37Z+vhrL -z!&$S~PN4U#J=uA~`#y;&1a~h#t6m?JnUS$Wtz`!xP$=LHbW#Pc9KI61VQ8vtAYShZ -z2u)*g+CYSdty(_!cv4DE!xoBmERGQTHqB*UDf>*r>zR(z&b2B{E8CT=K0wDP#&g}A -zb#c*C6|sfR%|cX0V0aIk%=` -zQV13cI&T@bNP2fygxt}T*;h$~T-uRlTRqon7{Q|xgg?&hYhQ8L)>0+zsfcKxcjv?_3;* -zhxM+_`Xvv~r=5u?>r$*g)*SmLBGVzN-v!abffBaMR+_H0Zx5ab4m^n7IDEU*Pw -zn-95LdBh>h&>H=?X&l@1ezqra -zu9=G^*ui;%>S=`;jwzxLwR44KemVEO7OFa|dJ=j}osWTx!as2Jeg9xj`-OWZ16*&z -z%n<_D>I?}Lp}Nk#rB{;fQn`*)y`9-qa)*WVGUV`_vKjhS-C8V%9* -z%;}>UexLo;EQux45Z}U89&Hm?Q@_Y>OD7h^qB3;|#hl?ll-4$`>P<}MLewl_<*U#p -z5j7}79dzBhYtxJqOmZyht9ei#scXF-u(s>8c&R$~_YwPVxjjtGm>;s53F7hAPI>ULZxg8{lM&WGrLViz3l{M -z&)(xqQhHiHs(B?J^*o!82|WbI!6kc<&4JIEL!DPK(0Bnno~MoVtgQb{Y*r8*=m)3| -z+po`fxH}la(14dL@<+k73D&5tE$Obrm -zTdJTgfuZVsAxG*HQPe@X>$B2i<>)>rz!FwDL3}UlD_T<3AtJY`(t@3Xk3nvl9G!b? -zCM-8FJHU*0M*?Flg@DJE{EIEi_@V~%D -zaRAt;+IIaI_lfRT_hAF$E)?)RY7YHOgCnft$n=(C4port78CI?>wh)zVy1Qqf51d|GL!%wL)L~eCx6|DI|~% -zozZ+FkD|Ke$A|p^d(UF4YV3CxyOkrjzHyOs3VAb0o^nXcQHw%bT&PlRIgNuPqY2j)t~eDV85GL`_JEB?esBnWK79+ -zR5KzwaB^g;CYqa*f-4MW9MFa7I4AY}etjzNenAF{G@!LX0#^A{%49$oBD%twS9RU@ -z!5xITx+Y;gqGuUppNn*_V8Zw=>x5{S$&uCVc3<1}30Y~C -z{RCWV`A)3Uaawq+toR)7$twwO6?XL8C`MW`LPcjUza3ZwZdkUL%I!h6IQdXitC9E| -ze97FC)9UJ#*+j9#uPFR$_@oY~y-O_f5bEL?i=X`fJDwo-5W$82mV@$y$B&7(zt-Dc -zaYGb4_`RZ)V~`R-BvbDlku0#Z44^djZ=Vj>q-Oz?Mm|ExZYrq&9En}{hsBzz-(vSF -z7qkZz7p`iX*=w)E)UjGJSSJX>MIIJXaajns+sZsTIQW&v!_rjedw0tvE4=|`UX -zEXcm98y|Aty(XBBJ7GURFr)}xabAYHKk$M;dj;7e%EdT~(#C5}PdN2tD0_DB`(gR8 -zuE9iRpzX}D`6uS8cL}wtm)ubAfqD5|zV0;{a!%RNV+nw1RQ&OWX)FRTjrkJ*rttv4 -zG>(K9RoZ_Y8TbzNhiSxB#)`4v1~82sI}67dLc0{`#-YR6s%pyWTbqbAB|@Ui9B>8C%n<1(Oj{G^Wn)Q;FBfZB0Jz*l`#2oH2g -zht&C485dAH-mG3Cj(3c^0BT3dOr+0_G3F=ecdkpEGRFB7lA$Wy(=M+5WLd)5s?I;vKCxkR7XqNWFooGT5oU#^GCajKAcIH^W_D}$y_jE}(18K2eWHgk_oWSJyfJ3Lfex-h$4?~#i-x&{HCazRg*Q_^ -z({Oj^5Xl$6pD-3R=+2!fs^%CR9(a&c_oSR3Dsx6b)od~6_lW*sXZ#BDRR?aGD>_D_ -zLD7k>RL@jZqufD#?$GL_DQQdEbP@1PO2;^K|FLUgk*W?dH~+N%noov%%AlmO`BQbR -z`abj!3LRCZ021f2EI?`O=#h!L^@!Tf=TILz5+ePb?o>S{FGM9^DTjM?tlS*lqj{HDye)vj9 -z+65k|ps(Bh;qTnM(ygONX%}X(;Np5!`r}~{by8VOTmv_E-q~dhw)U6(m{itu-baHW -zd)=uWq6n06sEPBD-SZDcVy_NPQ1Vsvr0YnmHOuB7mrXIc{p@64sLy-k;z)T4f6d1{ -z)50hXz<0Gm%Z*6)W*i=F&+{c_vf7VJ`Ad7b(F -zOZIL-{0L2f_;YU9Jg31`d${eaaa>btrUcu>3WiaLSY;^-6qgK`-QZ0hH_lL`t#A05 -z>PO2M;)&*l7d0(ZN7u@;aV}ete6|>ahnWXfL-q9+;`&w{$v`g6}JMQG^+ipG$w&S{81V+^q%XSy8Ej-R({tY#}IghAY(43kALIMNf_)! -zdpP6L|B@xRr?A`&zpA=ap!OB7>l`B_)qM}btzbTn?l+TDLa5~jrp>H -z4f|;CKmSzV@4_;((UrQjK5%w+U=E}|X~unpr6!bphfCZ?s2mkwuTF!0R0p}WP?vq= -zJr_-5o6R5B07|0cj7{&6!I+I-<$7HL8Tdq2lAEIdS*rIpc2v`vbp0DU8vcnL&oBPO -zj%j~lNB<6kk~y^hiXCw_>tiuqA)&qKPpD?aN)c&fA<>dyzjm9fZW4!Gc~{!4z^`5K -z>L-j69Zn5!qn~mjqlGy#@+dm9*DFH15pLxgqe$T4yzNX0{f!-$M**?pRX!kg{LC*G -z+{zWonj8KTJ2oi;W(OPqvE%Pb;w;i2KYG3C!?)2$KQipu{dc1*RJlPWP5N%H)TARfSH -zrvIQlkMKetD1%CZ!ojccUCLcI7uRj%+9_(VNa1hns9u$6{Yl+V!VZWXt(pR1$Ws1| -z9U}p;;}{@ztbvqw0K|?(Gk;>oE;4kxhW`~i?gSMcLIVS0#~h|SKT#r8UDRiLmDznYDYMWK;?@QoEe}rmCJ1zG8|)$?Zq}pZc5gRT -z7^msn$td+FcKk8%Mm)`$+dB}8eI(61o8E&@waS!*A#ikq@<(a>Yy8{$q -zXpyk^NV*PAM7?4(iRXN!v-q*~@_2-<<)#Ac;~*rvcRFKxZ%jvzYopp?M*cDojb|Tvpi~^)Lh-up%9dt{tb>9mZ0_=(r0AgNRejhP@7n0o}&GEGuP#;9x -z30CKJh|Oa^!|+C`p`&o`1 -zt1kax8q@y9j;`d6r@eoeMoB>I*px8(l^%>cAdFb%Fy>lN>Oy>wq{g{ZN75%>7WmY3 -z=;(#TWE@3m-UAIMlf+6Va?uSW#5*}&-7fmcDcV&Rs@~^0Gqq6q0+eUAPobJKDQ{j! -zC!JwdsME<{nXO2nA)1CZgm=0iBj;1)*NR(MxUJvFiy{!`hi>?}V+U#+sjG~G3?px? -zphhW?%;b0zVPz_a3V7uil=Ngw0QtG4HnePI(1&+1am)&CWG$n0?cQ_xF-)JY(MRN#FR)cV4p*PLcmM{+p8_u|w2o8#=mbBpI$Zn>9gu -zv&&p_XK1~;9Y0|?R9)3ujT||S>E*khUriUIQEGzZkihB-BM;_U%%6aqTcMEh_92BM -zuWs^jgN!3Fp8Z)71a~+4W`ecL;i!EFB^O&JLpCQO7}v2UAQ0}> -zA2BvW6`h$GHHFeBr}hMtO{QL^gv#WaNQI$4--i~$rT{c|U8eA@PRY}DmSfPuA-w}& -z=-zWBo!}XvTg2q|Orl{=6-z?w|*2yufVofZMdckTN -zqXk>vTLNOoO(m+@!+)5@;{TUvL`4t`f2{#){+DS?A}ufEfmO#6^U(l7Lv*^Tn%NgK -zXE2=wiXCx-qfh*x;^2*W<6T5<_6>LBGa2S&qSp^kqp0jVI9EE@ybRrRI>MTt5m;{x -z!97GiU?osR4GwMwy&L2mQ!2D)#qL=><>0vbO;lSAgPk;*a@n8w(0?#NQNSOwg4lg-cNJtsU&e@NS -zAoWu^x$XdLscA2Ptl<6w_ya8Vdm&1-M% -zi0wxzz1>w-gkx6tzBkFvtiU->FF|t){h{R(`?R&iWa0Bqe?arvT`g-)n%E)c0)&s_ -zuT>b~wcva^Us}L=Ejsf=ukKsX4jIpARoRY$IEJkNx8H9S*i%L6=y;^)pigsMV{T)| -z4@?YE^WE>ox|~FKo7rrcm_o(wr*=O{>3d}0rw%Jd?nh$|?x*a^g#-P_Q3F~n5wPrM -zu{bmJ`N|2oEkU-B6s;7Uo;E9KnLC~KAz_0ax;+E;=J*M(K0n!XyU{J7KQN-jHe$&B -zdtOnNSDqU3wxay2hJhcQ+IJTP`iagk2;bgS-_cTHNW}UhuxT}gq7<&UJS@6n!C0yk -z-YN)q(t1e=!3sOak)-I7%it9{#Pe?Nl3k!z%DJYN5_BLarMOyfpUp_SX!Nz}hg -zBjVJ*Od~*u1u%`NwB;qmQb}V@q+s~1qI=E6|Hh7MbIclm*zs+A{69>inLz;mpV*Q7 -zPwW^#`k&Y_g8<|H@D;X{sG_f -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 -z*fecq!hQ?w?FO~jf=;@79Devkx|MQw -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 -z57EeC{g-Hr#03zI*#99K#V?{UftfP?5RFl1 -zyKe3m*c8U%WW+}OLo}NIB^t+>=v@9K8kPSS(fIjaL?h=PqS0ubghR@k+AySsoBl7+NSZE}1+wCAF6;)%-oyHq*0X#@Hq(I`Gy?`~7al%ZuWF^q|kCfMa8Qy0}7at~}oAfk->!&Cph`F@}RpVNZ|_61ATGgCc=e)sN?9{96t87#0^N&KiK&{gCXunZ~CVg+Cje(FD({-CZ(^WqDI=k?Mb^C -z?{0(bEHQ!2@V7PWDW4LeaH~{w5xf_lhcx6;*R72XSa3+excb -z&mOGEaqCjNZ>22d$$b#k{5z -zp4b;+<|xWTdVL`fmCifz7~WxOd{pyy)t%tHM3rN^p}|bu3vdR-Ee3uTwvQ4eiy`1& -zq7f+~vJLpph6xz#pF-&@Ow|;O7gcxc(RtK+snPRe2W}zQvU}3mqL>9jiX;81{1{(<5e$9f0e#+>BY#pvsM%a&tQ!g -znw-5uhr2)I!0LXuYDv(3u4E;zE<}-xKNtNDt9V(q?O-J`AG5RzVdvgf6?f#>J*XrW -zKO2F`$683O(6?LSjQGf5OF% -z8@G$HkYQ5;tyqg@&&|KbKxPv|biIn_?E#2REwm_^g<&~iu_H$BE*Sz>C;`X9?LjIQ -z^dKG?A8h4zDh{+CLTp+29E(uS8phVIWupGc6=XmR$FOloS>m$gw&PajQw}j*2A1M) -zX1plux(v_p@BeZEf3-OJAi&r|;iGJ=4bG?B)U!G#0VIL|XsCLSjI1ZKBs!4R=Zy59 -zIKJoYno92MVbvurWWy9W2(B0e!YDH)2|+q#{CUl`x{!n~lY}i3zqzJ42o}S^Nr#^@ -z`5&T@^)JyVac=xgG$!{%bN?k8X*0ixM(;6t*BGyotmwtGEv$p0y=&{*5l=t*niIh2 -zVw?1i2Yc1YiU5gV+t2wT@-j&B4pJ%nfRLOP;=RDdIx`!uVPU3UMN@e9qJiS&r7Z$d -z3u=w18d#V_W~s4Ar?!q(!p)!{JHrFP+%5XG&vpVyn;H1$cWb=L`;HV)+RSBNC{OOI -z%|8DUjZ#sebbMp~n`jjJCK^lSHcs(AXg>AcR*|m*fXL7cspiaWrX0q{cVzP@{493B -zX*n~Hmp0p1T!xU$18QyUYdEfPMx%d4>UOfNud-$7?E}~`jAP-|Z&pN$E);|gN6X*E -zw=)x!oDA_7m+~vA)ssHv7q}{hTxg8L{zbjL5)BW`R!V~b);hiSkj4h&cT+(fX9I1v -z+YA;-UGj0~xy>%}GXXf8=vI13ZpWJ&)dlUqbgg9Qa}TGT$G^OJnPL`xD^;3$RM&`x(k`1=rb;ei2?y$d -zZJ9+R19SwrX*VN{mnfVz-{eSQ%+F6!oN8bSsh8FP2XTT~+& -z2tnBHg;A`3QKOT%nf5bgD-Vo4{KQwE^qH4|4*R>dfe(%r3 -zt~q!9-Tb9nFA=*xd=5PW*GT8^7gRet2}JDC*Y4+h$$xM+Vt>Pok0N*`;~C#D<2fMz -zsdOvk20M^#O0kRa2VGpYhAD=sc6IsQ<-`!1iv5Dd7`rPyDb6aeQlciBM6S5wG1FZW -zhpL>iaV3xw{XvkEwJj_Sx;f?__XDS$>!h{awQDsN)h3o6S;k&;nD5nYW5bb@`$wf6J4&p*s0@cXi@SRfJ5bm#j -zOeE6z4HL@`0|4ZyzbhzRrJM9MW9~164M$hVB+b`k`__V34C}KEq#yqfTfy@3G_%Mc -zGVqtO(Mft+OqiR0co|pAA61bPs(AiQ`w{=%a0WZp$mp#KNw)H4I1FD53T!+J8C}X- -zsFe{d^)(=yuTmeZ!nyvHqh){_eID1$$bka1jz^U{^K189A3A!XZ(2PY_)x&JOTI%N -zeC@&QZdFo-_)`QlThq&gpEO4xBQN|jb6zcNdPVw9VaS+_GixQM-1YV^%vcVuu;^yf -zZuk`Q4KohSE%4}L8HZH4l{V_xOq>s)5jU|i-yiGlmk`*fj5_Ma;ynC}^j}L5t4R0D -zha$7D25ZPgg`8%j0>~;u!UaPhK%; -z<5gm(^dc@&Wf~OzZK;YStZjfcan{$h;Rw|-w`<7g7RFk;HWlmkW29+KT2&|!&>z6Z -zr%Oc5p$Q0!V5-c&UNyRzwVIscB7X{u#4Y}&*OI$kBX`{U-qC*4JoTmT;xHn3c&=fc -ze6$Z5aYmm)!`E&@gVP>Dl0dKS4&W3amh^7t#Le2+u7lcEju|r(nr{Wnp8LpRO@;#c -z$zKsiyYL}57Fz)%5aA)fFvw1LnkP@E=s_u}8h;fE3+w|}(vh5PToTKN!z|nGP-U9z -zXj-o_T=n28?(|T%h^`;;?(=z5F93IjUUEd3N|~HiGW^SH*u+nv4bu3+0rKoz`H0zeq!_?-0Q33(zBnBh;N(=*%GS -zNl!UtAA0A#eDWCcAK!m|5*`P2B#AE__Egljq<|9fT#Y?gn`ByJMt@Kbna~o|4my#- -zx-~B-%OJ2;#}gOsh0omBgH2q{57ZNj#Br9}dN(4?*P2Ku&@Fvvd;FT# -zM27El=ghy;sJ?5zf9y-aB?#xZ)lUji)83P*rdWCEIKi^BihV|zU8UvY60H(lvPOg^ -ze3?$Lly-rVcoDbUGh-VjG;N8(I-ieD`H~|Wwk24mk@I>zlQ;#>DDQkc3=pvA5=t*B -zxBR)8SzM%-Lc0~LKW;|>B3o{;@hG(-*Pa^;Rv#>Dlk94vv=96A+Lod||IDAxCcL)_0_QVok5Q)a@fd68Y=p4R -zLjgpqF^-tY;fBSD3fgktqZ_Y}?_|GAL^CX@_k)M;vI@fV*977x!L;)Rb)@j6mrM@P2>S^6@={cOWFUER>}{L1X+#9%7oXlF>jslmrukgqs+^R|!M5c1H8hhiPuK -z+Zns8ad?HMV`fkv5#_zn!{vKg$&)v3h8hfPz7-Yr*NjCshb~E|aJi#1d3<%VD -zQlH}!KZSxVWfUySgU2@+*ULi^yMY{B9F!&Xofk{EGO~C-fu9Xn<4vbapvEGO5g@^05n;UQHA=`scuul{|4<)Cm}r@w -z-@7Luqwl88+l|iuI=$DcbY$Z}sGwu`xcA~@e+b|j_nI!5GpO3%&fY74z9|M}P}B_( -zf~e_v*IP0HSpa2rcz+t)|aOUZ6#OOVWRY -z%3*b^>D+EJqI3KbE(Awli@Q|IHyl>N9vQgmpH?3hBvAu=`iULSezr35WdoNdqz`UV -zEAlv>V+KE{i+l|)4SMzovlqzROWTffX>t9dnTHuy`%q9hKI9)P3 -z9=m{kTn7^GBycjebDCPac<=ngijAiAwo=$T1Cke9Y!X=rKX#`3NI>n(VogXF>753_ -z+;PiUPjs|v9MwT&&jS0T>+n9e@YLPD6nkok6H(e>McI+6e`P_o2d9G5KUj -z3Oay+>}-jUu^1WOqUz#ZEpfdk()NSI``CuGSGA|m?2BGNa9tKk6yp4+-XFv=fA4lY -zQ~0WqzRWy74dd)+Mz-NJKNxRz)$j^A1P=7t_AdSDDyD0msY{1C34}K2 -z6Y;f)$FsEG)yuDQ#ws;z5w3yLX$A-D3n>?$;x)}9NdUMe95NgKUofNfJ77#{)6!4e -zd3b|Y(wlohtfoBo=c4L4e@Bo;@$Q{Kw?RN8*`6m%A$sd4`z~BCPZ^?4o8K%9Jb8eL -zI5P~m%iNo+S(AihWLk#t!=Wrm2}}x`hA-26Fikp3HDhs)DZn}px8OC!HVkOfcKSeL -z3WhWM=28pxQ{EN9s;dZMA{U^!I%eGwgA~F-^p3~hm4Es{28_*)8--jG%?RKw~x%{ZgW-4!ehwrY@P`H&j -zw8Ji?@ANjcE?f~f_DE;~qjFe1+mA?t?h?X_3SIC5yO^C!x*#L49Tz&+_}Zc0NW$a; -zNx_3^s12AGI%vqI4 -zp@N^VEU}Y~3nfr$VXVAt_d~{T_-Pr_+Nn&wR -zf9kh8u$49NR{Y_7ECzEyln?gCy!_$z5`pWh -z2)yHrwe%?t>`@o*@3Z&1o46V*-?+L$fO<}^1eBuZ3*CrIH3KvQcHKeyIAPTdREj{= -zJCRc;)uegl3~XSSAMBP0O7&C3t);4|9~6kMN>lx{sVxvME>1^yZFbMz{CESb`%rfQmFFH%cSux -z>Q1*Mm$JGIC8C|d)&90|m@eNW!4AhN06!E*87aDnCju;`UDyxch8ID>$`}3+9b2)Z -z2E4!?0O%Obhjc^2Snh^MB(KSs_CQDG*X)v-P2N6&wVq~EIBw#+krpe23hr&U(miI( -z!2Mvkbu+M8VuT@P#m)`C8NKmyPZ!D8TC&Pq_Y$_4vc8e`>ubk_D*#|LO-QUZu{0Y} -zK8sdt+dHg~ahFjuv(_uIBxgfSG8?vb&4g0^Svdd2k+8sYCIcQi%_Anf7=TK2T$?3t -zJUqT=z^y0=EJr%`wQDdHNK`*B8uJf;1#}#{$^A5Ocnm-v6%w3VfAwYR=fvYsGY*s) -z`w9_CRg!j`1ur7%g#1D7U|p?C++37=#|sb>K<3AdFh&H+1r79^_!TfCqumgc-z$oT -zwd^S}tnd8}zbl}Poe-}Z44xAqu-v2!>oTBRJ+ij|{H;BDr_`E6wlr5T%kj11Wy`0T -zj($tHlRUYgtDof3WqR>kc=CasqW;dc0}yC;B`HZH9m^iOg(xN#$Jtb`86!XIM=WuR -zu{8BYrQ`IZ2n4g^ZZfp9zL0I*HJ>=n{ax}P7>G!$n4A~3+M4vVWAJ$Gcnc929h -z9pm#k+eLGLAB|r8k`sfUP>vHyb|n5A(>s6mc~GRg)%*+I-CB7jhq1{BEhz)d$F#`c -z)leeCUc4}|1Tsj0!U&%^gXQM8!vxRP8m9ZPP|N@0GA;~T@%`&E_I|sJVr!~pzi`O@ -z>oT5@eY=c_|BuT^@$E8RD)Bu@tj8#jphy45WlT<+wU-D~v-Ex8OZ&%V3=bt+bS{ye -zHC>zW(x(|HdOGkzp!hIRElr -zm(kvp$I$T(^4n$1d~es4#QE|6cNqyApD(f>4z5kxbV3|CXp<}5U?KHx=~&|A<(qif -z%->F6Bn-qYCfD)^_;W2+3Bh9@>fAhIw^ewLUv4Svr%(dH0b$(u?tI9Rm%&L6H-Ia{ -zuY)Q3hJoHJOVbKG9n_C_n6H6{9tC+(INU;M$n2^-2pcEdK_<-@z#XJ&5A=C%?Z8cc -z{WhxK)LL+Sx?mi{Re-W}1bctO=XD9ux!M67p#E~ZiNo$2St-I+uDATD>viacb$Ep5 -zZPo?MKwJf8mmlpS7FyeGM8ZNNohOw=P3u&bM -zDgquXZY1muiDOmLpnxc^S{7Vr;F5!DK)tZunW^dY%AG;LaW`LT!g4E=CPyKmfl9B# -zAoiy@&`w7=YXKVY=*yhZ#7|ig3N48aF_m8 -z^+&;^;Pmf-_d2;C%aF`*Ja^oEx3W6X%xAO*?XXzlx#o_MtQV#{Wg^sNV+#FDO=KVy -ziOpNBAUxgVToWim!TQNYo3LS1yHeu(;y5nn*t_T$ogX{l0Ry9&xzBZ%{ouf2L){or -zcK&D)YgJGsiW=6}&=5&t8A-Aw`CvrHm7|av%jhURM_h_%1JHbtjDZprB0$?Nj>o0D -zmlT@qqDvF`{o_Hx%U#ZV$Sl-L?TI$A1%2?> -zx%io)w$ShDs;eb#?<;bL5v<2BCq+89L5uWJG{hQFOikJm9Ob`oVeXrboxAM8ae}yi -zRo5Gs^{5vV`zV@dpvH)spZB6l=}`FZkb-{yW#U^!tByJL*ik%?tMDL!??Sk}oy^<@ -z#Rt0?Dj8VV$CLk#%KfTc+)w$l&5(#9DGA)u2&NGyYs0-M -zTC;aD`!kdMi3pL2Jh4MB^1(v3F)r4ME{QMh3X|{KpJGqgqW0ubL~WXMeiq@ONfGe} -zSw5E7adH_Uv4ri{=V?0aFhQlLH5XYCMyO2Xt%WyV&J$S_mV7Fy{Vl9eH`VfaHR&{c -zOcEA|pP&yJA!RF4?!mAU-rpt~`a~N2k^`9|SYOF+MZDr0Y!rT4rnEf0%%*l81C(~M -zOmw~`yg*`If3KaYMBYelfLO%!>F&?x{Iy}>zS35^FQ9#Vd!5*h;5K{G -zGKA?c+{O$-(?sW!lJ#`~KC$NPmRTY8kySeE7G1s^H*e5EB!@__5MG=6+C8ddH8Y;W -zyh+`c9o+Z>SdEm|6df4{N6A5!emK})1;;sP&bC=9`D4#ba9gb2n~kN?>XhwdRAs+! -zGB@Zrc=$IpQM=n8SW`k#&A8b2B-aDFeSAwTxR^cK=3W$mhC&GWmNcGCxNDb9gEZR5 -z3RlZ~sBGp`ZuRG*{G-A(H!Ys8$Pqtfzoz2oL?*Bn3NYz~PeKTm^(#ZvU)$@^104uP -zZIbei5!T&=t$sO8|0JzPdKY|Ait5`sK;aJ+EU2q(qN}f7K$W}2H7P#sNFBhleQz-i -zYV#ht5HB?w<$d{uW1t{-PF2_dWWl?CW9&!+{mO6-`dT~1%`)Lf7zn_Le{h8;6R+ty -z;-NQ+TyKE3qfaE+D52V3M`!947Q$<`Z(gt!@fdvRTk;wUOFwg31rF!LZE}|B>)9=y -zl6;y@YxIytmtwE`emTa(Y$fTH0|NHRxbb|}LC_s(jfd9;QZindqL@2ccr)V}mOjFS -zxaR0e{p20GbjIrkYm^FgNXqq?qq1rsUli9^)S|{Kp8KrCFdGOb|1d*ImT4ayq2RU&O$HtMC_%<5PuT*fN@3|EPdc-wTr(jdrjJUf{*ok^b!y2)rY?Q2rZU2^Zsh$LP91~#3YlyT2qQ6*5S@JS| -zc>+3Nap_pIXq5>oFvgXAxb^jVj-1~IC4%9RbnXrCb_l-j0NGzLuUaXntnA$~S>NFB -zn-+U!=$XlZr?dGNka+Gh`=Km|em^g^rDd_ARleIIln1}|RO8t~jV7QmzNX$J -z4_|_5k*)tQOBF1g5&74%D^UORmqWv4nieqsF&OijunsYU+Qh;tF`;GT^1@y`bBuY< -z&PPf}baG}8@?v>iXTLt!%(!T_@8|5v*T=#>q}HE=@GN=b9+Z3K_mOPxNv(+R71XH< -z*Mk##=CzcF&~u*X`ts#H(9u%a -z&BvV=PBQ4od8$kPLBz$;PkrRFSU^X{%$&M&%#OZf)eS!Zsk@saS_Py4KOP%L3Ls1A -z(m+s@N}-O~G>U{>hZd5pSorywjNQ5E*V}lE@A;Qy1o?+$9JmDgfXpke>8GHXLX6;F -z;AO_Y0q_pZ_9N+S;z);0tz6`q$s{)jN+s_C8(+^$&-X>{n7fsWUL9whffQu`JUBP5 -z5{TSCLVg@8N$iDj0Ftk+$~)n|(7}@!2MoS(7bLJ-Ie1o^A<+#meIkY;AFdI{Rkr#} -zCi3c1jd=xWAt4cruv-I~z*umdzYd=SW8c<$B9o$XUs0^vk<~qY*gLHPr2qzStGOVL -zi;oSju&@`SyK!oNFJbv5DWlm7hfu~)!?+nzo=Cv|CBNWM@6mD~Tn~B>XUHh(gla0Y -zW8^K@>3nwxL|2=mR@n)XUF%&jh8jr+_3$F`FaV(xRh5$V0z?>Z`nWjP?#-W=t__PEhE=jia -zP`$;5*}c6lF~=2ilJKF@94UBb=mUm}SV+gDlHbm@9ntnX?L`#T=?xYgU$=Zcu8$Wy -zEkT5d`~~Q4nUR%)|Dcd%Ea-KlDH{1nQK;rR#B9ndJ1Y)|H?$cUAP<9K5?OQ`uCU_= -zXAaXCM;$VbPc{Y?n2_JKsS4Z(j&S6)BVa6tcC3Ze4#wC^!S!(vWg`6Y6_c1`yFHC< -zSMuw0yAH`UW>6S=!!L$0lC+O^g3)6N)Cb~85dhuSLn6lYbNfDL?Al*aNkb*S2ZWL!~dy0WtAr#AHnSSuq#WHamztLct;t5Xsc~3r5KX -zi>q-d?PE}-42%#hhjtl41427j@JbC=r+z)M!3*Fy6@A7!?TPz<>n9naRY5OS&!5fn -z-wq6!wsNV*jD$-4ia!ky!;fMy`vfF@Lg1rJv8^iQNZJ7@STxTcB(kKqnBSdVz)(I6 -z*@DcpuXncP_v7oMYmt7oebQGtto(=GT=vY* -zXZC=H{F2nG2(XixkPfTv9hcvkR8XcZ8A?G@!6|)QP-x?ta~12Vf=?Na^~9)K@VV7Y -z6m#AUS;in&*3yEodGKg5r7?Z5WFh<|UK;_oFuJg*1>{3ro~#mR4zc$=KjA@6Zo@ubEGsE$A1H0L`5w^u)(yvhocKln8hHo&A;!CV0L(SskB(+1% -zkK|B^AfxCu?!O#bX*`o4Uvf|FbUSaj#M!U5YH9lqjC!yDJg~6jE#XF#R|H?&t|xE0 -z4=mfq4a5ibSM8+NHAtH*#R-;&6vxjtAT{aujkTS>&J4`lvRbcFBwIq*6P`;-7 -z)6FN`)`rt;a+wm8S3KQ6Ji`Qjyb|eBce`CXI6@6l%nm(3Z;uS>gQgoPLF*t!3aoOW -zDX?pUlVsu~b2$1tve)$``TukYd1l$-t{*(E4S$L#R#rIL?G?3L#vk%NQY&=-A@>_u -z+Mv-!&SZv&X6l+X+h$fZV~@GjXNfD<6fA3KJxO;bpujs4*YdO6!Oup?b{wdtZrK)K^Y -z!G#x~< -zdsGookEaW}n;F^&!j#(;o&h^J#M=Iu&8k|zm&fH1!1uV2?I+hiuGsQQGGWS>4f=ej -z?+BN~E|$XOOZs}1^Zp1VvxpIAhS6exnVJC6GviZWk^mZn>bqgfZ8;{>>g@4~ormC-=To%R`i{AMu>f-xA -zdAcKUlp=nVb3+;f97bBZZ^4O*BrWRql1K7KHMdybt|sYx`F_@7%qM4oy$r+)gK9_) -zWt741-QqX4m~hDgKwFj}*0)a5cyrWzzz9e`yV5CFpXaQnS-C88yCMK28n4{wRz|mS(T+j!gMqg -zet{jxKra?6pDau(Q)f{k{izy-d};|SOs_sGo@z@edks0cFNP4{?s$i1OC*~}N+fE1 -z(1A?0lIlhewbL0Kw%8ais?-22bg6n|4gttV5*GvyJ_w)l$YXNy?J|PY!9p&i&FpY8 -z^Ui#`jB^3-#>TwfA%_;dfQ2ad>GiB}cN{EbHyZKSTEF!Ha6&ss$M9U&14{?O_;wi~%l~m15f5tJzFkJ~e_Y1V|G10`RQN2nI^Qm1 -zKbK$fx65efK=bV~7Rg1%UYteZrfr5-R?C`D8E6 -z)!m700cB$}PT6mlF`H)f+hxrA*JVWib{V;`@Jpn1Q+!lRYO#^G1a1W(YLLr%D1?BG -zgMTpPFUJaLrQ+j*3;XFV$yN!?0W*cR9Y(z`)NQj~%0`#XB12l{p2d@#RjZK1IZ=fM -zHX~+PFc|(Mk;;9z%$z%;vGWlHWyO}?{4BkUWY@=^xoo}p9N8O^(s^@@Wc^sdz-(qC -zVYWwf_yl-swO10x!&>fp2$jdTh%*Ane|zBCfwmJOPn3h*UEl{(Zll%^mYDUAn;wVP -zZ=br!^1c)d;K^q)!9n$1Xv@QXQ)FOzu@_#H3F;CS)J1A(9g_calJ~B!C&8Uo-*3WN -z!~Nl|+~jc5L@Mf}x$9o)Cu-FcVVd&&l_HFpATb6|lr@hYHy^7sQsbh&!;NB9rG@d2G}46XHOKNZ -zcb<`Rh47fNPk`7S*e|kefE%j`k9;N}G8o$<5fBznEk5XWeCeQB)$9!@l|v}dAbcN= -zoFrqu%{SXo3K>{+AJ%a$js*%@~=yKMA!6C{&=t(ZW=Vkj{oIuWk-QY32LOin`cE0KEylfE)yG -z-C<8@E+9Wr2NxbXG1C0s4)^ARoVyzkSMB+4fU@SJlNK~|&Dd1-&)N)y=2*WD* -zRS0}$OJSW#1s7|s81k@|P8KzzX&B{dIsFWujY=L^Wzv;^EwnRT9-Nq%olzb43Mh1qFL~f9Oc*m2Zn*I;V$nniG-n?~{y0pbf)ujH*GU@_B`v1c+ -z@`2BjJS-KQ(ixiY)j-9iM94zx^C3N_0MCH1@?fuivy8l1?Xto^KL24Eqd+qjcv^*! -z?TOaHsQEiv#1%&x*!D=v0?Hq9>nalK@*-Z7Qpy(2Gc{AVZ9oUsD!1#KWjn1@v1T -zoDOz62!vquS*o(Uk#l|cTm<9fiim$Or5DFiv-C`Qhj9~He8?vp9Du=>7GKGp=3iJkY6EuF8l*-K?wZSR^?noFO;>8fekn%6Y31@T81rL*!A7d2 -z$mtC15kGC_k>hgFnr5gS61lwu5fV6Jxr8B{%~Zzl*lSckXmcAlo)2z*dX~<(TqZ2G -z!C4OoNVBIeQwx$U?LmPSJ=+!2rZW+&oKLxqnpk3s#z2pFPY8Y+3FxKuP){9A`xfGc -z(ojk_t!Lfx`28A&m@yGQLy9mx>bYmOG%;nx_KXOo+Cj`vbbcRjagh#N^aewR%^XKM -z#^uZxZqj5a<$*+(ZaX(;!JA4E0WVUGs$U1t5=>dK1KefEOIPE{k)(9^0Ku7K=-HC) -z_2cRc`^pd@#xS~|4h+fcz2hzP&g~l}S_6k4@kT}+8yC)t4oom6uc{4gO>Xwv9Ti^xx -z82LW`zbfM^aKT@dv4qWR^vJ+!aM&;9JO@Frz_L9whuC#eVcNo!) -z_oew979-tv{OGdV7}weN_(Qo6#*Hjp$+l5le} -zGZZka8;%(;SKD&tI|MqlAto^L2}`ok-o_cl&AKWU*(z7NsG6*veti4M#Ru>l#%^klyRIv+9Qu|Wm?1^ -zR>@0Rx<8lUh0AHLpV}Ys*u412jB9FsY%OL&FCs9Of>Fh?=fu|jMj1~jo9JG? -z>Zw5|WRhlsm*X_+;~ezK_Y%*6P7Wbod%&2}T7PlDv&98#b=Gc8*Y$lk#9qS()fq1pJaHo6k23K^nB!;>W1QH*En$_ZYD)+QNjk5USV<5oFl4h-vT)z?A}NZP -z{v24?gi`zEyTu&uJ;{0i9hYJX{6!h%#J*9+%Kw8h -zK76B$Js*6V|3w)YL;s?TMb%B_|3w)!_My1{K^ZTZ3(*9>QN}cAka*xLUB&BS%BA0r -z0>|;T$i){VW@JT((w=Nk=Knz%&Hp#bSQaX#oU|Uw(R%rfG9nZ6W4(!dqm1s*A5d1` -zC?noeO>*l@jrg-op;RSBhr2BcA}IFeiI!0oZ!)nbP))*Gs6%ImfZtoV3P}^0$xq;1 -z2XZ_R@0Qy9x`L~7Oh>DTTOUX=VAUigSC-6 -zXfY%9^#_7;jVBSH&#(^NXm(ZGA7d(5s1r5?YVm?$AEMA(-Yt!GnFbNTX5q)eb%#%@ -zi$lhQK&fZ16Td7R=dwBx-gP68OHwA>z{nL^V%^FF@6wK)Fg%Q`z2nd=&m=PKmcpL(XdcvX0L@d3 -zNu4rtOVeR0N+~9euVlCtDk1W5X_oj8Zr~ -z|DcQ*e^JJ|h}C~lM%e!wWo#fp`v+x|{YDuT;Vek9wlw}@7iG$#!jtktXMJWX$5{uuTH -z7?1-yf`iRWBSHNRND;E?58$>-}Ic -zZ|#)!_2MIxo-&^JHDtRVt&8^#5?sm}3C_m?;8ZLf@T#PCi3>;UPYK;j7J4;r5mX7y -zSEt$&Xy?*jTiW=G9E3`d)xejGSdGSWjx&^4a3%u%7op{M5;)A -za2SNFFwpDHTrM71l;QZLnS)el)g)2ivn#K;+1Z5^B8razU;X -zL|?#FlMn?ojVYRS1CG+I+ia7Q1{lGhEKYu>p-y=`X4JC@LVqT}xM+V*m}McU -zW)8}b+V0fc4xoaj-I)tPdRB=Y_MFVe -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 -zx96h9#SmA>euUftkF0* -z4Y0C#MrasIauP4`rhUPP7+jdbR?%3-Q|_wL`vbY3qdDKfppzBQu!7?zkMQ7@ZH`*G -zq{0T8H0dE+hJtr#b>wQ$S&i{LqkE7NM`K{#k|9E{?4d|d5)<`3Oy`b -zFKZ<Y%C -zFVIJV5;>K_9r-84pJ=L)nKxGfCYvtU#fg -z#|Z^Bx4XpGWXicC=P)hqxG)-G*FDS!2DLvvS=a?dVnYETatCW%i5w=o3dn0Z#KLgq -z>k^<2R{8!;Pb!u?1>&CWmZ{-LDVWmLPN9zx=y5V!U?q`jj-aLq;hHPT -z%70OQT6YQ$w)7r&Qvl_B(hWUwXhw-Ab&$4^V&tl0R3|5pnwe@PEJ+7u1}&L@k{*w6 -zARjf=fR--x`}HU#j99}Bt7DOrWSe@HMipl4YG~$dFt%I72F27YOfrW{-AaiFC1VTi -z|0Vg#=wmu1#y!3(V*6_CA)-in0(%w;5CCTC7q72T3#s7L>a_hxWu+d;{1Ik8hH$&r -zM_=sdg0lDP(iy;NxCm!p7&|}(r@5tvUX1R3y-*F9lUwYVHCN-^;rIx}u4Jmxu5ZJ3 -zMlaLzerGZggIwt+{o%E$*nDTP(dZG-t_}JF;SL0M_|LOKTtDM5w6_2j1cAf#uK7TX -z3OFjS5sH=echu{V2!>_s5g8ZEJa~&~)u|$;?1*Ww!x7<^v}qkcQbxaP-HkgywVk81 -z9|{j26kItDepCCt&exLk!+JVU1XXP58_rDnLBQOuI$^AdCb=*(Y7L@ROz!k4A4|JQ -z50=O`6bnOpdk)Tnjmof=*Zvr?(<6D@#C!;xHL9Br2+@C{sObE|yawb%LgBBnkkB}O -z;Ah?@w90Ym+>Zp{i|q%j@YQ-gCLr#btF9B`XShxe=zB&*q$vY%N3BwvR2wz~m)Hk{ -zIMt?%UxrO@5VaV&M&%5U1dL~MDM0GQYC-{A;kFE7Xnq=d5xh%y(ZOdXgZwlnV{Zy# -zWB_^UFn!Bkfg>zxLdid57im>{yZdHjmlaf4&k{)WBe=Or+fm$2|?*&8SNGOPes$RkR|r6etpf -z!UP93k*_F5HxFuR65ZuMy?02a#PEWtqJ}8$O`K3f>mwARkG=)mGe)Qyf{7Q5dYSS=~19PZrf~C|9+S8am{Q-xh -z?^N-MQ^pd}{3jirgcJA3D6AsFQ^_=Z0>zgMb{c(IC4hst`hnA0)Jg&n*_RSAChF0J -zb8<`kOg>3k@p3IK6cS2T8m$w8CVg}(I;Neea~^#@(uoeE8$zEy1kFu2u%oYx(hTpf -z${+USWIi;%debT;JIK>bw}nCY4RrWTloNx)SDkCi8D6)3`c^FoYYgM%S -zoDVYaj`T>%>EzA_k;|B3OJw -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 -zTGL -z^ZJ(LD#|fc!F2@nhvVPZR=S$YBW(Ak{KVt%#EW8EBigRr+f)X+Q&o4k!e3MdLU)P{ -z`aps?*^~L$!09~O!gW^Ily6#6(k&Q=>g|ylb^m}f9hjy6us)rwF|gGX7ajFzIZE)c -z=dd>dyPQ-RuderuwZj_XRJia-mmm1S9HT`U@jo -z!XQxa3E?l08b~(T&T9!PTHPR9kwj`4k^9yTU9=sXZ{qz_wD36wV6qPthmZX^ -z2Tr`fD&zySl^B}FN@@FY1ijf|>sD<79$?-42Og8cc}bx^A5-<{^RBfLn1BKxCgX@W -zU(3fT(83>)wS?Oan@}%-eqw=8EBm%-VIn=^i&C>&hfC|RXbaGBk~xc6j|ThB7_Gue6&JS0fT1mk%ijgX;h$TXp80?m531v=S9wWZ2*(zmy4W_VZZC+3}FK*X!ci~-!2>vBkN -zT+QvJb%U-wnOy5hqJ24j6F||RpwPO~b57$(H+QX=^BR3 -z*oGFkQYM}yOoaH_kbb$PytwRf0uAK(k&M?!T+PkmTiR_zG{JZ88Ut%hnSd~#lnmZ4 -zyN{m!2PwYCif)Ql4+wo}*bLeX;0z_Dzf4X{SoAtb1zUlwI}sS8R75hykt{tXaMV(- -zY+bXaZ&PAL-~>2#iY*A8L*;smJKAIIoR*k`2*{5FY?XS>3imuX6ZMN>bpbZFyRRwG -zf3Jb3y3-L0vGP0-ll`>7t01H)ehdau+Ve@41?vDJCn#~_metV=wnS&kpP4C8vVRu? -z>h9dC;^u-u3hNi$ue*R(rV7Vp6b!i&;CH1k)W;+kaP^wDJ=Kf7oMoaoDq-qo=aMRV -zPPpv_9ibXK9@R+ooAM3^I%HG*nHrlBX@{B=!wIym$+6$7xR|l)fVGRgF|Z -z_4CLL6dE25-BqhI_g*$q>s2Eb%0THn708v@oF?5JZSb;W%?Kly*={S;kO)_I@ligV -z{nMtV(&8o1x0hf^5;yzk#KJjHy;aG;C3Sm(-(sK~L^<0*SqfZwM?R}a?5VP2BYwlE -zF<%vwFS_tN5)pWi!UgNg11XO4N!k12mbF^EBk=?Mke9t>mLc85HrUE1J46Y|TE{%T -zP(d;wW-m>gB7LL5JTU=33+{OdyH`2n;TrdXNk$H=3sBHnkJx-1pkvCL5)ZNCb7Ijs -z2`rg%faxH+s8!m8RTdjGwHE98nm?lcFI9xiAwOs{tLayRM4;0VH6KRJp9RUTnDiJJ -z%`*DQZ1N9PJo1j@LA)jw|A#6HIo}Ui&izXj!3@DNOh~?|B8|`*F*?yVRV>N;rizXJ -z&IAnu$$^Y+;hW!7@nP^pG3t4;G*$64 -zYqMRlEtW$Df@+FvhqhLW$XZF!n-W6eHD8$r$rz*tQNuJz8fVTRRog?_7sPl}*%phw -zNhS$e>O?jD3{a(Cc4X_OwBLmeqE&w@4YdV@;1y$xM5&0Dc^aXRzo{b3zf>{tAF7BZ -zyaYwbD%nR^^Gy{~n_ny0$3Pxv>nhB1#egfFbN-=<3jb2Y3d(P)IGxa!pp`TCO%?m{ -zzNw-HlLB|E*HkOhKGYIw9th3HZ=fJf#rH@_ -zQ41Lb5N%f`v^D+Z>lJT -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 -zY_EjYnm!cpNoS)(MfZZ- -zjj8;f-A&`l2T;t!UsUnLp6fw*l$swpG2!KcsM{p#PC2kE1hr^zC{9-6XPdYZ+1*z7 -z`x)FpE8g^GUoKSp4&9~gi5zPXB8he|ULbl};fCW1NQCX2;n~PhzSn})ht*GlkLmZZ -z5UzL!u6}PHY#GNmoYH{$OGf|tHo6L*`w8Au233EE_d81Yux1qmuY!Cc6! -zFf*Fw_1$Ti{IEJz%tB=v6v8G)2qWG{cP(s-JQ?=oy_K6PGc`?*qlixJHqYh9A;F?R -zomZ#IURn4|TgjmP>im+6*t>n1Sm{u-j=@$S5c`blUN!-#Rs;)zT!v98>H}Rdo7_$A -zQ7)}G{n^tun-U~mG)`)rzYBR?D8z&q2W8=Yd!58W#FasnpWj}31(2Vrg>^5k=pwQ> -zJnmcz=*xETlL&VKOOUcJJE)Xk$Jln07=Qu-mYe+|9siivhO|-2gx|%#rfiR{yf&b( -z9!D2HiVZ|;CNygh2sc+BIuPj|Lwu5DuOkhOCktB!WflUiF7Iy4;t@V;;jr!K*&hYyI{P-eke)p+(b#M*v4--Mw41 -zO>X78uFOzX7{8tNUq=yT1%$4IK#JTVprniV{L(_3j=gEK2%B4c4gR!Xuw+G5kBG#o -zS{AYtHU_0-cEbCxjjJhd3Hbb*v6=wEOoN6mH`b7itm3P;QD){tC;Bs5?5cO4kN)p6 -zyoHbLjeTAu;vEj63U_FK1Ln9qguE}Xh -zJ5S7aNfs~#*!Mos@Z^CGZ}g2B4b -z-}<>5BT3uGrmIw>&Q4YIvmU^Qkkq8o*-Ov`JFFcQ>VA=bykn1*4!Y2HP4DtL?`eWv -zCl8E2SX6$+l7BsiPLM%_T6}kw+ -z{fhgLI$T{Q`w}!o7_MkYGJs8^C=U18Y1?e%sFYrz~V09jm~q$u+br*#== -ze?nfEdh=Rcy|Y`%_xKEi-$zb<0Znv^Vqg8Z_=CyEW{i`7pMR+2!Uy1>c;NPD#*>bR0dHy|ynE1~wP -z=BFgFbrElT4jj(5l8g>EUe)n;d=x7C{jYdAO*7d?skCNKWfSprSjLG>x%Z(LZ17a9TvIA`Z`%MM|G+bYd!{sSi4~c69FP15G%No+* -zANVTrQx8tM_uFKnBD*j~CD>UEvAi~WiIT6J@6H8&7PIV8-j{s?c=miTYp84=8C^Lx%b(#SZSmUUEcUq;E -z1Rkn1qF~T_t>e!u^!bg=VycTtV}G{_l^0&4tY80B^;K+&<0SD!1ScRgZ}guiCs0&# -z1fkJPd$-{a*BA<%@a=#@qi`{P)R!HE@2d5~Xp`5b%++6<2E~r@a#fr+e59jfg)uCo -z>_TA?jf6w>Pi)%&-Tod8d+apF({6cn-R4hV4Vzg)Ws02Nfyk_+=?;nc?J{~=c|>MW -z9J%C0Od#MrZf5w`<8A>Jp6?Dx-3)7HyVCc?XWw)3NUFb_z-TvM%j+;N@xExCBzi4wna{zb8fELH&AfVkYW&7rVYPm)^4J)L;P(gen@CP=Q4^lIei! -z!aRi-*0`|&i^a*+yvpB8Iy^>$Ko&qUH%I1}`y>d^(+71pk84MpYqv}F)`EIW`rS6H -z7OO -zE527Eh_KES%I(`l(9oHINzQgE`DbgI?~EApN7rX=O4u;wN`h?Dx~3wBOka>#SBw$6 -z+vm-k0#| -zx;Sk>*L&SbO6KSl{A>E#c6VV54;*tuVf^@Q%O>Eb*&mlk@R+8W@*`B{hTm51YnIqs -zA+}Pbs*C>Fc#>{nnx)vbMkvQ&*jbLq9SNy1tdtYoquxZ!8E0mW67H4jFB~IoLCxJu -z^q*1Xx>ja)wv#`$A{L2*7T#K+-cexzkyRuJwgc;8j8|6(DKBu&K;UiyMa!74ebvF& -z+r#hG#b6Bf05Bch1OBfE=3)8lT=js+7xC;e&xvJQ%*WDMDsxNTY^#O}N(rz?8C6fX -zmlRN(z$1T0o-o)waPFqtR%UAH!vYUoJ$~O$|5d^W@|@%I7aSj=)Y_(eYhe$($jQo|UX-*^J%jHq -za&)<9pcdrt6AyE-(N9fdf`02yjM7nXy4NQ6-p`oI2x@o2zkWMREW~3s?`3jXDIwar -zKotl?yVqKKtqo4RLN!+Ee|0Lc_+6rk1t^)H@7#HaEr6)Mm!0^?Isk`?YGQL1s5?%& -zORAZ;;lnfjfk^@1v+s)ubtPR`mo!m4n<1iN!jlrMz@#GOAqntbB$%U{*~ou0&x4M% -zb5p`nBAijTf<s6O -z=tIq>uDeCugO4atcmlipjYh%2luFJ_KZBs`{*VyqX^@M5oFtB`&7nWLxN6}qkVXyR -z4@-rnihX)0o`_Ol*FkVcG3;>4xa2#T`l(t8D!j$dx~RaUlqiZnKrR+!aink^bqo3k -zjoSe9sU1RDwawP4#}#7%Ob+O>UGOzrLKk}_S1LeN$jl;AsavviSb9Gf2Q3QV5qiAK -zxm&oxFmY9@J6Jm~!NZ?l<$_1QQk1+iPFD3?mJ0*y4@TIuy(IT$CkmZmtiVhV9m3m}h+wC<0tIIhjBXX4Y6=!_4m*(9Tl#^`CP` -z&8NIE(5LZLMp3*A9MT@5xjbO=HwhPPBv)QhN3GVi3mPRb6D?`$STSK|TU0^bp!3ME -z!}yuY2f$JHSAH_+!O#h_@bO7DJf68X;`0v4-dh?ib)#S4Q4Ea_2<%Cp8NGeSyDI%{=9F)w(qdiOiPrBZup -zm{}(ypkWci#R-ep4aWz+%2JIDG>OqyuoYeD*2qx`5m98TeS$_@gAr^bFDgFG)?X~L -z$|^U}U-;*PQ|x&8O!a4u-qju1a;v{n9gSdJj&glyhh3`<_0FK{uxh|r}wgN>F*qiR^`Z1Igg -zM`pc|{(6v3+r-WT`(%3XI0%FiNy(J%zKfhl*(!Y{8TZ8sfD#AA^F-P(7qGrHxmEnD -z{y;<)s=maqzOsE^(@F`d`!}_)=Gw4$sYp$SE1v$Z{%6El3$^5Px5#bg}9b-Qti@?GO3o0_DxMa?M4sR -zS);gHr08dHsQRe|p=9lTMBXI78w1}J$a`jt#;?EcyhSxiETg-?0JtE>K=FrLv=B>6 -zZLeCM)YED)%!9D<2#W^7L3$HL<^w|q8)m^yLe98%!&#xKEKFU!b<_1W+o_wt3wRYT -z`0_R1eGUXL?8+-^?1uO4rgU>+ukI$>ut;?vx(P6h#Z$+MY-h26kL?f%1Sa8OP&amj -zIl)I!=q~}5I01eC4*-ooa=*EQp9!~88B{=tv8rvE9J!YhVLUdLo9KK*8`zeuqDLh; -z8J7+Em52N<1-=F(F@5oB+wA~+<>HtID|mbmD@;{B&~eGX_NA5duQdm9R~UPrzCJJe -z9mv3A%VA^}$=l7uXbo4SE`I}JnJ?Kb4VOO2k`68GXBnZfnp!`uuL(V2o0G{CredDmt1zrS1HD(E10=cHvLq>p~RfUIq -zVAeJ3f91gavBlZ-1EDolV4gav5Bg;e?zXUw37=C5O20+uOEvG`J-lsQ&V4GgBGnTE9 -z2p6`-DhSu)-)OvkvC(gC+W$jdTNN}Y -zlr7nXCFh0knW>omm@;WxQfVb>LZRFW85s>{O#q}EbC0*SM&3CQ|9z7%8+i<>C=#Q{ -zlgUvqF*2K&kVipK6m61(EgZ7=l`3J!k*aCKBTGc(`vX1-94Chs_q;RFe@PVboT0r6!V1^u}wFFyNc -zJxctUuGisQj>}~$UPA(y{7a2wR8+CgP1-&>rIJ{ZTEe!7N>KM&w#Y$by)52d)VP)4 -zdA@u~oO8t_LI5Qm;JqJ;&GpGiIro6CBD|rf@Ug)DTB;`Lo?MoB4-Nc)(f^QOMGhWf -z%Lyc`o=7coo=G#F%z>m+EO}UuJlsJdp)=ZSHy1J8J-bY?hg8PfoD%;&h>zVN-#d5! -zG0Q(*yd;M(GPWs4)$H2O7hgHvM!ES%ADZt#42i1&+~}hn5@PHwSy)X=lPHtrNC?Dk -ziSlbjDsqxQ7#57;qh-=g)}->4m2KFt!C}-QSWU?Sm1IC7cZ_sf)2{<-+;o-4v}*K= -zwAYzHZxI{Gk^nmHdQ32)1XvbAprONI)N2zHkVjX?n6SKltY?1U0vZl^P}$)Zb#&q! -z#>ac?_xrEx?6pT&y)nguN}@f4M*@45zR`i{5?N7RLI)cv&y+;emB?YKm>sBx7%la7 -zZ|4@BJ`ZQe*8FttS&clACR75GmIaL03|+1up5W=(gDWtik-VpS&WR9aY2~v`a~?s| -z>3hczIGYGL8+7uics3WT;Ro`wyOf_lvW%30)izHr{yE-sKLm2YjT3t -zHO7sf3DDF9@d6N|%%BphkwC!R+<9uadrIH#ku~$^@N%8ewQn5|MNy0OZlzl!X=$*o -z*U?IX1hW0XEX);`r7&PpIi#B0oA>fA#7D`A-kpBs!SKzgFd_Bn>$A5KA5V|;n3YrC -zTwTYZvR!Vze4?54TvPH%nz=+8mt*pNC|Kyx+XA81RW>RfKQ>hPq76O+x~Z3ZVKq!+ -z3yD_ajBVFq&MKh?1HJ8eop0PhM#Qgna2DyQ-=vzB1?*DVdawnPS4v>KNr9xq -z83^0smGWRPTuMEgRS(HQNdn$JI<$P@=po)1m6%%XB#L8W=znarH-FPIl*HKau$x;N -zG1e(dTLOmx@0r_%^>%TNRWFORGQ@4dFlF2f>(>~N4670*+w9xuV^LmEuFRcYO+Sa -zQa$+qcgl7Clq+aJ^UIJmA9Tsl1eFkd%aA6?WtYSLR2xn&O(4xl`{JP!BRU+0sow^L -zTiGesBkF9bZ`zdbxHAr=H_Kknlyz=%9llXd3+hoI!S%TkNS+;X$y&p3SdVQwubEa# -zY??865kzaIPwYe5wWv{b(b7F?tYeXstTV3vdo$hhc}e1+5M@}xqS~9$s$LAX;;`-! -z;=RdZK8OV96m4q|8wvKGA(?=>O4s%C^$pfKq<|*+^~geZ5p_u$EZ{X5bl!5#lX)6o -z7?_%Ma6lSeuG`iMK`Pl6P*SW>+%1y&L1G}o{z@_!zZ&4_4bk;rArODOD0$x^)z9SB -zI#-F!*s0RltWh4?UIaDErc@H%==Tm{n+q7?<_+DulG*}U<~x4U;ssSZyvws>92~|c -zP%i~T*JO2xz`^7sdFjJH!@y-!!8Bu6u(0DP|C03`p4;cYQEAHJ%Cm*%5PUjAoZLN% -zA?&pc+eYZ0k#Zm(xN9XCBM(5N#P;*AKmMrpaglIpE@Z&KI7pmV6tE$7oS7@cbD~Er -zr^sn$9XjgvwlEUX?I&(D5e{LGgs7qrst%8-1+!$|V+_Pq}FGS3bDM -zBzc1traq>Zv)=(czoGh%YuH)dV!#qnq*4?u -zv5-n_5f{{5;t2sGwZHPrER8lOSW+GgT0RLMSj;5)V10(ViD8qxUM{Ka8lnRXr41gA -zu(Xt0>#amMb!o3fd5g`WcUjpb7-`(TrF+(?!#Sh^na|jSH9={0stRiUvO9VKvzq%H -zY&BbFgOKvhXSw&k{yN2|u!2#Bp5=NUz<&9`xLm?8>-1IYP*s&9Zu2ayoTCc|v@!mS -zT#3R$QD^Zjp+Hh7K28Z0!QikD$J*NI6x%3XwSrzysB^74HSAVED##XCa}O}*ndw{L -zagBV#3mHmC{+-eA^S>OeyCxS!(76GPc99oJ3>Gt&Hkrv9BRaMCBn_knOZ0~TG0rc|G=bwKx`hP$5cmh{Dr6O=t8E^%C6+iN4UsKtlBDRGy`yDxY9E~;Zh0rR#tuM9O6@H8GciT0W}Qf+)NGGNV(+U9&%mV%*NYNaiYZ#$G%C-cG(*! -zkDFa0tf4=*g0*EG6&>T*mP7Kr;k=0zurE`kyvF7=x@pksK&|s#Q3)f=FlwbB_K!%3 -z2^%^PmpQwq?Wq{&1(T!2LDC<9i@bA??lWC>jNNP>FjqgocXG;O>F>-pN_KiJ0*t1k -z7Kp11O#&y0k)bxQVhq|G5+sGFY*0i?sP=)qV~x)2kC@l#1ed>fK)(XmsvWqDJbjly -zGL1i1ShH{OACPGMi^~e!wB&kebBq_{U)EB)ghxkMru~i@H3rE>=lA^F=cBvLm!d*} -z?7Z|yc0kHO!t(Nj;0fxl7EBcrCJSUY8Dqot0?`vIIgtM)M{hS^aP2urC$fbvXT{iM -zW$!VuyFq_~=ql`RN{iFRVi2_t4Ol(EC*mhyNw95BZ$qkU^$v6=idbPLimyTYw&>>Q -z9|h60Jk(?MvJHg8QA_|Edzq^cx)T^r~dJwzUhthI@VSL_D2pl#! -zJ{EMzmmuCfi2}+oY?4P6oB!nqpYIiq9VzU9xH`x6N(@P%q -zlq~quS9-m8x@$qo0015=JBs9_s3Of;9MVfX-7Y_B4dIg0RsobupMdd!Zn?$Gp^ -z_KJxp`^eWZHms#URYXfp8U#0qUtHQtJo>7+Ft>OCTvXSeEtmz&DJY{^pNyA`<`1hh -zq;7O|2a-&Uk|5(uE+GSz+s00jBP|~QekKNV -zo8~WRx{L_b@YIi(gra$*hOm&*4+2Pq^G!At=$f2> -zl-lhyy=4uiUF&>0JA+ES@f!j3?sl*4=rBka2G@jNrswV(V}B~qdI;dKUT7Z(?TTfLs`S?gQZR3S8iF``Hz_VL#B~9KGVz5QGK%=y~kih!U}#CN#7eI298teH1zQ#9C7%k{NmebVv~L&M_iEi -z+}J+DT#~g7s_|_SN=dA7ojr;|B|$QVTOOfYZp)r^Lw;yF+*fb>&Kl0UcdW}WIXmzM -zfZoPN)S3fwRr*jq9(?ov34_~RmQP|J&yQq#m5tbE4qBRD0SQTwEmnI6@diEQ;vR4F={A1FxlWTyY`t@SW+-aBZTST*NaM#? -zF~Ri#1Ls+Q_NLt&`XZhAy`J~R9|SD -zzIp>hFdOrVdlq&i+&V{5>-|>X*7x`vHlL}IRT1_4uug_Wzs--SSHkk~qNVKOkzTm|54K$C -zAc?fy>Uoi~pI)X#=EjN&9f(3PV3AgvR5jtVYRpW2t{O7Z*9NX&vNDu%;Z&aB6SByW -zumtn(=0Dzspeor$4&TUmDNe96ePNWudkq|`YbTGW_?I>6Q2vC0$`RxuSsQUsL;4KY$w*VO`%HUTZ3H^K^g*7jCY|B={K9*C> -z94L?fCb0l=JCBfHeC&3Bj0H4D-W?EUWC#dQx5t$-PcFC2tI!nEG16b15i}%T=w}Il -z)1Ihg4}}2dL -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 -za&ap-eJ_Ap3D!(|x#R?9GO%~+^-6IyC!d`O -z!8F+OS&GQ!G -zXG{9T?X;*Wf}sVC5)PX*DMGyD{C`KOor^)}y -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 -zWN}gv5rp8e%1ldG&}X*PtP36(o?)da%R>8 -zvmr*Q9eahXF)!loovw=Oqy}IDe=;2R8zD6B>%OXA(>mB -z@g52QyIl(dhKz~DB1w59*d>_D7mTejoCVIxq#!Gs0s{-PsuohrjaeyH@75BSR0r#^ -zW&IngIV4}Hc$aFV+cRYR$BewC+jH6m)5sA7vsQX}&7l%JGu)ww4I%P5my)S<=uuaguXY3VmGa0 -zLQG+f4@hK*!ZLzddAt{t?a{j^yeYvt;6_pF-mg()ec{nE>`+m4U6RFJICn8#x66BF -zeWY+5M1|m$bc(lXcv-*68|;s6BB75t1lrOKc{Q4Lx}n}Qd3V>M0`dafMvfh=Rqp@xj@s!>umscEJfh>;*2l+uvBh_i&dYoVj; -z)-eHgtso-oVWA9&W-xkFCdw>S{M=mkWiF{Qg!RZe&NN=!bD*{Xz(4 -z8^({!6D>9Njmi+{3wd*q{vYz%tf0Yy+>~8ta-OK8ITe$S2`5JrQY%ps3Snu87;89d -zhEmcpw|HwQ=$Zo~$xjq!BacB9g<=#sGg=BNdZx1%bSemnyw0R>3y1jtQ7QxW;Rr*kh)$VG#0wMJ5wn?r92{b02Uj7wWIKBQS(fv -z4O~gwiJt43to)GE8X#emR&%CH5*4L5h;y^b3v)$@Z!Rqgey$lU*wq&&8!jOOSJB^X -zTgvp2wTwAyp_NIy{gCj`s%}xxACto4wZ7&<$xoqeyM5Z4W=URy0+{%vf-)-HK^L2} -zwKsAmvLv}wZjz^9eQkO~1|qBJ=B-KfTd1PP=858*7BdYFR^VHVT>spWk6j@X+*trI(jOZu$svsNWr$HVckRcl*wWw1d6!8GH*$ea-cvM7K}ws)65>-lgw9Uwqd~o -zcD)qAa&i}_F#{60VmMrzeiiuVhL%oKtI;B>MATo -z*)~1^dDI%lgv;yOyvGwx@M9N8o(+DHM<>RHyd7h|kDsEmmmXnu#VkXV679}i0(h|G -ziZ;xt&>iJXXkbHOtfGiI6H`nTvkmnSCAePB6O2ErGo?o~lGkvKIT6AvseG1UjKhpF$8Q({<0WDC2brNto=vFJY&a*K6c!_Y -z6+znA)!dO7ZyKuby~C4>Ke=yxzl&%{p7{+V$E>N{~R?->HRZu~`zACToynGl1io -zu7&YYw4zrda=CE$W~7>s6t(-amJ}Zkmv9+{onKvD#p7u!?R@%0HS4jaWHUKqiZq&S -zuzo1`?9tl-HCIay%5M(~s(fBYr~%!SO+IloOkxX)R{e;_F2$UgLJtn~mTNnn_z4;c -zxpU8ZM*e`c$e#Kj`ZP`8sgl-%-Y~kP0PdXNnBF)7VQXAc95IGV$)~*XaXV;Iz}tI+ -zmQOsyh$}`VCf7~z6X{=nOC2#I3EV6rEvXRdRT -zy7bB7De!JNoUBrp;9#*THD0@zD)(eMgHWUCy{G8na@j>x!I~9qL~P%+{4D&Kke-jr -zWB>dh6mPRoHEbxzyu{Dk>w -zK4Xl2^}A`gEP`g&p%l7=+c=NT^)M14#<(IwVPB(b$~+2(@k$sJmB3>J&|9;ukM7gpe -z6MwN>r6RgWe{W!B2UgC-f2t`1P)x)G<9^i$=l5D2?}fxhT=AVP$Yk2b8B@3wNjM59_9w^6yWIipOlWB@ifO;UokcHWL) -zf(?sxe2Tuy%PI}gG(+mIas<98&$rwu_HQz|zFz1CJx)3o^sp53J3M$=OHnmWe`x3@ -z?{9J%alMT4bt&$P)1}T%+cP_!kldZk6ylmvcwKL*N@g!%Jx6;Aeo@K+si>3P^7e1d -zXI#o$>=Zi2MtD?nvNGF9>hXA3$J~p>W$I?3tq}O^J~UdD3$c!ctp(?91wlfM -z?=aDna)y_1?;Q;ZTGQELlU@5Zs-UAHP?OBopzJ@X3*SH1cQ^hyM{X!rk69s& -zPE?dgC|Wn%N-(#SFbD7uX%qfYDSF-L~HqDni~Cxf>K8dhx!A+SM+h) -z9UO5$3DiGv22#RvIV -zWL72iB~&d6V1HOGy6bWzwFajtBh<1rP>sA8G~Nw=dh@Lb((?_MWWh4!>{I{+WN-4E -z7WXZsRl7`E|0sEuNI-l#i$O*&;&%v93kI0el_Z+ILc3x1i^51>Gv={oNWh}eX}ac{#BE1p+QykXSNTba)3#Dt5IQXa~ooy(lO24jB00Hk9R0E>|dX -z%J&FYiLEnsV`H$x?B$hktTlQDzX5>9NDsWsjN=1 -zBlTK^^Bznta9OK7Z~zyJc!H4q0gNEP?uW`(x8$isELt&p+lgGz$EC9lk)oN{2^{sM -zCY9k%NohG)jwF -ztEpHFfIp@Enuy?`92FP0bMkrOZF%x!(~(ZXTK5Z;1;5tqx{@_VDv(3JSFnU2W6g9? -z)i*DF0i<*CWF~laSL$28p=<(q -zXEDQZ*y)krgr6d;a3g5VVj6-gPKPmJBq3MWSO~HjJI_zAHp-=xi>H8``~<2wO$&0Z -zFVbdqZ{{1);}7IEFJDf+jito+)1m{`q3lDAWQmOfu7E}=+HN$4)xtv-iP|V3k}PPv -zi^RuUw^;d`_>t#Y37iPgN*5@>2AqLZ;XC+(`jKN)z{jzn5a1+o8E%K|#M_uT7BkfB -zRP7%%N%1=#j~(J((iFx|XS|jtPJXFb$UWbijF#x6(4|vb#1TA73wkaFI0gKPje9Ko -z1{^SMIYjb(c}W)?E7u2sfW1qrH0z-*pfcH3J=ho(WWw)WfLuh-J^a- -zQn~I$@fMN-w><=}P{mkKnv6_~PB_?aNtDC5q2pA{IGjY)_(^U6ve+Y-LMlO;J|_o! -z&q%0A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 -zJ}sd$2923?bryHCrPH_k>tE!0&Tz1#) -zPM9=N$YK!;u!I;f{iFbsr3?2@df(9bk;V66>+Yaj=Kl%3E>cT}o{ST%8IC_R>0*Cx -zp1`0yU{$8aB>`+t;tS+VsumXdg!8k*JAvoMc^5A`=%cjpVyMS3`{E^2!z+0HI|%nn -z{s*~^{yH_{!i-fBM>Ks>t+0l$|6&FFu|{`|v7xEh`s`j+JBF@Up-CH%&&x>BnsF{i -zWX}p8t~5`{I4y;lmV$O%ICdqo4_KMo6On+*kO{`OF!we>lqhzh1T*uahU7Fs7K)A# -zXEPe@&^nYKI4yV7TzCBnX -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 -zm3L6X2-jVEgQS&yXmsNnrI4HR3% -zySpr`3^;nH;9yY1@vi}QT_D=8Yx>~Wp?z%|=tq{8+l4)8mc14*MV@{a6O~1KK&#FT -z&P|c%A}iW*-q$CI<=*|ky&14NL4GSPSBQf3hODoCOkt2UkSpTK=F>+Q%Ls-L%~oS{ -zvCOU!-x>uFDHKr4&J`}UrRyfEIbGnf32wfwJzauaKtfi@g-hC2{7OO%ES>{w -z-K@y;S-zE`p`9T=L{t4*)?DoptMi9=68%8;py_ -zM|IdCKkopRSw_R%oH%KBQdlrqP-_nzx`$|BQrauVtS+a#>%zoQ>Teg)cSPc=3+dO+ -z&{lUp5IX4T(6=0QhdzlI7CYa{zTNqaZ(s~~ACSIoDDibv!eo?&{k2~aB|uJl&S{$f8OZ^E?%}rqh-a8 -zE|VGtiJhY6Vng}dPrb4$IT;i)4Dad_uusuQt}pqK)nnjP2y*^1Lo#U!Ec{C)Fz25z -zWW4>8^2VK)R$rtIOb=muR5$EX)>2I4w;I!Sh=!kQEadMKJ{H8XOAB+;XtJA&QEP1T -zxJ>Cxs=a*Bt58e&aUnKA{FAo@LVP#jE{Bc~kl9fSFpdQmxpLJQwY+GTie-+mT(F7M -zp3qTu!q1BNY0_XM?ZKc~@ldcA1l&P2DGu2v){Wa?%1_l#_=oAkg5$xeDb;FxZWAd8rr+tG9e#R|tjQ*klo+axW -zZ>2e~ne#)d-$xYO4|~wUAH>b7v_gg-qP=WnXBD)FuP=X!)q#J1`@9(^bo7R!Kw>gA -z33tqVeOZcNRzpxNBsN74F!+dvTeA5f(saufUo%`gvGZ_>D{*-#L3$G;Nk`g5!H#LF -zFMaZYG^l-h;bqQ%u?%tmqFYKo$N@JWo2#Hx*+_$yS6YKs2EFR$;;ngzJuwBdJow$V -zhn<*2ok~?aBlN)w@149@G}~1V??D-*)F6}MCUH>EI?r3j3Z4$MS#>GA1vQ_Ju%2nj&+QvEyuSl -zgy3H}NEn%UU{6d4cT2y&QK_#|u)6nN>jmvsb~3iSQkkdu*BEKil%XM2I&>-rtKiV$ -z7OY1s2lvy!U=?NwW0Jl6RBK>1{UO0msDrQ7QyI&J*;5mp-rbGF4J<<9zmsz!P?|e(1BAmZLEHmQ;IgR3){1c -z&V7t*ENsT7*#4nWLZjy6SFg4tr;hL1LF;arQR?TDM06QD((N&h$& -zJd>;@vfve!tMtp3Zl6v$J@(B|OO#n*N`0^^n~w;DbD#BjA&~pp!6`#(cHUGFmnOp7 -zpPzdSM(_I5%ruiP_Z4~6*MG!E%^E~##It&1_0cH3grjS@rfRVUF4Zzq$yT1f*A$bG -z99sL*ldjsn_59>rc*q7r^dNo;rtx0QeGn=uEfM#)gGcb`K=r$93vTJ7oV}SiH4!g52WkpaQfN}=wLn1tADlkxQPOZ6Ast^ks -zLf=E=yv&utcwL*i%zf(Pl*RPH5YAh#&j$l2@|0Lb33-xWMwm)FaAiU~aM2KL&H68L -zm=5gOf*0Lz9wFHXT_}>|mJuoDODvt7_#{~xjEc3Gd?T7XDzqW6aSAq)z@HI+t)s#G -zfUYHUSBxr#rV~;QUCzu= -z&W@%F=aU%d0j+)tX8cGqrOC1dl|^08T-%KOw3R -z-6oCZP699bWf^HssSjR>LVn`pn(w#7rS~2tyNXGOsJ8f!lL>?wDA4J8@CJIU&e(0O -z#LB>1AL8bPUBUBMC4E;Ko5{p2V0>8tz|4R9o|=NyEc{mEVsEsf&j5ol#x-d|v$yju -z(t2$p0*_^pqdY2WVP;;fFJ4|NhWf5V(h2xw-vQ>$GQ_Sg^+Dpqk-HF%yAUulQj=A& -z^EGqM5|Xq_hjB5r#laETc)jnycrr?vP*rM%;gJw4jGM0>RoL#Y>GN$7rm!O#WN~P= -zlycd?UE%ze3Am66vc)Pc12LE9{EGe3lA1oy%Y|xpivRO&SoKbkhn;Z&ZbFEJA)2I?ZptWZ3YXLMO0g`sua4zu*R{c_ -zoGM?JB9lDn-?Ff3AGYP8l#g-7svDgqzwADblUP2czMwT{OxA?Qu7Hj7VonUd+}DZ0 -z=_o -zE8A#s2Q2tq2HvOko6{RnM5ltB^l-6$TOxqpQq{;-&9y)-MgAvCSl>E+bI#s?jA$H) -z^r5)T4mi^A*YCu^2Vt5FGGehm_Gp6%6vr5xGBJ(X5y;=n)AfROWu}t%PHX060nXP1f$u3i -zK``wV?3E-u0=GAKITgiOz+&ew7QWv}>w4C^>5ivM<_zU^v=a}(GbTOi!i_~bago!Z -z1Pvha64(}s-@<(QK6;l*bv;0LN)+&=9TmQ`Bg$XxsQw@ASk^JXfJO6|}jzm{x@BJV@@BV5>p6tQs|7gdR -zh<|Cv7DT?8qA%_El~elCj;#MnJN`Bb^pXJm(vBZkRBQieM|H*jXh-P(YDap-{YP4{ -zZgkOqYeylCFYPEQ`=uR+zO>^efA~Br!oRd5;Nc(b2%b5K_@y10{%S`R=l^QQfxLSF -zAOalQ{@>aW;!8WSARw3c8QmWL)s9ZHDdGQ1J9d$oxv?Crv*k}!QRoxRf$N%g|-N_)rg -zo`}dU1J+`=Tf@z&o)nZa6t;obv}J?^VTiYW60|(y?z%JpXiu%{Iw9jA=)e!u>%GoZ -z7Da?FvQDj#@T&;P;VEg*IDCg%*cXifH{D6NEswlVWLb}EDi4<|B~%LA@}i3ix%=Lm -zX?4=EhUo;*GLhCH-4rMb;0ZUKws2$JUBV$9Y_|Jazq&d)eu&KxE2>@P!NMAMz(e45 -z+sv)u00nmP8tlyob})HZLPB!hWtx_i-cSgg%84#0pYZ#Vx^8M!!eF@I@$@nzIoJ9( -z_s-M}(I$gm_D62VRY@@I3+sb<@HDyBL4YW`wg3SI8iL9=+dvo`f{%GEF@|3^DQG~2*@ -zX~#{9wYC4zj&3;rXvf#of3&0TXl&{~+EKLud8F}wv?JetwIg238^Zr+NAxf4h#V54 -z1M(m3sQXttg42CzN4PKTxc@KhC}sRtJ8~KSK$tN8(vBk9UOii`o(?C~riw6RU3p%RO^}AYQ -z+l74VfFyGQ0y%^6AInoQS1Kf%DsL0hInLGyW!bQDAW1&VMI4%MH*CGqjog5Z3$k0 -zz||v^j3p|4kh#8bG$W1?aC3DcZ3UKlOH43mdV51vN0 -ztPRbB4km73Db*1+{L8OWM(dtwwP8P%8qjnGN&!Ph2}))2h(=igo#smV`aTiz%*PfQ -z2acm&_^Ps<_$1-mHEmEpy^CSY@<~`I5gWxxvIAq -zF0dk$0`%K7=FO#k;C3w0dIi}hab-fJzBF+#cWIx2_pXD_CC#<0onPjTDmyx5q74b$owMPRw -z0VA9l7~q2Iv0s(+m=ySs#GvZ)+ctW0oe7<>+}4Gul3jl(Pb>9ys`CWtfO{RoN8-cs -zg|=M*d--OvdSPI>Un;`b=(d~V6!tf9a#3{B3}!jUWLm$o36g>)Y`VN`myR)P&KkV{ -zT;-#1!Jya#fxQXAQ>|IjIxj$VBt6E~2lm0gSJmQ{__yeeE@57Q$v=&4&~-c`=^+>D -zbx7FboN(l^S$NBG4wEB(PV8boA+zMo-kg$Ec1vVP|4e+o#~>XR^f)%vjC^&+5Q=>o -zACqy0BK5;IQJdBOXl&#i?@n_6)aX7Wh!l2W55#R<7azjvrkJXhwni~;!h#N3qHloG -zMTy0uLJ9{rgSFvmK&E1KEG -z4Il6n%d8c)$WOLJtzyM3lco$CoiG$n}ropH&<|G{cCag@FGs||?~7MS(?%}c*C;;d|1dbp!VdrXIO^oZ0-J(xv(G2h1O -zH)#nbSVtjI_PoW*VB(`(KyR$12XI*SUXw%v=mh`(M(y)2% -zy=}=TWDD0GHpV)3=_7uV*~FVOdmUxA#=dB^=c|#L40wIm&=g>TH=i}+qvONmdJ@LV -znwpyTT$?hy1^&vj-Zj*laI={taTd5WgfO38Pi;b_t4kpd+rz;$1?x$cLY(F)ihA%V -z8^Hi|!NW#bX3MgL5YXO}t$0fR;Bb*2JCFHE>_+rWIZSfMoWswyu@YU{70GUwwe3Mvv{aTBsreHh -zK4Hdh9;VGacN};k04(NAIqA!+tK4D+za~$Z9$f^=YBf?Wts0rQb%wGj{;WA7H4E=s -z>aGqwcVyuP3vpcJZ4Y`uSV4-Cv!jO%K|v_;9LK%F>M{Np|9)^sNak74_Utip(pwJ^ -zO*0sbDaHYDQ<)gU;5CWM9plqyBigf7_2!J&qo^WLLU5$>FZs<7NL -z`C|k$wn)7obyf-dX -z#cVmXJ4U$0=6ymSR6QC;8MK6omc3z|n%NnbIRRAYZC-1`}br?K^nN&=?UM_C7uelY_6yl;Fu&^MbACnO{$P!2`!Mzlo -zDy-452Wx~SXZI)=MBG1u2-!&StZpcIb}WgMYWji?p_k)$LIfDDO(!)K1ekr7BybCk -zhIS#Qa7&n;xprc2PtlTX!|^Y%k_kc#R(ic!X>7-K{TkXXZs_FgH7b_;Sc)&t6ZirK -zGhrU~Sl)I^*RaJ~gr?_^43{u`|J-Yr>=3W3md+J8G8=#avz*~@{f@WnwXA4O=a@}? -z)@@8lPerT_g|kaFe9#!KZzTWK*zYolxqJ$4qXI5PdOV(ZaD&t`1>QZC_2Dd&*D)D|kuEXSN2PfF*H}?LuScyy-d7H==ONemLdF -zPWRIJxOKS~2!G6iJ|*9EJ=oOrN~R+ILh}fC-M9D^cYvuXzF59S97`AuZ$G*XHASAH -zve+pI_i2;snK!1K0c}c##gAcaV6P-)~{;jy)_2hELS%=5Ecku{R|IK7X#42H% -zD6P;68Fv$t7^?G7){{+C=jS -zM1uK+d4nch29o$9`=#afd%)IYg(EmAJpb~~y};j1VT){axaRLwY!y{d$Qe%Q>1)cv -zpj%i05_KcVA_ir5>|cdQDFKBFw87m5_ieq)OR{Y#1NIPu2C=hTH=7P!;3T5n!DOF6 -zRz>;mU1*H6jbgfeP?P`j2-N>=8;;=APv+VCt_zq#C65MaTIuLyP|O#ISDDnphNZyjxLb -ztv0Mxe6_xbmS`^{wC%K;nrp0NFMjH++ecJQU>{u8DGq1-qI`%7q)nbAJ$WqCa7AhG -zMvPPxDu*jkHgi|`2@Td6bV~80aFZYqtd}{_uC0|u>@HmblP01O&*nwTG4D~-C{Ha0 -zFvGF|%!qclL=fue!~%9EGej){w24TKb>i{Y|32t^ysfKEz(fxMuhy?g&@GICVx6Gr -z$Yl@Ur?2bP!)bvx%tR~hLpD%O`A!2ZxFhg`4DMBigGJ3&klIN_SbO#Xs3r)mY$ -zlqbr>ymEp`7%A>^O^0+OJ>hrt;9`>jGhlxC_(o%&p!54Iq=}c7Ck=3jv;2@KF^m^G -zUo6_)&1S@%U8Ao;pF(a>wYm=4Dm8t)Yxp{fcwg9u1fPDZ6DNVVz2 -z!he!NU5XRAPQ;5Lj#EvYlCUZsX;5nn}{S~uf}Hd;AH57?WVYPR6Km1`$0 -z1S2%D@#Ub>Qx9WXd#U5rr=jtw2}_gM464fHz&(kqmFpIfIA>M@ -zt@j^F{P@Li9T#0D*>xSqQ1ckLQL6~aby+X>ad1h0>51&G)}h)f)K*JjQl0$^b15>Po;ikl4{ -z(9|>BaI*iSU_c1^Jc=QM%ebRvMwl^U+6997jJ@w@-F1}-Z|lPZ%3pM5vuH#Fu|g`Q -zS;{;q{itE-CQC<&118$UK9$!Aw{6wydCA{tHfsnlC>}{TPZ(n9yfp8@P9rODPi4&j -z@@pt4Q^r!$n$n=gM=vD15+-xGteOjWc^ha&0L_&E&FikeHWaadCE=9Tr(rUL|AGZ) -zs7qwoQj3)Wa8CM7*U*t3+?zzjIhm*f>KA|-w|`aJ#hA_sc%!0>FNX;>Qx%D!nTLuL -zJ4bM*ltHccf`_SBO%a-9?f95Psguri)yDQGf4d4fl67ELF25D=F@k;5_~Bjl4~vo$ -z{DJvcVEu5XIMKc%H>2RV1#Qp{KHc3UjxuFL7-)U5P#I4f*)J-`r83-*xbn9Vd6wf! -zv#`PiOuf2WDKm{CmoxR&pb3-4bNAJVbZ}?9J$Fo8a4W<5K8Hy2f2h0br@G<>&-1uD -z7r3~?#ogVV;O_435Q4kA%f*Ac6Fj)P1-IZ3EQActGrK+0Tiv_Uy_Bm9X7w96dq;}i|M^dMBw-bV5hR;PW-6#s2B28zFsi1-W3swF1z{d@Va -zyY&(1#wuDA55{5%Ikr@zQ-N-q=nON3FS6jM_FJW3=|bEXA1stC%%xD=NqQV@IBJ@OvoFx2aXH@?0s*M9U$*Q2U%U -z`Z#f@v9f+v7tSk6-^NT%lhHusmAH -zVg8#3Rtb9_!&}7T+1V2O7?HI3^A^Wr=Bm?AgSi|M3sa)z6!mWeOv_E4*2r2 -z4`l?)dxfKM*9l^M|K1pLFq%qFcd|DlZEr0z`uVU?{uf0Nk=)&|**4h%nLB>pXZRn(?tO$kch4*x`T# -zTv5pxnC8NI!YpN&zmKBaGrHNc -zpe9Co9asqvxM|DQXA=|U4K|g-#3`J#iYE; -zWXy+MeU#8_10Rsd=609~;_xd=>>ZEmO4lHwQ)+t==%=`RKzcn=+w=Sv>^+3d$kNFR -zxxSw;dO{Voi+)}nS6q5=0EQ!;(WDH1d5MrjfwgG#9a9ngvqqz9<_*XM-Gp6T%4`JY -zEjzZ{Fp!)|? -zzCPPHnJX0HItYcRV83t6FW+y;y_*4OK?pQrpMvX))w`(?*kC4AR@B4O%NN14Kd6=Y -zv$g2>srOWs@FbWFFa|FQ<2fQ$)J -z2l?Xf(U=grlCut`-P=^J)2mygQkIQGL5g1uJ5pU}4r -zNXPBJ+!|GoGDsb)(8owChpdpAh|rQ~CNGY*vD%seqGCkxMV$lxpgcnnVaii8#x+cm -zg+>|CtWy-9ijJRH#Wv*0d`S>~5+5j7z;`Fcg -zSey!xSI2bcGfL2&H}`lmRt-*>c*ka>S;kFEanu_*+y2%);2lDaMfo=B_RKwF0{*6U0wKY?40ViBcb&J!;wUi=@v7({OCGnl -zI;YITf7posVMey -z7U~>~lOM22y%6u&XNDUG@flGlc1Ks3+JoLocjHBE09;>UiQhf0B(^?1j_t1EN{e*B+gFUJ -zNJJ^4cf>^3WwW_g2U{(DjBT-D|Ag)mc8?`Vr;!n}ASS|_ygWsIg$+D=D_Dn~XtHl; -zox>u&6#KYUUtdSrmK?RxS;8D49wCZJ7>8e5xrh2_MH2iR?qdwa+Z2Zs*N#oH`ZuxT -zr+;EcsecDMlC2D{{GZs-^WVUZ@s0ljJHD6xe}Ns7#s6d2@woGU#*WGVz>aJ*{}Xon -zW%Yl>j?Y{FHg+`nAK39gQfcPj!;U@wf*m{lv)D03=U-t*zkiJ#e>49JcI5mo$Bs4s -zf*p1K9qf2%^uL51wf`45-f*o%x{|m6=gX(`3c2xQQ -zId%kd{|k0B|IcAZ>VJhDRsQd=BkjM&j-Y>y9q+jQ%dlhq|A8I5F8@1r~0ndqO`QGDyaW5<|<-YTj8EwSU~|F+oiy!AhT9hd(z*irT0!;Yt!vR@MNkeGei -z`SN@pc99@0HdN6lrO_NXOm+xk%+o_riZkS>tKghg-qNFH2W(B@pH_^&^ShsvinV1b -zHx-yX-|1X2h*fC1-RJoCZo2KTu}UQHQUvopV-Q~_XRuZpk`rZN$nr22F)1qTx$eRfUbx(?)EA*IYh;B|c7UglB)v=I8P -zs9naYHd1r#d+BLF56p!~Lqsa&JQVIyjQyqF+(F7=@cY6Ek)NCpH?KjK5q`2*mV`5y -zbk(nh3nj>a;yX$TcnjR=3bun|98IDw&RbomWv^32>p+H=Hnf3rp`}C2qTMoqLo2p1 -z*XOPu=SGq~5Be>1_x)ae!A$}yLS0sqd|I?QxW!^yOEw>Kh)mwcqtZP+%U#@l3}r0V -zT&9xEw-9vv9pcRs0O41cjet3{E@K59lgh7Br(V>S%~13fYpeNFCNQMlbcWAG2Y`v* -zx_J1-Qk7MJB@XMp%xEXD;v08H<$EQu^Y=-*;SD-zdY{S_m)O*s1Og|UH3EjO#cg4c -z!~-O$)}k75w#Cn%D}zr%`72T|)CBbQRtVAyVWQ?%X}QvvMD37qr#JX4OJEY9H*7Q4 -zs2X2NNd{YD2dj%QX$P}B52<9NBP}0z*|r^aeO(ZJ;4FA{e<6pWzA5Av+||(F2Pk9g -zMgTFhTZld#>dh+W9pfwR#NCUb>mqJatzw!tSU6aK))p3{T;Up+6i*J~Y32jM7kuWd -z$nfHAme|kjO13$`@5ru#NVP7$lgq7*Src%&zyr2*a`e=*@GkKH3rxbx!SSE=#JNXF -z?sBlq4>=}~*k5QO77y;tuc&a0(6MaH(1m^Vj*2z9rA5N5S2Q -zf03*k&l4A;FBB24Iy{?bdTwr6h;Qs-69YtQSgw7)Zkch}B(vZ`4eHzk};Fk -zHFqqnI|kdYOXrIa#M)g*HH*Gg#ShKxY6%?l^~U4hp%d$(A_YB&P($WpySW+XZn0F{ -z`vdRUUm_brPWiokuA1g`P7Z| -zpkN#U08?37|FLABU~G9XSX_?@*xC(gTcTZ?e{C+)Ahc@n=v_kXZB{eHErdpdM40Kb}WuduDrcd -zGUrm-;%@BH4x2xCW^kThE3e3dOB49a4k0z^T~6b^DVzJ#afr}$Xjz4MHL1jm#gNDS -zn)qYOtn=4Iu=t&RL7lBVWq~v?bf2c}^&M!2l(g;`BR6=Q2d-s}>RL>T)Z -zrI=b-#btNTC$7^8`Qt_>N+U@XD5$_>Y83HEydfYrnwzmJcjB$ANZ+N@qE&5JBIE(` -zi!kfLW-5P~5+LC%U@V##YW9M*o})wckFK}J1~Mqa|1;%*!?+%Lv3eHT5k+%!@As2G<#-KosAUiGV^Ld4_Kl(u^19=@l5V*y -zyfi$mbWtVxXcgnz8|tIT80-9mD$}Dcv714fuZPH8a0~|Fo!Q=D%XBy9(jYizZyGRl -zDvM_nt2fGJbW`F=P6DdivU^9j?o?0^D=QuJQ@0>@*ZC#mxh`Tl%8xg(3Q!Xw$bd$X -zECimmgK88$)-rkPIWE2&K`C3LvK=Z_OTsNYt8(6sNrQ(sckh*_UyUE|VhJ10fU%A!F%mw>I6( -z5kM|IK2gU=)g-`oDsu-v*-a6Q2gA!p=bSv{d9T4^h=LYf4)HZOaqZMat>b!SWQB_) -zixoIK?ahzbNAXgeblcfxCGZpvzs3t;uypr2St;=IcAwfdn3RZXzQV$Ct5!fXFph*V -zO5`>OMdL3KlR#O;pPdAnDui9G=9q%euV@JRkm?vx!ivw@@=XNPhbFsoF&R1WupCm5 -z;L$KHVgaPydU{Q5HMd=GlKHkVV2i4)^rwwT>M8@82E?BNT`FHjvM6IDm*%3!+3W4b -zcwr<$Vf0BnRi)sk18!IEU}1GXw#RtSixr5K9ldf%Qr}rd3ps)f-)u8F0si@%V_)b| -zEUT}Xw8aEsUDd8CObxUzf3Tw}wq2BoW_Cq6F!dm>IPz0%m%*Nq;(x{g37FQ9?6554 -zn8KCsJ&^~-K;Tz>3tGgZXLr7bE!cwW91LLkaKX3 -zOO06quw^{W5o2XfuHCQvD?Hd|f`1xDlU6)=1!g+VCT6yHfL$zc+O(}nd{_&g -z2|UbdGitO+=Hn_RE*aDOhU!i15nRF3-|eJd`sLV5Makl$Ne4}@ekkyaHt;EiMf7O) -z_#KH;^2L -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 -z?(#7kx%5MZQorJNWn8(?Ef97Jxl#wApf(t? -z&o-ZdqBt$ZZ_knZ;Akn0^0s1&01+v8>{GWaGpX$Z4z1GJwe&1Tz=hCwwyaP56}Gdn -zxs_Q_^cJ+tZ?0G|7~_;w$&}T -z%fvj)77R~F*Ye8AGTxGlmdNKGekps6Srgrk>_1|rwYyB3r&%hdY9)T?s(X0RHm<&h -zM#MZZ`AqhbKj(RP{Cq#}63NX -z__y1^A1m3nkWv@ZvIBOdxgw>joZnY5I#F2m_&>8JUnIcmr#cUuMA2l59l%7{|3p;C!(ly*zI_iqnJZ@ -z7Q>k&`xz7C(H`I9o0uRLDH<1q05tUseVmXt -z%0}fi)5FVbe%WIj_8ZK6k{=l}3n)t031sWcj;6La(@?HT(q5$0l*d<6x6lxag-=9j -zPv>ev?c4K84i^V}0hw-K`6e2(pql_GJd}t}<-4%pj>3Swg}62vP7lVox93Sj%i}L> -z`e$^>4#Az&gnxyh8`|;9$ri50hmuI}DF1$S1*>=*L7)m^*(VH!29l0-jn%C*Zc2WW -z${zioiTrRf%b>7a_WLWnQ>1RsI6f){6`LUGb*WgQQ#?_YRD0S*P;wEq^F5JgyIq1W -z@7U-W?{bxOmdyA)1dVOp{K3dLXW$#CA+{nyjlQ2r_?bWrPe3t?E2rc+Tdcp5!_D(SWs)GOH|5 -zzio!9l1)vj;`^yaoAayS$8njNN<7^g2h_}fwdE+_{Ob|%_+EmmhQO8y*{t*rF0(_r -z)W|Z@+Yg)QL%IZB?ZEu>V6(17XG*fmJ&0QBZ*WRXAx8wFzON{W11QNdb>{Ng -zkju%!Bp(8ix>EW(F0pUw>B-7dQ9VMUe0r+bZ?YB}c1!6zqCYb}xMYQ~q_$u`p*hx& -zRZ$tXa-u&^j4AypSf_4qL-nZSeVR*dY`us1yt6XlV@W<_Bg5n}hvjfJHn&E7Q -zFfh>1PISZ;@n{`!49SSCB!~nn%pEs_Gmu1%<37i-mErW-cZX%z$C|xnSzy%v;wn6n -zLjO3By9I2tZC2FUW>~yC#w6j=73D>d51Up8LH|@*G;FpVd~k=Q)@78gKX%JCQU5-+ -zDElcu$!+~4VQUIWYD0Ou>V`FO8atem2@WoAEyG|i!;Mex>xWbHAV~|ghLza>y8q_) -z(c?uRf}aEHQEo%e{sx(B-FG~gn|rcV{V8U&@CpU<7)Jp2QwGe-x<+iyWW3@2hQ?`6 -zUR-QBw=!IrqHc+k5_ZG1{gxBk_dg6u?eKth$KqPJHBIOhbU -z5JmHEQNb=s0O=C{RNwA^zoCJSHfMN%MWq2+6uimE@)zE=Hoi|W-`Hk76mOwd%i2F) -zf8B;2qFfmg)!DiBrc_KCPaNE`fPyj)H7|Ry9T`&;XppuYv`y3GHp$Oz7c5^7$W0#d -z-Q##R=>ny>Sy0?>Wx%&3;F!>x8Vcx -zor6HtqFRf%X(J63JEgN>z`PYsH$Sw4KUPp)qDDyKFqr`eRt~n;QvKX#=Sh>pzh4RH+-k+XI83~Ha_>uX} -zOg*=BgN<#62%nMRDC79k!@=7;{k9Y^P0z)Yv}q(Yl!AuspD1{0NU|7HBbiW0FO6B9 -zTbEV&&Gf#nE4T`PM_(hzA@NYs6#RsP>JO`!AEh!TV?T(U -z9R6#1lB*KB;!k;Q5svbrBmR$4BNN6Y{NqKZ6;}Qih-pQaDHx)%oXy+eYU&N>Br!R7 -zG3Plks3iAk0&7fKAM&3;2m^!{{`-8Q2HaD=!p7<7UA}H!L~qUteP^l$ -zXig|4%?A%dNU-t#`m+a%`Sd@mtv)?<4g3j2Hkod`gFgsX=>;nKQgQWrdZ=bP5Q;5$ -zUv}Saj7aJQer<##rPGScE==E&y_%1klvchDYa0eIYFYVlsrDQdmx0@B?VpsRA49zJ|SNZ2sLG*WJeP~2!%5j38#LWm5sB@I44}80$19T -zAu*1riwUHfJ-{gGZXGqn8ihvub^7c{E`R-H1CHTt+>U)gt(57b$Vj#)ipF5}QDlTr -zbJN!`sXOJqv_71?5AzQP!#uKPEDVV0mNUh*a9=cHmwMYhTsWud^X(Z5@q9o+|Ab02j>PR-B -zg8+Gd*q8L=b&sIS$OssoJnTqfRQ!-A%iy)~#N+r?K52M->!7Y8wrA3<&)lCLFjGcU -z0KiRyWAfwV5?qVwkkL>dy$H<_raTZpG{>?IiU>t*2n$tW3{WWUoUTrrLA0|e79PRr -z;~gmjBK{kWtsdko25iH8)WKIoW8}n=Hx-P7PbQqUE0~P#1u{F`RpQdZ`jDL*9BO3g -zf`cI9PYCexdMsWf2bNd&4XgPDUb0#6qnJld!W9Nsr_F>N7 -zD0wl<{3*+dRKYsm9cd_<8&)Yvd2B7=r3lOadcb|_&M(uRN>OqvX$xKe(zywtwZp@% -zHV#(sb6t_=fiEZ`se{|GSJbjp^8v`g^nxtR)Xg2;UO&^?9$YeN;-im53H<_;dp6iH -z=%>xx@)c@_LXt+S)I^kh&{;wjvfO*KjSsL*c*hf#d#!Ic&R%=}KEL%ES#dDa{ng~c -z%i{fZCQ5(APu`>g%$fFO& -zYsbCq$_`%-1gXL0X)q-6n0^C(;@UQ~R4$b6c1(U{1^P;?L>d#F?`+$=Wdj^3f2qav -zD{W)~$>hpv@Z>9r3`K2F2-@NRm^|`qVDa5>WW5H?Pl5ipB}(2CqeoqsvgR1v0+P>~ -z{TnfdXk>@S<6r~v*x8zfQq8M?$Rr;iH)oF%rA;8fsNL+$5*eD2?;ID*Nj5Zq>?9`$ -zCZW!sfPy%6S;4Ag`DAn0{b3?*5z{F=m(u#(oFi~Inxb*B-n;M@O`k^|B$jr-Qd~Sq -z-?;W`BC7gE;X0&?SsDnsn -zfFZ>)nF0nL_df4v&7E$l2mi=3s`MlncGlzCO#l;%mP)PJ*q@i+Rv$XV(XVk1g)i#i -z`4QaHO6kq@%kqNsk#`9=QlXYD>vV7{8NqnW&l=L~ZdK>Dd#2#7F~fe0Mrm6!7wE6{DyToSZ0KZ6O3I(Xt6JU(q(yU{ -zgQqe!C3RnQ)NYcsx5$`c_FsUVO0*SIjs|ugS?Y;t$k+0Zp0fNNSw84eS7;Ro!cm}B -zjX!ZN@sT7vm_w$fhlVlt -zSuezzHb#|p+{)1by466+;_B*1{8FYggQqLdxozFFdhq}kAe0;RZ{x`#ob}Q&w7O|Z -zc$6>(O)zyq)HY?q$p>Wdh!+1z+YPLA&sj?e2gn!Qt%gIDSTT`R*23LvZ;HR4+}&@H*5;CVI7Xm`)IL)b;|f__c#e^whF;2qRRKG -zCu&oN@7VVGR{2y9`QI-sT}IZ)6H`PBp{bWkLa>D5twxBZ?+_@%R#9>aSWl}~k*(&9 -zUXp+Lj$CA}s(CLEkIe5A-Zh5u(aTk{8PO;u5$^>H1tVIjxZjoax%`HqK -ziHCn5&N)1Ey9;dNwfa7&Ta$MD?WfVuTV^SmY!enEj>k?gSkq3jPT%zd2CY~89=atZ -zE)Nfz%$rCZD_8HffYl=k1My5EV&Ia2(q^L^%Y(dKXQg??+9A=*+0~RFf&mP?}P_%9N|2SP3>LBgHF8gLmJg`lOmqku9yVc7?Lim}-e+>*tDZ{>k(rNIY~| -zx-+vgE9+BS28rFi?kW5)mnB8faT4pLx6i+O&XE -zv>JN;SA+3ATk_deZEgqeL4DmvHE!8X9rH@-bZlD7<@|G>l5_pqf{%tDg8P-$yth$* -z?GJN~oS_J0IfNBPH>WH)roERR+AYCAv43afO`{VHe1Bsv5x(`E{G(K*k!G>aLQW=z@sy-!mZgyqc$&0q_JbF|NfUvpS;5@4_8`l}*V -zKHc?V%b%d|S+TSTp3;Tw#fwSUAJNle^zljqO1WPORG -z>4B%RKfxP5-^!jdrvEJGtM@Y5W~=q3k3;0XZO@xm;|B%dcW0}>Muo$tA0m0M<<8$j -zK7XJ^?G_uBR>*P$H2 -zdLumoxd})@gQvWr?*r8$!Ngb%fX_6QYdz(b8R+WZLpvNye5|M!Hs}*a3m4sNGoxJT -zL%=^Gjg7YOCLD&6XCK)r8M7%0mrFSr0ow|L8hB*&xx1uq%;gm6T0SvAKvD*Gl|uJN -zRq>W!m@_RCZzO_%$~Pfzt^e-|zE?v_X!%=t<`kpNMoUUs0I2(oP$VJ$&(r>Y> -z4^7bulhaYG?FZ!26z{AhaFw@VGAnUuE?IPC1s^uw^*mfziYk`6^kaz3G%*%q%CsIZ -zmR2>BJkLqLfQPG;-m>t)NF&x8Uk&4=pfHYeY+7;7z3W4%!GeLv~l -z;<&A1MYv&24Hdx+1NZZM{1uYQtQps&sAMG -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 -zlH+rSoxZdv_5hBlbkz8^(n*x|UgtV9E_y72Q -zxjTF2-r2KXW~c0(!y@%vRx##4G>26nt3X<>f-`=*eFom6{o|AQ!%n{fslk)rX~9Uq -za}&AyBgS?nFKT&W7fjh#XOF%jPQ=UNGw!_67ZP~JRuSpRY3X@cF#qg}@9I-kzC^J@ -zrgJa9?fy@(@kN4D*r~h>1$Os2uIeVA-Hj`J&$>SohN;M&`a9ApS-}DmHyi`~BOiMl -zS|x0m`vgY{h9XkshvR0S33~net8SNv(|T36;N%i{r+7}o_m(Bcp&?Gw#JkDt>W^>7 -zCiCucsjLLS#Y^!I|68*)&E5d{ig%tIvh+o%{mppe8^X;Gq~6zbm+1B!BHr;uPem&aSzCQ9VP%g$Usi==CJV~%(*$NZ=E1t4mFO9#>xkUWzm-x7$je7DpAayZv -z1}v4LkQ8$GS^2cttZDcC-|p-|DGnf1mhpb}%LEhL^C#exOi^nsnN)ooSE?M@)ho)+ -zPf#4pWSO(7^J0X%^gqGIscUe7588bL?^Dm8{@$1D!G2gE8Os8fj-^is-8`j~Np7!w -zTfeK_)o?9V;mbdfUy8}>k<(XE^=cn%a&3))A>;8;tCWw={>s#)E8F#_=ii+yuv$zG -z5LaM#GD%0t8ALto!)s#X?w%eeTgJH}n{gC8U<;K#2lW*8Vg3(W%R969Fk3iTXLP|uwf6;Eleqk?*?n99jzocty=^wQ> -z^d&cJ_|$Hn#aIZm8@+YA@x?zL$H80!y}F>+vdpS473II!{*!&`j7yKU9)HZ$ejqR< -zGi8SDN4<|;-bJJ6L-Oypd++|H1N1rtZT?hiW@t+A#C!CwD!kKqq;Kbk(z$ee>GJdS -z{ZGB|CeFU{rgg9)k{kp;kTl4AsQH-@bOLrPM5c3KMsk>ctFgB-6z2y8>B} -zfhJkoThD97O}PHvDD9YHYUOp5`#Prb&OE0-Z2i9UM&6Bwfw`vw98$!@VwyC2m#?3% -z&l7A`RcCRAzEmu#x)5vi+bh`r7S2I&`nk9-vL{Bie>TX`tt#4QWMGQ{u8?ULN4FN@in_)2UNw5yL^K_h?t$cUm_ -zwn5+bpNu^(I7`Z_SZN2j>^6tix|6Low{PUWe@*VYGP!RzAa3CL>)V=UQtmUuEU!iF -zO3m2&4fAdqWn=Hqb>cO=_x|dySFqND!H(yoo-A8*Mqj?<4ayCaE8#QNvtcRK%;Z0d -z6B&E%?IIeWP0juiuw4~<<$3sGHHMk%QIv4k@5Dz&?eBo)ucyoUCjPiHIkH9Y{LP$u -z_im*MPY85=4wb;#m)>`|EDp(pB;A9v+5vAKChDui#h}}I~1&Du7c7h)U{lb8Veq5z<|6stjJ8{i7PdiPFBy@PAu)Q-cn -zw;|2uxD&m`*Br~N(e`=5Fw_`CElgfUt@v!*OoG?9Uzx93Zc4Pj_Wod-z{?u!V&q{O -zQ~i0NsU92jwtxvX?)W#*Ch|;%ZVcamp7d!+mm708UwCKm-TnOOZm@;x(iGtRs`|o} -z9@9Un`qwemBN^ydZkn=A7XO}{@_H7-w|9C>q-O%tuIsPA{Ni5OS%R9O;yoUnv2u3j -zIGLQ#x-Qh_MUOlMXXrGt7UI58Ab -zD@JsJ9rcMG>OwassxR{a$AMeY-5S -zhzG1+)lzhfmX3O)mAYWEu;yyWU!U(Pb6@vkSohAe))J0~e{G_oZYfAf8Q*uZ)>6=X -z@d5thNklIjyCEvhOuX|FmzJ;I59YIrGwpK`CPVuP5?NoY$G9vsow@C^TC@NLUtc{~ -zwi3Qe*0p`8t5g6}62Et|uVDONo@>Z$^p`_Z1y24~74WT%$4h}}7@DqzYl!7<$Q#j+@pi3F_-1vgLcq>=VG=s%$9g&PM1xx -z@J->e&cdSh2d@1r)5br`(Nj)RELVaynKVDE5AlrA?XA+8x%(ggc!O$mT$;S{dX-0= -zWnA(?_gNRCgl{?CP3drjOKYrZ?oSc}6PkMEyMjGAt- -zenzAyDuoOzX^MF3XdO$X6KMt6_d~3%wu9JA+CDGA?K4JeuH$5PJ!fz3JQ?{Q#o~w= -zvHA+;d?`@n97Fg(yx4vGUsj{-5B>Z5Cmn5hlq|d2fdG@EXzfKx5#QeoDxI~q#7h}n -zX3BI^G4ZjJexzP5$Au)(t87F`=@s$mGnd#QPtwD#dTP?MS6rgb*PRQgA+D3$X`7br8o%4RM(Da9 -zfonw62iA7dCB+jq6H1B|oFX#3=_|6d16- -z(EoYgK2?a#W4`Id6>*S?kZKfb9=V>rW^cT -z$mn~r5P35f6XoOZg>+1C9C5sGMPI#dxzYdjO3=IjTBPeN2&uE}I9S)rJ7>)2Tn8$4 -zq%O{|oo^(R_VJEc@BzY!8<(+fF(@d7hG-PiXk~QbGb=0534UF%TVim=rNTMf3w68s -z4)8i5w-jrnS_x_aPNAwvnru&tuFrv9ghMGPDgvwl-C+t&>UoUD9|%+g0&~c@KN9lR -zWa*a+<^2o1gyz0MN6|jH-j~P}ZZf`aZ5FCBh$MFcQRqy0zpF*%zBmEW9j($CH3ZOGrtMF^9(VJRA74Cb+o -z*L~&V6-cVqF!Ks{UkpW=p@6w0FEd`?I}vh>4ND|pu^)OIOdad&Kj_*&&fr$xkA1n0 -zpKogoX0rNC5K1ybhP6`i`-eZS%ri#j&=(N*(cAs(9o30)3|h6<%{x?nJc#M}J;>@b -zWteA}b_e{jNWM#{xd-*|SAhSaHlat_ktsQsUi}f!=W<~=>~6u~8~%%My6yf*+qdFE -zg}#`{ER{d54xE1ZrV0@I53dj8++eGI%M+~TjJN@t!*+zzgYCE2#))6avDB%RM!ez(x_T^;7el#-mEpf_HZ`M-5cGSs6LxCUT**Dpvd -zIkx<}ldAbk`_cl~N#JoxQ2p>>=QOWV$_9^eLNGzg)~PE=EJ9zZ3TJ8jG1CO{qD}&C -z7@GRs{owN1R0YM=bkXH2uZoj|&i}AY#`d~u_nVZS-m1Yy4EzALe{jFb-J4{{bTsL8 -z)y5#z`9;zJioIY#iLuGBU4H&!%1CUo!ff1U!cQe}Sjn5IZBH9n -zD~w8qzD4<}^+9)FqM%o?2g{V^D~L)l(td$F>mY!zohu!vBoRf5tx8w2<+qvV*#D(; -ziEkIC4GY_sbUsfBw=ecPCR6VeYEi(7T&Yj+Fr%ywn;)J7 -zyEcA7eKh3HeKox<+yPv74cnz0{ao)L3|2Yaa|Ie*o+nV+0F -zt%FFk>Z`jAFWcP<$$jGU4E~#cS>hT^9%TiKFooCwWvxAJ^;`ZFXsQy{N` -zr|{mNz3#K=1M2%BSJUKNfdxwd>LBBoQ%jmGdDC68!|q<=@i0$)>+z)3tIt|g%7=o> -zh@7$#PA(*C(aTG!ap1Td20@}B$l5u#hL=@pZ4Ez0Er$)xf!_|f -zwuOOJ=0qBi6w2JTvL|}7a}I1Av^M^Jd6T?zK$zPG%(Yc{p(l5CNd0qbrHIMxIYPmX -zJ&`h3TW%i`7&JKxKv|vX+|H$7$-8|x1QaIp5j`Fht{#A%1db(Y&Cc%0{Qn%PZnpXc -zj0cCqn<+-t5kXl&EvjA-L?U9wXF3HQi@@aV43)E@ClBVmN2`y6!_fSUKU~) -z7zCd|Y^u+-?GO>JmU_oWab+J0v#%|}?o?1_!l4NnX^%B8);%-QlU`$n(w89zshmP!sdkSNhjAjY|>>0p2-vKJp%!0BQv8t~qP(YXn&+=g?0itBhVRmd=V!$ZU`+s?Ngdlg|F -zfiUZ`wVf$FCN|=h)h{|4q!_|33H>}sG2NOTcSS{Oct49sr6Q6Jl)}McwP3|)~7nXLtGnV&p -z->wc?D -z_LgkKbq0!KKe9B#kh*s-{G8`;NppU{z2agyey&hJ{Oeybcb7$b8q^)eS|CLN1)0j? -zKszOi_M^8KOfiV{(r}>3M5NefEr&*HF3vNz4L>bk3$Gr&|Az%qbvO1;$(c*t5lw?0weJcA -z>$;r?d!%vk?|a>+pWbtl$%{o6MKkEhx$SQffC*Gaee? -zp@*DG^>mO?@Ycj|^~zAtb{OafG20e4UY8z%(Ul*|=%0#kGZYQ4C`% -zO!Z6`$oL-enG~FSXW1V*Z;Q?mj#SL|a||c`8jhXxFXw`nUlv1gA8LxNRi3qr=iIRP -zarzma>FKTY46$uLNMY@%AKq}0!~jDmaW-h(cBo)#6&ZYWHjjFemc#fbiP6nI?XZN$ -z78cPr(2W54?h$D30SPk!ph>?RUY`p(*`e#BM$C7++(ZNiQl0Qn5-_A7h`4#C($hr# -zzOS69v(?JOnn$9UN+8AdJ?TcxFyCal|FJ@Oetu;x^}ta8lc!%ZGKtpOpu6Ru$W+0P -zJA-wscX7#qKb~r3S+j?2#RxsE;1u1MEQ+c!dgS}Mb-(NO7=UwcmSu$gu9fxo>#; -z;hXj|)+v}zk3vZY3Wv%@?d{7{qct1LF2yC#IP@SI<()}Xnx&G#qANn;?k~U -zH_zC6mv7xQ8=u>z?H!j$xjv|TU049W=0*3L`g43v?xx`kyj;w1b{S9Umg2)4sa;~M=2Qw$_TlB$&aqI~RSEO5mtjkC$Y`vFI#mx7 -zKuE-zP4ch=t*wJWkoQB!Ghh^rQ))EiUsA>_V{i|fyWIbUdw~3c!|_8%+jTR)%3*0P -z{h;$uO&uqZVF+mhvllTuul;^s{ca$BbDwfdA&TMf+YLCKbqQD+J-z*Uf+ -zg_A+x%)9#As2siVfQBS*%u%5A^uwL8ATf;*LhmrWo3oyFfR_SOl4sj6r^u0rtlihA -z_(qK=o%l~E+-dO&kk#+0AlnSFU*oZs^xZPmRvb4xv$LGAi_1jao?9GW3||C-2ac7k4=O8dAV{G@1|*H7XMXK0EGOb=)h^ZT#GsDEpjCdn2N9o6NDMNTn-{ciy^joD>jKYJ -z2DJJiprxTcE4Z!?<@{^5ZOXp1B^zYu-cX|I>Lev7r<4*j%^&nN!l!VCAN?>Nj|mCg -zGc2^vJ0N#~^YQ%Mj+XzmG`9|-S}>M4C~N%qR^_1u`__XcQ7*Zi?z?lS17CHZT#6d` -zq=rx0;^QPC=-x^S(!TGr97;dH0`Oy>(v7|OdT7cCN@GFv;g7*^V8uPS0;p`7QcoM%A7 -z?&7>b9iia?YfXh*vFcYczrxkUyavhMh~ZfDUlA|wAd*v1li=LqN=qkmP%QSZSe_A7 -zd6Vw#LR;iN?iAnRRzd-0zteX&X?v60Hg3CU%OEA3+eS<$D@Ux5ns5Q#TRCcDw{63F -zX>9R#X{S*hH|nmKMo69c!Qz+9P6&uAt4X+G%{TaS|6-tAY(TT{Q0T+FDc`0etT*T8 -zq60jZ(k!~uA4+Qbs$k2FY7~VysLp2fFY?RABK+-YioQ_N16U&xf8El1j=U*>wgjbp -zJnWwQb|Z|eaO)xRK;c#|hMbeWf)6*+uL(NwIH`y0#kU>jq&vasI)?v&w0wN{$*^eh -z!OEK}ufX9(T3r8`{UBt?wJBB5SZe2ghMEs|k83^7iE)CT;77POS3b5Ki^+2~v$UDd -zkwk_0=0E5BK|cLf!A<34@36C)CqUsIT5Q -zX~6hD>N1kej#Eq31ni)H8OCsUWh@sHF{qsaN4kh_B*Uk4kM`dv`i8H)QT&KHP!Rf$ -zK(Z4)d{FzO9#nfVjBtY4(_op&vtySTA`h--R$Ie3y2gp-b|~;H_l_2=AskOn;^;aN -zt|A`Ml%RXLD2t+XCPAMcVEVO`iNt|lyVD>IXoC{w0U_5bWQ<0KGq!%mQU+*cR -zShfBHgV4F->N`bQ<=L_~1x90wLu(J|$6PIatMB+5J`jn -zfi@H(Bl3C`@A=JElZl|fpQDvN5_DE{tq?61%s5dAA(yLM28V4Q5W(}FOy|$mTu>~` -z4kzu1ukEozsfpkvQ)|gD(3sthMRk>6fgv>U3Vy=#NFlL!4eG<#ctBa@2GAxff;Z-# -zTs$8@=+}a6NPGY=20hpkkfI7+)m$0>}}1>uU6q* -zDx$G~{s4wKZQ2I}&2nb4#JEDooS#e*Hi;j-__zBRbpyN+JS8bRnn)W=Q-3Z*n94P! -zjeM$8(;NJIn4t?t9I_XZi_>ICOR3|EAZj5#6-qiP-1-cyN`$#=!9)o=RwpZ8lU0ZH -za8%1V`fkSo_1M`7Xu?Hs$-%^l9vO%=D^U3o%8eE=DzOI;QkoD1s2Y7Dd^SmNLZgma$HQwL3c}TNA8py -zcwQYDYo9S+v^DDHfK;O-w0b>^4FIx0-8vRx`dslp=~kg3c#7EbdBVJ+R^)c!5x*@n -z69q^VT0*SHQoTF=;0htqJ5gJ^fGHRqtIw}!ln=VtN6kyGr*C*+LE~K*>KJ2yQ7g5+ -zhcUA-ZNX7PBYiv%n+yUM?xLX7FR^xI6gOsr*ZFfl-}6HN^ptbL0NN4!`Bc9aoV#X- -zVKgvvIEH}X0W{K%oFo}vesjk=84A=`G+f1jZ{4^hHN;pY-Iz*;3G<424&>tYyF+SZ -zyvmt8&rlEMD2>Bn01-}G{plVP3!svvEyKE*DZTuy$i9r>wb%%3%9p#KA0S}7Lld<(fdaX!%#e5g^FTk?Q5R}`;JSO -z?yXVWRgl!_rI;4s!tpm#(sADzvXiJ*{$Coifi$q;X({&s#!xlK%vZ)b|Nb&hCWb-A -z>|Li~WL~2f2PXqs)d$OS_@p|FyH4A`b>x+-1*JnpcQ^cUycik=jdYLc+Dq0Kx -z`92RJos3;!h4VB)xsBx+JhD?l1gc^2;lHkgCI=8Xnf;>}i!&!H+br{%5kyNsyY&8~ -z#?QRjEzBK%L3;9ej+=9Tmu14S{p$YPj6)b4%*9p$k8na?w4>DuA6V@*L%&trvh?g% -zIWk(8a@+2xneXp=D$+d9Da2fFs-yFrF*;|!;{J-ICn -zl(#;&+;gZ;C-k4Y_=>uTo~ZXt^5!bt>tl$5i*s#S;K9t_I=6Z|WW@`69;*Fev~Oyn -z0|V>R(v&@o?k6YaU9(1%_*Ldq -z1?QI%nC3P>{DDkvieO#W%MgScq2r;P^>lYni^I^hXGnrYmr!HO -zEr>MTywQj0%ie-PZvK7s$_eN|m9ZEvj9VYp|FM$`PCENa+fs5vIklQosc*8F=R#Rq -z6X|Xb2siRdzT$AEzIBY`%I1L>>2bh?l-m^kP5@ZeBDR|(15epcRW$8{>YrO5t0|!3 -zGMhhQN?}Sn{4M_B;8E3k>Au603v2WFgqsICqtCu=7J)n_ygaZh!^^m#+}WG>=|LE$ -zdiBOPg9I<$SDv=gG8>}`kMQV*rhtloAO{ieyJ1jP<*4G)FlFV4OPxpIY`1w;KV1Bh -z+&I!wfiCx84_($P~57FxCZo@%NiBFtsCDrCR_lmv?Lc8v2@Q+2F;O*&3)H?_{v_Ws4v78KiioG!anV~Z#&L01PP8GsDOW8fwCnaPi#c2- -zmu{el6^Lwm^$5J;f!gg1`ZX;bws@hfEf5Ef^@EdYcrb0gM0<9U{fvE4jl)|@wSk5d -zf63QdRhAXWF);Yt%ve^13!fVjGTIOS2d9`E)*~99=QibA!lk;Z;34lQFOM=xvz|7> -zLw?__i|fmjtH@Img*e+do!*zu%6vM2LVDP@)mt0V^z-g1ZYsZ*t0>kwPwyLp2&SQG -zT)7?{m&<;k`QG_}K15w{K;n!H`eu7Go9PCY&>l8>o6MY>t^2uLOHih*>{s7-OI@h!8&e|@@cX8hIY94he -zW4ViwWbiwxMovw3<^5PGsAHKihoQ^C6@X=p1Rcu^Y|uCw2(Ta&R)_4uy+|dA_sJ##nhE@ncV6#R+rk)8Ad~Kff>{!MnfM8PW!jU2 -zuxg?IWZhXk##wlJV`W{V#e7q^Q#1G5xyMNusl`w&-k;K)`qluUa&uf{)ZDZ0y|-h0 -zg`~XJZ$9>?xkA% -z)oq}v{-X^!`+k+&a!|KrtUCE<>>~OoA@H=T}WS4pa4%q5& -z%99ScQz*qr&32@K@e8@+*!s1nz}>wWrO8b~vI1&8JnEY0E)qx!&;NeFw$rPXgA(*s8D?60QPB3FKZJdlU -zyduQ&Gps2QQ8`nwSH9U8DD?6?IpE+{#NL+6_7NU(KI2XE3O4Jd&WlFm0xak_`Sm7n -zMmTUId|!teB7oStm2^d7>{eRkXTQ`7xv=lhun?`KfZnP$gq*K|U=}J*Eqmi);123t -z*iRk-)h5Jc`21Tac`~#x9eGc7SB`|+DOkLQr}KwelidNc`A1riY3`s>TIxwjZ98@n -zNLi%UiP`5Jb<*4c!p4+E?`fa;{3POHQR-<1f7nw#Z5n%9?a}eBgFAg#s5yfrp&_MyABH93HNxGH6jJpuon?|5@&*gW -z(0l6l;~_`NZo3rEh2^tB?=i?@Nx^DAo}U)>rX^K!on_38?q6(lYyvKr2uG7=RJ`6% -z1FYLNL;k)4+26|LFhIq|g959j*B@g)4f~4?dwpLs6&_scg*$jQjJNwwGFLmQLhWrg -zj9o5o-hIY*&kVWR5*M0R%Ie|cFB<@q_O;7)GJq773Y+5mk-1diZk6Z0HU9VI_T{v> -zN0gdW&(h_|WsrW$<-14zrd-g_X$vK8;AtLE`s)U9B&i589j@AE9&~^-+n=uVw|!Al -zUe3GtQYm%m%gy;0En%9LFAJV(Xev$0mpt0(Q~UB~XoUYEH7!UBzgouP_M>~iy=%PJ -z+@LT~jL%ZAq#?fnw<1kWGkCg2me&YwCA!dQa4vL&9XBH%9^3hv)QR8s##p1e%km%E0azyT$-$P -zRPU)4U -za0Ipb&P^&RRLoYa?5*MBbKazW6t%yan*F%(z73>af)7|vMw6b1k6UxWy6FF}*B -zydX6hhI!^eLyT9*!8WQxv~nmmb$6JCfO^!6)b0a_aH8$4vp5`T>Q-w*$2^CZ07I_& -zmeWl3mTSoUOvW`e$_{48X@Og={q=6S&kQufFH?y@12GB{3mFix+bT~fr6aPxYgGzx -zg)|O4Z5%TY<%?8(8`ZcoM#0b1Nk`kU#KE9shTkQQ)-WPH_N?V7h=1gIoP?M(BsMnE -z@c7ip`VaaTo%f+!ED^VI+|a1$q_YnJ -zVeh;65e8M+(QubG>XUO7d5}=zHtJ&x8i;Dqp?{zou**K@u^Z=op?pznA2xU|(BJ>A -z5ukI!Dc_a1{OMxMGhiC$)O1L3=VRroj -zEPdo1rbjS(lY9(!H7-Og<8TjR`sAR;SvFJ*9!fai0jfa^7->j; -z`9XO2J`9R`s<~UtEvgk$1-b9(%%6F=OhV@47sq!rpxv~mRcIYkdtRY^It5Dq!sy60 -zsx6JCkv1J?8E4s#gR^U3Y5?|HnYbd9$=us6qH@r^vND~BMwYw^y^Ll<)q?)0|xQ2%1^~S?npg4b4rTO -LDIkFN#;N}YR&K&K - -literal 0 -HcmV?d00001 - -diff --git a/components/resources/adblocking/update.sh b/components/resources/adblocking/update.sh -new file mode 100755 ---- /dev/null -+++ b/components/resources/adblocking/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 . -+ -+# 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/resources/components_resources.grd b/components/resources/components_resources.grd ---- a/components/resources/components_resources.grd -+++ b/components/resources/components_resources.grd -@@ -1,4 +1,9 @@ - -+ - -+ -+ -+ -+ -+ -+ -+ -+ Ad-Filtering Internals -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    
    -+
    -+
    -+
    -diff --git a/chrome/browser/resources/adblock_internals/adblock_internals.ts b/chrome/browser/resources/adblock_internals/adblock_internals.ts
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/resources/adblock_internals/adblock_internals.ts
    -@@ -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 .
    -+
    -+import {getRequiredElement} from 'chrome://resources/js/util.js';
    -+import {AdblockInternalsPageHandler} from './adblock_internals.mojom-webui.js';
    -+
    -+async function debugInfo(): Promise {
    -+  const info = await AdblockInternalsPageHandler.getRemote().getDebugInfo();
    -+  return info.debugInfo;
    -+}
    -+
    -+async function refresh() {
    -+  getRequiredElement('content').innerText = await debugInfo();
    -+}
    -+
    -+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);
    -+
    -+document.addEventListener('DOMContentLoaded', refresh);
    -diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
    ---- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
    -+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
    -@@ -5,6 +5,10 @@
    - // This test creates a fake safebrowsing service, where we can inject known-
    - // threat urls.  It then uses a real browser to go to these urls, and sends
    - // "goback" or "proceed" commands and verifies they work.
    -+//
    -+// 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 "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"
    - 
    -@@ -683,6 +687,8 @@ class SafeBrowsingBlockingPageBrowserTest
    -         {tag_and_attribute, add_warning_shown_timestamp_csbrrs,
    -          create_warning_shown_csbrrs, abusive_notification_revocation},
    -         {});
    -+    scoped_feature_list_.InitWithFeaturesAndParameters(
    -+        {tag_and_attribute}, {adblock::kAdblockPlusFeature});
    -   }
    - 
    -   SafeBrowsingBlockingPageBrowserTest(
    -diff --git a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
    ---- a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
    -+++ b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
    -@@ -1,6 +1,10 @@
    - // Copyright 2017 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 "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h"
    - 
    -@@ -22,6 +26,7 @@
    - #include "chrome/common/chrome_features.h"
    - #include "chrome/common/chrome_paths.h"
    - #include "chrome/test/base/chrome_test_utils.h"
    -+#include "components/adblock/core/features.h"
    - #include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
    - #include "components/content_settings/browser/page_specific_content_settings.h"
    - #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
    -@@ -152,7 +157,8 @@ void SubresourceFilterSharedBrowserTest::NavigateFrame(const char* frame_name,
    - SubresourceFilterBrowserTest::SubresourceFilterBrowserTest() {
    -   scoped_feature_list_.InitWithFeatures(
    -       /*enabled_features=*/{kAdTagging},
    --      /*disabled_features=*/{features::kHttpsUpgrades});
    -+      /*disabled_features=*/{features::kHttpsUpgrades,
    -+                             adblock::kAdblockPlusFeature});
    - }
    - 
    - SubresourceFilterBrowserTest::~SubresourceFilterBrowserTest() = default;
    -diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
    ---- a/chrome/browser/ui/BUILD.gn
    -+++ b/chrome/browser/ui/BUILD.gn
    -@@ -1,6 +1,10 @@
    - # 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/buildflags_paint_preview.gni")
    - import("//build/config/compiler/compiler.gni")
    -@@ -146,6 +150,10 @@ static_library("ui") {
    -     "webui/about/about_ui.h",
    -     "webui/accessibility/accessibility_ui.cc",
    -     "webui/accessibility/accessibility_ui.h",
    -+    "webui/adblock_internals/adblock_internals_page_handler_impl.cc",
    -+    "webui/adblock_internals/adblock_internals_page_handler_impl.h",
    -+    "webui/adblock_internals/adblock_internals_ui.cc",
    -+    "webui/adblock_internals/adblock_internals_ui.h",
    -     "webui/autofill_and_password_manager_internals/autofill_internals_ui.cc",
    -     "webui/autofill_and_password_manager_internals/autofill_internals_ui.h",
    -     "webui/autofill_and_password_manager_internals/internals_ui_handler.cc",
    -@@ -382,6 +390,7 @@ static_library("ui") {
    -     "//chrome/browser/signin",
    -     "//chrome/browser/signin:identity_manager_provider",
    -     "//chrome/browser/signin:impl",
    -+    "//chrome/browser/ui/webui/adblock_internals:mojo_bindings",
    -     "//chrome/browser/storage_access_api",
    -     "//chrome/browser/sync",
    -     "//chrome/browser/task_manager:impl",
    -@@ -430,6 +439,7 @@ static_library("ui") {
    -     "//components/access_code_cast/common:metrics",
    -     "//components/account_id",
    -     "//components/affiliations/core/browser:affiliations",
    -+    "//components/adblock/content:browser",
    -     "//components/autofill/content/browser",
    -     "//components/autofill/content/browser:risk_proto",
    -     "//components/autofill/core/common:credit_card_number_validation",
    -diff --git a/chrome/browser/ui/prefs/pref_watcher.cc b/chrome/browser/ui/prefs/pref_watcher.cc
    ---- a/chrome/browser/ui/prefs/pref_watcher.cc
    -+++ b/chrome/browser/ui/prefs/pref_watcher.cc
    -@@ -22,6 +22,7 @@
    - #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
    - #include "chrome/common/pref_names.h"
    - #include "components/keyed_service/core/keyed_service.h"
    -+#include "components/adblock/core/common/adblock_prefs.h"
    - #include "components/language/core/browser/pref_names.h"
    - #include "components/live_caption/pref_names.h"
    - #include "components/prefs/pref_change_registrar.h"
    -@@ -82,6 +83,13 @@ const char* const kWebPrefsToObserve[] = {
    -     prefs::kAccessibilityFocusHighlightEnabled,
    - #endif
    -     prefs::kPageColorsBlockList,
    -+
    -+    adblock::common::prefs::kAdblockAllowedDomainsLegacy,
    -+    adblock::common::prefs::kAdblockCustomFiltersLegacy,
    -+    adblock::common::prefs::kAdblockCustomSubscriptionsLegacy,
    -+    adblock::common::prefs::kAdblockSubscriptionsLegacy,
    -+    adblock::common::prefs::kEnableAcceptableAdsLegacy,
    -+    adblock::common::prefs::kEnableAdblockLegacy,
    - };
    - 
    - }  // namespace
    -diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
    ---- a/chrome/browser/ui/tab_helpers.cc
    -+++ b/chrome/browser/ui/tab_helpers.cc
    -@@ -1,6 +1,10 @@
    - // 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.
    - 
    - #include "chrome/browser/ui/tab_helpers.h"
    - 
    -@@ -13,6 +17,9 @@
    - #include "base/time/default_tick_clock.h"
    - #include "base/trace_event/trace_event.h"
    - #include "build/build_config.h"
    -+#include "chrome/browser/adblock/element_hider_factory.h"
    -+#include "chrome/browser/adblock/sitekey_storage_factory.h"
    -+#include "chrome/browser/adblock/subscription_service_factory.h"
    - #include "chrome/browser/bookmarks/bookmark_model_factory.h"
    - #include "chrome/browser/breadcrumbs/breadcrumb_manager_tab_helper.h"
    - #include "chrome/browser/browser_process.h"
    -@@ -111,6 +118,7 @@
    - #include "chrome/common/chrome_features.h"
    - #include "chrome/common/chrome_isolated_world_ids.h"
    - #include "chrome/common/chrome_switches.h"
    -+#include "components/adblock/content/browser/adblock_webcontents_observer.h"
    - #include "components/autofill/content/browser/content_autofill_client.h"
    - #include "components/autofill/content/browser/content_autofill_driver_factory.h"
    - #include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
    -@@ -332,6 +340,16 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) {
    -                                                    optimization_guide_decider);
    -     }
    -   }
    -+
    -+  AdblockWebContentObserver::CreateForWebContents(
    -+      web_contents,
    -+      adblock::SubscriptionServiceFactory::GetForBrowserContext(
    -+          web_contents->GetBrowserContext()),
    -+      adblock::ElementHiderFactory::GetForBrowserContext(
    -+          web_contents->GetBrowserContext()),
    -+      adblock::SitekeyStorageFactory::GetForBrowserContext(
    -+          web_contents->GetBrowserContext()),
    -+      std::make_unique());
    -   autofill::AutofillClientProvider& autofill_client_provider =
    -       autofill::AutofillClientProviderFactory::GetForProfile(profile);
    -   autofill_client_provider.CreateClientForWebContents(web_contents);
    -diff --git a/chrome/browser/ui/webui/adblock_internals/BUILD.gn b/chrome/browser/ui/webui/adblock_internals/BUILD.gn
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/BUILD.gn
    -@@ -0,0 +1,23 @@
    -+#
    -+# This file is part of eyeo Chromium SDK,
    -+# Copyright (C) 2006-present eyeo GmbH
    -+#
    -+# eyeo Chromium SDK is free software: you can redistribute it and/or modify
    -+# it under the terms of the GNU General Public License version 3 as
    -+# published by the Free Software Foundation.
    -+#
    -+# eyeo Chromium SDK is distributed in the hope that it will be useful,
    -+# but WITHOUT ANY WARRANTY; without even the implied warranty of
    -+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    -+# GNU General Public License for more details.
    -+#
    -+# You should have received a copy of the GNU General Public License
    -+# along with eyeo Chromium SDK.  If not, see .
    -+
    -+import("//mojo/public/tools/bindings/mojom.gni")
    -+
    -+mojom("mojo_bindings") {
    -+  sources = [ "adblock_internals.mojom" ]
    -+  webui_module_path = "/"
    -+  #use_typescript_sources = true
    -+}
    -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom b/chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom
    -@@ -0,0 +1,20 @@
    -+// This file is part of eyeo Chromium SDK,
    -+// Copyright (C) 2006-present eyeo GmbH
    -+//
    -+// eyeo Chromium SDK is free software: you can redistribute it and/or modify
    -+// it under the terms of the GNU General Public License version 3 as
    -+// published by the Free Software Foundation.
    -+//
    -+// eyeo Chromium SDK is distributed in the hope that it will be useful,
    -+// but WITHOUT ANY WARRANTY; without even the implied warranty of
    -+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    -+// GNU General Public License for more details.
    -+//
    -+// You should have received a copy of the GNU General Public License
    -+// along with eyeo Chromium SDK.  If not, see .
    -+
    -+module mojom.adblock_internals;
    -+
    -+interface AdblockInternalsPageHandler {
    -+  GetDebugInfo() => (string debug_info);
    -+};
    -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.cc
    -@@ -0,0 +1,115 @@
    -+/*
    -+ * This file is part of eyeo Chromium SDK,
    -+ * Copyright (C) 2006-present eyeo GmbH
    -+ *
    -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
    -+ * it under the terms of the GNU General Public License version 3 as
    -+ * published by the Free Software Foundation.
    -+ *
    -+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
    -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
    -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    -+ * GNU General Public License for more details.
    -+ *
    -+ * You should have received a copy of the GNU General Public License
    -+ * along with eyeo Chromium SDK.  If not, see .
    -+ */
    -+
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.h"
    -+
    -+#include "base/time/time_to_iso8601.h"
    -+#include "chrome/browser/adblock/adblock_telemetry_service_factory.h"
    -+#include "chrome/browser/adblock/session_stats_factory.h"
    -+#include "chrome/browser/adblock/subscription_service_factory.h"
    -+#include "components/adblock/core/adblock_telemetry_service.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 {
    -+
    -+std::string SubscriptionInstallationStateToString(
    -+    adblock::Subscription::InstallationState state) {
    -+  using State = adblock::Subscription::InstallationState;
    -+  switch (state) {
    -+    case State::Installed:
    -+      return "Installed";
    -+    case State::Installing:
    -+      return "Installing";
    -+    case State::Preloaded:
    -+      return "Preloaded";
    -+    case State::Unknown:
    -+      return "Unknown";
    -+  }
    -+  NOTREACHED();
    -+  return "";
    -+}
    -+
    -+std::string DebugLine(std::string name, std::string value, int level) {
    -+  return std::string(2 * level, ' ') + name + ": " + value + '\n';
    -+}
    -+
    -+std::string DebugLine(std::string name, int value, int level) {
    -+  return DebugLine(name, std::to_string(value), level);
    -+}
    -+
    -+}  // namespace
    -+
    -+AdblockInternalsPageHandlerImpl::AdblockInternalsPageHandlerImpl(
    -+    Profile* profile,
    -+    mojo::PendingReceiver
    -+        receiver)
    -+    : profile_(profile), receiver_(this, std::move(receiver)) {}
    -+
    -+AdblockInternalsPageHandlerImpl::~AdblockInternalsPageHandlerImpl() = default;
    -+
    -+void AdblockInternalsPageHandlerImpl::GetDebugInfo(
    -+    GetDebugInfoCallback callback) {
    -+  CHECK(profile_);
    -+  auto* service =
    -+      adblock::SubscriptionServiceFactory::GetForBrowserContext(profile_);
    -+  auto* stats = adblock::SessionStatsFactory::GetForBrowserContext(profile_);
    -+  auto allowed = stats->GetSessionAllowedAdsCount();
    -+  auto blocked = stats->GetSessionBlockedAdsCount();
    -+  std::string content;
    -+  for (auto* config : service->GetInstalledFilteringConfigurations()) {
    -+    content += DebugLine("Configuration", config->GetName(), 0);
    -+    content += DebugLine("Enabled", config->IsEnabled(), 1);
    -+    for (const auto& it : config->GetAllowedDomains()) {
    -+      content += DebugLine("Allowed domain", it, 1);
    -+    }
    -+    for (const auto& it : config->GetCustomFilters()) {
    -+      content += DebugLine("Custom filter", it, 1);
    -+    }
    -+    for (auto it : service->GetCurrentSubscriptions(config)) {
    -+      auto url = it->GetSourceUrl();
    -+      content += DebugLine("Subscription", url.spec(), 1);
    -+      content += DebugLine(
    -+          "State",
    -+          SubscriptionInstallationStateToString(it->GetInstallationState()), 2);
    -+      content += DebugLine("Title", it->GetTitle(), 2);
    -+      content += DebugLine("Version", it->GetCurrentVersion(), 2);
    -+      content += DebugLine("Last update",
    -+                           base::TimeToISO8601(it->GetInstallationTime()), 2);
    -+      content += DebugLine("Total allowed", allowed[url], 2);
    -+      content += DebugLine("Total blocked", blocked[url], 2);
    -+    }
    -+  }
    -+
    -+  auto* telemetry_service =
    -+      adblock::AdblockTelemetryServiceFactory::GetForProfile(profile_);
    -+  telemetry_service->GetTopicProvidersDebugInfo(base::BindOnce(
    -+      &AdblockInternalsPageHandlerImpl::OnTelemetryServiceInfoArrived,
    -+      std::move(callback), std::move(content)));
    -+}
    -+
    -+void AdblockInternalsPageHandlerImpl::OnTelemetryServiceInfoArrived(
    -+    GetDebugInfoCallback callback,
    -+    std::string content,
    -+    std::vector 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));
    -+}
    -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.h b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.h
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.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 .
    -+ */
    -+
    -+#ifndef CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_PAGE_HANDLER_IMPL_H_
    -+#define CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_PAGE_HANDLER_IMPL_H_
    -+
    -+#include "base/memory/raw_ptr.h"
    -+#include "chrome/browser/profiles/profile.h"
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom.h"
    -+#include "mojo/public/cpp/bindings/receiver.h"
    -+
    -+class AdblockInternalsPageHandlerImpl
    -+    : public mojom::adblock_internals::AdblockInternalsPageHandler {
    -+ public:
    -+  explicit AdblockInternalsPageHandlerImpl(
    -+      Profile* profile,
    -+      mojo::PendingReceiver<
    -+          mojom::adblock_internals::AdblockInternalsPageHandler> receiver);
    -+  AdblockInternalsPageHandlerImpl(const AdblockInternalsPageHandlerImpl&) =
    -+      delete;
    -+  AdblockInternalsPageHandlerImpl& operator=(
    -+      const AdblockInternalsPageHandlerImpl&) = delete;
    -+  ~AdblockInternalsPageHandlerImpl() override;
    -+
    -+  // mojom::adblock_internals::AdblockInternalsPageHandler:
    -+  void GetDebugInfo(GetDebugInfoCallback callback) override;
    -+
    -+ private:
    -+  static void OnTelemetryServiceInfoArrived(
    -+      GetDebugInfoCallback callback,
    -+      std::string content,
    -+      std::vector topic_provider_content);
    -+  raw_ptr profile_;
    -+  mojo::Receiver
    -+      receiver_;
    -+};
    -+#endif  // CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_PAGE_HANDLER_IMPL_H_
    -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.cc
    -@@ -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 . \
    -+ */
    -+
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.h"
    -+
    -+#include "chrome/browser/profiles/profile.h"
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals_page_handler_impl.h"
    -+#include "chrome/common/url_constants.h"
    -+#include "chrome/grit/adblock_internals_resources.h"
    -+#include "chrome/grit/adblock_internals_resources_map.h"
    -+#include "content/public/browser/web_ui_data_source.h"
    -+#include "ui/webui/webui_util.h"
    -+
    -+AdblockInternalsUI::AdblockInternalsUI(content::WebUI* web_ui)
    -+    : ui::MojoWebUIController(web_ui), profile_(Profile::FromWebUI(web_ui)) {
    -+  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
    -+      profile_, chrome::kChromeUIAdblockInternalsHost);
    -+  webui::SetupWebUIDataSource(source,
    -+                              base::make_span(kAdblockInternalsResources,
    -+                                              kAdblockInternalsResourcesSize),
    -+                              IDR_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_HTML);
    -+}
    -+
    -+AdblockInternalsUI::~AdblockInternalsUI() = default;
    -+
    -+WEB_UI_CONTROLLER_TYPE_IMPL(AdblockInternalsUI)
    -+
    -+void AdblockInternalsUI::BindInterface(
    -+    mojo::PendingReceiver
    -+        receiver) {
    -+  handler_ = std::make_unique(
    -+      profile_, std::move(receiver));
    -+}
    -diff --git a/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.h b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.h
    -new file mode 100644
    ---- /dev/null
    -+++ b/chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.h
    -@@ -0,0 +1,57 @@
    -+/*
    -+ * This file is part of eyeo Chromium SDK,
    -+ * Copyright (C) 2006-present eyeo GmbH
    -+ *
    -+ * eyeo Chromium SDK is free software: you can redistribute it and/or modify
    -+ * it under the terms of the GNU General Public License version 3 as
    -+ * published by the Free Software Foundation.
    -+ *
    -+ * eyeo Chromium SDK is distributed in the hope that it will be useful,
    -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
    -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    -+ * GNU General Public License for more details.
    -+ *
    -+ * You should have received a copy of the GNU General Public License
    -+ * along with eyeo Chromium SDK.  If not, see .
    -+ */
    -+
    -+#ifndef CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_UI_H_
    -+#define CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_UI_H_
    -+
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals.mojom.h"
    -+#include "chrome/common/webui_url_constants.h"
    -+#include "content/public/browser/webui_config.h"
    -+#include "mojo/public/cpp/bindings/pending_receiver.h"
    -+#include "ui/webui/mojo_web_ui_controller.h"
    -+
    -+class Profile;
    -+
    -+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();
    -+
    -+  raw_ptr profile_;
    -+  std::unique_ptr
    -+      handler_;
    -+};
    -+
    -+class AdblockInternalsUIConfig : public content::DefaultWebUIConfig {
    -+ public:
    -+  AdblockInternalsUIConfig()
    -+      : DefaultWebUIConfig(content::kChromeUIScheme,
    -+                           chrome::kChromeUIAdblockInternalsHost) {}
    -+};
    -+
    -+#endif  // CHROME_BROWSER_UI_WEBUI_ADBLOCK_INTERNALS_ADBLOCK_INTERNALS_UI_H_
    -diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    ---- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    -+++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    -@@ -55,6 +55,8 @@
    - #include "extensions/buildflags/buildflags.h"
    - #include "printing/buildflags/buildflags.h"
    - 
    -+#include "chrome/browser/ui/webui/adblock_internals/adblock_internals_ui.h"
    -+
    - #include "chrome/browser/ui/webui/proxy_config_ui.h"
    - 
    - #if BUILDFLAG(IS_ANDROID)
    -@@ -406,4 +408,5 @@ void RegisterChromeWebUIConfigs() {
    - #if BUILDFLAG(IS_ANDROID)
    -   map.AddWebUIConfig(std::make_unique());
    - #endif  // BUILDFLAG(IS_ANDROID)
    -+  map.AddWebUIConfig(std::make_unique());
    - }
    -diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
    ---- a/chrome/common/BUILD.gn
    -+++ b/chrome/common/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/buildflag_header.gni")
    - import("//build/config/chrome_build.gni")
    -diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
    ---- a/chrome/common/webui_url_constants.cc
    -+++ b/chrome/common/webui_url_constants.cc
    -@@ -1,6 +1,10 @@
    - // Copyright 2017 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 "chrome/common/webui_url_constants.h"
    - 
    -diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
    ---- a/chrome/common/webui_url_constants.h
    -+++ b/chrome/common/webui_url_constants.h
    -@@ -1,6 +1,10 @@
    - // Copyright 2017 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.
    - 
    - // Contains constants for WebUI UI/Host/SubPage constants. Anything else go in
    - // chrome/common/url_constants.h.
    -@@ -41,6 +45,7 @@ inline constexpr char kChromeUIActivateSafetyCheckSettingsURL[] =
    - inline constexpr char kChromeUIAllSitesPath[] = "/content/all";
    - inline constexpr char kChromeUIAppIconHost[] = "app-icon";
    - inline constexpr char kChromeUIAppIconURL[] = "chrome://app-icon/";
    -+inline constexpr char kChromeUIAdblockInternalsHost[] = "adblock-internals";
    - inline constexpr char kChromeUIAppLauncherPageHost[] = "apps";
    - inline constexpr char kChromeUIAppsURL[] = "chrome://apps/";
    - inline constexpr char kChromeUIAppsWithDeprecationDialogURL[] =
    ---
    diff --git a/build/cromite_patches/kill-Auth.patch b/build/cromite_patches/kill-Auth.patch
    index cac3ae1481c8215336f98d8d16363e710373137f..39d9ea1c78057a729bd92a82b01ff8f7bf45ef01 100644
    --- a/build/cromite_patches/kill-Auth.patch
    +++ b/build/cromite_patches/kill-Auth.patch
    @@ -10,9 +10,9 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java b/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
     --- a/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
     +++ b/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
    -@@ -24,9 +24,6 @@ import android.os.SystemClock;
    - 
    - import androidx.annotation.Nullable;
    +@@ -24,9 +24,6 @@ import android.os.PatternMatcher;
    + import android.os.Process;
    + import android.os.SystemClock;
      
     -import com.google.android.gms.auth.GoogleAuthException;
     -import com.google.android.gms.auth.GoogleAuthUtil;
    @@ -20,7 +20,7 @@ diff --git a/components/signin/public/android/java/src/org/chromium/components/s
      import org.chromium.base.ApiCompatibilityUtils;
      import org.chromium.base.Callback;
      import org.chromium.base.ContextUtils;
    -@@ -88,18 +85,6 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
    +@@ -92,18 +89,6 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
      
          @Override
          public Account[] getAccountsSynchronous() throws AccountManagerDelegateException {
    @@ -39,7 +39,7 @@ diff --git a/components/signin/public/android/java/src/org/chromium/components/s
              // Don't report any accounts if we don't have permission.
              // TODO(crbug.com/40942462): Throw an exception if permission was denied.
              return new Account[] {};
    -@@ -110,31 +95,12 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
    +@@ -115,31 +100,12 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
                  throws AuthException {
              ThreadUtils.assertOnBackgroundThread();
              assert AccountUtils.GOOGLE_ACCOUNT_TYPE.equals(account.type);
    @@ -73,10 +73,10 @@ diff --git a/components/signin/public/android/java/src/org/chromium/components/s
          }
      
          protected boolean hasFeatures(Account account, String[] features) {
    -@@ -166,24 +132,6 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
    +@@ -171,24 +137,6 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
          @SuppressLint("MissingPermission")
          @Override
    -     public void createAddAccountIntent(Callback callback) {
    +     public void createAddAccountIntent(Callback<@Nullable Intent> callback) {
     -        AccountManagerCallback accountManagerCallback =
     -                accountManagerFuture -> {
     -                    try {
    @@ -98,10 +98,10 @@ diff --git a/components/signin/public/android/java/src/org/chromium/components/s
          }
      
          // No permission is needed on 23+ and Chrome always has MANAGE_ACCOUNTS permission on lower APIs
    -@@ -218,14 +166,7 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
    -     @Nullable
    +@@ -222,14 +170,7 @@ public class SystemAccountManagerDelegate implements AccountManagerDelegate {
    + 
          @Override
    -     public GaiaId getAccountGaiaId(String accountEmail) {
    +     public @Nullable GaiaId getAccountGaiaId(String accountEmail) {
     -        try {
     -            return new GaiaId(
     -                    GoogleAuthUtil.getAccountId(
    diff --git a/build/cromite_patches/kill-Location-fall-back-to-system.patch b/build/cromite_patches/kill-Location-fall-back-to-system.patch
    index 2ff214899375a7c22decfa6f9206454f9dfa9bc2..49994eb8204c956caf32fb651ff0429aadda9610 100644
    --- a/build/cromite_patches/kill-Location-fall-back-to-system.patch
    +++ b/build/cromite_patches/kill-Location-fall-back-to-system.patch
    @@ -12,7 +12,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/services/device/geolocation/BUILD.gn b/services/device/geolocation/BUILD.gn
     --- a/services/device/geolocation/BUILD.gn
     +++ b/services/device/geolocation/BUILD.gn
    -@@ -154,7 +154,6 @@ if (is_android) {
    +@@ -152,7 +152,6 @@ if (is_android) {
            "android/java/src/org/chromium/device/geolocation/LocationProviderAdapter.java",
            "android/java/src/org/chromium/device/geolocation/LocationProviderAndroid.java",
            "android/java/src/org/chromium/device/geolocation/LocationProviderFactory.java",
    @@ -48,7 +48,7 @@ diff --git a/services/device/geolocation/android/java/src/org/chromium/device/ge
     diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
     --- a/services/device/public/cpp/device_features.cc
     +++ b/services/device/public/cpp/device_features.cc
    -@@ -58,6 +58,7 @@ const base::FeatureParam kWinSystemLocationPermissionPollingParam{
    +@@ -64,6 +64,7 @@ const base::FeatureParam kWinSystemLocationPermissionPollingParam{
      BASE_FEATURE(kLocationProviderManager,
                   "LocationProviderManager",
                   base::FEATURE_DISABLED_BY_DEFAULT);
    @@ -56,7 +56,7 @@ diff --git a/services/device/public/cpp/device_features.cc b/services/device/pub
      
      #if BUILDFLAG(IS_CHROMEOS)
      // Enables crash key logging for USB device open operations on ChromeOS. See
    -@@ -99,7 +100,7 @@ const base::FeatureParam::Option
    +@@ -105,7 +106,7 @@ const base::FeatureParam::Option
      const base::FeatureParam
          kLocationProviderManagerParam{
              &kLocationProviderManager, "LocationProviderManagerMode",
    diff --git a/build/cromite_patches/kill-Vision.patch b/build/cromite_patches/kill-Vision.patch
    index 309dfa2a024418b9aa7027600e47645e286d6ba3..bd059133b2dc52b51d7f139b15a4bdd1d00c7015 100644
    --- a/build/cromite_patches/kill-Vision.patch
    +++ b/build/cromite_patches/kill-Vision.patch
    @@ -15,7 +15,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
    -@@ -319,8 +319,6 @@ if (current_toolchain == default_toolchain) {
    +@@ -317,8 +317,6 @@ if (current_toolchain == default_toolchain) {
            "$google_play_services_package:google_play_services_gcm_java",
            "$google_play_services_package:google_play_services_iid_java",
            "$google_play_services_package:google_play_services_tasks_java",
    diff --git a/build/cromite_patches/prefs-always-prompt-for-download-directory.patch b/build/cromite_patches/prefs-always-prompt-for-download-directory.patch
    index 101810e949d47d9c546b6ab6d34c7e2847c5e786..e6097e21115a27d058bb91c1d21cff22f28da0f2 100644
    --- a/build/cromite_patches/prefs-always-prompt-for-download-directory.patch
    +++ b/build/cromite_patches/prefs-always-prompt-for-download-directory.patch
    @@ -31,7 +31,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/chrome/browser/download/download_prefs.cc b/chrome/browser/download/download_prefs.cc
     --- a/chrome/browser/download/download_prefs.cc
     +++ b/chrome/browser/download/download_prefs.cc
    -@@ -274,7 +274,7 @@ void DownloadPrefs::RegisterProfilePrefs(
    +@@ -283,7 +283,7 @@ void DownloadPrefs::RegisterProfilePrefs(
          user_prefs::PrefRegistrySyncable* registry) {
        registry->RegisterBooleanPref(
            prefs::kPromptForDownload,
    diff --git a/build/cromite_patches/ungoogled-chromium-Disable-profile-avatar.patch b/build/cromite_patches/ungoogled-chromium-Disable-profile-avatar.patch
    index 1c27d3a9415a3fe747617432a779752e11fc6010..82648b0ad6f29109c3d58d0d5ffb64887a5550a9 100644
    --- a/build/cromite_patches/ungoogled-chromium-Disable-profile-avatar.patch
    +++ b/build/cromite_patches/ungoogled-chromium-Disable-profile-avatar.patch
    @@ -14,7 +14,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
     --- a/chrome/browser/BUILD.gn
     +++ b/chrome/browser/BUILD.gn
    -@@ -1286,8 +1286,6 @@ static_library("browser") {
    +@@ -1295,8 +1295,6 @@ static_library("browser") {
          "profiles/keep_alive/scoped_profile_keep_alive.h",
          "profiles/off_the_record_profile_impl.cc",
          "profiles/off_the_record_profile_impl.h",
    diff --git a/build/cromite_patches/ungoogled-chromium-Disable-translate-integration.patch b/build/cromite_patches/ungoogled-chromium-Disable-translate-integration.patch
    index 15d26e99f063c0675d43c0dd34529b823fc49d9f..d1023b7ef3c441e31a2c07f5c797a4822634af17 100644
    --- a/build/cromite_patches/ungoogled-chromium-Disable-translate-integration.patch
    +++ b/build/cromite_patches/ungoogled-chromium-Disable-translate-integration.patch
    @@ -173,7 +173,7 @@ diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browse
     diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
     --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
     +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
    -@@ -2059,9 +2059,6 @@ Your Google account may have other forms of browsing history like searches and a
    +@@ -2096,9 +2096,6 @@ Your Google account may have other forms of browsing history like searches and a
            
              Let websites know the languages you speak. They’ll show content in those languages, when possible.
            
    diff --git a/build/cromite_patches/ungoogled-chromium-no-special-hosts-domains.patch b/build/cromite_patches/ungoogled-chromium-no-special-hosts-domains.patch
    index d4caa215b73fc8e7a39aa3aa53709b7143fdfcd1..288d2bc9310c4d1c81d3606c456bac20a583149e 100644
    --- a/build/cromite_patches/ungoogled-chromium-no-special-hosts-domains.patch
    +++ b/build/cromite_patches/ungoogled-chromium-no-special-hosts-domains.patch
    @@ -71,7 +71,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
     --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
    -@@ -104,7 +104,6 @@ import org.chromium.chrome.browser.messages.MessageContainerObserver;
    +@@ -95,7 +95,6 @@ import org.chromium.chrome.browser.messages.MessageContainerObserver;
      import org.chromium.chrome.browser.messages.MessagesResourceMapperInitializer;
      import org.chromium.chrome.browser.metrics.UmaSessionStats;
      import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
    @@ -79,7 +79,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordi
      import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxActionDelegateImpl;
      import org.chromium.chrome.browser.paint_preview.DemoPaintPreview;
      import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer;
    -@@ -1194,10 +1193,8 @@ public class RootUiCoordinator
    +@@ -1154,10 +1153,8 @@ public class RootUiCoordinator
              String url =
                      TemplateUrlServiceFactory.getForProfile(tab.getProfile())
                              .getUrlForSearchQuery(query);
    @@ -93,7 +93,7 @@ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordi
     diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
     --- a/chrome/browser/BUILD.gn
     +++ b/chrome/browser/BUILD.gn
    -@@ -887,10 +887,6 @@ static_library("browser") {
    +@@ -896,10 +896,6 @@ static_library("browser") {
          "page_info/privacy_policy_insights_service_factory.h",
          "page_load_metrics/observers/bookmark_bar_page_load_metrics_observer.cc",
          "page_load_metrics/observers/bookmark_bar_page_load_metrics_observer.h",
    @@ -104,7 +104,7 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
          "page_load_metrics/observers/core/amp_page_load_metrics_observer.cc",
          "page_load_metrics/observers/core/amp_page_load_metrics_observer.h",
          "page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc",
    -@@ -899,10 +895,6 @@ static_library("browser") {
    +@@ -908,10 +904,6 @@ static_library("browser") {
          "page_load_metrics/observers/document_write_page_load_metrics_observer.h",
          "page_load_metrics/observers/foreground_duration_ukm_observer.cc",
          "page_load_metrics/observers/foreground_duration_ukm_observer.h",
    @@ -115,7 +115,7 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
          "page_load_metrics/observers/histogram_suffixes.cc",
          "page_load_metrics/observers/histogram_suffixes.h",
          "page_load_metrics/observers/https_engagement_metrics/https_engagement_page_load_metrics_observer.cc",
    -@@ -1548,8 +1540,6 @@ static_library("browser") {
    +@@ -1563,8 +1555,6 @@ static_library("browser") {
          "supervised_user/classify_url_navigation_throttle.h",
          "supervised_user/supervised_user_browser_utils.cc",
          "supervised_user/supervised_user_browser_utils.h",
    @@ -124,7 +124,7 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
          "supervised_user/supervised_user_interstitial_tab_closer.cc",
          "supervised_user/supervised_user_interstitial_tab_closer.h",
          "supervised_user/supervised_user_metrics_service_factory.cc",
    -@@ -1732,10 +1722,7 @@ static_library("browser") {
    +@@ -1747,10 +1737,7 @@ static_library("browser") {
          "//chrome/browser/content_settings:impl",
          "//chrome/browser/devtools",
          "//chrome/browser/favicon",
    @@ -135,7 +135,7 @@ diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
          "//chrome/browser/ip_protection",
          "//chrome/browser/media/webrtc",
          "//chrome/browser/navigation_predictor:impl",
    -@@ -1814,7 +1801,6 @@ static_library("browser") {
    +@@ -1829,7 +1816,6 @@ static_library("browser") {
          # New dependencies inside of //chrome/browser that generate header files
          # should be added to browser_generated_files.
          ":browser_public_dependencies",
    @@ -166,7 +166,7 @@ diff --git a/chrome/browser/android/metrics/uma_session_stats.cc b/chrome/browse
     diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
     --- a/chrome/browser/browser_process_impl.cc
     +++ b/chrome/browser/browser_process_impl.cc
    -@@ -1231,7 +1231,6 @@ BrowserProcessImpl::component_updater() {
    +@@ -1227,7 +1227,6 @@ BrowserProcessImpl::component_updater() {
            std::make_unique();
      
        std::string brand;
    @@ -177,7 +177,7 @@ diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_pro
     diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
     --- a/chrome/browser/chrome_browser_main.cc
     +++ b/chrome/browser/chrome_browser_main.cc
    -@@ -557,11 +557,6 @@ void ProcessSingletonNotificationCallbackImpl(
    +@@ -462,11 +462,6 @@ void ProcessSingletonNotificationCallbackImpl(
      
        StartupBrowserCreator::ProcessCommandLineAlreadyRunning(
            command_line, current_directory, startup_profile_path_info);
    @@ -189,9 +189,9 @@ diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_brows
      }
      #endif  // BUILDFLAG(ENABLE_PROCESS_SINGLETON)
      
    -@@ -1830,11 +1825,6 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
    +@@ -1717,11 +1712,6 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
          upgrade_util::SaveLastModifiedTimeOfExe();
    - #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
    + #endif
      
     -    // Record now as the last successful chrome start.
     -    if constexpr (kShouldRecordActiveUse) {
    @@ -204,7 +204,7 @@ diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_brows
     diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
     --- a/chrome/browser/chrome_content_browser_client.cc
     +++ b/chrome/browser/chrome_content_browser_client.cc
    -@@ -2967,7 +2967,6 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
    +@@ -2979,7 +2979,6 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
              switches::kForcePNaClSubzero,
      #endif
              switches::kForceUIDirection,
    @@ -212,7 +212,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch
              switches::kJavaScriptHarmony,
              switches::kEnableExperimentalWebAssemblyFeatures,
              embedder_support::kOriginTrialDisabledFeatures,
    -@@ -5356,9 +5355,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation(
    +@@ -5358,9 +5357,6 @@ ChromeContentBrowserClient::CreateThrottlesForNavigation(
            &throttles);
      #endif
      
    @@ -222,7 +222,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch
      
          MaybeAddThrottle(
              supervised_user::MaybeCreateClassifyUrlNavigationThrottleFor(handle),
    -@@ -5963,7 +5959,8 @@ GetClientDataHeader(content::FrameTreeNodeId frame_tree_node_id) {
    +@@ -5973,7 +5969,8 @@ GetClientDataHeader(content::FrameTreeNodeId frame_tree_node_id) {
      }
      #endif
      
    @@ -232,7 +232,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch
      #if BUILDFLAG(IS_ANDROID)
          const std::string& client_data_header,
      #endif
    -@@ -6003,7 +6000,7 @@ std::unique_ptr CreateGoogleURLLoaderThrottle(
    +@@ -6013,7 +6010,7 @@ std::unique_ptr CreateGoogleURLLoaderThrottle(
            std::move(bound_session_request_throttled_handler),
      #endif
            std::move(dynamic_params));
    @@ -241,7 +241,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch
      
      std::vector>
      ChromeContentBrowserClient::CreateURLLoaderThrottles(
    -@@ -6060,15 +6057,6 @@ ChromeContentBrowserClient::CreateURLLoaderThrottles(
    +@@ -6070,15 +6067,6 @@ ChromeContentBrowserClient::CreateURLLoaderThrottles(
            GetClientDataHeader(frame_tree_node_id);
      #endif
      
    @@ -257,7 +257,7 @@ diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/ch
        {
          auto* factory =
              ProtocolHandlerRegistryFactory::GetForBrowserContext(browser_context);
    -@@ -6123,20 +6111,6 @@ ChromeContentBrowserClient::CreateURLLoaderThrottlesForKeepAlive(
    +@@ -6133,20 +6121,6 @@ ChromeContentBrowserClient::CreateURLLoaderThrottlesForKeepAlive(
        }
      #endif
      
    @@ -327,14 +327,14 @@ diff --git a/chrome/browser/domain_reliability/service_factory.cc b/chrome/brows
     diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
     --- a/chrome/browser/extensions/BUILD.gn
     +++ b/chrome/browser/extensions/BUILD.gn
    -@@ -780,7 +780,6 @@ source_set("extensions") {
    -       "//chrome/browser/devtools",
    -       "//chrome/browser/extensions:cws_item_service_proto",
    -       "//chrome/browser/favicon",
    --      "//chrome/browser/google",
    -       "//chrome/browser/image_decoder",
    -       "//chrome/browser/media/router",
    -       "//chrome/browser/media/router:media_router_feature",
    +@@ -284,7 +284,6 @@ source_set("extensions") {
    +     "//chrome/browser/extensions:cws_item_service_proto",
    +     "//chrome/browser/extensions/api:api_registration",
    +     "//chrome/browser/favicon",
    +-    "//chrome/browser/google",
    +     "//chrome/browser/prefetch",
    +     "//chrome/browser/prefs",
    +     "//chrome/browser/preloading:prefs",
     diff --git a/chrome/browser/feedback/BUILD.gn b/chrome/browser/feedback/BUILD.gn
     --- a/chrome/browser/feedback/BUILD.gn
     +++ b/chrome/browser/feedback/BUILD.gn
    @@ -454,7 +454,7 @@ diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service.cc b/c
     diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
     --- a/chrome/browser/metrics/chrome_metrics_service_client.cc
     +++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
    -@@ -606,7 +606,7 @@ ChromeMetricsServiceClient::GetNetworkTimeTracker() {
    +@@ -601,7 +601,7 @@ ChromeMetricsServiceClient::GetNetworkTimeTracker() {
      }
      
      bool ChromeMetricsServiceClient::GetBrand(std::string* brand_code) {
    @@ -488,7 +488,7 @@ diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc b/
     diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/metrics/metrics_reporting_state.cc
     --- a/chrome/browser/metrics/metrics_reporting_state.cc
     +++ b/chrome/browser/metrics/metrics_reporting_state.cc
    -@@ -33,65 +33,6 @@
    +@@ -32,65 +32,6 @@
      #include "components/policy/core/common/features.h"
      #endif  // BUILDFLAG(IS_ANDROID)
      
    @@ -554,7 +554,7 @@ diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/
      void ChangeMetricsReportingState(
          bool enabled,
          ChangeMetricsReportingStateCalledFrom called_from) {
    -@@ -127,11 +68,6 @@ void ChangeMetricsReportingStateWithReply(
    +@@ -126,11 +67,6 @@ void ChangeMetricsReportingStateWithReply(
          return;
        }
      #endif
    @@ -566,7 +566,7 @@ diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/
      }
      
      void UpdateMetricsPrefsOnPermissionChange(
    -@@ -197,13 +133,7 @@ void UpdateMetricsPrefsOnPermissionChange(
    +@@ -196,13 +132,7 @@ void UpdateMetricsPrefsOnPermissionChange(
        crash_keys::ClearMetricsClientId();
      }
      
    @@ -580,11 +580,11 @@ diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/
     +void ApplyMetricsReportingPolicy() {}
      
      bool IsMetricsReportingPolicyManaged() {
    - #if BUILDFLAG(IS_CHROMEOS_ASH)
    + #if BUILDFLAG(IS_CHROMEOS)
     diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
     --- a/chrome/browser/net/system_network_context_manager.cc
     +++ b/chrome/browser/net/system_network_context_manager.cc
    -@@ -849,7 +849,6 @@ void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams(
    +@@ -848,7 +848,6 @@ void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams(
      void SystemNetworkContextManager::ConfigureDefaultNetworkContextParams(
          network::mojom::NetworkContextParams* network_context_params) {
        variations::UpdateCorsExemptHeaderForVariations(network_context_params);
    @@ -595,7 +595,7 @@ diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/brows
     diff --git a/chrome/browser/new_tab_page/one_google_bar/one_google_bar_loader_impl.cc b/chrome/browser/new_tab_page/one_google_bar/one_google_bar_loader_impl.cc
     --- a/chrome/browser/new_tab_page/one_google_bar/one_google_bar_loader_impl.cc
     +++ b/chrome/browser/new_tab_page/one_google_bar/one_google_bar_loader_impl.cc
    -@@ -327,10 +327,11 @@ bool OneGoogleBarLoaderImpl::SetAdditionalQueryParams(
    +@@ -344,10 +344,11 @@ bool OneGoogleBarLoaderImpl::SetAdditionalQueryParams(
      }
      
      GURL OneGoogleBarLoaderImpl::GetApiUrl() const {
    @@ -639,12 +639,14 @@ diff --git a/chrome/browser/page_load_metrics/observers/from_gws_page_load_metri
     diff --git a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
     --- a/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
     +++ b/chrome/browser/page_load_metrics/page_load_metrics_initialize.cc
    -@@ -166,12 +166,7 @@ void PageLoadMetricsEmbedder::RegisterObservers(
    +@@ -168,14 +168,9 @@ void PageLoadMetricsEmbedder::RegisterObservers(
          tracker->AddObserver(std::make_unique());
          tracker->AddObserver(std::make_unique());
          tracker->AddObserver(std::make_unique());
     -    tracker->AddObserver(std::make_unique());
     -    tracker->AddObserver(std::make_unique());
    +     tracker->AddObserver(
    +         std::make_unique());
          tracker->AddObserver(std::make_unique());
     -    tracker->AddObserver(
     -        std::make_unique());
    @@ -679,7 +681,7 @@ diff --git a/chrome/browser/profile_resetter/profile_resetter.cc b/chrome/browse
     diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
     --- a/chrome/browser/safe_browsing/BUILD.gn
     +++ b/chrome/browser/safe_browsing/BUILD.gn
    -@@ -27,7 +27,6 @@ static_library("safe_browsing") {
    +@@ -25,7 +25,6 @@ static_library("safe_browsing") {
          "//build:branding_buildflags",
          "//chrome/app:generated_resources",
          "//chrome/browser:browser_process",
    @@ -690,14 +692,14 @@ diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsin
     diff --git a/chrome/browser/search_engines/BUILD.gn b/chrome/browser/search_engines/BUILD.gn
     --- a/chrome/browser/search_engines/BUILD.gn
     +++ b/chrome/browser/search_engines/BUILD.gn
    -@@ -32,7 +32,6 @@ source_set("search_engines") {
    +@@ -34,7 +34,6 @@ source_set("search_engines") {
      
        deps = [
          "//chrome/browser:browser_process",
     -    "//chrome/browser/google",
    +     "//chrome/browser/regional_capabilities",
    +     "//chrome/browser/search_engine_choice",
          "//chrome/common:channel_info",
    -     "//chrome/common:non_code_constants",
    -     "//components/google/core/common",
     diff --git a/chrome/browser/search_engines/ui_thread_search_terms_data.cc b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
     --- a/chrome/browser/search_engines/ui_thread_search_terms_data.cc
     +++ b/chrome/browser/search_engines/ui_thread_search_terms_data.cc
    @@ -737,7 +739,7 @@ diff --git a/chrome/browser/signin/android/signin_manager_android.cc b/chrome/br
     diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
     --- a/chrome/browser/ui/BUILD.gn
     +++ b/chrome/browser/ui/BUILD.gn
    -@@ -352,7 +352,6 @@ static_library("ui") {
    +@@ -354,7 +354,6 @@ static_library("ui") {
          "//chrome/browser/feedback:feedback_impl",
          "//chrome/browser/file_system_access",
          "//chrome/browser/fingerprinting_protection",
    @@ -849,7 +851,7 @@ diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/brow
     diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
     --- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
     +++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
    -@@ -476,12 +476,6 @@ content::WebUIDataSource* CreateAndAddNewTabPageUiHtmlSource(Profile* profile) {
    +@@ -483,12 +483,6 @@ content::WebUIDataSource* CreateAndAddNewTabPageUiHtmlSource(Profile* profile) {
        // chrome-untrusted://new-tab-page for other external content and resources.
        // NOTE: Use caution when overriding content security policies as that cean
        // lead to subtle security bugs such as https://crbug.com/1251541.
    @@ -881,7 +883,7 @@ diff --git a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc b/chrome/b
     diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
     --- a/chrome/common/BUILD.gn
     +++ b/chrome/common/BUILD.gn
    -@@ -144,8 +144,6 @@ static_library("common_lib") {
    +@@ -140,8 +140,6 @@ static_library("common_lib") {
          "content_restriction.h",
          "crash_keys.cc",
          "crash_keys.h",
    @@ -927,7 +929,7 @@ diff --git a/chrome/renderer/url_loader_throttle_provider_impl.cc b/chrome/rende
     diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
     --- a/chrome/test/BUILD.gn
     +++ b/chrome/test/BUILD.gn
    -@@ -6408,7 +6408,6 @@ test("unit_tests") {
    +@@ -6431,7 +6431,6 @@ test("unit_tests") {
          "//chrome/browser/file_system_access:unit_tests",
          "//chrome/browser/fingerprinting_protection",
          "//chrome/browser/first_party_sets",
    @@ -1841,7 +1843,7 @@ new file mode 100644
     diff --git a/net/base/url_util.cc b/net/base/url_util.cc
     --- a/net/base/url_util.cc
     +++ b/net/base/url_util.cc
    -@@ -511,34 +511,11 @@ bool HasGoogleHost(const GURL& url) {
    +@@ -519,34 +519,11 @@ bool HasGoogleHost(const GURL& url) {
      }
      
      bool IsGoogleHost(std::string_view host) {
    @@ -1880,7 +1882,7 @@ diff --git a/net/base/url_util.cc b/net/base/url_util.cc
     diff --git a/services/network/network_context.cc b/services/network/network_context.cc
     --- a/services/network/network_context.cc
     +++ b/services/network/network_context.cc
    -@@ -2965,7 +2965,7 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
    +@@ -3015,7 +3015,7 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
        }
      #endif  // BUILDFLAG(IS_CT_SUPPORTED)
      
    diff --git a/build/cromite_patches_list.txt b/build/cromite_patches_list.txt
    index a324576df03f4833c51cf243cf7a84678cd89705..8c625555b6a00fd8223f05def9de694e6f3abb87 100644
    --- a/build/cromite_patches_list.txt
    +++ b/build/cromite_patches_list.txt
    @@ -180,10 +180,11 @@ Fix-chromium-build-bugs.patch
     Disable-privacy-issues-in-password-manager.patch
     Restore-chrome-password-store.patch
     Use-browser-navigation-handler.patch
    +Disable-Android-Tab-Declutter.patch
     
    -eyeo-beta-118.0.5993.48-base.patch
    -eyeo-beta-118.0.5993.48-chrome_integration.patch
    -eyeo-beta-118.0.5993.48-android_api.patch
    -eyeo-beta-118.0.5993.48-android_settings.patch
    -eyeo-beta-118.0.5993.48-extension_api.patch
    +eyeo-133.0.6943.49-base.patch
    +eyeo-133.0.6943.49-chrome_integration.patch
    +eyeo-133.0.6943.49-android_api.patch
    +eyeo-133.0.6943.49-android_settings.patch
    +eyeo-133.0.6943.49-extension_api.patch
     Eyeo-Adblock-for-Cromite.patch
    diff --git a/build/e_patches/Add-Mojeek-Search-engine.patch b/build/e_patches/Add-Mojeek-Search-engine.patch
    index 3c34c33bd7a412b43d3468980f5bfe16fcd11c43..c56c9f446e4f3938fe8ab9dd532feac3508e2c14 100644
    --- a/build/e_patches/Add-Mojeek-Search-engine.patch
    +++ b/build/e_patches/Add-Mojeek-Search-engine.patch
    @@ -1,4 +1,4 @@
    -From 95389da2dc6c832af501fc1a2e1f64fbc603777d Mon Sep 17 00:00:00 2001
    +From 4fa5dcf9e893751e2c8124577e9f66906eeae5bc Mon Sep 17 00:00:00 2001
     From: TheScarastic 
     Date: Wed, 16 Nov 2022 11:14:01 +0000
     Subject: Add Mojeek Search engine
    @@ -8,7 +8,7 @@ Subject: Add Mojeek Search engine
      1 file changed, 1 insertion(+)
     
     diff --git a/components/search_engines/search_engine_countries-inc.cc b/components/search_engines/search_engine_countries-inc.cc
    -index 2184d45677dcd..29cca1a481e63 100644
    +index d24cf8e7f4478..cf7f5bbed4c4a 100644
     --- a/components/search_engines/search_engine_countries-inc.cc
     +++ b/components/search_engines/search_engine_countries-inc.cc
     @@ -43,6 +43,7 @@ constexpr EngineAndTier engines_default[] = {
    diff --git a/build/e_patches/Browser-Change-adblock-url-to-ours.patch b/build/e_patches/Browser-Change-adblock-url-to-ours.patch
    index 742d82c899a2ead4ffbbf50cc15c1691aa91c86e..8bac3b770cd288edd67f546e94e03ff8604423bd 100644
    --- a/build/e_patches/Browser-Change-adblock-url-to-ours.patch
    +++ b/build/e_patches/Browser-Change-adblock-url-to-ours.patch
    @@ -1,4 +1,4 @@
    -From f0dee9e987e3b3b61ceec0752207c586fccb227c Mon Sep 17 00:00:00 2001
    +From af82ab8ef3220845d46d1f926ad6c7bea44156ef Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Tue, 10 Sep 2024 12:17:23 +0530
     Subject: Browser: Change adblock url to ours
    @@ -43,10 +43,10 @@ index ef6d1652c97f0..24cd56084827d 100644
              mAdBlockFiltersUrlEdit.setText(AdBlockNativeGateway.getAdBlockFiltersURL());
              mAdBlockFiltersUrlEdit.addTextChangedListener(this);
     diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
    -index f7213b3c8777f..85d715c53ff41 100644
    +index 49e287fc930ec..5f6707c2278b5 100644
     --- a/chrome/browser/browser_process_impl.cc
     +++ b/chrome/browser/browser_process_impl.cc
    -@@ -1251,11 +1251,11 @@ BrowserProcessImpl::adblock_updater() {
    +@@ -1247,11 +1247,11 @@ BrowserProcessImpl::adblock_updater() {
        std::unique_ptr scheduler =
            std::make_unique();
      
    @@ -63,7 +63,7 @@ index f7213b3c8777f..85d715c53ff41 100644
        }
      
        adblock_updater_ = std::make_unique(
    -@@ -1263,7 +1263,7 @@ BrowserProcessImpl::adblock_updater() {
    +@@ -1259,7 +1259,7 @@ BrowserProcessImpl::adblock_updater() {
                std::move(scheduler),
                g_browser_process->subresource_filter_ruleset_service(),
                local_state()->GetBoolean(prefs::kAdBlockEnabled),
    @@ -73,10 +73,10 @@ index f7213b3c8777f..85d715c53ff41 100644
        return adblock_updater_.get();
      }
     diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
    -index 75be09b615fca..6eec2f773af01 100644
    +index 32f549a54cd00..70057afef3ab3 100644
     --- a/chrome/browser/net/system_network_context_manager.cc
     +++ b/chrome/browser/net/system_network_context_manager.cc
    -@@ -645,7 +645,7 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) {
    +@@ -644,7 +644,7 @@ void SystemNetworkContextManager::RegisterPrefs(PrefRegistrySimple* registry) {
      
        registry->RegisterBooleanPref(prefs::kAdBlockEnabled, false);
        registry->RegisterStringPref(prefs::kAdBlockFiltersURL,
    @@ -86,5 +86,5 @@ index 75be09b615fca..6eec2f773af01 100644
        // Static auth params
        registry->RegisterStringPref(prefs::kAuthSchemes,
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Disable-Android-native-autofill-by-default.patch b/build/e_patches/Browser-Disable-Android-native-autofill-by-default.patch
    index 0519234e6380621eaf7f86909a35ad478be6ad2c..af13f9d29e57f897548c250955877172422aa10e 100644
    --- a/build/e_patches/Browser-Disable-Android-native-autofill-by-default.patch
    +++ b/build/e_patches/Browser-Disable-Android-native-autofill-by-default.patch
    @@ -1,4 +1,4 @@
    -From f6430b15ca89e4c6370ddf0515bd0b4f5ad10687 Mon Sep 17 00:00:00 2001
    +From 4da6a7e9ff30dcbf84e691fc3d28373749019115 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Tue, 22 Aug 2023 06:16:40 +0000
     Subject: Browser: Disable Android native autofill by default
    @@ -8,7 +8,7 @@ Subject: Browser: Disable Android native autofill by default
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/components/autofill/core/common/autofill_prefs.cc b/components/autofill/core/common/autofill_prefs.cc
    -index 8e5113f7318cd..3d48d92a09d17 100644
    +index f4e7b5879b254..171c4c98c7754 100644
     --- a/components/autofill/core/common/autofill_prefs.cc
     +++ b/components/autofill/core/common/autofill_prefs.cc
     @@ -46,7 +46,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
    @@ -21,5 +21,5 @@ index 8e5113f7318cd..3d48d92a09d17 100644
        registry->RegisterIntegerPref(prefs::kAutocompleteLastVersionRetentionPolicy,
                                      0);
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Enable-external-intent-requests.patch b/build/e_patches/Browser-Enable-external-intent-requests.patch
    index 17b8882e3e2939790e8ba31604434be3c466c810..c78cf8d1d8248665d0209b3d06741e9a902e53ef 100644
    --- a/build/e_patches/Browser-Enable-external-intent-requests.patch
    +++ b/build/e_patches/Browser-Enable-external-intent-requests.patch
    @@ -1,4 +1,4 @@
    -From 934dfd8ebc541920fd20cf9a49d239af00949c4f Mon Sep 17 00:00:00 2001
    +From c5a5c1d50d7efd4e0d5f4f66995a4484190d7721 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Tue, 5 Nov 2024 10:08:21 +0530
     Subject: Browser: Enable external intent requests
    @@ -31,7 +31,7 @@ index 32672a37830e3..b2329f2646d16 100644
              android:key="security_section"
              android:title="@string/security_section_title" />
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
    -index 270eb6a4750de..f056bb3900541 100644
    +index 7401741d913cd..a446ef7720250 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
     @@ -17,6 +17,7 @@ import androidx.annotation.VisibleForTesting;
    @@ -50,7 +50,7 @@ index 270eb6a4750de..f056bb3900541 100644
      import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
      import org.chromium.chrome.browser.fullscreen.FullscreenManager;
      import org.chromium.chrome.browser.init.ChromeActivityNativeDelegate;
    -@@ -134,7 +134,7 @@ public class CustomTabDelegateFactory implements TabDelegateFactory {
    +@@ -128,7 +128,7 @@ public class CustomTabDelegateFactory implements TabDelegateFactory {
      
              @Override
              public boolean shouldDisableAllExternalIntents() {
    @@ -201,5 +201,5 @@ index c4015ad7de119..0000000000000
     -                "AllowExternalIntentRequests",
     -                base::FEATURE_DISABLED_BY_DEFAULT);
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Enable-unified-autoplay-by-default.patch b/build/e_patches/Browser-Enable-unified-autoplay-by-default.patch
    index fa025d74d08a19d0f238b34e9fb2351fddc0d6d4..a4388563a03751b0e44aede8eeafd0984f1c5108 100644
    --- a/build/e_patches/Browser-Enable-unified-autoplay-by-default.patch
    +++ b/build/e_patches/Browser-Enable-unified-autoplay-by-default.patch
    @@ -1,4 +1,4 @@
    -From 522711c75db96e749bc59cc961e48e39ad24e95b Mon Sep 17 00:00:00 2001
    +From 38ee2061d5b911a0c939dfcc52d0f44c9effade3 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Fri, 29 Sep 2023 16:30:17 +0530
     Subject: Browser: Enable unified autoplay by default
    @@ -9,7 +9,7 @@ Subject: Browser: Enable unified autoplay by default
      2 files changed, 2 insertions(+), 2 deletions(-)
     
     diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
    -index faf5d8a5eae14..11561e8ec1a95 100644
    +index 0f865d060a067..b738b065892f3 100644
     --- a/components/content_settings/core/browser/content_settings_registry.cc
     +++ b/components/content_settings/core/browser/content_settings_registry.cc
     @@ -248,7 +248,7 @@ void ContentSettingsRegistry::Init() {
    @@ -22,7 +22,7 @@ index faf5d8a5eae14..11561e8ec1a95 100644
                 /*valid_settings=*/{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK},
                 WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE,
     diff --git a/third_party/blink/renderer/core/html/media/autoplay_policy.cc b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
    -index ef64ebec77cd6..22dbfa2224201 100644
    +index 2be0fa8d55043..235fb5eb1ef52 100644
     --- a/third_party/blink/renderer/core/html/media/autoplay_policy.cc
     +++ b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
     @@ -458,7 +458,7 @@ bool AutoplayPolicy::IsAutoplayAllowedPerSettings() const {
    @@ -35,5 +35,5 @@ index ef64ebec77cd6..22dbfa2224201 100644
        return true;
      }
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Hide-settings-when-parental-control-is-active.patch b/build/e_patches/Browser-Hide-settings-when-parental-control-is-active.patch
    index 8b4c7c02f4fe4a35e9df9c51910e8f8308e4252a..12ca4bc357f561ce12c4768084acc830f0d1cf78 100644
    --- a/build/e_patches/Browser-Hide-settings-when-parental-control-is-active.patch
    +++ b/build/e_patches/Browser-Hide-settings-when-parental-control-is-active.patch
    @@ -1,4 +1,4 @@
    -From beb9674b8cb78b05db8b6c32869ad7ee80544afd Mon Sep 17 00:00:00 2001
    +From db8cd5e2d8fccd7b1cb6c407ccdbd229d6438cb7 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Thu, 11 Jul 2024 11:11:15 +0530
     Subject: Browser: Hide settings when parental control is active
    @@ -11,18 +11,18 @@ Subject: Browser: Hide settings when parental control is active
      4 files changed, 40 insertions(+), 1 deletion(-)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
    -index 9016fe25aad08..509e1e254b06d 100644
    +index 7287d541b043f..1f785c9eb267a 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
    -@@ -71,6 +71,7 @@ import org.chromium.chrome.browser.PlayServicesVersionInfo;
    - import org.chromium.chrome.browser.WarmupManager;
    +@@ -71,6 +71,7 @@ import org.chromium.chrome.browser.WarmupManager;
    + import org.chromium.chrome.browser.ai.AiAssistantService;
      import org.chromium.chrome.browser.app.appmenu.AppMenuPropertiesDelegateImpl;
      import org.chromium.chrome.browser.app.download.DownloadMessageUiDelegate;
     +import org.chromium.chrome.browser.app.flags.ChromeCachedFlags;
      import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
      import org.chromium.chrome.browser.app.tab_activity_glue.ReparentingDelegateFactory;
      import org.chromium.chrome.browser.app.tab_activity_glue.TabReparentingController;
    -@@ -2372,7 +2373,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
    +@@ -2373,7 +2374,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
              @BrowserProfileType
              int type = Profile.getBrowserProfileTypeFromProfile(getCurrentTabModel().getProfile());
      
    @@ -32,7 +32,7 @@ index 9016fe25aad08..509e1e254b06d 100644
                          SettingsNavigationFactory.createSettingsNavigation();
                  settingsNavigation.startSettings(this);
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
    -index 3b04979dba79f..e676bf22c49bf 100644
    +index ec19a7db64a51..59b8b9fc57e7f 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
     @@ -37,6 +37,7 @@ import org.chromium.base.supplier.OneshotSupplier;
    @@ -43,7 +43,7 @@ index 3b04979dba79f..e676bf22c49bf 100644
      import org.chromium.chrome.browser.ActivityTabProvider;
      import org.chromium.chrome.browser.AlwaysIncognitoLinkInterceptor;
      import org.chromium.chrome.browser.profiles.ProfileManager;
    -@@ -664,6 +665,10 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
    +@@ -674,6 +675,10 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
                      item.setEnabled(isIncognitoEnabled() && !isIncognitoReauthShowing);
                  }
      
    @@ -100,10 +100,10 @@ index 6040e96c47e0d..23a9a0d8dfdf4 100644
     +    }
      }
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
    -index fa591514927fa..e9f6dd252b502 100644
    +index 3fe67443571f8..5e83dbc4b856e 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
    -@@ -24,6 +24,7 @@ import org.chromium.base.supplier.ObservableSupplierImpl;
    +@@ -30,6 +30,7 @@ import org.chromium.base.supplier.ObservableSupplierImpl;
      import org.chromium.base.task.PostTask;
      import org.chromium.base.task.TaskTraits;
      import org.chromium.chrome.R;
    @@ -111,7 +111,7 @@ index fa591514927fa..e9f6dd252b502 100644
      import org.chromium.chrome.browser.autofill.options.AutofillOptionsFragment;
      import org.chromium.chrome.browser.autofill.options.AutofillOptionsFragment.AutofillOptionsReferrer;
      import org.chromium.chrome.browser.autofill.settings.SettingsNavigationHelper;
    -@@ -168,6 +169,9 @@ public class MainSettings extends ChromeBaseSettingsFragment
    +@@ -182,6 +183,9 @@ public class MainSettings extends ChromeBaseSettingsFragment
      
          @Override
          public void onStart() {
    @@ -122,5 +122,5 @@ index fa591514927fa..e9f6dd252b502 100644
              SyncService syncService = SyncServiceFactory.getForProfile(getProfile());
              if (syncService != null) {
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Modify-default-search-engines.patch b/build/e_patches/Browser-Modify-default-search-engines.patch
    index 305e1a382c2f78dd9014577ea023d75df5fb58db..efba82fcefadf28ef6c56912fb58114c9bae1e9e 100644
    --- a/build/e_patches/Browser-Modify-default-search-engines.patch
    +++ b/build/e_patches/Browser-Modify-default-search-engines.patch
    @@ -1,4 +1,4 @@
    -From 7461780b80b4b4bd73f8a1c5fbbc16c431be74e4 Mon Sep 17 00:00:00 2001
    +From d91e41ab5facc78ed043dc31025dcef5537ebdbc Mon Sep 17 00:00:00 2001
     From: Aayush Gupta 
     Date: Thu, 18 Mar 2021 13:42:44 +0100
     Subject: Browser: Modify default search engines
    @@ -192,7 +192,7 @@ index 0d2ebbf08bfc7..9a7616e880b02 100644
                  "name": "Google in English",
                  "keyword": "googleen",
     diff --git a/components/search_engines/search_engine_countries-inc.cc b/components/search_engines/search_engine_countries-inc.cc
    -index 235377432cf9c..2184d45677dcd 100644
    +index 18fee305eb091..d24cf8e7f4478 100644
     --- a/components/search_engines/search_engine_countries-inc.cc
     +++ b/components/search_engines/search_engine_countries-inc.cc
     @@ -38,9 +38,11 @@ struct EngineAndTier {
    @@ -211,15 +211,15 @@ index 235377432cf9c..2184d45677dcd 100644
      
      // Note, the below entries are sorted by country code, not the name in comment.
     diff --git a/components/search_engines/search_engine_type.h b/components/search_engines/search_engine_type.h
    -index 18413c59ccdfd..0b33877ee8bac 100644
    +index 205baa60bf23a..5b6b03f2aee08 100644
     --- a/components/search_engines/search_engine_type.h
     +++ b/components/search_engines/search_engine_type.h
    -@@ -93,11 +93,13 @@ enum SearchEngineType {
    -   SEARCH_ENGINE_LILO = 74,
    -   SEARCH_ENGINE_GOOGLE_EN = 75,
    -   SEARCH_ENGINE_DUCKDUCKGOLIGHT = 76,
    -+  SEARCH_ENGINE_MURENASEARCH = 77,
    -+  SEARCH_ENGINE_SPOT = 78,
    +@@ -96,11 +96,13 @@ enum SearchEngineType {
    +   SEARCH_ENGINE_MCAFEE = 77,
    +   SEARCH_ENGINE_GOOGLE_EN = 78,
    +   SEARCH_ENGINE_DUCKDUCKGOLIGHT = 79,
    ++  SEARCH_ENGINE_MURENASEARCH = 80,
    ++  SEARCH_ENGINE_SPOT = 81,
      
        SEARCH_ENGINE_MAX  // Bounding value needed for UMA histogram macro.
      };
    @@ -253,27 +253,27 @@ index 4b32c2424922b..9808a5e92ff3a 100644
          if (SameDomain(url, GURL(engine->search_url))) {
            return engine->type;
     diff --git a/components/search_engines/template_url_prepopulate_data.cc b/components/search_engines/template_url_prepopulate_data.cc
    -index 5738c4f9853c7..8c9beddc44a59 100644
    +index 3883a557fa6a0..fc5db64634650 100644
     --- a/components/search_engines/template_url_prepopulate_data.cc
     +++ b/components/search_engines/template_url_prepopulate_data.cc
    -@@ -86,6 +86,11 @@ GetPrepopulatedEnginesForEeaRegionCountries(int country_id,
    +@@ -75,6 +75,11 @@ GetPrepopulatedEnginesForEeaRegionCountries(CountryID country_id,
      std::vector> GetPrepopulatedTemplateURLData(
    -     int country_id,
    -     PrefService* prefs) {
    +     CountryID country_id,
    +     PrefService& prefs) {
     +  bool use_default = true;
     +  if (use_default) {
     +    return GetDefaultPrepopulatedEngines();
     +  }
     +
    -   std::vector> t_urls;
    - 
    -   if (!prefs) {
    -@@ -320,7 +325,7 @@ std::unique_ptr GetPrepopulatedFallbackSearch(
    -     PrefService* prefs,
    -     search_engines::SearchEngineChoiceService* search_engine_choice_service) {
    -   return FindPrepopulatedEngineInternal(prefs, search_engine_choice_service,
    --                                        google.id,
    -+                                        murenasearch.id,
    +   if (regional_capabilities::HasSearchEngineCountryListOverride()) {
    +     auto country_override =
    +         std::get(
    +@@ -284,7 +289,7 @@ void ClearPrepopulatedEnginesInPrefs(PrefService* prefs) {
    + std::unique_ptr GetPrepopulatedFallbackSearch(
    +     PrefService& prefs,
    +     CountryID country_id) {
    +-  return FindPrepopulatedEngineInternal(prefs, country_id, google.id,
    ++  return FindPrepopulatedEngineInternal(prefs, country_id, murenasearch.id,
                                              /*use_first_as_fallback=*/true);
      }
      
    diff --git a/build/e_patches/Browser-Redirect-users-to-e-foundation-docs.patch b/build/e_patches/Browser-Redirect-users-to-e-foundation-docs.patch
    index e237ad037dcbebdff752ebaa312cab41bd609c5d..7bb36b434acb864676e4384f539b46bd2dda78e6 100644
    --- a/build/e_patches/Browser-Redirect-users-to-e-foundation-docs.patch
    +++ b/build/e_patches/Browser-Redirect-users-to-e-foundation-docs.patch
    @@ -1,4 +1,4 @@
    -From bd8517c324ddccb286553b1b742e41a043026410 Mon Sep 17 00:00:00 2001
    +From 1413de2e16fdbc6e46db0c6c684a04be7e7f3434 Mon Sep 17 00:00:00 2001
     From: Aayush Gupta 
     Date: Tue, 11 May 2021 14:22:00 +0200
     Subject: Browser: Redirect users to e foundation docs
    @@ -23,12 +23,12 @@ index ba21d894a0cf4..62cbeb31f6c51 100644
          private static ProfileKeyedMap sProfileToLauncherMap;
          private final HelpAndFeedbackLauncherDelegate mDelegate;
     diff --git a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherDelegate.java b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherDelegate.java
    -index b4410a1abd95f..1f99d13ab8b86 100644
    +index b900240bfa2f2..53b13800197a5 100644
     --- a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherDelegate.java
     +++ b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherDelegate.java
    -@@ -14,7 +14,7 @@ import androidx.annotation.NonNull;
    - 
    +@@ -15,7 +15,7 @@ import org.chromium.build.annotations.NullMarked;
      /** Delegate that handles the display of the HelpAndFeedback flows. */
    + @NullMarked
      public interface HelpAndFeedbackLauncherDelegate {
     -    static final String FALLBACK_SUPPORT_URL = "https://support.google.com/chrome/topic/6069782";
     +    static final String FALLBACK_SUPPORT_URL = "https://doc.e.foundation/how-tos/";
    @@ -36,5 +36,5 @@ index b4410a1abd95f..1f99d13ab8b86 100644
          /**
           * Starts an activity showing a help page for the specified context ID.
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Remove-send-to-devices-option.patch b/build/e_patches/Browser-Remove-send-to-devices-option.patch
    index 26eb2619e3a015986c6c918fd9de31d741b084d6..f54caa4365323b1c0ac4413f7210aec59e60c6e8 100644
    --- a/build/e_patches/Browser-Remove-send-to-devices-option.patch
    +++ b/build/e_patches/Browser-Remove-send-to-devices-option.patch
    @@ -1,4 +1,4 @@
    -From 0346dfed9faa0e81cdf6b62e8bf151b633dc2752 Mon Sep 17 00:00:00 2001
    +From 0a0069d5d4f81b36d5b3fbe62c5bd0aa68040a52 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Fri, 20 Oct 2023 18:23:09 +0530
     Subject: Browser: Remove send to devices option
    @@ -8,18 +8,18 @@ Subject: Browser: Remove send to devices option
      1 file changed, 34 deletions(-)
     
     diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
    -index a3fb55b84de0e..e3b5dd7bc4bad 100644
    +index 9a03bf9c79ad0..1c5b2f27a8e89 100644
     --- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
     +++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
    -@@ -275,7 +275,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
    -             maybeAddLongScreenshotFirstPartyOption();
    +@@ -276,7 +276,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
    +             }
                  maybeAddPrintFirstPartyOption();
              }
     -        maybeAddSendTabToSelfFirstPartyOption();
              maybeAddQrCodeFirstPartyOption();
          }
      
    -@@ -296,14 +295,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
    +@@ -290,14 +289,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
              }
          }
      
    @@ -34,7 +34,7 @@ index a3fb55b84de0e..e3b5dd7bc4bad 100644
          private void maybeAddQrCodeFirstPartyOption() {
          }
      
    -@@ -413,31 +404,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
    +@@ -407,31 +398,6 @@ public abstract class ChromeProvidedSharingOptionsProviderBase {
                      .build();
          }
      
    @@ -67,5 +67,5 @@ index a3fb55b84de0e..e3b5dd7bc4bad 100644
              return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE)
                      .setIcon(R.drawable.sharing_print, R.string.print_share_activity_title)
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Rename-strings-to-browser.patch b/build/e_patches/Browser-Rename-strings-to-browser.patch
    index 9869c908298c2767543ecf8c3877aaa129986153..ec7d17da2280a15e8c929c855c85e3cabd234629 100644
    --- a/build/e_patches/Browser-Rename-strings-to-browser.patch
    +++ b/build/e_patches/Browser-Rename-strings-to-browser.patch
    @@ -1,4 +1,4 @@
    -From 9a2f09c23d0c166ec9b3d6bdbeb8ff24189e4a51 Mon Sep 17 00:00:00 2001
    +From 671e4a51c8c183773c0bd777859c49709d6b739c Mon Sep 17 00:00:00 2001
     From: Aayush Gupta 
     Date: Fri, 26 Nov 2021 07:12:30 +0000
     Subject: Browser: Rename strings to browser
    @@ -53,7 +53,7 @@ index 45645bf509f4d..c42374eac7404 100644
      
          @Override
     diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
    -index 78c68195aa835..9e6001628d75b 100644
    +index 50e71ac6b7926..b88199f8c4cb0 100644
     --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
     +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
     @@ -202,6 +202,8 @@ CHAR_LIMIT guidelines:
    @@ -82,5 +82,5 @@ index 0000000000000..49da40abd38e7
     +  
     +
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Use-adaptive-icon-for-recents-UI.patch b/build/e_patches/Browser-Use-adaptive-icon-for-recents-UI.patch
    index b3d830852d280a240e9869fd7b77d381b6d08d64..ee6b422c0a789875502be1e7891cb84ea1cbd25c 100644
    --- a/build/e_patches/Browser-Use-adaptive-icon-for-recents-UI.patch
    +++ b/build/e_patches/Browser-Use-adaptive-icon-for-recents-UI.patch
    @@ -1,4 +1,4 @@
    -From 2779a7c2faaa76168983f028bf1602a69b14ebf5 Mon Sep 17 00:00:00 2001
    +From 856d574a4fffcd1f00247dd202962918137d1bfb Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 14 Dec 2022 12:18:50 +0530
     Subject: Browser: Use adaptive icon for recents UI
    @@ -8,7 +8,7 @@ Subject: Browser: Use adaptive icon for recents UI
      1 file changed, 7 insertions(+), 1 deletion(-)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
    -index 3694e68b204b3..b654d24300ee1 100644
    +index a2c5828940f53..53c225b0a4d5c 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
     @@ -14,6 +14,8 @@ import android.app.ActivityManager.TaskDescription;
    @@ -20,7 +20,7 @@ index 3694e68b204b3..b654d24300ee1 100644
      import android.os.Build;
      import android.os.Build.VERSION;
      import android.os.Build.VERSION_CODES;
    -@@ -488,11 +490,15 @@ public class ChromeBaseAppCompatActivity extends AppCompatActivity
    +@@ -501,11 +503,15 @@ public class ChromeBaseAppCompatActivity extends AppCompatActivity
      
          /** Sets the default task description that will appear in the recents UI. */
          protected void setDefaultTaskDescription() {
    @@ -38,5 +38,5 @@ index 3694e68b204b3..b654d24300ee1 100644
          @Override
          public void onNightModeStateChanged() {
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Use-correct-switch-scale.patch b/build/e_patches/Browser-Use-correct-switch-scale.patch
    index f5d91ebf159ac6b5a140634cec675d2b7bec67cd..ba995c7a9ed2a67d946b5ac3b88b4c1bcc5c382e 100644
    --- a/build/e_patches/Browser-Use-correct-switch-scale.patch
    +++ b/build/e_patches/Browser-Use-correct-switch-scale.patch
    @@ -1,4 +1,4 @@
    -From 7984d39d3b1c5b394cc3ff3af80aafc67d5453aa Mon Sep 17 00:00:00 2001
    +From 413c299ec99c3920682f18905610518716eedcee Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 7 Aug 2024 14:05:16 +0530
     Subject: Browser: Use correct switch scale
    @@ -8,7 +8,7 @@ Subject: Browser: Use correct switch scale
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
    -index 6092501463614..7746e411747a2 100644
    +index f2311c163e3fe..44e4009d5b366 100644
     --- a/components/browser_ui/widget/android/java/res/values/dimens.xml
     +++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
     @@ -218,7 +218,7 @@ found in the LICENSE file.
    @@ -21,5 +21,5 @@ index 6092501463614..7746e411747a2 100644
          60dp
          @dimen/min_touch_target_size
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-Use-our-custom-icon-for-browser.patch b/build/e_patches/Browser-Use-our-custom-icon-for-browser.patch
    index 922843258c0b239c1ccda794912f4cb4075b2ebd..851504f5b843f59f7a570d6e985b9ab53d33bdab 100644
    --- a/build/e_patches/Browser-Use-our-custom-icon-for-browser.patch
    +++ b/build/e_patches/Browser-Use-our-custom-icon-for-browser.patch
    @@ -1,4 +1,4 @@
    -From 6b67f1d17b3b63a6d2126edee73177792e4e0b53 Mon Sep 17 00:00:00 2001
    +From 2e43c3258f56cb84a9f86667091338f2b3566890 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Fri, 18 Nov 2022 10:07:39 +0000
     Subject: Browser: Use our custom icon for browser
    @@ -60,10 +60,10 @@ Subject: Browser: Use our custom icon for browser
      delete mode 100644 chrome/android/java/res_chromium_base/mipmap-xxxhdpi/layered_app_icon_background.png
     
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
    -index 6aa78bbbf2bcd..bd9c7455c18d1 100644
    +index 6966676e1fc4e..59fa0e9f1099a 100644
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
    -@@ -174,22 +174,24 @@ if (current_toolchain == default_toolchain) {
    +@@ -171,22 +171,24 @@ if (current_toolchain == default_toolchain) {
            "java/res_base/values/ic_launcher_round_alias.xml",
            "java/res_base/values/values.xml",
            "java/res_base/xml/network_security_config.xml",
    @@ -98,7 +98,7 @@ index 6aa78bbbf2bcd..bd9c7455c18d1 100644
            "java/res_chromium_base/values/channel_constants.xml",
          ]
        }
    -@@ -1584,22 +1586,24 @@ if (current_toolchain == default_toolchain) {
    +@@ -1597,22 +1599,24 @@ if (current_toolchain == default_toolchain) {
        android_resources("chrome_public_apk_base_module_resources") {
          resource_overlay = true
          sources = [
    @@ -4325,5 +4325,5 @@ index 41072a348f244..ef0766c75cd67 100644
     +    
      
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Browser-disable-price-shopping-commerce-integration.patch b/build/e_patches/Browser-disable-price-shopping-commerce-integration.patch
    index 2afacea5acb10e2adec1be63c2335da3f06cde72..1b25e1fba0f9b61b6e2608737dd5ecd302aa7ba4 100644
    --- a/build/e_patches/Browser-disable-price-shopping-commerce-integration.patch
    +++ b/build/e_patches/Browser-disable-price-shopping-commerce-integration.patch
    @@ -1,4 +1,4 @@
    -From 0691a525040be3f754b3e44a2c46764f97aa4d32 Mon Sep 17 00:00:00 2001
    +From a4e8a2559d7e8f13111dad4cacc0afd8d8a314c0 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 4 Oct 2023 19:36:24 +0530
     Subject: Browser: disable price shopping commerce integration
    @@ -9,10 +9,10 @@ Subject: Browser: disable price shopping commerce integration
      2 files changed, 6 insertions(+), 4 deletions(-)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
    -index 405a5509a86d1..3b04979dba79f 100644
    +index ea52a6bcfebec..ec19a7db64a51 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
    -@@ -1241,7 +1241,7 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
    +@@ -1251,7 +1251,7 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
              }
      
              Profile profile = currentTab.getProfile();
    @@ -21,7 +21,7 @@ index 405a5509a86d1..3b04979dba79f 100644
              ShoppingService.ProductInfo info = null;
              if (service != null) {
                  info = service.getAvailableProductInfoForUrl(currentTab.getUrl());
    -@@ -1274,7 +1274,9 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
    +@@ -1284,7 +1284,9 @@ public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate
              } else {
                  startPriceTrackingMenuItem.setVisible(true);
                  stopPriceTrackingMenuItem.setVisible(false);
    @@ -31,9 +31,9 @@ index 405a5509a86d1..3b04979dba79f 100644
     +        stopPriceTrackingMenuItem.setVisible(false);
          }
      
    -     /**
    +     private void updateAiMenuItemRow(
     diff --git a/components/commerce/core/commerce_feature_list.cc b/components/commerce/core/commerce_feature_list.cc
    -index 1047fa376d888..07274f39eb157 100644
    +index 92209b46f99f5..abcca7e722315 100644
     --- a/components/commerce/core/commerce_feature_list.cc
     +++ b/components/commerce/core/commerce_feature_list.cc
     @@ -120,7 +120,7 @@ BASE_FEATURE(kCommerceAllowLocalImages,
    @@ -45,7 +45,7 @@ index 1047fa376d888..07274f39eb157 100644
      
      BASE_FEATURE(kCommerceMerchantViewer,
                   "CommerceMerchantViewer",
    -@@ -342,7 +342,7 @@ BASE_FEATURE(kShoppingPageTypesRegionLaunched,
    +@@ -346,7 +346,7 @@ BASE_FEATURE(kShoppingPageTypesRegionLaunched,
                   "ShoppingPageTypesRegionLaunched",
                   base::FEATURE_ENABLED_BY_DEFAULT);
      
    @@ -55,5 +55,5 @@ index 1047fa376d888..07274f39eb157 100644
      BASE_FEATURE(kCommerceDeveloper,
                   "CommerceDeveloper",
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Change-accent-and-switch-to-match-our-palette.patch b/build/e_patches/Change-accent-and-switch-to-match-our-palette.patch
    index ce490d948e92eb2c8b3a9a4bbff5dd479d95175e..ce4ada77ce2f41baaa4eae282fb3fe7db46ed354 100644
    --- a/build/e_patches/Change-accent-and-switch-to-match-our-palette.patch
    +++ b/build/e_patches/Change-accent-and-switch-to-match-our-palette.patch
    @@ -1,4 +1,4 @@
    -From 7fcd94677c868e4788cb6b5eb04e79ae5eedbb63 Mon Sep 17 00:00:00 2001
    +From 2e447117df26affe8e08f19a29cc8b005d445cde Mon Sep 17 00:00:00 2001
     From: Nishith Khanna 
     Date: Tue, 20 Feb 2024 20:35:25 +0530
     Subject: Change accent and switch to match our palette
    @@ -49,7 +49,7 @@ index 9d1713c43e8b1..5cefd325aae52 100644
              android:layout_marginStart="16dp"
              android:layout_width="48dp"
     diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
    -index 52c2f2b2ffbfc..4836ec12d1a04 100644
    +index 9d8b39203d2ff..44f5110e1c361 100644
     --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
     +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
     @@ -114,7 +114,7 @@ public class TabUiThemeProvider {
    @@ -221,7 +221,7 @@ index 56a1aac203baa..40a9798da1323 100644
     -    app:drawableTint="?attr/colorPrimary" />
     +    app:drawableTint="@color/default_control_color_active_baseline" />
     diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
    -index ee0db8b28c933..c804658474de9 100644
    +index be538b0b26b4c..a0fd551143424 100644
     --- a/components/browser_ui/styles/android/BUILD.gn
     +++ b/components/browser_ui/styles/android/BUILD.gn
     @@ -60,6 +60,8 @@ android_resources("java_resources") {
    @@ -233,7 +233,7 @@ index ee0db8b28c933..c804658474de9 100644
          "java/res/drawable-hdpi/btn_star_filled.png",
          "java/res/drawable-hdpi/ic_delete_white_24dp.png",
          "java/res/drawable-hdpi/ic_edit_24dp.png",
    -@@ -236,6 +238,8 @@ android_resources("java_resources") {
    +@@ -239,6 +241,8 @@ android_resources("java_resources") {
          "java/res/drawable/smartphone_black_24dp.xml",
          "java/res/drawable/star_outline_24dp.xml",
          "java/res/drawable/toolbar_hairline.xml",
    @@ -364,7 +364,7 @@ index 0fc8e8e6559ff..fcea0bb5b51d1 100644
              
     diff --git a/components/browser_ui/theme/android/java/res/values/themes.xml b/components/browser_ui/theme/android/java/res/values/themes.xml
    -index 547fab112eacf..ec7313150b87e 100644
    +index 7adfa901b7cef..13941ab79f395 100644
     --- a/components/browser_ui/theme/android/java/res/values/themes.xml
     +++ b/components/browser_ui/theme/android/java/res/values/themes.xml
     @@ -46,7 +46,7 @@ found in the LICENSE file.
    @@ -376,7 +376,7 @@ index 547fab112eacf..ec7313150b87e 100644
      
              
              @color/filled_button_bg_dynamic_list
    -@@ -70,7 +70,7 @@ found in the LICENSE file.
    +@@ -72,7 +72,7 @@ found in the LICENSE file.
              
              @style/Widget.BrowserUI.CheckBox
              @style/Widget.BrowserUI.RadioButton
    @@ -385,7 +385,7 @@ index 547fab112eacf..ec7313150b87e 100644
      
              
              @macro/default_bg_color
    -@@ -159,7 +159,7 @@ found in the LICENSE file.
    +@@ -161,7 +161,7 @@ found in the LICENSE file.
              
              true
              @color/gm3_baseline_surface_tint
    @@ -394,7 +394,7 @@ index 547fab112eacf..ec7313150b87e 100644
      
              
              @color/filled_button_bg_dynamic_list
    -@@ -171,7 +171,7 @@ found in the LICENSE file.
    +@@ -175,7 +175,7 @@ found in the LICENSE file.
              ?attr/colorPrimary
      
              
    @@ -403,7 +403,7 @@ index 547fab112eacf..ec7313150b87e 100644
              
              @style/SwitchPreference
    -@@ -247,7 +247,7 @@ found in the LICENSE file.
    +@@ -253,7 +253,7 @@ found in the LICENSE file.
      
              
              @style/SpinnerStyle
    @@ -470,5 +470,5 @@ index 43dbabd12dc90..8b66a2573ec56 100644
     +    #C8CED0
      
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Disable-AGSA-by-default.patch b/build/e_patches/Disable-AGSA-by-default.patch
    index 56e018ffa76040bedd6947717ad558e6ac171f14..bd6b7425b1bed8169bef20013e9c705dc6bbfb8a 100644
    --- a/build/e_patches/Disable-AGSA-by-default.patch
    +++ b/build/e_patches/Disable-AGSA-by-default.patch
    @@ -1,4 +1,4 @@
    -From 2305bcf20d0c3e59edb1957ce4e6f09b51256e44 Mon Sep 17 00:00:00 2001
    +From 42b6ad54b30e1f3a6151a98b368fd4e9b3badc55 Mon Sep 17 00:00:00 2001
     From: csagan5 <32685696+csagan5@users.noreply.github.com>
     Date: Sun, 26 Sep 2021 11:17:53 +0200
     Subject: Disable AGSA by default
    @@ -16,7 +16,7 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      8 files changed, 8 insertions(+), 64 deletions(-)
     
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
    -index bd9c7455c18d1..774c88f571b23 100644
    +index 59fa0e9f1099a..701d3a40566ed 100644
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
     @@ -404,7 +404,6 @@ if (current_toolchain == default_toolchain) {
    @@ -28,7 +28,7 @@ index bd9c7455c18d1..774c88f571b23 100644
            "//chrome/browser/history_clusters:java",
            "//chrome/browser/hub:factory_java",
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
    -index 7f2039e0d8130..ddbb1bab0c669 100644
    +index 6f38639d178f2..846c5420ab88f 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
     @@ -45,7 +45,6 @@ import org.chromium.chrome.browser.document.ChromeLauncherActivity;
    @@ -39,7 +39,7 @@ index 7f2039e0d8130..ddbb1bab0c669 100644
      import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
      import org.chromium.chrome.browser.profiles.Profile;
      import org.chromium.chrome.browser.profiles.ProfileManager;
    -@@ -263,9 +262,6 @@ public class IntentHandler {
    +@@ -275,9 +274,6 @@ public class IntentHandler {
          public static final String EXTRA_SKIP_PRECONNECT =
                  "org.chromium.chrome.browser.skip_preconnect";
      
    @@ -49,7 +49,7 @@ index 7f2039e0d8130..ddbb1bab0c669 100644
          private static Pair sPendingReferrer;
          private static int sReferrerId;
          private static String sPendingIncognitoUrl;
    -@@ -310,7 +306,6 @@ public class IntentHandler {
    +@@ -322,7 +318,6 @@ public class IntentHandler {
              ExternalAppId.NEWS,
              ExternalAppId.LINE,
              ExternalAppId.WHATSAPP,
    @@ -57,7 +57,7 @@ index 7f2039e0d8130..ddbb1bab0c669 100644
              ExternalAppId.WEBAPK,
              ExternalAppId.YAHOO_MAIL,
              ExternalAppId.VIBER,
    -@@ -333,7 +328,6 @@ public class IntentHandler {
    +@@ -345,7 +340,6 @@ public class IntentHandler {
              int NEWS = 8;
              int LINE = 9;
              int WHATSAPP = 10;
    @@ -65,7 +65,7 @@ index 7f2039e0d8130..ddbb1bab0c669 100644
              int WEBAPK = 12;
              int YAHOO_MAIL = 13;
              int VIBER = 14;
    -@@ -539,8 +533,6 @@ public class IntentHandler {
    +@@ -551,8 +545,6 @@ public class IntentHandler {
                  return ExternalAppId.LINE;
              } else if (packageName.equals(PACKAGE_WHATSAPP)) {
                  return ExternalAppId.WHATSAPP;
    @@ -75,7 +75,7 @@ index 7f2039e0d8130..ddbb1bab0c669 100644
                  return ExternalAppId.CHROME;
              } else if (packageName.startsWith(WEBAPK_PACKAGE_PREFIX)) {
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
    -index 3007aa264750d..26d36d2576328 100644
    +index 3f3bb7395cde7..06f7d0481c8f7 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
     @@ -38,7 +38,6 @@ import org.chromium.chrome.browser.ephemeraltab.EphemeralTabCoordinator;
    @@ -86,7 +86,7 @@ index 3007aa264750d..26d36d2576328 100644
      import org.chromium.chrome.browser.lens.LensController;
      import org.chromium.chrome.browser.lens.LensEntryPoint;
      import org.chromium.chrome.browser.lens.LensIntentParams;
    -@@ -972,12 +971,6 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
    +@@ -910,12 +909,6 @@ public class ChromeContextMenuPopulator implements ContextMenuPopulator {
                          LensMetrics.LensSupportStatus.ACTIVITY_NOT_ACCESSIBLE);
                  return false;
              }
    @@ -188,10 +188,10 @@ index d28f7ade3313c..8ff94a355de29 100644
      
          public static boolean isGoogleLensFeatureEnabled(boolean isIncognito) {
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
    -index 6f2a59310728b..4088b4a7b1d6c 100644
    +index a7e1814341ac3..a428d2b4b5a18 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
    -@@ -923,23 +923,6 @@ public class RootUiCoordinator
    +@@ -883,23 +883,6 @@ public class RootUiCoordinator
                                  mLayoutStateProviderOneShotSupplier,
                                  mFullscreenManager);
                  mReadAloudControllerSupplier.set(controller);
    @@ -216,10 +216,10 @@ index 6f2a59310728b..4088b4a7b1d6c 100644
              if (BuildInfo.getInstance().isAutomotive
                      && ChromeFeatureList.isEnabled(
     diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
    -index a589c22f74b92..155935ec710d4 100644
    +index fadbd63847261..40fa56a05fe92 100644
     --- a/chrome/browser/flags/android/chrome_feature_list.cc
     +++ b/chrome/browser/flags/android/chrome_feature_list.cc
    -@@ -833,8 +833,8 @@ BASE_FEATURE(kEnableXAxisActivityTransition,
    +@@ -854,8 +854,8 @@ BASE_FEATURE(kEnableXAxisActivityTransition,
                   base::FEATURE_DISABLED_BY_DEFAULT);
      
      BASE_FEATURE(kExperimentsForAgsa,
    @@ -231,5 +231,5 @@ index a589c22f74b92..155935ec710d4 100644
      BASE_FEATURE(kFloatingSnackbar,
                   "FloatingSnackbar",
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Disable-Component-Updates.patch b/build/e_patches/Disable-Component-Updates.patch
    new file mode 100644
    index 0000000000000000000000000000000000000000..74ccf54d1e6f462708266e89857188b6eec4caa2
    --- /dev/null
    +++ b/build/e_patches/Disable-Component-Updates.patch
    @@ -0,0 +1,59 @@
    +From 66f2061b4ea37668a7241db66c18118c2cc06072 Mon Sep 17 00:00:00 2001
    +From: uazo 
    +Date: Tue, 8 Nov 2022 12:41:22 +0000
    +Subject: Disable Component Updates
    +
    +License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
    +---
    + chrome/browser/component_updater/registration.cc          | 1 +
    + components/component_updater/component_installer.cc       | 1 +
    + components/component_updater/component_updater_service.cc | 8 +-------
    + 3 files changed, 3 insertions(+), 7 deletions(-)
    +
    +diff --git a/chrome/browser/component_updater/registration.cc b/chrome/browser/component_updater/registration.cc
    +index 605666f6dd511..bb494c4f90445 100644
    +--- a/chrome/browser/component_updater/registration.cc
    ++++ b/chrome/browser/component_updater/registration.cc
    +@@ -125,6 +125,7 @@
    + namespace component_updater {
    + 
    + void RegisterComponentsForUpdate() {
    ++  if ((true)) return;
    +   auto* const cus = g_browser_process->component_updater();
    + 
    + #if BUILDFLAG(IS_WIN)
    +diff --git a/components/component_updater/component_installer.cc b/components/component_updater/component_installer.cc
    +index 080346092b907..d9f3fec1bfb5c 100644
    +--- a/components/component_updater/component_installer.cc
    ++++ b/components/component_updater/component_installer.cc
    +@@ -123,6 +123,7 @@ void ComponentInstaller::Register(
    +     base::OnceClosure callback,
    +     const base::Version& registered_version,
    +     const base::Version& max_previous_product_version) {
    ++  if ((true)) return;
    +   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    + 
    +   if (!installer_policy_) {
    +diff --git a/components/component_updater/component_updater_service.cc b/components/component_updater/component_updater_service.cc
    +index 210010b011552..ea48c55691e23 100644
    +--- a/components/component_updater/component_updater_service.cc
    ++++ b/components/component_updater/component_updater_service.cc
    +@@ -542,14 +542,8 @@ std::unique_ptr ComponentUpdateServiceFactory(
    + 
    + // Register prefs required by the component update service.
    + void RegisterComponentUpdateServicePrefs(PrefRegistrySimple* registry) {
    +-  // If the preference is not set the component updates are enabled by default
    +-  // unless in Chrome for Testing where we never want components to be updated
    +-  // automatically.
    +-  constexpr bool kComponentUpdatesEnabledByDefault =
    +-      !BUILDFLAG(CHROME_FOR_TESTING);
    +-
    +   registry->RegisterBooleanPref(prefs::kComponentUpdatesEnabled,
    +-                                kComponentUpdatesEnabledByDefault);
    ++                                false);
    + }
    + 
    + }  // namespace component_updater
    +-- 
    +2.34.1
    +
    diff --git a/build/e_patches/Disable-adding-a-signed-out-avatar-on-new-tab-pages-by-default.patch b/build/e_patches/Disable-adding-a-signed-out-avatar-on-new-tab-pages-by-default.patch
    index 12716a603e220d83d2e2162c0ec8dac32dc88eb0..66a4e0752c8cd8480c12e2b2f95a9bcb98929c0d 100644
    --- a/build/e_patches/Disable-adding-a-signed-out-avatar-on-new-tab-pages-by-default.patch
    +++ b/build/e_patches/Disable-adding-a-signed-out-avatar-on-new-tab-pages-by-default.patch
    @@ -1,4 +1,4 @@
    -From a2260c8b820d2ee426a2b437dcf79c8eba27eb55 Mon Sep 17 00:00:00 2001
    +From 9f9bc7f9dcfb3895078823b467e374c2f9cdb319 Mon Sep 17 00:00:00 2001
     From: fgei 
     Date: Tue, 8 Aug 2023 09:10:36 +0000
     Subject: Disable adding a signed out avatar on new tab pages by default
    @@ -8,7 +8,7 @@ Subject: Disable adding a signed out avatar on new tab pages by default
      1 file changed, 12 insertions(+)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
    -index 1c23291823910..4fb7434759ff8 100644
    +index 3ee920a069bea..f7d521ce36a2e 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
     @@ -68,6 +68,9 @@ public class IdentityDiscController
    @@ -33,7 +33,7 @@ index 1c23291823910..4fb7434759ff8 100644
              String email = CoreAccountInfo.getEmailFrom(getSignedInAccountInfo());
              ensureProfileDataCache();
      
    -@@ -383,6 +391,10 @@ public class IdentityDiscController
    +@@ -393,6 +401,10 @@ public class IdentityDiscController
          }
      
          private String getContentDescription(@Nullable String email) {
    @@ -45,5 +45,5 @@ index 1c23291823910..4fb7434759ff8 100644
                  return mContext.getString(R.string.accessibility_toolbar_btn_signed_out_identity_disc);
              }
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Disable-support-for-RAR-files-inspection.patch b/build/e_patches/Disable-support-for-RAR-files-inspection.patch
    index d8ee9c125b97f396357aa73d0ac93b77054d3bb6..eb0fb0a3b1d31f161395d824f45e75aa8a15ab73 100644
    --- a/build/e_patches/Disable-support-for-RAR-files-inspection.patch
    +++ b/build/e_patches/Disable-support-for-RAR-files-inspection.patch
    @@ -1,4 +1,4 @@
    -From 8b489966cb6ba06e9dc425592ccedc628f8aae8b Mon Sep 17 00:00:00 2001
    +From 206b538f63ce4521511e4dd7bcd53ff270994fa4 Mon Sep 17 00:00:00 2001
     From: Michael Gilbert 
     Date: Wed, 21 Nov 2018 02:37:35 +0000
     Subject: Disable support for RAR files inspection
    @@ -13,18 +13,18 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      3 files changed, 4 insertions(+), 15 deletions(-)
     
     diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
    -index 2df9537dd5dd4..faca32313ee35 100644
    +index 2a930f44908f4..a155cc6fecf64 100644
     --- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
     +++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
    -@@ -75,7 +75,7 @@ void FileAnalyzer::Start(const base::FilePath& target_path,
    -   if (inspection_type == DownloadFileType::ZIP) {
    -     StartExtractZipFeatures();
    -   } else if (inspection_type == DownloadFileType::RAR) {
    +@@ -79,7 +79,7 @@ void FileAnalyzer::Start(const base::FilePath& target_file_name,
    +   }
    + 
    +   if (inspection_type == DownloadFileType::RAR) {
     -    StartExtractRarFeatures();
     +    LOG(WARNING) << "Safebrowser inspection of rar files is disabled in this build";
    - #if BUILDFLAG(IS_MAC)
    -   } else if (inspection_type == DownloadFileType::DMG) {
    -     StartExtractDmgFeatures();
    +     return;
    +   }
    + 
     diff --git a/chrome/services/file_util/safe_archive_analyzer.cc b/chrome/services/file_util/safe_archive_analyzer.cc
     index f5a860c8b1943..5473bfb95f599 100644
     --- a/chrome/services/file_util/safe_archive_analyzer.cc
    @@ -65,5 +65,5 @@ index b1302b7928484..5410eab6c164f 100644
      #endif
      
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Enable-sensors-by-default.patch b/build/e_patches/Enable-sensors-by-default.patch
    index 039f30087608e86b75139754fa7b7659d511ce4c..c888005aac5ae15ba023654f6a8629938e33761b 100644
    --- a/build/e_patches/Enable-sensors-by-default.patch
    +++ b/build/e_patches/Enable-sensors-by-default.patch
    @@ -1,4 +1,4 @@
    -From ceb1efe62471b4a537af15b56c873b90d09428eb Mon Sep 17 00:00:00 2001
    +From 250790cce46171376f5c691403bea7c16a2c6afe Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Mon, 12 Feb 2024 15:07:39 +0530
     Subject: Enable sensors by default
    @@ -8,7 +8,7 @@ Subject: Enable sensors by default
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
    -index 11561e8ec1a95..3abbbc5adf304 100644
    +index b738b065892f3..a33042c2ca2af 100644
     --- a/components/content_settings/core/browser/content_settings_registry.cc
     +++ b/components/content_settings/core/browser/content_settings_registry.cc
     @@ -384,7 +384,7 @@ void ContentSettingsRegistry::Init() {
    @@ -21,5 +21,5 @@ index 11561e8ec1a95..3abbbc5adf304 100644
                 /*valid_settings=*/{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK},
                 WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE,
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Handle-web-search-action-in-browser.patch b/build/e_patches/Handle-web-search-action-in-browser.patch
    index 649086e4622b319bd21718c5d6ce54acc4ca9497..9f9a643aa9a45f10d01d22bdace21f1aef6b972c 100644
    --- a/build/e_patches/Handle-web-search-action-in-browser.patch
    +++ b/build/e_patches/Handle-web-search-action-in-browser.patch
    @@ -1,4 +1,4 @@
    -From c81109cb8db17017aa0da89d250b4a6e3c76c295 Mon Sep 17 00:00:00 2001
    +From 8a5ba394b04124a98e54cb9b13ff71d6bf8ea798 Mon Sep 17 00:00:00 2001
     From: fgei 
     Date: Wed, 28 Sep 2022 05:37:00 +0200
     Subject: Handle web search action in browser
    @@ -10,10 +10,10 @@ Subject: Handle web search action in browser
      3 files changed, 15 insertions(+), 1 deletion(-)
     
     diff --git a/base/android/java/src/org/chromium/base/PackageManagerUtils.java b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
    -index 65a120550836a..0ce705561751b 100644
    +index 85c04eaf5a0d8..4c82f0441ecb6 100644
     --- a/base/android/java/src/org/chromium/base/PackageManagerUtils.java
     +++ b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
    -@@ -90,6 +90,16 @@ public class PackageManagerUtils {
    +@@ -92,6 +92,16 @@ public class PackageManagerUtils {
              return canResolveActivity(intent, 0);
          }
      
    @@ -59,5 +59,5 @@ index 48bb063ba1643..d5beb5e309619 100644
                  mActivity.startActivity(searchIntent);
              } else {
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Multiple-fingerprinting-mitigations.patch b/build/e_patches/Multiple-fingerprinting-mitigations.patch
    index cccbcbf322971c7d29f8b8670114e3e22bcb7f74..62336b1732b5193727285483fe16848c930beccf 100644
    --- a/build/e_patches/Multiple-fingerprinting-mitigations.patch
    +++ b/build/e_patches/Multiple-fingerprinting-mitigations.patch
    @@ -1,4 +1,4 @@
    -From 3f8beea28a6439d6f364e23d9fde8dd9754388da Mon Sep 17 00:00:00 2001
    +From bed4a637a3fae5eda1348f45143744099a503bbf Mon Sep 17 00:00:00 2001
     From: csagan5 <32685696+csagan5@users.noreply.github.com>
     Date: Fri, 30 Mar 2018 10:09:03 +0200
     Subject: Multiple fingerprinting mitigations
    @@ -61,11 +61,11 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      create mode 100644 third_party/ungoogled/ungoogled_switches.h
     
     diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
    -index da9c0a16cc4f4..5ac58b00cad2d 100644
    +index 744fdcba44cd9..192f98cdc4a81 100644
     --- a/chrome/browser/BUILD.gn
     +++ b/chrome/browser/BUILD.gn
    -@@ -2465,6 +2465,7 @@ static_library("browser") {
    -     "//services/device/public/cpp:device_features",
    +@@ -2468,6 +2468,7 @@ static_library("browser") {
    +     "//services/device/public/cpp/bluetooth",
          "//services/device/public/cpp/geolocation",
          "//services/device/public/cpp/usb",
     +    "//third_party/ungoogled:switches",
    @@ -73,10 +73,10 @@ index da9c0a16cc4f4..5ac58b00cad2d 100644
          "//services/device/public/mojom:usb",
          "//services/image_annotation:service",
     diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
    -index fd318c00cf7ee..6ce02bdaa365d 100644
    +index 93b46c8a42a43..50d06234684bf 100644
     --- a/chrome/browser/about_flags.cc
     +++ b/chrome/browser/about_flags.cc
    -@@ -259,6 +259,8 @@
    +@@ -261,6 +261,8 @@
      #include "extensions/common/extension_features.h"
      #endif
      
    @@ -86,10 +86,10 @@ index fd318c00cf7ee..6ce02bdaa365d 100644
      #include "base/allocator/buildflags.h"
      #endif
     diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
    -index fcb315726a70b..80c8c6b027dfd 100644
    +index 1bc1315a26668..fb423dad7e153 100644
     --- a/content/browser/BUILD.gn
     +++ b/content/browser/BUILD.gn
    -@@ -283,6 +283,7 @@ source_set("browser") {
    +@@ -290,6 +290,7 @@ source_set("browser") {
          "//third_party/re2",
          "//third_party/snappy",
          "//third_party/sqlite",
    @@ -98,10 +98,10 @@ index fcb315726a70b..80c8c6b027dfd 100644
          "//third_party/zlib",
          "//tools/v8_context_snapshot:buildflags",
     diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
    -index aac718fdd1741..1bc2340a81077 100644
    +index c4447c160f16a..4b5e045e8022c 100644
     --- a/content/browser/renderer_host/render_process_host_impl.cc
     +++ b/content/browser/renderer_host/render_process_host_impl.cc
    -@@ -214,6 +214,7 @@
    +@@ -217,6 +217,7 @@
      #include "url/gurl.h"
      #include "url/origin.h"
      
    @@ -120,10 +120,10 @@ index aac718fdd1741..1bc2340a81077 100644
            switches::kDisableGpuMemoryBufferVideoFrames,
            switches::kDisableHistogramCustomizer,
     diff --git a/content/child/BUILD.gn b/content/child/BUILD.gn
    -index f89259d511ef1..fcdbd8a37ad8a 100644
    +index d3dd4586104a4..5ca3953276ffb 100644
     --- a/content/child/BUILD.gn
     +++ b/content/child/BUILD.gn
    -@@ -109,6 +109,7 @@ target(link_target_type, "child") {
    +@@ -110,6 +110,7 @@ target(link_target_type, "child") {
          "//third_party/blink/public/common:buildflags",
          "//third_party/blink/public/strings",
          "//third_party/ced",
    @@ -132,7 +132,7 @@ index f89259d511ef1..fcdbd8a37ad8a 100644
          "//ui/base",
          "//ui/events/blink",
     diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
    -index 3d2a4f4f6d205..7a52933c1e5a2 100644
    +index 0fa8341523476..5e3735e3cefdf 100644
     --- a/content/child/runtime_features.cc
     +++ b/content/child/runtime_features.cc
     @@ -48,6 +48,8 @@
    @@ -144,7 +144,7 @@ index 3d2a4f4f6d205..7a52933c1e5a2 100644
      #if BUILDFLAG(IS_ANDROID)
      #include "base/android/build_info.h"
      #endif
    -@@ -517,6 +519,12 @@ void SetRuntimeFeaturesFromCommandLine(const base::CommandLine& command_line) {
    +@@ -524,6 +526,12 @@ void SetRuntimeFeaturesFromCommandLine(const base::CommandLine& command_line) {
      // as a last resort.
      void SetCustomizedRuntimeFeaturesFromCombinedArgs(
          const base::CommandLine& command_line) {
    @@ -194,7 +194,7 @@ index d7d110b9f8f30..136b67c51f120 100644
        static void EnableFluentOverlayScrollbars(bool);
        static void EnableVibration(bool);
     diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
    -index 8213627591d1b..116a6255d5f33 100644
    +index 5ccca83ad1fbf..5b4529f68d6a7 100644
     --- a/third_party/blink/renderer/core/dom/document.cc
     +++ b/third_party/blink/renderer/core/dom/document.cc
     @@ -39,6 +39,7 @@
    @@ -205,7 +205,7 @@ index 8213627591d1b..116a6255d5f33 100644
      #include "base/metrics/histogram_functions.h"
      #include "base/not_fatal_until.h"
      #include "base/notreached.h"
    -@@ -866,6 +867,17 @@ Document::Document(const DocumentInit& initializer,
    +@@ -867,6 +868,17 @@ Document::Document(const DocumentInit& initializer,
        TRACE_EVENT_WITH_FLOW0("blink", "Document::Document", TRACE_ID_LOCAL(this),
                               TRACE_EVENT_FLAG_FLOW_OUT);
        DCHECK(agent_);
    @@ -223,7 +223,7 @@ index 8213627591d1b..116a6255d5f33 100644
        if (base::FeatureList::IsEnabled(features::kDelayAsyncScriptExecution) &&
            features::kDelayAsyncScriptExecutionDelayByDefaultParam.Get()) {
          script_runner_delayer_->Activate();
    -@@ -2492,6 +2504,14 @@ void Document::UpdateStyleAndLayoutTreeForThisDocument() {
    +@@ -2493,6 +2505,14 @@ void Document::UpdateStyleAndLayoutTreeForThisDocument() {
      #endif
      }
      
    @@ -239,10 +239,10 @@ index 8213627591d1b..116a6255d5f33 100644
        DCHECK(IsActive());
        DCHECK(IsMainThread());
     diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
    -index 107b145f58486..280337088dc2f 100644
    +index a3181b3dcd9ee..121bec8637f3d 100644
     --- a/third_party/blink/renderer/core/dom/document.h
     +++ b/third_party/blink/renderer/core/dom/document.h
    -@@ -540,6 +540,10 @@ class CORE_EXPORT Document : public ContainerNode,
    +@@ -545,6 +545,10 @@ class CORE_EXPORT Document : public ContainerNode,
          return static_cast(xml_standalone_);
        }
        bool HasXMLDeclaration() const { return has_xml_declaration_; }
    @@ -253,7 +253,7 @@ index 107b145f58486..280337088dc2f 100644
      
        void SetXMLEncoding(const String& encoding) {
          xml_encoding_ = encoding;
    -@@ -2641,6 +2645,9 @@ class CORE_EXPORT Document : public ContainerNode,
    +@@ -2644,6 +2648,9 @@ class CORE_EXPORT Document : public ContainerNode,
      
        base::ElapsedTimer start_time_;
      
    @@ -264,10 +264,10 @@ index 107b145f58486..280337088dc2f 100644
        Member script_runner_delayer_;
      
     diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
    -index e6cc380be7c6f..d94efdc1d575d 100644
    +index 33c2bc9979f7a..b75620bad00ea 100644
     --- a/third_party/blink/renderer/core/dom/element.cc
     +++ b/third_party/blink/renderer/core/dom/element.cc
    -@@ -2723,6 +2723,7 @@ void Element::ClientQuads(Vector& quads) const {
    +@@ -2640,6 +2640,7 @@ void Element::ClientQuads(Vector& quads) const {
            quads.push_back(element_layout_object->LocalToAbsoluteQuad(
                gfx::QuadF(element_layout_object->ObjectBoundingBox())));
          }
    @@ -275,7 +275,7 @@ index e6cc380be7c6f..d94efdc1d575d 100644
          return;
        }
      
    -@@ -2731,6 +2732,10 @@ void Element::ClientQuads(Vector& quads) const {
    +@@ -2648,6 +2649,10 @@ void Element::ClientQuads(Vector& quads) const {
            element_layout_object->IsBR()) {
          element_layout_object->AbsoluteQuads(quads);
        }
    @@ -286,7 +286,7 @@ index e6cc380be7c6f..d94efdc1d575d 100644
      }
      
      DOMRectList* Element::getClientRects() {
    -@@ -2776,6 +2781,9 @@ gfx::RectF Element::GetBoundingClientRectNoLifecycleUpdate() const {
    +@@ -2693,6 +2698,9 @@ gfx::RectF Element::GetBoundingClientRectNoLifecycleUpdate() const {
        DCHECK(element_layout_object);
        GetDocument().AdjustRectForScrollAndAbsoluteZoom(result,
                                                         *element_layout_object);
    @@ -327,10 +327,10 @@ index b0e1831b283b4..f79e0316d9120 100644
        return result;
      }
     diff --git a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
    -index c7c80baf54b8d..fc688c75d492f 100644
    +index 82d59224dd3c6..63c3eb5e664a5 100644
     --- a/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
     +++ b/third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.cc
    -@@ -26,6 +26,7 @@
    +@@ -21,6 +21,7 @@
      #include "third_party/blink/renderer/core/fileapi/blob.h"
      #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
      #include "third_party/blink/renderer/platform/graphics/image_data_buffer.h"
    @@ -338,7 +338,7 @@ index c7c80baf54b8d..fc688c75d492f 100644
      #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
      #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
      #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
    -@@ -219,6 +220,9 @@ CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(
    +@@ -214,6 +215,9 @@ CanvasAsyncBlobCreator::CanvasAsyncBlobCreator(
                                 std::min(info.height(), max_dimension));
              src_data_.reset(info, src_data_.addr(), src_data_.rowBytes());
            }
    @@ -349,10 +349,10 @@ index c7c80baf54b8d..fc688c75d492f 100644
        }
      
     diff --git a/third_party/blink/renderer/core/html/canvas/text_metrics.cc b/third_party/blink/renderer/core/html/canvas/text_metrics.cc
    -index ebe1e6095cf28..e31186ff1b4d5 100644
    +index b81464e5515ed..adfc1002a3e76 100644
     --- a/third_party/blink/renderer/core/html/canvas/text_metrics.cc
     +++ b/third_party/blink/renderer/core/html/canvas/text_metrics.cc
    -@@ -102,6 +102,24 @@ const ShapeResult* ShapeWord(const TextRun& word_run, const Font& font) {
    +@@ -107,6 +107,24 @@ const ShapeResult* ShapeWord(const TextRun& word_run, const Font& font) {
      }
      }  // namespace
      
    @@ -374,22 +374,22 @@ index ebe1e6095cf28..e31186ff1b4d5 100644
     +  baselines_->setIdeographic(baselines_->ideographic() * factor);
     +}
     +
    - void TextMetrics::Update(const Font& font,
    + void TextMetrics::Update(const Font* font,
                               const TextDirection& direction,
    -                          const TextBaseline& baseline,
    +                          const V8CanvasTextBaseline::Enum baseline,
     diff --git a/third_party/blink/renderer/core/html/canvas/text_metrics.h b/third_party/blink/renderer/core/html/canvas/text_metrics.h
    -index 4fdd28cdfe0a8..727f51c8ee77e 100644
    +index 287a83d1db21e..3bbf5c451a02a 100644
     --- a/third_party/blink/renderer/core/html/canvas/text_metrics.h
     +++ b/third_party/blink/renderer/core/html/canvas/text_metrics.h
    -@@ -103,6 +103,8 @@ class CORE_EXPORT TextMetrics final : public ScriptWrappable {
    +@@ -112,6 +112,8 @@ class CORE_EXPORT TextMetrics final : public ScriptWrappable {
          float x_position_;
        };
      
     +  void Shuffle(const double factor);
     +
       private:
    -   void Update(const Font&,
    -               const TextDirection&,
    +   void Update(const Font*,
    +               const TextDirection& direction,
     diff --git a/third_party/blink/renderer/core/svg/svg_graphics_element.cc b/third_party/blink/renderer/core/svg/svg_graphics_element.cc
     index ed8e6f5b855bf..a97783e63bc16 100644
     --- a/third_party/blink/renderer/core/svg/svg_graphics_element.cc
    @@ -484,10 +484,10 @@ index 5a99fbc971f44..1132b34307b5a 100644
        return -1;
      }
     diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
    -index 21d7d9e9fd521..b6b195ba94fbb 100644
    +index 2570b5a8e377a..7d45ab0a79990 100644
     --- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
     +++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
    -@@ -197,6 +197,11 @@
    +@@ -205,6 +205,11 @@
      #include "ui/gfx/geometry/size.h"
      #include "ui/gfx/geometry/size_f.h"
      #include "ui/gfx/geometry/skia_conversions.h"
    @@ -499,7 +499,7 @@ index 21d7d9e9fd521..b6b195ba94fbb 100644
      #include "ui/gfx/geometry/vector2d.h"
      #include "ui/gfx/geometry/vector2d_f.h"
      #include "v8/include/v8-local-handle.h"
    -@@ -2970,6 +2975,10 @@ ImageData* BaseRenderingContext2D::getImageDataInternal(
    +@@ -2964,6 +2969,10 @@ ImageData* BaseRenderingContext2D::getImageDataInternal(
                snapshot->PaintImageForCurrentFrame().GetSkImageInfo().bounds();
            DCHECK(!bounds.intersect(SkIRect::MakeXYWH(sx, sy, sw, sh)));
          }
    @@ -510,13 +510,17 @@ index 21d7d9e9fd521..b6b195ba94fbb 100644
        }
      
        return image_data;
    -@@ -3705,8 +3714,22 @@ TextMetrics* BaseRenderingContext2D::measureText(const String& text) {
    +@@ -3763,12 +3772,26 @@ TextMetrics* BaseRenderingContext2D::measureText(const String& text) {
        TextDirection direction = ToTextDirection(
            state.GetDirection(), GetCanvasRenderingContextHost(), computed_style);
      
     -  return MakeGarbageCollected(
     +  auto* text_metrics = MakeGarbageCollected(
    -       font, direction, state.GetTextBaseline(), state.GetTextAlign(), text);
    +       font, direction, state.GetTextBaseline().AsEnum(),
    +       state.GetTextAlign().AsEnum(), text,
    +       RuntimeEnabledFeatures::CanvasTextNgEnabled()
    +           ? &GetCanvasRenderingContextHost()->GetPlainTextPainter()
    +           : nullptr);
     +  // Scale text metrics if enabled
     +  if (RuntimeEnabledFeatures::FingerprintingCanvasMeasureTextNoiseEnabled()) {
     +    OffscreenCanvas* offscreen_canvas = HostAsOffscreenCanvas();
    @@ -535,10 +539,10 @@ index 21d7d9e9fd521..b6b195ba94fbb 100644
      
      void BaseRenderingContext2D::SnapshotStateForFilter() {
     diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
    -index 871916a18d58d..d7a192f5f7381 100644
    +index 8546d85128cc9..758a47e3fe8eb 100644
     --- a/third_party/blink/renderer/platform/BUILD.gn
     +++ b/third_party/blink/renderer/platform/BUILD.gn
    -@@ -1706,7 +1706,9 @@ component("platform") {
    +@@ -1717,7 +1717,9 @@ component("platform") {
          "//third_party/blink/renderer:non_test_config",
        ]
      
    @@ -549,7 +553,7 @@ index 871916a18d58d..d7a192f5f7381 100644
      
        allow_circular_includes_from = [
          "//third_party/blink/renderer/platform/blob",
    -@@ -1791,6 +1793,7 @@ component("platform") {
    +@@ -1801,6 +1803,7 @@ component("platform") {
          "//third_party/blink/public/strings",
          "//third_party/blink/renderer/platform/wtf",
          "//third_party/ced",
    @@ -580,22 +584,21 @@ index fa290b49acf77..cbadd2def8699 100644
     +
      }  // namespace blink
     diff --git a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
    -index 4918c94167884..54e5509e2d2f4 100644
    +index 04b28c377e17d..e797596516811 100644
     --- a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
     +++ b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
    -@@ -36,8 +36,11 @@
    +@@ -34,7 +34,10 @@
      
      #include "base/compiler_specific.h"
      #include "base/memory/ptr_util.h"
     +#include "base/rand_util.h"
     +#include "base/logging.h"
    - #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
      #include "third_party/blink/renderer/platform/image-encoders/image_encoder.h"
     +#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
      #include "third_party/blink/renderer/platform/wtf/text/base64.h"
    - #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
    - #include "third_party/blink/renderer/platform/wtf/vector.h"
    -@@ -146,6 +149,11 @@ bool ImageDataBuffer::EncodeImageInternal(const ImageEncodingMimeType mime_type,
    + #include "third_party/skia/include/core/SkImage.h"
    + #include "third_party/skia/include/core/SkSurface.h"
    +@@ -140,6 +143,11 @@ bool ImageDataBuffer::EncodeImageInternal(const ImageEncodingMimeType mime_type,
                                                const SkPixmap& pixmap) const {
        DCHECK(is_valid_);
      
    @@ -608,7 +611,7 @@ index 4918c94167884..54e5509e2d2f4 100644
          SkJpegEncoder::Options options;
          options.fQuality = ImageEncoder::ComputeJpegQuality(quality);
     diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
    -index 0a575fd624b4b..9d39aca95278e 100644
    +index 2d9a294e195f9..10fb72e575c0b 100644
     --- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
     +++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.cc
     @@ -4,6 +4,8 @@
    @@ -620,12 +623,12 @@ index 0a575fd624b4b..9d39aca95278e 100644
      #include "base/numerics/checked_math.h"
      #include "gpu/command_buffer/client/gles2_interface.h"
      #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
    -@@ -18,6 +20,9 @@
    +@@ -19,6 +21,9 @@
      #include "ui/gfx/geometry/skia_conversions.h"
      #include "v8/include/v8.h"
      
     +#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
    -+#include "third_party/skia/include/private/SkColorData.h"
    ++#include "third_party/skia/src/core/SkColorData.h"
     +
      namespace blink {
      
    @@ -785,10 +788,10 @@ index 0a575fd624b4b..9d39aca95278e 100644
     +
      }  // namespace blink
     diff --git a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
    -index 5125aac465daa..6ab07060932f4 100644
    +index 5872cea92c158..ee16ff35f2af4 100644
     --- a/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
     +++ b/third_party/blink/renderer/platform/graphics/static_bitmap_image.h
    -@@ -38,6 +38,8 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
    +@@ -39,6 +39,8 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
      
        StaticBitmapImage(ImageOrientation orientation) : orientation_(orientation) {}
      
    @@ -798,10 +801,10 @@ index 5125aac465daa..6ab07060932f4 100644
      
        // Methods overridden by all sub-classes
     diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
    -index d4b5b73702b8e..db236f0330354 100644
    +index 198f4505cebb5..c97233fa6f65a 100644
     --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
     +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
    -@@ -2993,6 +2993,15 @@
    +@@ -3033,6 +3033,15 @@
            status: {"Mac": "test", "default": "stable"},
            base_feature: "none",
          },
    @@ -882,5 +885,5 @@ index 0000000000000..28d542c0e999b
     +
     +#endif  // THIRD_PARTY_UNGOOGLED_FINGERPRINTING_SWITCHES_H_
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Reintroduce-kWebAuthCable.patch b/build/e_patches/Reintroduce-kWebAuthCable.patch
    index 56cc4d50a2857c36f998e4f3b6a9b4a2531d0bb6..005d2f39eac836358d1f0122dc878dd74af81e66 100644
    --- a/build/e_patches/Reintroduce-kWebAuthCable.patch
    +++ b/build/e_patches/Reintroduce-kWebAuthCable.patch
    @@ -1,4 +1,4 @@
    -From cf3bf72c6505c7288bd1bf92f4cc2433a2ac0cef Mon Sep 17 00:00:00 2001
    +From caca814732666d3a95267edb7d5f97d253320fc3 Mon Sep 17 00:00:00 2001
     From: csagan5 <32685696+csagan5@users.noreply.github.com>
     Date: Sun, 4 Sep 2022 18:47:58 +0200
     Subject: Re-introduce kWebAuthCable
    @@ -43,7 +43,7 @@ index 826860213ee00..aeb91d3a89438 100644
            dialog_model_->SetStep(
                AuthenticatorRequestDialogModel::Step::kMechanismSelection);
     diff --git a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
    -index b35cb4db63825..f955638f4e9bc 100644
    +index 4ee8d8389c432..4b2b961873740 100644
     --- a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
     +++ b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
     @@ -98,6 +98,12 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
    @@ -88,10 +88,10 @@ index b0bad6d998d45..04a53db5809fb 100644
        raw_ptr controller_ =
            nullptr;
     diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
    -index dca681a3f79ff..2689e3024f11f 100644
    +index 7856c9a605b19..4567e9ac7f987 100644
     --- a/content/browser/webauth/authenticator_impl_unittest.cc
     +++ b/content/browser/webauth/authenticator_impl_unittest.cc
    -@@ -1512,7 +1512,12 @@ TEST_F(AuthenticatorImplTest, OversizedCredentialId) {
    +@@ -1510,7 +1510,12 @@ TEST_F(AuthenticatorImplTest, OversizedCredentialId) {
        }
      }
      
    @@ -106,10 +106,10 @@ index dca681a3f79ff..2689e3024f11f 100644
        NavigateAndCommit(GURL(kTestOrigin1));
      
     diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
    -index 666267bff0159..084b7f68aca8a 100644
    +index 1c1548d2b7eb2..912fa38889669 100644
     --- a/content/public/common/content_features.cc
     +++ b/content/public/common/content_features.cc
    -@@ -1217,6 +1217,13 @@ BASE_FEATURE(kWebAssemblyTrapHandler,
    +@@ -1284,6 +1284,13 @@ BASE_FEATURE(kWebAssemblyTrapHandler,
      #endif
      );
      
    @@ -124,11 +124,11 @@ index 666267bff0159..084b7f68aca8a 100644
      // https://webbluetoothcg.github.io/web-bluetooth/
      BASE_FEATURE(kWebBluetooth, "WebBluetooth", base::FEATURE_DISABLED_BY_DEFAULT);
     diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
    -index 89a8e1ce97965..f823c26d37502 100644
    +index 2ca4fee02fe00..f193abdfa38a5 100644
     --- a/content/public/common/content_features.h
     +++ b/content/public/common/content_features.h
    -@@ -270,6 +270,7 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyLazyCompilation);
    - CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyMemory64);
    +@@ -282,6 +282,7 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kElementCaptureOfOtherTabs);
    + CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyLazyCompilation);
      CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyTiering);
      CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyTrapHandler);
     +CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAuthCable);
    @@ -136,5 +136,5 @@ index 89a8e1ce97965..f823c26d37502 100644
      CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebBluetoothNewPermissionsBackend);
      CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebOtpBackendAuto);
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Remove-bromite-auto-updater-option.patch b/build/e_patches/Remove-bromite-auto-updater-option.patch
    index 660b5743f62c3563f76d4b1a1120df8a18f9b163..36671bed1e9da752c23c014dc998d604d571bf50 100644
    --- a/build/e_patches/Remove-bromite-auto-updater-option.patch
    +++ b/build/e_patches/Remove-bromite-auto-updater-option.patch
    @@ -1,4 +1,4 @@
    -From adb1e082c7b2563ff1ae8d1a538d936fee2ac339 Mon Sep 17 00:00:00 2001
    +From 4182b9d226ceb29d46dc799c33049d33ac7dd5cb Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 20 Sep 2023 09:59:55 +0530
     Subject: Remove bromite auto updater option
    @@ -98,19 +98,19 @@ index 785b07f756479..c9ca5463e25b5 100644
      
          protected View getToSAndPrivacyText() {
     diff --git a/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java b/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
    -index 8194988c68715..afbe972a84643 100644
    +index 911a9b371304a..bd399ab02ae6c 100644
     --- a/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
     +++ b/chrome/browser/omaha/android/java/src/org/chromium/chrome/browser/omaha/UpdateStatusProvider.java
    -@@ -16,8 +16,6 @@ import androidx.annotation.IntDef;
    - import androidx.annotation.NonNull;
    - import androidx.annotation.Nullable;
    +@@ -16,8 +16,6 @@ import android.text.TextUtils;
    + 
    + import androidx.annotation.IntDef;
      
     -import com.google.android.gms.common.GooglePlayServicesUtil;
     -
      import org.chromium.base.BuildInfo;
      import org.chromium.base.Callback;
      import org.chromium.base.ObserverList;
    -@@ -291,9 +289,7 @@ public class UpdateStatusProvider {
    +@@ -296,9 +294,7 @@ public class UpdateStatusProvider {
                      boolean allowedToUpdate =
                              checkForSufficientStorage()
                                      // Disable the version update check for automotive. See b/297925838.
    @@ -122,5 +122,5 @@ index 8194988c68715..afbe972a84643 100644
                              allowedToUpdate ? UpdateState.UPDATE_AVAILABLE : UpdateState.NONE;
      
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Remove-google-pref-from-main-menu.patch b/build/e_patches/Remove-google-pref-from-main-menu.patch
    index 5e7f4e906255541431c1ab222d7a01e9c3118324..7d3a0c7fed887dda088af8a360b3e7df6a335119 100644
    --- a/build/e_patches/Remove-google-pref-from-main-menu.patch
    +++ b/build/e_patches/Remove-google-pref-from-main-menu.patch
    @@ -1,4 +1,4 @@
    -From 4d495392f7eac03468b521442f6294faf874e88e Mon Sep 17 00:00:00 2001
    +From 36e1512f9ff91d7fb5267a0a2c6beb46f23a0123 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Mon, 12 Feb 2024 13:41:11 +0530
     Subject: Remove google pref from main menu
    @@ -8,10 +8,10 @@ Subject: Remove google pref from main menu
      1 file changed, 3 insertions(+)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
    -index 2f65cc21433f5..fa591514927fa 100644
    +index 74ec036044528..3fe67443571f8 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/MainSettings.java
    -@@ -259,6 +259,9 @@ public class MainSettings extends ChromeBaseSettingsFragment
    +@@ -280,6 +280,9 @@ public class MainSettings extends ChromeBaseSettingsFragment
      
              TemplateUrlService templateUrlService =
                      TemplateUrlServiceFactory.getForProfile(getProfile());
    @@ -22,5 +22,5 @@ index 2f65cc21433f5..fa591514927fa 100644
                  templateUrlService.registerLoadListener(this);
                  templateUrlService.load();
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Revert-TM-Add-themed-app-icons.patch b/build/e_patches/Revert-TM-Add-themed-app-icons.patch
    index e11f24e27dc592f1ddfda780d078cdc9c702e514..22733997a9d73369f42c58fe0aa335318951a5f5 100644
    --- a/build/e_patches/Revert-TM-Add-themed-app-icons.patch
    +++ b/build/e_patches/Revert-TM-Add-themed-app-icons.patch
    @@ -1,4 +1,4 @@
    -From fcd5260fea1ee12edf5ac7a9bcfd03492912bfa4 Mon Sep 17 00:00:00 2001
    +From 3294fb350805150f2172054dd5eb41532a8454ba Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Fri, 18 Nov 2022 09:48:02 +0000
     Subject: Revert "[TM] Add themed app icons"
    @@ -13,10 +13,10 @@ This reverts commit 9faa242a0c09838268d7c969acdf493f29ef3db5.
      delete mode 100644 chrome/android/java/res_chromium_base/drawable/themed_app_icon.xml
     
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
    -index aad386a8ece8c..6aa78bbbf2bcd 100644
    +index 29664e2f72b93..6966676e1fc4e 100644
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
    -@@ -174,7 +174,6 @@ if (current_toolchain == default_toolchain) {
    +@@ -171,7 +171,6 @@ if (current_toolchain == default_toolchain) {
            "java/res_base/values/ic_launcher_round_alias.xml",
            "java/res_base/values/values.xml",
            "java/res_base/xml/network_security_config.xml",
    @@ -24,7 +24,7 @@ index aad386a8ece8c..6aa78bbbf2bcd 100644
            "java/res_chromium_base/mipmap-hdpi/app_icon.png",
            "java/res_chromium_base/mipmap-hdpi/layered_app_icon.png",
            "java/res_chromium_base/mipmap-hdpi/layered_app_icon_background.png",
    -@@ -1585,7 +1584,6 @@ if (current_toolchain == default_toolchain) {
    +@@ -1598,7 +1597,6 @@ if (current_toolchain == default_toolchain) {
        android_resources("chrome_public_apk_base_module_resources") {
          resource_overlay = true
          sources = [
    @@ -88,5 +88,5 @@ index 32a95bc6ee4d5..0000000000000
     -    
     -
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Update-i18n-zhCN-support.patch b/build/e_patches/Update-i18n-zhCN-support.patch
    index 2409d1dbec0b4b061ff4cfc788428f2e667624a8..424775eaabc874dba9b7248bce0ebf116d6c1e16 100644
    --- a/build/e_patches/Update-i18n-zhCN-support.patch
    +++ b/build/e_patches/Update-i18n-zhCN-support.patch
    @@ -1,4 +1,4 @@
    -From 440ed03f9b22e8437a0e135adb2ce86a4d7163c5 Mon Sep 17 00:00:00 2001
    +From b026675e5f98b55e04ec368995a0ee9aa6afb397 Mon Sep 17 00:00:00 2001
     From: mars 
     Date: Sun, 2 Aug 2020 00:37:49 +0800
     Subject: Update i18n zh_CN support
    @@ -14,10 +14,10 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      6 files changed, 133 insertions(+), 6 deletions(-)
     
     diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/resources/generated_resources_zh-CN.xtb
    -index 851825359e742..d4b732e63be1e 100644
    +index 3c530876be0b1..2ed6422cd9edb 100644
     --- a/chrome/app/resources/generated_resources_zh-CN.xtb
     +++ b/chrome/app/resources/generated_resources_zh-CN.xtb
    -@@ -11680,4 +11680,6 @@
    +@@ -11722,4 +11722,6 @@
      您需要开启 Chrome 同步才能使用 Wi-Fi 同步功能。了解详情
      同步标签页分组
      检查(&N)
    @@ -27,10 +27,10 @@ index 851825359e742..d4b732e63be1e 100644
     +配置广告拦截和过滤规则地址
     +
     diff --git a/chrome/app/resources/google_chrome_strings_zh-CN.xtb b/chrome/app/resources/google_chrome_strings_zh-CN.xtb
    -index 40142311b6f71..e58f50e00abfd 100644
    +index 2028b272444c3..c27e0ac159f73 100644
     --- a/chrome/app/resources/google_chrome_strings_zh-CN.xtb
     +++ b/chrome/app/resources/google_chrome_strings_zh-CN.xtb
    -@@ -655,4 +655,5 @@
    +@@ -619,4 +619,5 @@
      更新服务器没有应用的任何哈希数据,因此安装失败。
      Google Chrome 是您的默认浏览器
       可用于切换 Chrome 个人资料
    @@ -39,13 +39,13 @@ index 40142311b6f71..e58f50e00abfd 100644
     +关于 Bromite
     +
     diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_zh-CN.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_zh-CN.xtb
    -index 90be8f85089d7..269fa9cbc38cf 100644
    +index 4be72a91233fc..ebf90d098dd8c 100644
     --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_zh-CN.xtb
     +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_zh-CN.xtb
    -@@ -1854,4 +1854,74 @@
    - 若要删除您设备中的无痕浏览记录,请关闭所有无痕式标签页。
    +@@ -1851,4 +1851,74 @@
       个标签页
      动态卡片上的菜单已关闭
    + ,无痕式标签页
     -
     \ No newline at end of file
     +关于 Bromite
    @@ -120,10 +120,10 @@ index 90be8f85089d7..269fa9cbc38cf 100644
     +全选
     +
     diff --git a/components/browser_ui/strings/android/translations/browser_ui_strings_zh-CN.xtb b/components/browser_ui/strings/android/translations/browser_ui_strings_zh-CN.xtb
    -index 5fd5f5e80f337..e5afe781ddb83 100644
    +index 3649dd7c4830f..e879d4e3a0672 100644
     --- a/components/browser_ui/strings/android/translations/browser_ui_strings_zh-CN.xtb
     +++ b/components/browser_ui/strings/android/translations/browser_ui_strings_zh-CN.xtb
    -@@ -574,4 +574,28 @@
    +@@ -599,4 +599,28 @@
      让各项内容井井有条
      已存储的数据
      禁止网站使用 V8 优化工具。
    @@ -155,10 +155,10 @@ index 5fd5f5e80f337..e5afe781ddb83 100644
     +WebRTC
     +
     diff --git a/components/strings/components_strings_zh-CN.xtb b/components/strings/components_strings_zh-CN.xtb
    -index 702e6d0810909..80ea7d4b54f6f 100644
    +index c09ac4c84f1b2..8787f1e9e7adb 100644
     --- a/components/strings/components_strings_zh-CN.xtb
     +++ b/components/strings/components_strings_zh-CN.xtb
    -@@ -4694,4 +4694,33 @@
    +@@ -4699,4 +4699,33 @@
      添加 IBAN
      YouTube
      来自 Google Pay
    @@ -207,5 +207,5 @@ index a7a5bb8d41c6e..eb88a676d443b 100644
     +无法保存到所选文件
     +
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/Whitelist-murena-search.patch b/build/e_patches/Whitelist-murena-search.patch
    index d522b68ca6d7da89f624d15c34a193d4364ac788..5c4cec78eef1e4bb4a99e6375e9604446b8448d2 100644
    --- a/build/e_patches/Whitelist-murena-search.patch
    +++ b/build/e_patches/Whitelist-murena-search.patch
    @@ -1,4 +1,4 @@
    -From c9d89a1e494a311b37bbf3aa43c58acda316aa1e Mon Sep 17 00:00:00 2001
    +From 8be18b698dd8db1b442593edbab386f986aa7e81 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 16 Apr 2025 16:39:02 +0000
     Subject: Whitelist murena search
    @@ -7,20 +7,20 @@ Subject: Whitelist murena search
      .../settings/AdblockAllowedDomainsFragment.java  | 16 ++++++++++++++++
      1 file changed, 16 insertions(+)
     
    -diff --git a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java
    -index d3fa1142c4a90..fc03240220242 100644
    ---- a/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java
    -+++ b/components/adblock/android/java/src/org/chromium/components/adblock/settings/AdblockAllowedDomainsFragment.java
    -@@ -20,6 +20,8 @@ package org.chromium.components.adblock.settings;
    - import android.app.Activity;
    +diff --git a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java
    +index 35e2366b2cae9..04ab934279b9e 100644
    +--- a/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java
    ++++ b/chrome/browser/adblock/android/java/src/org/chromium/chrome/browser/adblock/settings/AdblockAllowedDomainsFragment.java
    +@@ -18,6 +18,8 @@
    + package org.chromium.chrome.browser.adblock.settings;
    + 
      import android.os.Bundle;
    - import android.view.View;
     +import android.content.SharedPreferences;
     +import android.content.Context;
      
    - import org.chromium.components.adblock.AdblockController;
    - import org.chromium.components.adblock.R;
    -@@ -27,6 +29,8 @@ import org.chromium.components.adblock.R;
    + import org.chromium.chrome.browser.adblock.R;
    + import org.chromium.chrome.browser.profiles.ProfileManager;
    +@@ -26,12 +28,26 @@ import org.chromium.components.adblock.AdblockController;
      import java.util.List;
      
      public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment {
    @@ -29,7 +29,6 @@ index d3fa1142c4a90..fc03240220242 100644
          public AdblockAllowedDomainsFragment() {}
      
          @Override
    -@@ -38,6 +42,18 @@ public class AdblockAllowedDomainsFragment extends AdblockCustomItemFragment allowedDomains = AdblockController.getInstance().getAllowedDomains();
    ++            List allowedDomains = AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()).getAllowedDomains();
     +
     +            if (!wasAdded && allowedDomains != null && !allowedDomains.contains(domain)) {
    -+                AdblockController.getInstance().addAllowedDomain(domain);
    ++                AdblockController.getInstance(ProfileManager.getLastUsedRegularProfile()).addAllowedDomain(domain);
     +                prefs.edit().putBoolean(domain, true).apply();
     +            }
     +        }
    diff --git a/build/e_patches/add-trichrome-browser-apk-targets.patch b/build/e_patches/add-trichrome-browser-apk-targets.patch
    index 98cb660b26b2fe44548e8f24da8f113e36d9f1f9..2adae374f57dfabca2649e3bc2c0ca4a1edeb454 100644
    --- a/build/e_patches/add-trichrome-browser-apk-targets.patch
    +++ b/build/e_patches/add-trichrome-browser-apk-targets.patch
    @@ -1,4 +1,4 @@
    -From 247a706451d3eeb77cee4ae6b292397e1929a66f Mon Sep 17 00:00:00 2001
    +From fb59b43e6ba6fde42305e7b1624f06eb45ab6537 Mon Sep 17 00:00:00 2001
     From: Daniel Micay 
     Date: Thu, 27 May 2021 07:30:02 -0400
     Subject: add trichrome browser apk targets
    @@ -9,10 +9,10 @@ Subject: add trichrome browser apk targets
      2 files changed, 37 insertions(+)
     
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
    -index 5fc9fb6826c67..aad386a8ece8c 100644
    +index c2846f592476f..29664e2f72b93 100644
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
    -@@ -1843,6 +1843,10 @@ if (current_toolchain == default_toolchain) {
    +@@ -1856,6 +1856,10 @@ if (current_toolchain == default_toolchain) {
          is_trichrome = true
          is_bundle_module = true
        }
    @@ -23,7 +23,7 @@ index 5fc9fb6826c67..aad386a8ece8c 100644
      
        # Exists separately from chrome_public_base_module_java_for_test to allow
        # downstream to depend on test support packages without needing to depend on
    -@@ -2211,6 +2215,37 @@ if (current_toolchain == default_toolchain) {
    +@@ -2226,6 +2230,37 @@ if (current_toolchain == default_toolchain) {
            }
          }
        }
    @@ -62,7 +62,7 @@ index 5fc9fb6826c67..aad386a8ece8c 100644
        # As compared to chrome_public_test_apk, this target contains only unit tests
        # that require on device capabilities. These tests are smaller, more tightly
     diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
    -index ff00662732c1b..640d564c5dca5 100644
    +index fcb4e5340515b..02274fc426098 100644
     --- a/chrome/android/chrome_public_apk_tmpl.gni
     +++ b/chrome/android/chrome_public_apk_tmpl.gni
     @@ -476,6 +476,8 @@ template("chrome_common_apk_or_module_tmpl") {
    @@ -75,5 +75,5 @@ index ff00662732c1b..640d564c5dca5 100644
            assert(!_is_trichrome)
            asset_deps += [ "//chrome/android:chrome_apk_pak_assets" ]
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/disable-browser-sign-in-feature-by-default.patch b/build/e_patches/disable-browser-sign-in-feature-by-default.patch
    index 056bc2078f1f2adb6db8da5f469a953954a91ed0..1b6be2b547eb8dc097d48cdac0cba6bfdef6b84a 100644
    --- a/build/e_patches/disable-browser-sign-in-feature-by-default.patch
    +++ b/build/e_patches/disable-browser-sign-in-feature-by-default.patch
    @@ -1,4 +1,4 @@
    -From b6a722c671deb376d2fb3156d172520e788d171f Mon Sep 17 00:00:00 2001
    +From 9d30540d0e13f4521ae90fcf9e6bd431867376a7 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Mon, 12 Feb 2024 13:26:25 +0530
     Subject: disable browser sign in feature by default
    @@ -8,10 +8,10 @@ Subject: disable browser sign in feature by default
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/chrome/browser/signin/account_consistency_mode_manager.cc b/chrome/browser/signin/account_consistency_mode_manager.cc
    -index 75d4f871e93ec..bf475559b3e4b 100644
    +index cc676d3c1b201..81829e00f3409 100644
     --- a/chrome/browser/signin/account_consistency_mode_manager.cc
     +++ b/chrome/browser/signin/account_consistency_mode_manager.cc
    -@@ -117,7 +117,7 @@ AccountConsistencyModeManager::~AccountConsistencyModeManager() = default;
    +@@ -112,7 +112,7 @@ AccountConsistencyModeManager::~AccountConsistencyModeManager() = default;
      // static
      void AccountConsistencyModeManager::RegisterProfilePrefs(
          user_prefs::PrefRegistrySyncable* registry) {
    @@ -21,5 +21,5 @@ index 75d4f871e93ec..bf475559b3e4b 100644
      
      // static
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/disable-fetching-variations.patch b/build/e_patches/disable-fetching-variations.patch
    index c6f0243643ce98c8f8b270e249cb43a294a6a2a3..98b9a46a095c5535a11e6fe8b9d9949542df2965 100644
    --- a/build/e_patches/disable-fetching-variations.patch
    +++ b/build/e_patches/disable-fetching-variations.patch
    @@ -1,4 +1,4 @@
    -From 7df0bc91e1cdfeda41f48651d5362ee1bb3d3025 Mon Sep 17 00:00:00 2001
    +From ef0a95bd7aa84cd04bdefe1acf389333fe72aa47 Mon Sep 17 00:00:00 2001
     From: Daniel Micay 
     Date: Wed, 18 Nov 2020 19:08:58 -0500
     Subject: disable fetching variations
    @@ -8,7 +8,7 @@ Subject: disable fetching variations
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
    -index d0b05aa67320e..373771e3cf12f 100644
    +index 56c7d503f3fed..289ce4824a324 100644
     --- a/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
     +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/AsyncInitTaskRunner.java
     @@ -39,7 +39,7 @@ public abstract class AsyncInitTaskRunner {
    @@ -21,5 +21,5 @@ index d0b05aa67320e..373771e3cf12f 100644
      
          @VisibleForTesting
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/disable-navigation-error-correction-by-default.patch b/build/e_patches/disable-navigation-error-correction-by-default.patch
    index af38b63838dca971b98fbcb135fa80e7c5cccda1..d2613b8170d808167f9ea64bc4f132c8dbb2d20b 100644
    --- a/build/e_patches/disable-navigation-error-correction-by-default.patch
    +++ b/build/e_patches/disable-navigation-error-correction-by-default.patch
    @@ -1,4 +1,4 @@
    -From 9e2fd6568ae5e6ff575a52c5d04278314f309d68 Mon Sep 17 00:00:00 2001
    +From d7d9fbefcf4ea596b0dbc29bca7e69348e389bba Mon Sep 17 00:00:00 2001
     From: Daniel Micay 
     Date: Wed, 23 Nov 2016 08:29:58 -0500
     Subject: disable navigation error correction by default
    @@ -8,7 +8,7 @@ Subject: disable navigation error correction by default
      1 file changed, 1 insertion(+), 1 deletion(-)
     
     diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
    -index 856b50b8b319f..6e6aeb6aa5619 100644
    +index 8afd6a88dc240..22e9aa1c347e4 100644
     --- a/chrome/browser/net/profile_network_context_service.cc
     +++ b/chrome/browser/net/profile_network_context_service.cc
     @@ -504,7 +504,7 @@ void ProfileNetworkContextService::ConfigureNetworkContextParams(
    @@ -21,5 +21,5 @@ index 856b50b8b319f..6e6aeb6aa5619 100644
        registry->RegisterBooleanPref(prefs::kGloballyScopeHTTPAuthCacheEnabled,
                                      false);
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/disable-seedbased-field-trials.patch b/build/e_patches/disable-seedbased-field-trials.patch
    index dae1c8da46d2f17c38f0aab69113d883e34887e6..92f50240a9f8661c5cab6e5ba7a49d1064e9f979 100644
    --- a/build/e_patches/disable-seedbased-field-trials.patch
    +++ b/build/e_patches/disable-seedbased-field-trials.patch
    @@ -1,4 +1,4 @@
    -From fcdc7a174455aa22c3daca095ed6f865dd110483 Mon Sep 17 00:00:00 2001
    +From db2aa61752096587e19104ef25daa03245e68747 Mon Sep 17 00:00:00 2001
     From: Daniel Micay 
     Date: Tue, 25 Dec 2018 16:19:51 -0500
     Subject: disable seed-based field trials
    @@ -8,10 +8,10 @@ Subject: disable seed-based field trials
      1 file changed, 2 insertions(+)
     
     diff --git a/components/variations/service/variations_field_trial_creator_base.cc b/components/variations/service/variations_field_trial_creator_base.cc
    -index 8ef4305f81736..06f923dcbc559 100644
    +index 5ca71d4b7aa19..c421d868fba44 100644
     --- a/components/variations/service/variations_field_trial_creator_base.cc
     +++ b/components/variations/service/variations_field_trial_creator_base.cc
    -@@ -333,9 +333,11 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials(
    +@@ -335,9 +335,11 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials(
      
        bool used_seed = false;
        if (!used_testing_config && client_filterable_state) {
    @@ -24,5 +24,5 @@ index 8ef4305f81736..06f923dcbc559 100644
      
        platform_field_trials->RegisterFeatureOverrides(feature_list.get());
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/fixup-Bromite-subresource-adblocker.patch b/build/e_patches/fixup-Bromite-subresource-adblocker.patch
    index 98f96a9b6454cf9cdbb2dc8754968f52907b779e..170d09e0792306b3766677aea060aa69c9d19075 100644
    --- a/build/e_patches/fixup-Bromite-subresource-adblocker.patch
    +++ b/build/e_patches/fixup-Bromite-subresource-adblocker.patch
    @@ -1,4 +1,4 @@
    -From f0f0e095d9b5e51d48f932aca316cf276f99e95c Mon Sep 17 00:00:00 2001
    +From 4e1bb4a1059d36fccc2a093688ae94b0ff6ae9ac Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Thu, 11 Apr 2024 19:45:52 +0530
     Subject: fixup Bromite subresource adblocker
    @@ -8,10 +8,10 @@ Subject: fixup Bromite subresource adblocker
      1 file changed, 1 deletion(-)
     
     diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
    -index 3c3f3eb23b491..5fc9fb6826c67 100644
    +index 1ad9548de1751..c2846f592476f 100644
     --- a/chrome/android/BUILD.gn
     +++ b/chrome/android/BUILD.gn
    -@@ -334,7 +334,6 @@ if (current_toolchain == default_toolchain) {
    +@@ -332,7 +332,6 @@ if (current_toolchain == default_toolchain) {
            "//chrome/android/features/tab_ui/public:ui_java_resources",
            "//chrome/android/modules/stack_unwinder/provider:java",
            "//chrome/android/webapk/libs/client:client_java",
    @@ -20,5 +20,5 @@ index 3c3f3eb23b491..5fc9fb6826c67 100644
            "//chrome/android/webapk/libs/common:splash_java",
            "//chrome/android/webapk/libs/runtime_library:webapk_service_aidl_java",
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/fixup-Disable-fetching-of-all-field-trials.patch b/build/e_patches/fixup-Disable-fetching-of-all-field-trials.patch
    index 65010e0995946cd247a7330609aa2afc06e77645..ca1556565ec85287952c675e8d35b3fb35cedb65 100644
    --- a/build/e_patches/fixup-Disable-fetching-of-all-field-trials.patch
    +++ b/build/e_patches/fixup-Disable-fetching-of-all-field-trials.patch
    @@ -1,4 +1,4 @@
    -From b0f447736d895a7a400b143a01d7e1faffd1015a Mon Sep 17 00:00:00 2001
    +From 36178a338285bb44d21d1d4186b0d6c9ac0bcd92 Mon Sep 17 00:00:00 2001
     From: althafvly 
     Date: Wed, 20 Sep 2023 09:48:52 +0530
     Subject: fixup Disable fetching of all field trials
    @@ -29,7 +29,7 @@ index ad626ddfe4ef8..d6b55d4be62e4 100644
      
          @Override
     diff --git a/build/android/gyp/proguard.py b/build/android/gyp/proguard.py
    -index c1e6b2bbd929b..fbbb26c634e48 100755
    +index d26686f001652..72b94943197ec 100755
     --- a/build/android/gyp/proguard.py
     +++ b/build/android/gyp/proguard.py
     @@ -55,6 +55,8 @@ _IGNORE_WARNINGS = (
    @@ -39,13 +39,13 @@ index c1e6b2bbd929b..fbbb26c634e48 100755
     +        # No fetching of trials
     +        r'org\.chromium\.android_webview\.services\.AwVariationsSeedFetcher',
          ]) + ')',
    -     # TODO(agrieve): Remove once we update to U SDK.
    -     r'OnBackAnimationCallback',
    +     # We enforce that this class is removed via -checkdiscard.
    +     r'FastServiceLoader\.class:.*Could not inline ServiceLoader\.load',
     diff --git a/components/variations/service/variations_field_trial_creator_base.cc b/components/variations/service/variations_field_trial_creator_base.cc
    -index a2b965170aeca..8ef4305f81736 100644
    +index b6f0db5c4e200..5ca71d4b7aa19 100644
     --- a/components/variations/service/variations_field_trial_creator_base.cc
     +++ b/components/variations/service/variations_field_trial_creator_base.cc
    -@@ -340,6 +340,8 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials(
    +@@ -342,6 +342,8 @@ bool VariationsFieldTrialCreatorBase::SetUpFieldTrials(
      
        platform_field_trials->RegisterFeatureOverrides(feature_list.get());
      
    @@ -55,5 +55,5 @@ index a2b965170aeca..8ef4305f81736 100644
      
        // For testing Variations Safe Mode, maybe crash here.
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution-Chrome-Integration-Module.patch b/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution-Chrome-Integration-Module.patch
    deleted file mode 100644
    index 9ce3b3ba1f81ceac39cc4dfc09836211af28ca8f..0000000000000000000000000000000000000000
    --- a/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution-Chrome-Integration-Module.patch
    +++ /dev/null
    @@ -1,23 +0,0 @@
    -From 8999d325225b4de7fd740fc3440cf2c92c10d9ea Mon Sep 17 00:00:00 2001
    -From: Nishith Khanna 
    -Date: Fri, 24 Jan 2025 14:13:51 +0530
    -Subject: fixup: eyeo Browser Ad filtering Solution: Chrome Integration Module
    -
    ----
    - chrome/browser/ui/webui/chrome_web_ui_configs.cc | 2 +-
    - 1 file changed, 1 insertion(+), 1 deletion(-)
    -
    -diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    -index 67ef42c7139a3..2cc468e0173cc 100644
    ---- a/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    -+++ b/chrome/browser/ui/webui/chrome_web_ui_configs.cc
    -@@ -400,5 +400,5 @@ void RegisterChromeWebUIConfigs() {
    - #if BUILDFLAG(IS_ANDROID)
    -   map.AddWebUIConfig(std::make_unique());
    - #endif
    --}
    -   map.AddWebUIConfig(std::make_unique());
    -+}
    --- 
    -2.48.1
    -
    diff --git a/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution.patch b/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution.patch
    new file mode 100644
    index 0000000000000000000000000000000000000000..23c71f5d79f2944fa53bb5e61153bcb2a7d986a4
    --- /dev/null
    +++ b/build/e_patches/fixup-eyeo-Browser-Ad-filtering-Solution.patch
    @@ -0,0 +1,30 @@
    +From f168aa3e44847b01fc762827387bad827a5b1aac Mon Sep 17 00:00:00 2001
    +From: althafvly 
    +Date: Mon, 21 Apr 2025 18:27:07 +0530
    +Subject: fixup: eyeo Browser Ad filtering Solution
    +
    +---
    + chrome/browser/BUILD.gn | 7 +++++++
    + 1 file changed, 7 insertions(+)
    +
    +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
    +index 192f98cdc4a81..37ea7a5a006c4 100644
    +--- a/chrome/browser/BUILD.gn
    ++++ b/chrome/browser/BUILD.gn
    +@@ -2560,6 +2560,13 @@ static_library("browser") {
    +   }
    + 
    +   if (is_android) {
    ++    ### Android API module start
    ++    deps += [
    ++      "//components/adblock/android:java_bindings",
    ++      "//components/adblock/android:jni_headers",
    ++    ]
    ++    ### Android API module end
    ++
    +     sources += [
    +       "accessibility/accessibility_prefs/android/accessibility_prefs_controller.cc",
    +       "accessibility/accessibility_prefs/android/accessibility_prefs_controller.h",
    +-- 
    +2.34.1
    +
    diff --git a/build/e_patches/openH264-enable-ARMARM64-optimizations.patch b/build/e_patches/openH264-enable-ARMARM64-optimizations.patch
    index 665f823dfc923a31507379c8f51c28841d355a03..0080417153f1f4bf875165846cd4c385a354d5f4 100644
    --- a/build/e_patches/openH264-enable-ARMARM64-optimizations.patch
    +++ b/build/e_patches/openH264-enable-ARMARM64-optimizations.patch
    @@ -1,4 +1,4 @@
    -From 6a5248e37c5a6b48ba84f2ff79f28f71e1f30931 Mon Sep 17 00:00:00 2001
    +From eca43b45a08c8993eafd05943f1ceaf0f4567bef Mon Sep 17 00:00:00 2001
     From: csagan5 <32685696+csagan5@users.noreply.github.com>
     Date: Sat, 20 Jan 2018 21:17:27 +0100
     Subject: openH264: enable ARM/ARM64 optimizations
    @@ -13,10 +13,10 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      2 files changed, 21 insertions(+), 9 deletions(-)
     
     diff --git a/third_party/blink/renderer/modules/mediarecorder/h264_encoder.cc b/third_party/blink/renderer/modules/mediarecorder/h264_encoder.cc
    -index dcd8a4fcc3ac3..28c18e2e0afc4 100644
    +index 841cb9b687c2a..73250ebbc58e9 100644
     --- a/third_party/blink/renderer/modules/mediarecorder/h264_encoder.cc
     +++ b/third_party/blink/renderer/modules/mediarecorder/h264_encoder.cc
    -@@ -237,11 +237,11 @@ bool H264Encoder::ConfigureEncoder(const gfx::Size& size) {
    +@@ -236,11 +236,11 @@ bool H264Encoder::ConfigureEncoder(const gfx::Size& size) {
          init_params.iRCMode = RC_OFF_MODE;
        }
      
    @@ -81,5 +81,5 @@ index 99340a7369dd7..7666a1cfc8fb2 100644
        include_dirs = openh264_encoder_include_dirs
      
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/ungoogledchromium-Disable-Gaia.patch b/build/e_patches/ungoogledchromium-Disable-Gaia.patch
    index b804b25893e00602db1a94ee8882d97355200bdb..8f2f9237af26eb413c261e23b46e25fcca792a77 100644
    --- a/build/e_patches/ungoogledchromium-Disable-Gaia.patch
    +++ b/build/e_patches/ungoogledchromium-Disable-Gaia.patch
    @@ -1,4 +1,4 @@
    -From 8c39380c3b683e4248c6b7b4daef59c415e1434f Mon Sep 17 00:00:00 2001
    +From 30f95b8f264b4a2c546766d6a3f0f5fe0e692d46 Mon Sep 17 00:00:00 2001
     From: csagan5 <32685696+csagan5@users.noreply.github.com>
     Date: Mon, 12 Feb 2018 21:37:52 +0100
     Subject: ungoogled-chromium: Disable Gaia
    @@ -14,10 +14,10 @@ License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
      1 file changed, 2 insertions(+)
     
     diff --git a/google_apis/gaia/gaia_auth_fetcher.cc b/google_apis/gaia/gaia_auth_fetcher.cc
    -index 2c0caab95ba2b..cf7734fe61419 100644
    +index 778caae464c16..83e33045e0bef 100644
     --- a/google_apis/gaia/gaia_auth_fetcher.cc
     +++ b/google_apis/gaia/gaia_auth_fetcher.cc
    -@@ -252,6 +252,7 @@ void GaiaAuthFetcher::CreateAndStartGaiaFetcher(
    +@@ -248,6 +248,7 @@ void GaiaAuthFetcher::CreateAndStartGaiaFetcher(
          const net::NetworkTrafficAnnotationTag& traffic_annotation) {
        DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
      
    @@ -25,7 +25,7 @@ index 2c0caab95ba2b..cf7734fe61419 100644
        auto resource_request = std::make_unique();
        resource_request->url = gaia_gurl;
        original_url_ = gaia_gurl;
    -@@ -307,6 +308,7 @@ void GaiaAuthFetcher::CreateAndStartGaiaFetcher(
    +@@ -303,6 +304,7 @@ void GaiaAuthFetcher::CreateAndStartGaiaFetcher(
                           base::Unretained(this)),
            // Limit to 1 MiB.
            1024 * 1024);
    @@ -34,5 +34,5 @@ index 2c0caab95ba2b..cf7734fe61419 100644
      
      // static
     -- 
    -2.48.1
    +2.34.1
     
    diff --git a/build/e_patches/updater-disable-updater-pings.patch b/build/e_patches/updater-disable-updater-pings.patch
    new file mode 100644
    index 0000000000000000000000000000000000000000..e2ef8657b68c2dd81bf941ddd2ec6cc1d1505945
    --- /dev/null
    +++ b/build/e_patches/updater-disable-updater-pings.patch
    @@ -0,0 +1,87 @@
    +From b03751c27e16da18a2388d08c0a608d785e54139 Mon Sep 17 00:00:00 2001
    +From: Jan Engelhardt 
    +Date: Tue, 2 Jun 2015 11:01:50 +0200
    +Subject: updater: disable updater pings
    +
    +Despite auto-updater being arguably disabled (see previous commit),
    +Chromium would still send background requests. Kill it.
    +(trk:170, trk:171)
    +
    +License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
    +---
    + .../component_updater/component_updater_url_constants.cc | 4 ++--
    + components/component_updater/configurator_impl.cc        | 9 +++------
    + components/component_updater/configurator_impl.h         | 2 --
    + 3 files changed, 5 insertions(+), 10 deletions(-)
    +
    +diff --git a/components/component_updater/component_updater_url_constants.cc b/components/component_updater/component_updater_url_constants.cc
    +index 95c0d48942654..50112da3fa7f4 100644
    +--- a/components/component_updater/component_updater_url_constants.cc
    ++++ b/components/component_updater/component_updater_url_constants.cc
    +@@ -15,9 +15,9 @@ namespace component_updater {
    + // The value of |kDefaultUrlSource| can be overridden with
    + // --component-updater=url-source=someurl.
    + const char kUpdaterJSONDefaultUrl[] =
    +-    "https://update.googleapis.com/service/update2/json";
    ++    "about:blank";
    + 
    + const char kUpdaterJSONFallbackUrl[] =
    +-    "http://update.googleapis.com/service/update2/json";
    ++    "about:blank";
    + 
    + }  // namespace component_updater
    +diff --git a/components/component_updater/configurator_impl.cc b/components/component_updater/configurator_impl.cc
    +index 042e1026c76e7..4071347c0d4a1 100644
    +--- a/components/component_updater/configurator_impl.cc
    ++++ b/components/component_updater/configurator_impl.cc
    +@@ -40,8 +40,6 @@ ConfiguratorImpl::ConfiguratorImpl(
    +     : background_downloads_enabled_(config_policy.BackgroundDownloadsEnabled()),
    +       deltas_enabled_(config_policy.DeltaUpdatesEnabled()),
    +       fast_update_(config_policy.FastUpdate()),
    +-      pings_enabled_(config_policy.PingsEnabled()),
    +-      require_encryption_(require_encryption),
    +       url_source_override_(config_policy.UrlSourceOverride()),
    +       initial_delay_(config_policy.InitialDelay()) {
    +   if (config_policy.TestRequest()) {
    +@@ -77,22 +75,21 @@ base::TimeDelta ConfiguratorImpl::UpdateDelay() const {
    + 
    + std::vector ConfiguratorImpl::UpdateUrl() const {
    +   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    ++  if ((true)) return std::vector();
    +   if (url_source_override_.is_valid()) {
    +     return {GURL(url_source_override_)};
    +   }
    + 
    +   std::vector urls{GURL(kUpdaterJSONDefaultUrl),
    +                          GURL(kUpdaterJSONFallbackUrl)};
    +-  if (require_encryption_) {
    +-    update_client::RemoveUnsecureUrls(&urls);
    +-  }
    ++  update_client::RemoveUnsecureUrls(&urls);
    + 
    +   return urls;
    + }
    + 
    + std::vector ConfiguratorImpl::PingUrl() const {
    +   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    +-  return pings_enabled_ ? UpdateUrl() : std::vector();
    ++  return std::vector();
    + }
    + 
    + const base::Version& ConfiguratorImpl::GetBrowserVersion() const {
    +diff --git a/components/component_updater/configurator_impl.h b/components/component_updater/configurator_impl.h
    +index 629673757fce2..5c319b9305021 100644
    +--- a/components/component_updater/configurator_impl.h
    ++++ b/components/component_updater/configurator_impl.h
    +@@ -106,8 +106,6 @@ class ConfiguratorImpl {
    +   const bool background_downloads_enabled_;
    +   const bool deltas_enabled_;
    +   const bool fast_update_;
    +-  const bool pings_enabled_;
    +-  const bool require_encryption_;
    +   const GURL url_source_override_;
    +   const base::TimeDelta initial_delay_;
    + };
    +-- 
    +2.34.1
    +
    diff --git a/build/e_patches_list.txt b/build/e_patches_list.txt
    index f2eb64f1fe483f54117756d8449b626442b2b5f3..65a0ebb7ed0ea5bf201a3c55c323b98d63e1027a 100644
    --- a/build/e_patches_list.txt
    +++ b/build/e_patches_list.txt
    @@ -1,7 +1,7 @@
    -fixup-eyeo-Browser-Ad-filtering-Solution-Chrome-Integration-Module.patch
     fixup-Revert-removal-of-execution-context-address-space.patch
     fixup-Bromite-subresource-adblocker.patch
     fixup-Disable-fetching-of-all-field-trials.patch
    +fixup-eyeo-Browser-Ad-filtering-Solution.patch
     add-trichrome-browser-apk-targets.patch
     Browser-disable-price-shopping-commerce-integration.patch
     Remove-bromite-auto-updater-option.patch
    @@ -62,3 +62,5 @@ Browser-Fix-open-in-browser-text.patch
     Browser-Enable-external-intent-requests.patch
     Whitelist-murena-search.patch
     Migrate-to-murena-search-from-spot.patch
    +updater-disable-updater-pings.patch
    +Disable-Component-Updates.patch
    \ No newline at end of file
    diff --git a/build/third_party/bump-prepopulated-engine-data-version-to-update-exis.patch b/build/third_party/bump-prepopulated-engine-data-version-to-update-exis.patch
    deleted file mode 100644
    index 8f0f873b2cc8e39afa3465c90f3600b327eccff1..0000000000000000000000000000000000000000
    --- a/build/third_party/bump-prepopulated-engine-data-version-to-update-exis.patch
    +++ /dev/null
    @@ -1,25 +0,0 @@
    -From 66d25a23d93202a55e985572432046f4efe7c21d Mon Sep 17 00:00:00 2001
    -From: althafvly 
    -Date: Wed, 16 Apr 2025 16:43:10 +0530
    -Subject: [PATCH] bump prepopulated engine data version to update exisiting
    -
    ----
    - definitions/prepopulated_engines.json | 2 +-
    - 1 file changed, 1 insertion(+), 1 deletion(-)
    -
    -diff --git a/definitions/prepopulated_engines.json b/definitions/prepopulated_engines.json
    -index 1a331e7..43ba2aa 100644
    ---- a/definitions/prepopulated_engines.json
    -+++ b/definitions/prepopulated_engines.json
    -@@ -26,7 +26,7 @@
    -     // existing data should get a new version. Otherwise, existing data may
    -     // continue to be used and updates made here will not always appear.
    -     // Also then run tools/search_engine_choice/generate_search_engine_icons.py.
    --    "kCurrentDataVersion": 170
    -+    "kCurrentDataVersion": 171
    -   },
    - 
    -   // The following engines are included in country lists and are added to the
    --- 
    -2.34.1
    -
    diff --git a/build/third_party_patches_list.txt b/build/third_party_patches_list.txt
    deleted file mode 100644
    index 77963e87f9b962bb2da250147993a692806503e0..0000000000000000000000000000000000000000
    --- a/build/third_party_patches_list.txt
    +++ /dev/null
    @@ -1 +0,0 @@
    -search_engines_data/resources bump-prepopulated-engine-data-version-to-update-exis.patch
    diff --git a/domain_substitution/domain_substitution.list b/domain_substitution/domain_substitution.list
    index beead751534767bb48399b8716e55a0fc0da7fcf..ac717d0ff06f4c14e7f98e40d8517bcea356b39a 100644
    --- a/domain_substitution/domain_substitution.list
    +++ b/domain_substitution/domain_substitution.list
    @@ -152,7 +152,6 @@ ash/system/video_conference/video_conference_tray_unittest.cc
     ash/wallpaper/test_wallpaper_controller_client.cc
     ash/wallpaper/wallpaper_image_downloader.cc
     ash/webui/boca_ui/boca_app_page_handler_unittest.cc
    -ash/webui/boca_ui/boca_ui.cc
     ash/webui/boca_ui/provider/classroom_page_handler_impl.cc
     ash/webui/boca_ui/webview_auth_handler_unittest.cc
     ash/webui/camera_app_ui/camera_app_untrusted_ui.cc
    @@ -213,7 +212,7 @@ ash/webui/projector_app/test/projector_oauth_token_fetcher_unittest.cc
     ash/webui/projector_app/test/projector_xhr_sender_unittest.cc
     ash/webui/projector_app/test/untrusted_projector_page_handler_impl_unittest.cc
     ash/webui/projector_app/untrusted_projector_ui.cc
    -ash/webui/recorder_app_ui/resources/components/onboarding-dialog.ts
    +ash/webui/recorder_app_ui/resources/components/unescapable-dialog.ts
     ash/webui/recorder_app_ui/resources/core/externs.d.ts
     ash/webui/recorder_app_ui/resources/core/platform_handler.ts
     ash/webui/recorder_app_ui/resources/core/recording_session.ts
    @@ -241,6 +240,7 @@ base/BUILD.gn
     base/PRESUBMIT.py
     base/allocator/partition_alloc_support.cc
     base/allocator/partition_allocator/PRESUBMIT.py
    +base/allocator/partition_allocator/src/partition_alloc/BUILD.gn
     base/allocator/partition_allocator/src/partition_alloc/address_space_randomization.h
     base/allocator/partition_allocator/src/partition_alloc/build_config.h
     base/allocator/partition_allocator/src/partition_alloc/in_slot_metadata.h
    @@ -501,6 +501,7 @@ cc/metrics/average_lag_tracker.h
     cc/metrics/ukm_smoothness_data.h
     cc/paint/paint_image.h
     cc/raster/one_copy_raster_buffer_provider.cc
    +cc/scheduler/scheduler_state_machine.cc
     cc/tiles/gpu_image_decode_cache.h
     cc/trees/layer_tree_host_impl.cc
     cc/trees/layer_tree_host_unittest_animation.cc
    @@ -511,6 +512,7 @@ chrome/app/PRESUBMIT.py
     chrome/app/app_management_strings.grdp
     chrome/app/chrome_command_ids.h
     chrome/app/chrome_main.cc
    +chrome/app/chrome_main_delegate.cc
     chrome/app/chromeos_strings.grdp
     chrome/app/chromium_strings.grd
     chrome/app/delay_load_failure_hook_win.cc
    @@ -578,7 +580,6 @@ chrome/browser/apps/app_service/publishers/arc_apps_unittest.cc
     chrome/browser/apps/app_service/publishers/publisher_unittest.cc
     chrome/browser/apps/app_service/web_contents_app_id_utils.h
     chrome/browser/apps/app_service/webapk/webapk_install_task.cc
    -chrome/browser/apps/digital_goods/digital_goods_ash.cc
     chrome/browser/apps/digital_goods/digital_goods_factory_impl.cc
     chrome/browser/apps/guest_view/web_view_browsertest.cc
     chrome/browser/apps/intent_helper/intent_chip_display_prefs_unittest.cc
    @@ -658,7 +659,7 @@ chrome/browser/ash/attestation/tpm_challenge_key_subtle_unittest.cc
     chrome/browser/ash/bluetooth/bluetooth_log_controller_unittest.cc
     chrome/browser/ash/bluetooth/debug_logs_manager_unittest.cc
     chrome/browser/ash/boca/on_task/on_task_locked_session_navigation_throttle.cc
    -chrome/browser/ash/boca/on_task/on_task_locked_session_navigation_throttle_browsertest.cc
    +chrome/browser/ash/boca/on_task/on_task_locked_session_navigation_throttle_interactive_ui_test.cc
     chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker_browsertest.cc
     chrome/browser/ash/boca/spotlight/spotlight_crd_manager_impl_unittest.cc
     chrome/browser/ash/bruschetta/bruschetta_download.cc
    @@ -669,7 +670,6 @@ chrome/browser/ash/calendar/calendar_keyed_service_unittest.cc
     chrome/browser/ash/cert_provisioning/cert_provisioning_test_helpers.cc
     chrome/browser/ash/certs/system_token_cert_db_initializer.h
     chrome/browser/ash/child_accounts/constants/child_account_constants.h
    -chrome/browser/ash/child_accounts/family_user_device_metrics_browsertest.cc
     chrome/browser/ash/child_accounts/parent_access_code/parent_access_service_browsertest.cc
     chrome/browser/ash/child_accounts/time_limits/app_time_limit_utils_unittest.cc
     chrome/browser/ash/child_accounts/website_approval_notifier_unittest.cc
    @@ -717,6 +717,7 @@ chrome/browser/ash/input_method/ime_service_connector.cc
     chrome/browser/ash/integration_tests/featured_integration_test.cc
     chrome/browser/ash/kerberos/kerberos_credentials_manager_unittest.cc
     chrome/browser/ash/lobster/lobster_feedback_unittest.cc
    +chrome/browser/ash/lobster/lobster_system_state_provider_impl_unittest.cc
     chrome/browser/ash/login/app_mode/kiosk_launch_controller_unittest.cc
     chrome/browser/ash/login/app_mode/test/kiosk_enterprise_browsertest.cc
     chrome/browser/ash/login/ash_hud_login_browsertest.cc
    @@ -833,9 +834,10 @@ chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_
     chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils_unittest.cc
     chrome/browser/ash/tpm/tpm_firmware_update.h
     chrome/browser/ash/u2f/u2f_notification.cc
    +chrome/browser/ash/wallpaper_handlers/google_photos_wallpaper_handlers.cc
    +chrome/browser/ash/wallpaper_handlers/google_photos_wallpaper_handlers_unittest.cc
     chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher.cc
     chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
    -chrome/browser/ash/wallpaper_handlers/wallpaper_handlers_unittest.cc
     chrome/browser/autocomplete/autocomplete_browsertest.cc
     chrome/browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc
     chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier_unittest.cc
    @@ -850,7 +852,6 @@ chrome/browser/autofill/automated_tests/cache_replayer.cc
     chrome/browser/autofill/automated_tests/cache_replayer.h
     chrome/browser/autofill/automated_tests/cache_replayer_unittest.cc
     chrome/browser/autofill/captured_sites_test_utils.cc
    -chrome/browser/autofill_ai/autofill_ai_browsertest.cc
     chrome/browser/autofill_ai/chrome_autofill_ai_client_unittest.cc
     chrome/browser/background/background_contents_service.cc
     chrome/browser/background_sync/periodic_background_sync_permission_context.cc
    @@ -878,6 +879,8 @@ chrome/browser/browsing_data/counters/site_data_counting_helper_unittest.cc
     chrome/browser/browsing_data/counters/site_settings_counter_unittest.cc
     chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
     chrome/browser/browsing_topics/browsing_topics_service_factory.cc
    +chrome/browser/btm/btm_browser_signin_detector.cc
    +chrome/browser/btm/btm_browser_signin_detector_unittest.cc
     chrome/browser/chrome_browser_application_mac.mm
     chrome/browser/chrome_browser_main_win.cc
     chrome/browser/chrome_browser_main_win_unittest.cc
    @@ -919,6 +922,7 @@ chrome/browser/content_settings/content_settings_origin_value_map_unittest.cc
     chrome/browser/content_settings/content_settings_policy_provider_unittest.cc
     chrome/browser/content_settings/content_settings_pref_provider_unittest.cc
     chrome/browser/content_settings/host_content_settings_map_unittest.cc
    +chrome/browser/content_settings/javascript_optimizer_provider_android_unittest.cc
     chrome/browser/content_settings/sound_content_setting_observer_unittest.cc
     chrome/browser/contextual_cueing/contextual_cueing_helper_interactive_uitest.cc
     chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
    @@ -932,12 +936,11 @@ chrome/browser/devtools/device/adb/mock_adb_server.cc
     chrome/browser/devtools/devtools_browsertest.cc
     chrome/browser/devtools/devtools_ui_bindings.cc
     chrome/browser/devtools/devtools_ui_bindings_unittest.cc
    +chrome/browser/devtools/features.cc
     chrome/browser/devtools/process_sharing_infobar_delegate.cc
     chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc
     chrome/browser/devtools/protocol/devtools_pwa_browsertest.cc
     chrome/browser/devtools/url_constants.cc
    -chrome/browser/dips/dips_browser_signin_detector.cc
    -chrome/browser/dips/dips_browser_signin_detector_unittest.cc
     chrome/browser/download/chrome_download_manager_delegate_unittest.cc
     chrome/browser/download/download_browsertest.cc
     chrome/browser/download/insecure_download_blocking.cc
    @@ -950,6 +953,7 @@ chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_unittest
     chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
     chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate_unittest.cc
     chrome/browser/enterprise/connectors/analysis/file_transfer_analysis_delegate_unittest.cc
    +chrome/browser/enterprise/connectors/analysis/page_print_request_handler_unittest.cc
     chrome/browser/enterprise/connectors/device_trust/attestation/ash/ash_attestation_cleanup_manager_unittest.cc
     chrome/browser/enterprise/connectors/device_trust/attestation/ash/ash_attestation_service_impl_unittest.cc
     chrome/browser/enterprise/connectors/device_trust/attestation/browser/google_keys.cc
    @@ -962,6 +966,7 @@ chrome/browser/enterprise/connectors/device_trust/signals/decorators/ash/ash_sig
     chrome/browser/enterprise/connectors/device_trust/signals/decorators/browser/browser_signals_decorator_unittest.cc
     chrome/browser/enterprise/connectors/device_trust/test/test_constants.cc
     chrome/browser/enterprise/connectors/reporting/extension_telemetry_event_router_unittest.cc
    +chrome/browser/enterprise/connectors/reporting/reporting_event_router_unittest.cc
     chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.cc
     chrome/browser/enterprise/data_controls/chrome_rules_service_unittest.cc
     chrome/browser/enterprise/data_controls/reporting_service_unittest.cc
    @@ -990,7 +995,6 @@ chrome/browser/extensions/activity_log/counting_policy_unittest.cc
     chrome/browser/extensions/activity_log/fullstream_ui_policy_unittest.cc
     chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc
     chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc
    -chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc
     chrome/browser/extensions/api/bookmarks/bookmark_apitest.cc
     chrome/browser/extensions/api/braille_display_private/braille_display_private_apitest.cc
     chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
    @@ -1029,6 +1033,7 @@ chrome/browser/extensions/api/page_capture/page_capture_api_unittest.cc
     chrome/browser/extensions/api/passwords_private/password_check_delegate_unittest.cc
     chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc
     chrome/browser/extensions/api/passwords_private/passwords_private_utils_unittest.cc
    +chrome/browser/extensions/api/permissions/permissions_api_helpers.cc
     chrome/browser/extensions/api/permissions/permissions_api_unittest.cc
     chrome/browser/extensions/api/preference/preference_api_prefs_unittest.cc
     chrome/browser/extensions/api/printing/printing_api_utils.h
    @@ -1049,6 +1054,7 @@ chrome/browser/extensions/api/web_request/web_request_permissions_unittest.cc
     chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
     chrome/browser/extensions/api/webstore_private/extension_install_status_unittest.cc
     chrome/browser/extensions/background_xhr_browsertest.cc
    +chrome/browser/extensions/bookmarks/bookmarks_helpers_unittest.cc
     chrome/browser/extensions/chrome_extension_function_details.cc
     chrome/browser/extensions/component_extensions_allowlist/allowlist.h
     chrome/browser/extensions/content_script_apitest.cc
    @@ -1088,7 +1094,6 @@ chrome/browser/extensions/orb_and_cors_extension_browsertest.cc
     chrome/browser/extensions/permission_message_combinations_unittest.cc
     chrome/browser/extensions/permissions/active_tab_apitest.cc
     chrome/browser/extensions/permissions/active_tab_unittest.cc
    -chrome/browser/extensions/permissions/permissions_helpers.cc
     chrome/browser/extensions/permissions/permissions_updater.cc
     chrome/browser/extensions/permissions/permissions_updater_unittest.cc
     chrome/browser/extensions/permissions/scripting_permissions_modifier.h
    @@ -1123,15 +1128,12 @@ chrome/browser/flag-metadata.json
     chrome/browser/flag_descriptions.cc
     chrome/browser/flags/android/chrome_session_state.h
     chrome/browser/font_prewarmer_tab_helper_browsertest.cc
    -chrome/browser/glic/border_view_interactive_uitest.cc
     chrome/browser/glic/glic_cookie_synchronizer.cc
     chrome/browser/glic/glic_cookie_synchronizer_unittest.cc
     chrome/browser/glic/glic_fre_page_handler.cc
    +chrome/browser/glic/glic_metrics_unittest.cc
     chrome/browser/glic/glic_page_handler.cc
    -chrome/browser/glic/glic_policy_browsertest.cc
    -chrome/browser/glic/glic_window_resize_animation_browsertest.cc
    -chrome/browser/glic/guest_util_browsertest.cc
    -chrome/browser/glic/interactive_glic_test.h
    +chrome/browser/glic/glic_ui.cc
     chrome/browser/google/google_update_win.cc
     chrome/browser/guest_view/web_view/context_menu_content_type_web_view.cc
     chrome/browser/hid/chrome_hid_delegate_unittest.cc
    @@ -1300,6 +1302,7 @@ chrome/browser/password_manager/android/password_checkup_launcher_helper.h
     chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.h
     chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
     chrome/browser/password_manager/android/password_manager_settings_service_android_impl_unittest.cc
    +chrome/browser/password_manager/android/password_manager_settings_service_android_migration_impl_unittest.cc
     chrome/browser/password_manager/android/password_settings_updater_android_bridge_helper_impl_unittest.cc
     chrome/browser/password_manager/android/password_store_android_account_backend_unittest.cc
     chrome/browser/password_manager/android/password_store_android_backend_bridge_helper_impl_unittest.cc
    @@ -1371,6 +1374,7 @@ chrome/browser/preloading/preview/preview_navigation_throttle.h
     chrome/browser/preloading/preview/preview_zoom_controller.h
     chrome/browser/printing/print_preview_dialog_controller_unittest.cc
     chrome/browser/privacy/secure_dns_bridge.cc
    +chrome/browser/privacy_sandbox/PRESUBMIT.py
     chrome/browser/privacy_sandbox/privacy_sandbox_activity_types_service.h
     chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc
     chrome/browser/privacy_sandbox/privacy_sandbox_service_impl_unittest.cc
    @@ -1466,9 +1470,12 @@ chrome/browser/resources/chromeos/accessibility/common/paragraph_utils_unittest.
     chrome/browser/resources/chromeos/accessibility/common/testing/mock_tts.js
     chrome/browser/resources/chromeos/accessibility/common/tutorial/chromevox_tutorial.js
     chrome/browser/resources/chromeos/accessibility/definitions/command_line_private.d.ts
    -chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
    -chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
    -chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak.ts
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_navigation_control_test.js
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_unittest.js
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak.ts
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak_navigation_control_test.js
    +chrome/browser/resources/chromeos/accessibility/select_to_speak/mv3/select_to_speak_unittest.js
     chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
     chrome/browser/resources/chromeos/accessibility/strings/chromevox_strings.grdp
     chrome/browser/resources/chromeos/accessibility/switch_access/mv2/item_scan_manager_test.js
    @@ -1516,8 +1523,8 @@ chrome/browser/resources/gaia_auth_host/authenticator.js
     chrome/browser/resources/gaia_auth_host/password_change_authenticator.js
     chrome/browser/resources/gaia_auth_host/saml_handler.js
     chrome/browser/resources/gaia_auth_host/saml_password_attributes.js
    -chrome/browser/resources/glic/glic_api/glic_api.ts
    -chrome/browser/resources/glic/glic_app_controller.ts
    +chrome/browser/resources/glic/url_pattern.d.ts
    +chrome/browser/resources/glic/webview.ts
     chrome/browser/resources/hangout_services/manifest_v2.json
     chrome/browser/resources/hangout_services/manifest_v3.json
     chrome/browser/resources/inspect/inspect.html
    @@ -1556,7 +1563,6 @@ chrome/browser/resources/settings/a11y_page/captions_subpage.ts
     chrome/browser/resources/settings/about_page/about_page.html
     chrome/browser/resources/settings/about_page/about_page.ts
     chrome/browser/resources/settings/appearance_page/appearance_page.ts
    -chrome/browser/resources/settings/autofill_page/payments_section.ts
     chrome/browser/resources/settings/icons.html
     chrome/browser/resources/settings/incompatible_applications_page/incompatible_application_item.ts
     chrome/browser/resources/settings/languages_page/edit_dictionary_page.ts
    @@ -1589,6 +1595,7 @@ chrome/browser/safe_browsing/download_protection/check_client_download_request_b
     chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
     chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
     chrome/browser/safe_browsing/download_protection/download_feedback.cc
    +chrome/browser/safe_browsing/download_protection/download_protection_delegate_desktop.cc
     chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
     chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
     chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_browsertest.cc
    @@ -1619,7 +1626,6 @@ chrome/browser/serial/chrome_serial_browsertest.cc
     chrome/browser/serial/serial_chooser_context_unittest.cc
     chrome/browser/serial/serial_policy_allowed_ports_unittest.cc
     chrome/browser/sessions/app_session_service_unittest.cc
    -chrome/browser/sessions/better_session_restore_browsertest.cc
     chrome/browser/sessions/session_restore_browsertest.cc
     chrome/browser/sessions/session_restore_observer_unittest.cc
     chrome/browser/sessions/session_service.h
    @@ -1634,7 +1640,6 @@ chrome/browser/sharing_hub/sharing_hub_model_unittest.cc
     chrome/browser/shell_integration_linux_unittest.cc
     chrome/browser/signin/account_id_from_account_info_unittest.cc
     chrome/browser/signin/accounts_policy_manager_unittest.cc
    -chrome/browser/signin/android/web_signin_bridge_unittest.cc
     chrome/browser/signin/bound_session_credentials/bound_session_cookie_controller_impl_unittest.cc
     chrome/browser/signin/bound_session_credentials/bound_session_cookie_observer_unittest.cc
     chrome/browser/signin/bound_session_credentials/bound_session_cookie_refresh_service_impl_browsertest.cc
    @@ -1686,14 +1691,14 @@ chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
     chrome/browser/startup_data.h
     chrome/browser/storage/durable_storage_permission_context_unittest.cc
     chrome/browser/storage/shared_storage_browsertest.cc
    -chrome/browser/storage_access_api/api_browsertest.cc
     chrome/browser/storage_access_api/storage_access_header_service_browsertest.cc
     chrome/browser/subresource_filter/subresource_filter_browsertest.cc
     chrome/browser/subresource_filter/subresource_filter_fenced_frame_browsertest.cc
     chrome/browser/supervised_user/android/java/res/drawable/ic_family_link.xml
     chrome/browser/supervised_user/android/supervised_user_settings_test_bridge.cc
     chrome/browser/supervised_user/kids_profile_interactive_uitest.cc
    -chrome/browser/supervised_user/linux_mac_windows/parent_access_dialog_web_contents_observer_browsertest.cc
    +chrome/browser/supervised_user/linux_mac_windows/parent_access_dialog_result_observer_browsertest.cc
    +chrome/browser/supervised_user/linux_mac_windows/supervised_user_web_content_handler_impl_browsertest.cc
     chrome/browser/supervised_user/supervised_user_browser_utils.cc
     chrome/browser/supervised_user/supervised_user_browser_utils_unittest.cc
     chrome/browser/supervised_user/supervised_user_google_auth_navigation_throttle.cc
    @@ -1793,7 +1798,6 @@ chrome/browser/ui/ash/wallpaper/wallpaper_controller_client_impl.cc
     chrome/browser/ui/ash/web_view/ash_web_view_impl_browsertest.cc
     chrome/browser/ui/ash/wm/coral_browsertest.cc
     chrome/browser/ui/autofill/address_editor_controller_unittest.cc
    -chrome/browser/ui/autofill/autofill_signin_promo_tab_helper_browsertest.cc
     chrome/browser/ui/autofill/delete_address_profile_dialog_controller_impl_unittest.cc
     chrome/browser/ui/autofill/payments/offer_notification_bubble_controller_impl_unittest.cc
     chrome/browser/ui/autofill/payments/offer_notification_controller_android_browsertest.cc
    @@ -1871,12 +1875,14 @@ chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_brows
     chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_unittest.cc
     chrome/browser/ui/plus_addresses/plus_address_menu_model_unittest.cc
     chrome/browser/ui/profiles/profile_picker_unittest.cc
    +chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager_unittest.cc
     chrome/browser/ui/safety_hub/notification_permission_review_service_unittest.cc
     chrome/browser/ui/safety_hub/password_status_check_service_unittest.cc
     chrome/browser/ui/search/ntp_user_data_logger_unittest.cc
     chrome/browser/ui/search/search_tab_helper.h
     chrome/browser/ui/search/third_party_ntp_browsertest.cc
     chrome/browser/ui/signin/cookie_clear_on_exit_migration_notice_browsertest.cc
    +chrome/browser/ui/signin/promos/signin_promo_tab_helper_browsertest.cc
     chrome/browser/ui/signin/signin_view_controller.cc
     chrome/browser/ui/signin/signin_view_controller_browsertest.cc
     chrome/browser/ui/signin/signin_view_controller_interactive_uitest.cc
    @@ -1890,7 +1896,7 @@ chrome/browser/ui/startup/startup_tab_provider_unittest.cc
     chrome/browser/ui/tab_helpers.cc
     chrome/browser/ui/tab_helpers.h
     chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate_unittest.cc
    -chrome/browser/ui/tabs/pinned_tab_codec_unittest.cc
    +chrome/browser/ui/tabs/pinned_tab_codec_browsertest.cc
     chrome/browser/ui/tabs/pinned_tab_service_browsertest.cc
     chrome/browser/ui/tabs/pinned_tab_service_unittest.cc
     chrome/browser/ui/tabs/saved_tab_groups/collaboration_messaging_tab_data_unittest.cc
    @@ -1901,7 +1907,6 @@ chrome/browser/ui/test/popup_browsertest.cc
     chrome/browser/ui/toolbar/app_menu_model_interactive_uitest.cc
     chrome/browser/ui/toolbar/cast/cast_contextual_menu.cc
     chrome/browser/ui/toolbar/location_bar_model_unittest.cc
    -chrome/browser/ui/ui_features.cc
     chrome/browser/ui/url_identity_unittest.cc
     chrome/browser/ui/views/accelerator_table.cc
     chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.cc
    @@ -1932,9 +1937,7 @@ chrome/browser/ui/views/download/bubble/download_bubble_contents_view_unittest.c
     chrome/browser/ui/views/download/bubble/download_bubble_security_view_unittest.cc
     chrome/browser/ui/views/download/bubble/download_toolbar_button_view_unittest.cc
     chrome/browser/ui/views/extensions/device_chooser_extension_browsertest.cc
    -chrome/browser/ui/views/extensions/dialogs/upload_extension_to_account_dialog_browsertest.cc
     chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
    -chrome/browser/ui/views/extensions/extension_installed_bubble_view_signin_browsertest.cc
     chrome/browser/ui/views/extensions/extension_uninstall_dialog_view_browsertest.cc
     chrome/browser/ui/views/extensions/web_file_handlers/web_file_handlers_file_launch_dialog.cc
     chrome/browser/ui/views/file_system_access/file_system_access_usage_bubble_view_browsertest.cc
    @@ -1952,7 +1955,6 @@ chrome/browser/ui/views/omnibox/omnibox_view_views.h
     chrome/browser/ui/views/omnibox/omnibox_view_views_browsertest.cc
     chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
     chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
    -chrome/browser/ui/views/page_action/pwa_install.cc
     chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator.cc
     chrome/browser/ui/views/page_info/about_this_site_side_panel_coordinator_browsertest.cc
     chrome/browser/ui/views/page_info/merchant_trust_side_panel_coordinator.cc
    @@ -1978,6 +1980,7 @@ chrome/browser/ui/views/profiles/profile_picker_ui_browsertest.cc
     chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
     chrome/browser/ui/views/profiles/profiles_pixel_test_utils.cc
     chrome/browser/ui/views/profiles/sync_confirmation_ui_browsertest.cc
    +chrome/browser/ui/views/promos/ios_promo_constants.h
     chrome/browser/ui/views/qrcode_generator/qrcode_generator_bubble_unittest.cc
     chrome/browser/ui/views/select_file_dialog_extension/select_file_dialog_extension_browsertest.cc
     chrome/browser/ui/views/session_crashed_bubble_view.cc
    @@ -1987,17 +1990,14 @@ chrome/browser/ui/views/sharing_hub/preview_view_unittest.cc
     chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl_unittest.cc
     chrome/browser/ui/views/site_data/page_specific_site_data_dialog_unittest.cc
     chrome/browser/ui/views/sync/inline_login_ui_browsertest.cc
    -chrome/browser/ui/views/tabs/collaboration_messaging_page_action_icon_view_interactive_uitest.cc
     chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view_browsertest.cc
     chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view_interactive_uitest.cc
     chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
    -chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
     chrome/browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc
     chrome/browser/ui/views/user_education/browser_user_education_service.cc
    -chrome/browser/ui/views/user_education/low_usage_promo.cc
     chrome/browser/ui/views/web_apps/force_installed_preinstalled_deprecated_app_dialog_view.cc
     chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_browsertest.cc
    -chrome/browser/ui/views/webid/account_selection_view_base.cc
    +chrome/browser/ui/views/webauthn/passkey_upgrade_bubble_view.cc
     chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc
     chrome/browser/ui/web_applications/sub_apps_admin_policy_browsertest.cc
     chrome/browser/ui/web_applications/web_app_browsertest.cc
    @@ -2104,18 +2104,18 @@ chrome/browser/ui/webui/whats_new/whats_new_ui.cc
     chrome/browser/ui/window_sizer/window_sizer_chromeos.cc
     chrome/browser/unified_consent/unified_consent_sync_to_signin_browsertest.cc
     chrome/browser/updates/announcement_notification/announcement_notification_service_unittest.cc
    +chrome/browser/upgrade_detector/version_history_client.cc
    +chrome/browser/upgrade_detector/version_history_client_unittest.cc
     chrome/browser/usb/chrome_usb_browsertest.cc
     chrome/browser/usb/chrome_usb_delegate_unittest.cc
     chrome/browser/usb/usb_chooser_context_unittest.cc
     chrome/browser/usb/usb_chooser_controller_unittest.cc
     chrome/browser/usb/usb_policy_allowed_devices_unittest.cc
     chrome/browser/usb/web_usb_detector_unittest.cc
    -chrome/browser/user_annotations/user_annotations_service_browsertest.cc
     chrome/browser/visited_url_ranking/desktop_tab_model_url_visit_data_fetcher_unittest.cc
     chrome/browser/vr/PRESUBMIT.py
     chrome/browser/vr/test/gl_test_environment_unittest.cc
     chrome/browser/wallet/android/boarding_pass_detector_unittest.cc
    -chrome/browser/web_applications/chromeos_web_app_experiments_browsertest.cc
     chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
     chrome/browser/web_applications/commands/install_app_from_verified_manifest_command.cc
     chrome/browser/web_applications/commands/install_app_from_verified_manifest_command_browsertest.cc
    @@ -2385,6 +2385,7 @@ chrome/test/chromedriver/test/run_py_tests.py
     chrome/test/chromedriver/window_commands.cc
     chrome/test/chromedriver/window_commands_unittest.cc
     chrome/test/enterprise/e2e/connector/chrome_reporting_connector_test_case.py
    +chrome/test/enterprise/e2e/connector/client_certs/client_certs_test.py
     chrome/test/enterprise/e2e/connector/common/realtime_reporting_ui_test.py
     chrome/test/enterprise/e2e/connector/identity_connector/managed_profile_test.py
     chrome/test/enterprise/e2e/connector/local_content_analysis_connector/local_content_analysis_connector_test.py
    @@ -2490,6 +2491,7 @@ chromeos/ash/components/boca/babelorca/tachyon_constants.h
     chromeos/ash/components/boca/babelorca/tachyon_registrar.cc
     chromeos/ash/components/boca/babelorca/transcript_receiver.cc
     chromeos/ash/components/boca/babelorca/transcript_sender_impl.cc
    +chromeos/ash/components/boca/boca_app_client.cc
     chromeos/ash/components/boca/boca_metrics_manager_unittest.cc
     chromeos/ash/components/boca/boca_role_util_unittest.cc
     chromeos/ash/components/boca/boca_session_manager_unittest.cc
    @@ -2637,14 +2639,14 @@ components/autofill/content/renderer/html_based_username_detector_browsertest.cc
     components/autofill/content/renderer/page_passwords_analyser.cc
     components/autofill/content/renderer/password_form_conversion_utils.cc
     components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc
    -components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.js
    +components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.ts
     components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager.cc
     components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager_unittest.cc
     components/autofill/core/browser/crowdsourcing/determine_possible_field_types_unittest.cc
     components/autofill/core/browser/data_manager/addresses/address_data_cleaner_unittest.cc
     components/autofill/core/browser/data_manager/addresses/address_data_manager_unittest.cc
     components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
    -components/autofill/core/browser/data_model/autofill_profile_unittest.cc
    +components/autofill/core/browser/data_model/addresses/autofill_profile_unittest.cc
     components/autofill/core/browser/form_import/form_data_importer_unittest.cc
     components/autofill/core/browser/form_structure_unittest.cc
     components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
    @@ -2665,16 +2667,20 @@ components/autofill/core/browser/payments/credit_card_otp_authenticator_unittest
     components/autofill/core/browser/payments/credit_card_risk_based_authenticator_unittest.cc
     components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc
     components/autofill/core/browser/payments/full_card_request_unittest.cc
    +components/autofill/core/browser/payments/multiple_request_payments_network_interface_base.cc
     components/autofill/core/browser/payments/payments_network_interface_base.cc
     components/autofill/core/browser/payments/payments_network_interface_test_base.cc
     components/autofill/core/browser/payments/payments_network_interface_unittest.cc
     components/autofill/core/browser/payments/payments_requests/unmask_card_request_unittest.cc
     components/autofill/core/browser/payments/payments_service_url.cc
     components/autofill/core/browser/payments/payments_service_url_unittest.cc
    +components/autofill/core/browser/strike_databases/autofill_ai/autofill_ai_save_strike_database_by_host_unittest.cc
     components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc
     components/autofill/core/browser/test_utils/autofill_test_utils.cc
     components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
     components/autofill/core/browser/ui/autofill_image_fetcher.cc
    +components/autofill/core/browser/ui/payments/bnpl_tos_controller_impl.cc
    +components/autofill/core/browser/ui/payments/bnpl_tos_controller_impl_unittest.cc
     components/autofill/core/browser/webdata/addresses/autofill_profile_sync_bridge.h
     components/autofill/core/browser/webdata/payments/autofill_wallet_sync_bridge.h
     components/autofill/core/browser/webdata/payments/autofill_wallet_sync_bridge_unittest.cc
    @@ -2722,6 +2728,7 @@ components/certificate_transparency/tools/testdata/input.json
     components/cloud_devices/common/cloud_device_description.h
     components/cloud_devices/common/printer_description.h
     components/collaboration/internal/android/messaging/messaging_backend_service_bridge_unittest.cc
    +components/collaboration/internal/collaboration_service_impl_unittest.cc
     components/collaboration/internal/messaging/messaging_backend_service_impl_unittest.cc
     components/collaboration_strings.grdp
     components/commerce/core/account_checker.cc
    @@ -2817,6 +2824,7 @@ components/enterprise/data_controls/core/browser/conditions/attributes_condition
     components/enterprise/data_controls/core/browser/data_controls_policy_handler_unittest.cc
     components/enterprise/data_controls/core/browser/rule_unittest.cc
     components/enterprise/obfuscation/core/utils.h
    +components/enterprise/signin/enterprise_identity_service_unittest.cc
     components/error_page/common/localized_error.cc
     components/error_page_strings.grdp
     components/exo/keyboard_unittest.cc
    @@ -2854,8 +2862,6 @@ components/fingerprinting_protection_filter/browser/fingerprinting_protection_we
     components/fingerprinting_protection_filter/common/fingerprinting_protection_breakage_exception_unittest.cc
     components/fingerprinting_protection_filter/common/fingerprinting_protection_filter_constants.h
     components/fingerprinting_protection_filter/interventions/browser/interventions_web_contents_helper_unittest.cc
    -components/flags_ui/flags_test_helpers.cc
    -components/flags_ui/resources/app.html.ts
     components/gcm_driver/gcm_account_mapper.cc
     components/gcm_driver/gcm_account_mapper_unittest.cc
     components/gcm_driver/gcm_client_impl.cc
    @@ -2921,7 +2927,7 @@ components/invalidation/impl/fcm_invalidation_service_unittest.cc
     components/invalidation/impl/per_user_topic_subscription_manager.cc
     components/invalidation/impl/per_user_topic_subscription_manager_unittest.cc
     components/ip_protection/common/ip_protection_config_http.cc
    -components/ip_protection/common/ip_protection_issuer_token_direct_fetcher.cc
    +components/ip_protection/common/ip_protection_probabilistic_reveal_token_direct_fetcher.cc
     components/ip_protection/common/ip_protection_proxy_config_direct_fetcher.cc
     components/ip_protection/common/ip_protection_token_manager_impl.cc
     components/ip_protection/common/masked_domain_list_manager_unittest.cc
    @@ -2945,6 +2951,7 @@ components/manta/mahi_provider.cc
     components/manta/manta_service_callbacks.cc
     components/manta/orca_provider.cc
     components/manta/orca_provider_unittest.cc
    +components/manta/scanner_provider.cc
     components/manta/scanner_provider.h
     components/manta/scanner_provider_unittest.cc
     components/media_router/browser/android/flinging_controller_bridge.cc
    @@ -3001,6 +3008,7 @@ components/omnibox/browser/autocomplete_controller.h
     components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
     components/omnibox/browser/autocomplete_controller_unittest.cc
     components/omnibox/browser/autocomplete_input.cc
    +components/omnibox/browser/autocomplete_match.cc
     components/omnibox/browser/autocomplete_match.h
     components/omnibox/browser/autocomplete_match_test_util.cc
     components/omnibox/browser/autocomplete_match_type_unittest.cc
    @@ -3015,6 +3023,8 @@ components/omnibox/browser/document_provider.cc
     components/omnibox/browser/document_provider_unittest.cc
     components/omnibox/browser/document_suggestions_service.cc
     components/omnibox/browser/document_suggestions_service_unittest.cc
    +components/omnibox/browser/enterprise_search_aggregator_provider.cc
    +components/omnibox/browser/enterprise_search_aggregator_provider_unittest.cc
     components/omnibox/browser/enterprise_search_aggregator_suggestions_service.cc
     components/omnibox/browser/enterprise_search_aggregator_suggestions_service_unittest.cc
     components/omnibox/browser/fake_autocomplete_provider_client.cc
    @@ -3510,6 +3520,7 @@ components/safety_check/url_constants.cc
     components/saved_tab_groups/internal/android/tab_group_sync_service_android_unittest.cc
     components/saved_tab_groups/internal/saved_tab_group_sync_bridge_unittest.cc
     components/saved_tab_groups/internal/shared_tab_group_data_sync_bridge_unittest.cc
    +components/saved_tab_groups/internal/shared_tab_group_precondition_checker.cc
     components/saved_tab_groups/internal/tab_group_sync_bridge_mediator_unittest.cc
     components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc
     components/saved_tab_groups/public/saved_tab_group_unittest.cc
    @@ -3522,6 +3533,7 @@ components/search_engines/keyword_table_unittest.cc
     components/search_engines/template_url.cc
     components/search_engines/template_url_data_unittest.cc
     components/search_engines/template_url_prepopulate_data_unittest.cc
    +components/search_engines/template_url_service.cc
     components/search_engines/template_url_service_unittest.cc
     components/search_engines/template_url_starter_pack_data.cc
     components/search_engines/template_url_unittest.cc
    @@ -3612,6 +3624,7 @@ components/signin/public/base/consent_level.h
     components/signin/public/base/hybrid_encryption_key.cc
     components/signin/public/base/session_binding_utils.cc
     components/signin/public/base/session_binding_utils_unittest.cc
    +components/signin/public/browser/web_signin_tracker_unittest.cc
     components/signin/public/identity_manager/access_token_fetcher.cc
     components/signin/public/identity_manager/access_token_fetcher_unittest.cc
     components/signin/public/identity_manager/account_info.h
    @@ -3781,6 +3794,8 @@ components/webapps/browser/android/webapk/webapk_single_icon_hasher_unittest.cc
     components/webapps/browser/banners/app_banner_settings_helper_unittest.cc
     components/webapps/browser/installable/installable_evaluator_unittest.cc
     components/webauthn/core/browser/passkey_change_quota_tracker_unittest.cc
    +components/webui/flags/flags_test_helpers.cc
    +components/webui/flags/resources/app.html.ts
     components/webxr/android/arcore_install_helper.cc
     components/webxr/android/arcore_install_helper.h
     components/wifi/wifi_service_win.cc
    @@ -3811,6 +3826,10 @@ content/browser/browser_url_handler_impl.cc
     content/browser/browsing_data/browsing_data_filter_builder_impl.cc
     content/browser/browsing_data/browsing_data_filter_builder_impl_unittest.cc
     content/browser/browsing_topics/browsing_topics_url_loader_unittest.cc
    +content/browser/btm/btm_bounce_detector_browsertest.cc
    +content/browser/btm/btm_database_unittest.cc
    +content/browser/btm/btm_storage_unittest.cc
    +content/browser/btm/cookie_access_filter_unittest.cc
     content/browser/buckets/bucket_manager_host_unittest.cc
     content/browser/cache_storage/cache_storage_context_unittest.cc
     content/browser/child_process_sandbox_support_win_unittest.cc
    @@ -3825,10 +3844,6 @@ content/browser/devtools/devtools_protocol_encoding_cbor_fuzzer.cc
     content/browser/devtools/protocol/bluetooth_emulation_handler.h
     content/browser/devtools/protocol/devtools_protocol_browsertest.cc
     content/browser/devtools/shared_storage_worklet_devtools_agent_host_unittest.cc
    -content/browser/dips/cookie_access_filter_unittest.cc
    -content/browser/dips/dips_bounce_detector_browsertest.cc
    -content/browser/dips/dips_database_unittest.cc
    -content/browser/dips/dips_storage_unittest.cc
     content/browser/download/mhtml_generation_browsertest.cc
     content/browser/download/save_package.h
     content/browser/download/save_package_unittest.cc
    @@ -3860,7 +3875,6 @@ content/browser/interest_group/bidding_and_auction_server_key_fetcher.cc
     content/browser/interest_group/bidding_and_auction_server_key_fetcher.h
     content/browser/interest_group/interest_group_browsertest.cc
     content/browser/interest_group/interest_group_update_manager.cc
    -content/browser/interest_group/trusted_signals_fetcher.cc
     content/browser/loader/file_url_loader_factory_unittest.cc
     content/browser/loader/keep_alive_url_loader.cc
     content/browser/loader/keep_alive_url_loader.h
    @@ -3917,6 +3931,7 @@ content/browser/renderer_host/navigation_request_unittest.cc
     content/browser/renderer_host/navigator.cc
     content/browser/renderer_host/navigator_unittest.cc
     content/browser/renderer_host/policy_container_host_browsertest.cc
    +content/browser/renderer_host/randomized_confidence_utils.cc
     content/browser/renderer_host/recently_destroyed_hosts_unittest.cc
     content/browser/renderer_host/render_frame_host_impl.cc
     content/browser/renderer_host/render_frame_host_impl_browsertest.cc
    @@ -3940,6 +3955,7 @@ content/browser/sandbox_support_win_impl.cc
     content/browser/screen_orientation/screen_orientation_provider_unittest.cc
     content/browser/security/dip/document_isolation_policy_browsertest.cc
     content/browser/serial/serial_unittest.cc
    +content/browser/service_host/utility_sandbox_delegate_win.cc
     content/browser/service_worker/service_worker_hid_delegate_observer_unittest.cc
     content/browser/service_worker/service_worker_main_resource_loader.h
     content/browser/service_worker/service_worker_security_utils.cc
    @@ -3964,10 +3980,10 @@ content/browser/text_fragment_browsertest.cc
     content/browser/tpcd_heuristics/opener_heuristic_browsertest.cc
     content/browser/tpcd_heuristics/opener_heuristic_utils.cc
     content/browser/tpcd_heuristics/opener_heuristic_utils_unittest.cc
    +content/browser/tracing/tracing_end_to_end_browsertest.cc
     content/browser/url_info.h
     content/browser/url_loader_factory_params_helper.cc
     content/browser/usb/web_usb_service_impl_unittest.cc
    -content/browser/utility_sandbox_delegate_win.cc
     content/browser/web_contents/aura/gesture_nav_simple_unittest.cc
     content/browser/web_contents/web_contents_android.cc
     content/browser/web_contents/web_contents_impl.cc
    @@ -4468,11 +4484,14 @@ media/capture/video/win/video_capture_device_mf_win.cc
     media/capture/video/win/video_capture_device_utils_win.cc
     media/capture/video/win/video_capture_device_utils_win.h
     media/capture/video/win/video_capture_device_win.cc
    +media/cast/encoding/external_video_encoder.cc
     media/cast/encoding/media_video_encoder_wrapper_unittest.cc
    +media/cast/encoding/size_adaptable_video_encoder_base_unittest.cc
     media/cast/encoding/vpx_encoder.cc
     media/cast/encoding/vpx_quantizer_parser_unittest.cc
     media/cast/sender/openscreen_frame_sender.h
     media/cast/sender/video_bitrate_suggester.h
    +media/cast/sender/video_sender_unittest.cc
     media/cdm/win/media_foundation_cdm.cc
     media/cdm/win/test/media_foundation_clear_key_decryptor.cc
     media/cdm/win/test/mock_media_protection_pmp_server.h
    @@ -4517,6 +4536,7 @@ media/gpu/vaapi/vaapi_unittest.cc
     media/gpu/vaapi/vaapi_webp_decoder_unittest.cc
     media/gpu/windows/d3d11_video_decoder_wrapper.cc
     media/gpu/windows/d3d12_copy_command_list_wrapper.cc
    +media/gpu/windows/d3d12_video_encode_h264_delegate.cc
     media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
     media/gpu/windows/mf_audio_encoder.cc
     media/gpu/windows/mf_audio_encoder.h
    @@ -4785,7 +4805,6 @@ net/tools/print_certificates.py
     net/tools/quic/quic_simple_client_bin.cc
     net/tools/testserver/testserver.py.vpython3
     net/tools/transport_security_state_generator/transport_security_state_generator.cc
    -net/url_request/url_request_context_builder.cc
     net/url_request/url_request_context_builder_unittest.cc
     net/url_request/url_request_http_job_unittest.cc
     net/url_request/url_request_job_unittest.cc
    @@ -4849,8 +4868,11 @@ printing/metafile_skia.cc
     printing/print_settings_initializer_win.cc
     printing/printing_context.h
     printing/test/PRESUBMIT.py
    +remoting/base/compute_engine_service_client.cc
    +remoting/base/compute_engine_service_client.h
     remoting/base/corp_auth_util.cc
     remoting/base/crash/crashpad_linux.cc
    +remoting/base/instance_identity_token_getter_unittest.cc
     remoting/base/is_google_email.cc
     remoting/base/service_urls.cc
     remoting/base/session_policies.h
    @@ -4868,7 +4890,6 @@ remoting/host/it2me/it2me_confirmation_dialog_proxy_unittest.cc
     remoting/host/it2me/it2me_constants.cc
     remoting/host/it2me/it2me_host_unittest.cc
     remoting/host/it2me/it2me_native_messaging_host_unittest.cc
    -remoting/host/linux/input_injector_wayland.cc
     remoting/host/linux/input_injector_x11.cc
     remoting/host/linux/linux_me2me_host.py
     remoting/host/linux/remoting_user_session.cc
    @@ -4978,6 +4999,7 @@ services/network/public/cpp/cors/origin_access_entry_unittest.cc
     services/network/public/cpp/cors/origin_access_list_unittest.cc
     services/network/public/cpp/is_potentially_trustworthy.cc
     services/network/public/cpp/network_switches.cc
    +services/network/public/cpp/permissions_policy/origin_with_possible_wildcards_unittest.cc
     services/network/public/cpp/resource_request.h
     services/network/public/cpp/transferable_socket.cc
     services/network/public/mojom/permissions_policy/PRESUBMIT.py
    @@ -5132,6 +5154,7 @@ third_party/afl/src/qemu_mode/patches/afl-qemu-cpu-inl.h
     third_party/afl/src/test-instr.c
     third_party/afl/src/types.h
     third_party/android_build_tools/aapt2/3pp/fetch.py
    +third_party/android_deps/autorolled/fetch_all_autorolled.py
     third_party/android_deps/libs/com_android_support_support_annotations/3pp/fetch.py
     third_party/android_deps/libs/com_android_tools_common/3pp/fetch.py
     third_party/android_deps/libs/com_android_tools_layoutlib_layoutlib_api/3pp/fetch.py
    @@ -5164,6 +5187,7 @@ third_party/android_deps/libs/com_google_android_libraries_identity_googleid_goo
     third_party/android_deps/libs/com_google_android_material_material/3pp/fetch.py
     third_party/android_deps/libs/com_google_android_play_core_common/3pp/fetch.py
     third_party/android_deps/libs/com_google_android_play_feature_delivery/3pp/fetch.py
    +third_party/android_deps/libs/com_google_ar_impress/3pp/fetch.py
     third_party/android_deps/libs/com_google_firebase_firebase_annotations/3pp/fetch.py
     third_party/android_deps/libs/com_google_firebase_firebase_common/3pp/fetch.py
     third_party/android_deps/libs/com_google_firebase_firebase_components/3pp/fetch.py
    @@ -5380,7 +5404,6 @@ third_party/blink/common/origin_trials/trial_token.cc
     third_party/blink/common/origin_trials/trial_token_validator_unittest.cc
     third_party/blink/common/page_state/page_state_serialization.cc
     third_party/blink/common/page_state/page_state_serialization_unittest.cc
    -third_party/blink/common/permissions_policy/origin_with_possible_wildcards_unittest.cc
     third_party/blink/common/shared_storage/module_script_downloader.cc
     third_party/blink/common/webid/federated_auth_request_mojom_traits_unittest.cc
     third_party/blink/public/common/custom_handlers/protocol_handler_utils.h
    @@ -5408,20 +5431,18 @@ third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
     third_party/blink/renderer/bindings/core/v8/window_proxy.cc
     third_party/blink/renderer/bindings/core/v8/window_proxy.h
     third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
    -third_party/blink/renderer/build/scripts/update_css_ranking.py
     third_party/blink/renderer/controller/memory_usage_monitor_posix.cc
     third_party/blink/renderer/core/BUILD.gn
    -third_party/blink/renderer/core/animation/pending_animations.cc
     third_party/blink/renderer/core/animation/sampled_effect.cc
     third_party/blink/renderer/core/clipboard/data_transfer.h
     third_party/blink/renderer/core/css/css_computed_style_declaration.cc
     third_party/blink/renderer/core/css/css_font_face_rule.idl
     third_party/blink/renderer/core/css/css_global_rule_set.h
    +third_party/blink/renderer/core/css/css_identifier_value_mappings.h
     third_party/blink/renderer/core/css/css_import_rule.idl
     third_party/blink/renderer/core/css/css_math_expression_node_test.cc
     third_party/blink/renderer/core/css/css_media_rule.idl
     third_party/blink/renderer/core/css/css_page_rule.idl
    -third_party/blink/renderer/core/css/css_primitive_value_mappings.h
     third_party/blink/renderer/core/css/css_property_value_set.cc
     third_party/blink/renderer/core/css/css_rule.idl
     third_party/blink/renderer/core/css/css_selector.cc
    @@ -5502,6 +5523,7 @@ third_party/blink/renderer/core/editing/serializers/serialization.cc
     third_party/blink/renderer/core/editing/spellcheck/cold_mode_spell_check_requester.h
     third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h
     third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.h
    +third_party/blink/renderer/core/editing/spellcheck/spell_checker_test.cc
     third_party/blink/renderer/core/editing/visible_units_line_test.cc
     third_party/blink/renderer/core/events/before_unload_event.cc
     third_party/blink/renderer/core/events/before_unload_event.h
    @@ -5530,7 +5552,6 @@ third_party/blink/renderer/core/execution_context/navigator_base.cc
     third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
     third_party/blink/renderer/core/exported/web_searchable_form_data.cc
     third_party/blink/renderer/core/fetch/fetch_manager.cc
    -third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
     third_party/blink/renderer/core/fetch/response.h
     third_party/blink/renderer/core/fileapi/file_reader_client.h
     third_party/blink/renderer/core/fragment_directive/text_fragment_anchor_metrics_test.cc
    @@ -5620,8 +5641,10 @@ third_party/blink/renderer/core/layout/geometry/logical_size.h
     third_party/blink/renderer/core/layout/geometry/physical_offset.h
     third_party/blink/renderer/core/layout/geometry/physical_rect.h
     third_party/blink/renderer/core/layout/geometry/physical_size.h
    +third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.h
     third_party/blink/renderer/core/layout/hit_test_cache.h
     third_party/blink/renderer/core/layout/hit_test_location.h
    +third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
     third_party/blink/renderer/core/layout/inline/inline_items_builder.h
     third_party/blink/renderer/core/layout/inline/offset_mapping.h
     third_party/blink/renderer/core/layout/inline/offset_mapping_builder.h
    @@ -5768,6 +5791,7 @@ third_party/blink/renderer/modules/accessibility/ax_node_object.cc
     third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
     third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
     third_party/blink/renderer/modules/accessibility/testing/internals_accessibility.h
    +third_party/blink/renderer/modules/ai/on_device_translation/ai_translator_factory.cc
     third_party/blink/renderer/modules/bluetooth/bluetooth_error.cc
     third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
     third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
    @@ -5791,7 +5815,6 @@ third_party/blink/renderer/modules/mediarecorder/vpx_encoder.cc
     third_party/blink/renderer/modules/mediastream/media_devices_test.cc
     third_party/blink/renderer/modules/mediastream/media_stream_utils.cc
     third_party/blink/renderer/modules/mediastream/media_stream_video_renderer_sink.h
    -third_party/blink/renderer/modules/on_device_translation/translation.cc
     third_party/blink/renderer/modules/payments/android_pay_method_data.idl
     third_party/blink/renderer/modules/payments/on_payment_response_test.cc
     third_party/blink/renderer/modules/payments/payment_request.cc
    @@ -5801,6 +5824,7 @@ third_party/blink/renderer/modules/payments/payment_response_test.cc
     third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc
     third_party/blink/renderer/modules/presentation/presentation_connection_available_event.h
     third_party/blink/renderer/modules/service_worker/service_worker_content_settings_proxy.cc
    +third_party/blink/renderer/modules/service_worker/service_worker_event_queue.cc
     third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
     third_party/blink/renderer/modules/smart_card/smart_card_error.cc
     third_party/blink/renderer/modules/storage/cached_storage_area.h
    @@ -6066,6 +6090,7 @@ third_party/catapult/catapult_build/run_dev_server_tests.py
     third_party/catapult/common/bin/update_chrome_reference_binaries.py
     third_party/catapult/common/lab/commits.py
     third_party/catapult/common/lab/hardware.py
    +third_party/catapult/common/node_runner/node_runner/package-lock.json
     third_party/catapult/common/py_trace_event/py_trace_event/trace_event_impl/perfetto_proto_classes.py
     third_party/catapult/common/py_utils/py_utils/cloud_storage.py
     third_party/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
    @@ -7244,11 +7269,13 @@ third_party/crossbench/crossbench/cli/exception_formatter.py
     third_party/crossbench/crossbench/plt/android_adb.py
     third_party/crossbench/crossbench/probes/chrome_histograms.py
     third_party/crossbench/crossbench/probes/perfetto/downloader.py
    +third_party/crossbench/crossbench/probes/perfetto/trace_processor/queries/loadline/breakdown.sql
     third_party/crossbench/crossbench/probes/performance_entries.py
     third_party/crossbench/crossbench/probes/profiling/browser_profiling.py
     third_party/crossbench/crossbench/probes/profiling/context/base.py
     third_party/crossbench/crossbench/probes/profiling/enum.py
     third_party/crossbench/crossbench/probes/web_page_replay/recorder.py
    +third_party/crossbench/tests/crossbench/benchmarks/loading/action_runner/test_default_bond_action_runner.py
     third_party/crossbench/tests/crossbench/benchmarks/loading/config/test_example_configs.py
     third_party/crossbench/tests/crossbench/benchmarks/loading/config/test_login.py
     third_party/crossbench/tests/crossbench/benchmarks/loading/config/test_pages.py
    @@ -7271,7 +7298,6 @@ third_party/dawn/src/dawn/dawn.json
     third_party/dawn/src/dawn/native/CommandAllocator.cpp
     third_party/dawn/src/dawn/native/CopyTextureForBrowserHelper.cpp
     third_party/dawn/src/dawn/native/Features.cpp
    -third_party/dawn/src/dawn/native/PipelineLayout.cpp
     third_party/dawn/src/dawn/native/Sampler.h
     third_party/dawn/src/dawn/native/SubresourceStorage.h
     third_party/dawn/src/dawn/native/d3d/D3DError.cpp
    @@ -7549,6 +7575,7 @@ third_party/depot_tools/infra_lib/telemetry/__init__.py
     third_party/depot_tools/infra_lib/telemetry/clearcut_span_exporter.py
     third_party/depot_tools/infra_lib/telemetry/proto/clientanalytics_pb2.py
     third_party/depot_tools/infra_lib/telemetry/proto/trace_span_pb2.py
    +third_party/depot_tools/man/README.txt
     third_party/depot_tools/man/html/depot_tools.html
     third_party/depot_tools/man/html/depot_tools_tutorial.html
     third_party/depot_tools/man/html/git-cl.html
    @@ -7698,6 +7725,7 @@ third_party/depot_tools/tests/git_cache_test.py
     third_party/depot_tools/tests/git_cl_creds_check_report.txt
     third_party/depot_tools/tests/git_cl_test.py
     third_party/depot_tools/tests/metrics_test.py
    +third_party/depot_tools/tests/presubmit_canned_checks_test.py
     third_party/depot_tools/tests/presubmit_support_test.py
     third_party/depot_tools/tests/presubmit_unittest.py
     third_party/depot_tools/tests/reclient_helper_test.py
    @@ -7714,6 +7742,7 @@ third_party/devtools-frontend/src/extensions/cxx_debugging/third_party/lit-html/
     third_party/devtools-frontend/src/front_end/core/common/ColorConverter.ts
     third_party/devtools-frontend/src/front_end/core/host/AidaClient.test.ts
     third_party/devtools-frontend/src/front_end/core/host/InspectorFrontendHost.ts
    +third_party/devtools-frontend/src/front_end/core/host/UserMetrics.ts
     third_party/devtools-frontend/src/front_end/core/i18n/i18n.test.ts
     third_party/devtools-frontend/src/front_end/core/i18n/locales/af.json
     third_party/devtools-frontend/src/front_end/core/i18n/locales/am.json
    @@ -7800,6 +7829,7 @@ third_party/devtools-frontend/src/front_end/core/platform/StringUtilities.test.t
     third_party/devtools-frontend/src/front_end/core/platform/StringUtilities.ts
     third_party/devtools-frontend/src/front_end/core/platform/UIString.ts
     third_party/devtools-frontend/src/front_end/core/root/Runtime.test.ts
    +third_party/devtools-frontend/src/front_end/core/root/Runtime.ts
     third_party/devtools-frontend/src/front_end/core/sdk/CSSMetadata.ts
     third_party/devtools-frontend/src/front_end/core/sdk/Cookie.ts
     third_party/devtools-frontend/src/front_end/core/sdk/NetworkManager.ts
    @@ -7836,7 +7866,11 @@ third_party/devtools-frontend/src/front_end/models/live-metrics/LiveMetrics.ts
     third_party/devtools-frontend/src/front_end/models/persistence/NetworkPersistenceManager.test.ts
     third_party/devtools-frontend/src/front_end/models/persistence/NetworkPersistenceManager.ts
     third_party/devtools-frontend/src/front_end/models/persistence/PersistenceImpl.test.ts
    +third_party/devtools-frontend/src/front_end/models/persistence/PlatformFileSystem.ts
    +third_party/devtools-frontend/src/front_end/models/project_settings/ProjectSettingsModel.ts
     third_party/devtools-frontend/src/front_end/models/trace/ModelImpl.test.ts
    +third_party/devtools-frontend/src/front_end/models/trace/extras/ThirdParties.test.ts
    +third_party/devtools-frontend/src/front_end/models/trace/extras/TraceTree.ts
     third_party/devtools-frontend/src/front_end/models/trace/handlers/MetaHandler.test.ts
     third_party/devtools-frontend/src/front_end/models/trace/handlers/MetaHandler.ts
     third_party/devtools-frontend/src/front_end/models/trace/handlers/NetworkRequestsHandler.test.ts
    @@ -7853,7 +7887,7 @@ third_party/devtools-frontend/src/front_end/models/trace/insights/FontDisplay.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/ForcedReflow.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/ImageDelivery.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/InteractionToNextPaint.ts
    -third_party/devtools-frontend/src/front_end/models/trace/insights/LongCriticalNetworkTree.ts
    +third_party/devtools-frontend/src/front_end/models/trace/insights/NetworkDependencyTree.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/RenderBlocking.test.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/SlowCSSSelector.ts
     third_party/devtools-frontend/src/front_end/models/trace/insights/ThirdParties.test.ts
    @@ -7870,12 +7904,15 @@ third_party/devtools-frontend/src/front_end/models/trace/types/TraceEvents.ts
     third_party/devtools-frontend/src/front_end/panels/accessibility/AccessibilityNodeView.ts
     third_party/devtools-frontend/src/front_end/panels/accessibility/AccessibilityStrings.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/AiAssistancePanel.ts
    +third_party/devtools-frontend/src/front_end/panels/ai_assistance/ExtensionScope.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/agents/FileAgent.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/agents/PerformanceAgent.test.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/agents/PerformanceAgent.ts
    -third_party/devtools-frontend/src/front_end/panels/ai_assistance/components/ChatView.ts
    +third_party/devtools-frontend/src/front_end/panels/ai_assistance/agents/PerformanceInsightsAgent.test.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/components/UserActionRow.ts
     third_party/devtools-frontend/src/front_end/panels/ai_assistance/components/chatView.css
    +third_party/devtools-frontend/src/front_end/panels/ai_assistance/data_formatters/PerformanceInsightFormatter.test.ts
    +third_party/devtools-frontend/src/front_end/panels/animation/AnimationTimeline.ts
     third_party/devtools-frontend/src/front_end/panels/application/AppManifestView.ts
     third_party/devtools-frontend/src/front_end/panels/application/ApplicationPanelSidebar.ts
     third_party/devtools-frontend/src/front_end/panels/application/BackgroundServiceView.ts
    @@ -7888,9 +7925,13 @@ third_party/devtools-frontend/src/front_end/panels/application/StorageView.ts
     third_party/devtools-frontend/src/front_end/panels/application/components/EndpointsGrid.ts
     third_party/devtools-frontend/src/front_end/panels/application/components/FrameDetailsView.ts
     third_party/devtools-frontend/src/front_end/panels/application/components/ReportsGrid.ts
    +third_party/devtools-frontend/src/front_end/panels/application/components/SharedStorageAccessGrid.ts
     third_party/devtools-frontend/src/front_end/panels/application/components/TrustTokensView.ts
    +third_party/devtools-frontend/src/front_end/panels/application/preloading/PreloadingView.ts
    +third_party/devtools-frontend/src/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts
     third_party/devtools-frontend/src/front_end/panels/application/preloading/components/PreloadingDisabledInfobar.ts
     third_party/devtools-frontend/src/front_end/panels/application/preloading/components/PreloadingString.ts
    +third_party/devtools-frontend/src/front_end/panels/application/preloading/components/RuleSetDetailsView.ts
     third_party/devtools-frontend/src/front_end/panels/application/preloading/components/UsedPreloadingView.test.ts
     third_party/devtools-frontend/src/front_end/panels/application/preloading/components/UsedPreloadingView.ts
     third_party/devtools-frontend/src/front_end/panels/autofill/AutofillView.ts
    @@ -7898,6 +7939,7 @@ third_party/devtools-frontend/src/front_end/panels/browser_debugger/DOMBreakpoin
     third_party/devtools-frontend/src/front_end/panels/changes/ChangesView.ts
     third_party/devtools-frontend/src/front_end/panels/console/ConsoleViewMessage.ts
     third_party/devtools-frontend/src/front_end/panels/console/ErrorStackParser.test.ts
    +third_party/devtools-frontend/src/front_end/panels/coverage/CoverageView.ts
     third_party/devtools-frontend/src/front_end/panels/css_overview/components/CSSOverviewStartView.ts
     third_party/devtools-frontend/src/front_end/panels/elements/ElementStatePaneWidget.test.ts
     third_party/devtools-frontend/src/front_end/panels/elements/ElementStatePaneWidget.ts
    @@ -7909,8 +7951,12 @@ third_party/devtools-frontend/src/front_end/panels/issues/AffectedCookiesView.ts
     third_party/devtools-frontend/src/front_end/panels/issues/AffectedDirectivesView.ts
     third_party/devtools-frontend/src/front_end/panels/issues/AffectedMetadataAllowedSitesView.ts
     third_party/devtools-frontend/src/front_end/panels/issues/AffectedTrackingSitesView.ts
    +third_party/devtools-frontend/src/front_end/panels/issues/IssuesPane.ts
     third_party/devtools-frontend/src/front_end/panels/lighthouse/LighthouseController.ts
    +third_party/devtools-frontend/src/front_end/panels/linear_memory_inspector/LinearMemoryInspectorPane.ts
    +third_party/devtools-frontend/src/front_end/panels/media/MainView.ts
     third_party/devtools-frontend/src/front_end/panels/mobile_throttling/CalibrationController.ts
    +third_party/devtools-frontend/src/front_end/panels/network/BlockedURLsPane.ts
     third_party/devtools-frontend/src/front_end/panels/network/NetworkConfigView.ts
     third_party/devtools-frontend/src/front_end/panels/network/NetworkDataGridNode.ts
     third_party/devtools-frontend/src/front_end/panels/network/NetworkLogView.ts
    @@ -7956,7 +8002,9 @@ third_party/devtools-frontend/src/front_end/panels/timeline/utils/AICallTree.tes
     third_party/devtools-frontend/src/front_end/panels/timeline/utils/EntityMapper.test.ts
     third_party/devtools-frontend/src/front_end/panels/timeline/utils/EntryStyles.ts
     third_party/devtools-frontend/src/front_end/panels/timeline/utils/Helpers.test.ts
    +third_party/devtools-frontend/src/front_end/panels/timeline/utils/Helpers.ts
     third_party/devtools-frontend/src/front_end/panels/timeline/utils/IgnoreList.test.ts
    +third_party/devtools-frontend/src/front_end/panels/web_audio/WebAudioView.ts
     third_party/devtools-frontend/src/front_end/panels/webauthn/WebauthnPane.ts
     third_party/devtools-frontend/src/front_end/panels/whats_new/ReleaseNote.test.ts
     third_party/devtools-frontend/src/front_end/panels/whats_new/ReleaseNoteText.ts
    @@ -8244,9 +8292,6 @@ third_party/devtools-frontend/src/node_modules/@sinonjs/commons/lib/prototypes/t
     third_party/devtools-frontend/src/node_modules/@sinonjs/commons/types/prototypes/throws-on-proto.d.ts
     third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/lib/create-set.js
     third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/lib/deep-equal.js
    -third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons/lib/class-name.js
    -third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons/lib/prototypes/throws-on-proto.js
    -third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons/types/prototypes/throws-on-proto.d.ts
     third_party/devtools-frontend/src/node_modules/@sinonjs/samsam/types/create-set.d.ts
     third_party/devtools-frontend/src/node_modules/@sinonjs/text-encoding/package.json
     third_party/devtools-frontend/src/node_modules/@tootallnate/quickjs-emscripten/dist/context.d.ts
    @@ -8354,16 +8399,25 @@ third_party/devtools-frontend/src/node_modules/cookie/package.json
     third_party/devtools-frontend/src/node_modules/cors/package.json
     third_party/devtools-frontend/src/node_modules/cosmiconfig/package.json
     third_party/devtools-frontend/src/node_modules/css-functions-list/package.json
    -third_party/devtools-frontend/src/node_modules/css-select/node_modules/domutils/lib/stringify.d.ts
    -third_party/devtools-frontend/src/node_modules/css-select/node_modules/domutils/lib/stringify.js
     third_party/devtools-frontend/src/node_modules/css-tree/data/patch.json
    -third_party/devtools-frontend/src/node_modules/css-tree/dist/csstree.js
     third_party/devtools-frontend/src/node_modules/css-tree/lib/lexer/generic.js
     third_party/devtools-frontend/src/node_modules/css-tree/package.json
     third_party/devtools-frontend/src/node_modules/cssnano-preset-default/package.json
     third_party/devtools-frontend/src/node_modules/cssnano/package.json
    -third_party/devtools-frontend/src/node_modules/csso/dist/csso.js
     third_party/devtools-frontend/src/node_modules/csso/lib/restructure/6-restructBlock.js
    +third_party/devtools-frontend/src/node_modules/csso/lib/restructure/prepare/specificity.js
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/css-tree/data/patch.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/css-tree/lib/lexer/generic.js
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/css-tree/package.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/at-rules.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/at-rules.schema.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/properties.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/properties.schema.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/selectors.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/selectors.schema.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/types.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/css/types.schema.json
    +third_party/devtools-frontend/src/node_modules/csso/node_modules/mdn-data/package.json
     third_party/devtools-frontend/src/node_modules/csso/package.json
     third_party/devtools-frontend/src/node_modules/custom-event/index.js
     third_party/devtools-frontend/src/node_modules/data-view-buffer/package.json
    @@ -8389,6 +8443,10 @@ third_party/devtools-frontend/src/node_modules/diff/lib/convert/dmp.js
     third_party/devtools-frontend/src/node_modules/diff/lib/index.es6.js
     third_party/devtools-frontend/src/node_modules/diff/package.json
     third_party/devtools-frontend/src/node_modules/dir-glob/package.json
    +third_party/devtools-frontend/src/node_modules/domutils/lib/esm/stringify.d.ts
    +third_party/devtools-frontend/src/node_modules/domutils/lib/esm/stringify.js
    +third_party/devtools-frontend/src/node_modules/domutils/lib/stringify.d.ts
    +third_party/devtools-frontend/src/node_modules/domutils/lib/stringify.js
     third_party/devtools-frontend/src/node_modules/end-of-stream/package.json
     third_party/devtools-frontend/src/node_modules/engine.io-parser/build/cjs/index.js
     third_party/devtools-frontend/src/node_modules/engine.io-parser/build/esm/index.js
    @@ -8595,9 +8653,6 @@ third_party/devtools-frontend/src/node_modules/nise/nise.js
     third_party/devtools-frontend/src/node_modules/node-releases/package.json
     third_party/devtools-frontend/src/node_modules/normalize-package-data/package.json
     third_party/devtools-frontend/src/node_modules/normalize-path/index.js
    -third_party/devtools-frontend/src/node_modules/normalize-url/index.d.ts
    -third_party/devtools-frontend/src/node_modules/normalize-url/index.js
    -third_party/devtools-frontend/src/node_modules/normalize-url/package.json
     third_party/devtools-frontend/src/node_modules/object-assign/index.js
     third_party/devtools-frontend/src/node_modules/object-assign/package.json
     third_party/devtools-frontend/src/node_modules/object-keys/package.json
    @@ -8622,13 +8677,16 @@ third_party/devtools-frontend/src/node_modules/possible-typed-array-names/packag
     third_party/devtools-frontend/src/node_modules/postcss-colormin/package.json
     third_party/devtools-frontend/src/node_modules/postcss-colormin/src/index.js
     third_party/devtools-frontend/src/node_modules/postcss-convert-values/package.json
    +third_party/devtools-frontend/src/node_modules/postcss-discard-comments/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/postcss-discard-comments/package.json
     third_party/devtools-frontend/src/node_modules/postcss-discard-duplicates/package.json
     third_party/devtools-frontend/src/node_modules/postcss-discard-empty/package.json
     third_party/devtools-frontend/src/node_modules/postcss-discard-overridden/package.json
     third_party/devtools-frontend/src/node_modules/postcss-merge-longhand/package.json
    +third_party/devtools-frontend/src/node_modules/postcss-merge-rules/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/postcss-merge-rules/package.json
     third_party/devtools-frontend/src/node_modules/postcss-minify-gradients/package.json
    +third_party/devtools-frontend/src/node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/postcss-minify-selectors/package.json
     third_party/devtools-frontend/src/node_modules/postcss-normalize-display-values/package.json
     third_party/devtools-frontend/src/node_modules/postcss-normalize-positions/package.json
    @@ -8637,6 +8695,7 @@ third_party/devtools-frontend/src/node_modules/postcss-normalize-string/package.
     third_party/devtools-frontend/src/node_modules/postcss-normalize-timing-functions/package.json
     third_party/devtools-frontend/src/node_modules/postcss-normalize-unicode/package.json
     third_party/devtools-frontend/src/node_modules/postcss-normalize-url/package.json
    +third_party/devtools-frontend/src/node_modules/postcss-normalize-url/src/normalize.js
     third_party/devtools-frontend/src/node_modules/postcss-normalize-whitespace/package.json
     third_party/devtools-frontend/src/node_modules/postcss-ordered-values/package.json
     third_party/devtools-frontend/src/node_modules/postcss-reduce-initial/package.json
    @@ -8644,6 +8703,7 @@ third_party/devtools-frontend/src/node_modules/postcss-reduce-transforms/package
     third_party/devtools-frontend/src/node_modules/postcss-resolve-nested-selector/package.json
     third_party/devtools-frontend/src/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/postcss-svgo/package.json
    +third_party/devtools-frontend/src/node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/postcss-unique-selectors/package.json
     third_party/devtools-frontend/src/node_modules/postcss/lib/comment.d.ts
     third_party/devtools-frontend/src/node_modules/postcss/lib/declaration.d.ts
    @@ -8772,6 +8832,7 @@ third_party/devtools-frontend/src/node_modules/sinon/lib/sinon/proxy-invoke.js
     third_party/devtools-frontend/src/node_modules/sinon/lib/sinon/util/core/extend.js
     third_party/devtools-frontend/src/node_modules/sinon/lib/sinon/util/core/get-property-descriptor.js
     third_party/devtools-frontend/src/node_modules/sinon/node_modules/diff/dist/diff.js
    +third_party/devtools-frontend/src/node_modules/sinon/node_modules/diff/dist/diff.min.js
     third_party/devtools-frontend/src/node_modules/sinon/node_modules/diff/lib/convert/dmp.js
     third_party/devtools-frontend/src/node_modules/sinon/node_modules/diff/lib/index.es6.js
     third_party/devtools-frontend/src/node_modules/sinon/node_modules/diff/package.json
    @@ -8813,6 +8874,7 @@ third_party/devtools-frontend/src/node_modules/string.prototype.trimstart/packag
     third_party/devtools-frontend/src/node_modules/strip-ansi/package.json
     third_party/devtools-frontend/src/node_modules/strip-bom/package.json
     third_party/devtools-frontend/src/node_modules/strip-json-comments/package.json
    +third_party/devtools-frontend/src/node_modules/stylehacks/node_modules/postcss-selector-parser/package.json
     third_party/devtools-frontend/src/node_modules/stylehacks/package.json
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/@csstools/selector-specificity/package.json
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/css-tree/data/patch.json
    @@ -8829,9 +8891,6 @@ third_party/devtools-frontend/src/node_modules/stylelint/node_modules/mdn-data/c
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/mdn-data/css/types.json
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/mdn-data/css/types.schema.json
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/mdn-data/package.json
    -third_party/devtools-frontend/src/node_modules/stylelint/node_modules/postcss-selector-parser/package.json
    -third_party/devtools-frontend/src/node_modules/stylelint/node_modules/postcss/lib/comment.d.ts
    -third_party/devtools-frontend/src/node_modules/stylelint/node_modules/postcss/lib/declaration.d.ts
     third_party/devtools-frontend/src/node_modules/stylelint/node_modules/resolve-from/package.json
     third_party/devtools-frontend/src/node_modules/supports-color/package.json
     third_party/devtools-frontend/src/node_modules/supports-hyperlinks/node_modules/has-flag/package.json
    @@ -8840,6 +8899,7 @@ third_party/devtools-frontend/src/node_modules/supports-preserve-symlinks-flag/p
     third_party/devtools-frontend/src/node_modules/svg-tags/package.json
     third_party/devtools-frontend/src/node_modules/svgo/dist/svgo.browser.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/_collections.js
    +third_party/devtools-frontend/src/node_modules/svgo/plugins/convertOneStopGradients.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/inlineStyles.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/mergeStyles.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/minifyStyles.js
    @@ -8847,6 +8907,8 @@ third_party/devtools-frontend/src/node_modules/svgo/plugins/prefixIds.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/removeAttributesBySelector.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/removeDesc.js
     third_party/devtools-frontend/src/node_modules/svgo/plugins/removeTitle.js
    +third_party/devtools-frontend/src/node_modules/svgo/plugins/removeXlink.js
    +third_party/devtools-frontend/src/node_modules/svgo/plugins/reusePaths.js
     third_party/devtools-frontend/src/node_modules/table/node_modules/ansi-regex/package.json
     third_party/devtools-frontend/src/node_modules/table/node_modules/strip-ansi/package.json
     third_party/devtools-frontend/src/node_modules/tar-stream/package.json
    @@ -8982,7 +9044,6 @@ third_party/devtools-frontend/src/node_modules/wrap-ansi/node_modules/strip-ansi
     third_party/devtools-frontend/src/node_modules/wrap-ansi/package.json
     third_party/devtools-frontend/src/node_modules/ws/package.json
     third_party/devtools-frontend/src/node_modules/y18n/package.json
    -third_party/devtools-frontend/src/node_modules/yaml/package.json
     third_party/devtools-frontend/src/node_modules/yargs-unparser/node_modules/decamelize/package.json
     third_party/devtools-frontend/src/node_modules/yauzl/package.json
     third_party/devtools-frontend/src/node_modules/yocto-queue/index.d.ts
    @@ -9119,7 +9180,6 @@ third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/Complex.h
     third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/MathFunctions.h
     third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/MatrixProduct.h
     third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/MatrixProductMMA.h
    -third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/MatrixVectorProduct.h
     third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/PacketMath.h
     third_party/eigen3/src/Eigen/src/Core/arch/AltiVec/TypeCasting.h
     third_party/eigen3/src/Eigen/src/Core/arch/Default/ConjHelper.h
    @@ -9431,6 +9491,7 @@ third_party/eigen3/src/test/geo_transformations.cpp
     third_party/eigen3/src/test/half_float.cpp
     third_party/eigen3/src/test/hessenberg.cpp
     third_party/eigen3/src/test/householder.cpp
    +third_party/eigen3/src/test/incomplete_LUT.cpp
     third_party/eigen3/src/test/incomplete_cholesky.cpp
     third_party/eigen3/src/test/indexed_view.cpp
     third_party/eigen3/src/test/initializer_list_construction.cpp
    @@ -10577,112 +10638,44 @@ third_party/googletest/src/googletest/src/gtest.cc
     third_party/googletest/src/googletest/test/googletest-death-test-test.cc
     third_party/googletest/src/googletest/test/gtest_list_output_unittest_.cc
     third_party/googletest/src/googletest/test/gtest_skip_test.cc
    -third_party/grpc/src/CMakeLists.txt
    -third_party/grpc/src/include/grpc/grpc.h
    -third_party/grpc/src/include/grpc/grpc_security_constants.h
    -third_party/grpc/src/include/grpc/support/port_platform.h
    -third_party/grpc/src/include/grpcpp/create_channel_binder.h
    -third_party/grpc/src/include/grpcpp/security/binder_security_policy.h
    -third_party/grpc/src/include/grpcpp/support/proto_buffer_reader.h
    -third_party/grpc/src/include/grpcpp/support/proto_buffer_writer.h
    -third_party/grpc/src/package.xml
    -third_party/grpc/src/setup.py
    -third_party/grpc/src/src/boringssl/gen_build_yaml.py
    -third_party/grpc/src/src/core/ext/filters/client_channel/resolver/google_c2p/google_c2p_resolver.cc
    -third_party/grpc/src/src/core/ext/transport/binder/security_policy/security_policy.h
    -third_party/grpc/src/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
    -third_party/grpc/src/src/core/ext/xds/xds_api.cc
    -third_party/grpc/src/src/core/ext/xds/xds_cluster.cc
    -third_party/grpc/src/src/core/lib/event_engine/windows/iocp.cc
    -third_party/grpc/src/src/core/lib/gpr/string.h
    -third_party/grpc/src/src/core/lib/gprpp/status_helper.cc
    -third_party/grpc/src/src/core/lib/gprpp/time.h
    -third_party/grpc/src/src/core/lib/iomgr/socket_windows.cc
    -third_party/grpc/src/src/core/lib/json/json_object_loader.cc
    -third_party/grpc/src/src/core/lib/json/json_util.h
    -third_party/grpc/src/src/core/lib/security/authorization/cel_authorization_engine.h
    -third_party/grpc/src/src/core/lib/security/credentials/credentials.h
    -third_party/grpc/src/src/core/lib/security/credentials/external/external_account_credentials.cc
    -third_party/grpc/src/src/core/lib/security/credentials/google_default/google_default_credentials.cc
    -third_party/grpc/src/src/core/lib/security/credentials/jwt/json_token.h
    -third_party/grpc/src/src/core/lib/security/credentials/jwt/jwt_verifier.h
    -third_party/grpc/src/src/core/lib/security/security_connector/ssl_utils_config.cc
    -third_party/grpc/src/src/core/lib/service_config/service_config.h
    -third_party/grpc/src/src/core/lib/service_config/service_config_impl.h
    -third_party/grpc/src/src/cpp/ext/gcp/observability.cc
    -third_party/grpc/src/src/cpp/ext/gcp/observability_logging_sink.cc
    -third_party/grpc/src/src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml
    -third_party/grpc/src/src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml
    -third_party/grpc/src/src/objective-c/tests/CppCronetTests/CppCronetEnd2EndTests.mm
    -third_party/grpc/src/src/python/grpcio/grpc/__init__.py
    -third_party/grpc/src/src/python/grpcio_admin/setup.py
    -third_party/grpc/src/src/python/grpcio_channelz/setup.py
    -third_party/grpc/src/src/python/grpcio_csds/setup.py
    -third_party/grpc/src/src/python/grpcio_health_checking/setup.py
    -third_party/grpc/src/src/python/grpcio_reflection/setup.py
    -third_party/grpc/src/src/python/grpcio_status/setup.py
    -third_party/grpc/src/src/python/grpcio_testing/setup.py
    -third_party/grpc/src/src/python/grpcio_tests/tests/unit/_api_test.py
    -third_party/grpc/src/src/python/grpcio_tests/tests/unit/_auth_context_test.py
    -third_party/grpc/src/src/python/grpcio_tests/tests_aio/unit/auth_context_test.py
    -third_party/grpc/src/test/core/client_channel/http_proxy_mapper_test.cc
    -third_party/grpc/src/test/core/client_channel/resolvers/binder_resolver_test.cc
    -third_party/grpc/src/test/core/client_channel/resolvers/dns_resolver_test.cc
    -third_party/grpc/src/test/core/client_channel/resolvers/sockaddr_resolver_test.cc
    -third_party/grpc/src/test/core/security/credentials_test.cc
    -third_party/grpc/src/test/core/security/evaluate_args_test.cc
    -third_party/grpc/src/test/core/security/grpc_alts_credentials_options_test.cc
    -third_party/grpc/src/test/core/security/json_token_test.cc
    -third_party/grpc/src/test/core/security/jwt_verifier_test.cc
    -third_party/grpc/src/test/core/security/print_google_default_creds_token.cc
    -third_party/grpc/src/test/core/tsi/alts/handshaker/alts_handshaker_client_test.cc
    -third_party/grpc/src/test/core/tsi/alts/handshaker/alts_tsi_handshaker_test.cc
    -third_party/grpc/src/test/core/tsi/ssl_transport_security_test.cc
    -third_party/grpc/src/test/core/tsi/transport_security_test.cc
    -third_party/grpc/src/test/core/uri/uri_parser_test.cc
    -third_party/grpc/src/test/core/xds/google_mesh_ca_certificate_provider_factory_test.cc
    -third_party/grpc/src/test/core/xds/xds_bootstrap_test.cc
    -third_party/grpc/src/test/core/xds/xds_client_test.cc
    -third_party/grpc/src/test/core/xds/xds_cluster_resource_type_test.cc
    -third_party/grpc/src/test/core/xds/xds_common_types_test.cc
    -third_party/grpc/src/test/core/xds/xds_http_filters_test.cc
    -third_party/grpc/src/test/core/xds/xds_lb_policy_registry_test.cc
    -third_party/grpc/src/test/core/xds/xds_listener_resource_type_test.cc
    -third_party/grpc/src/test/core/xds/xds_route_config_resource_type_test.cc
    -third_party/grpc/src/test/cpp/end2end/end2end_test.cc
    -third_party/grpc/src/test/cpp/end2end/tls_key_export_test.cc
    -third_party/grpc/src/test/cpp/end2end/xds/xds_end2end_test.cc
    -third_party/grpc/src/test/cpp/end2end/xds/xds_server.h
    -third_party/grpc/src/test/cpp/qps/parse_json.cc
    -third_party/grpc/src/test/cpp/qps/report.cc
    -third_party/grpc/src/test/cpp/util/channel_trace_proto_helper.cc
    -third_party/grpc/src/test/cpp/util/create_test_channel.cc
    -third_party/grpc/src/third_party/objective_c/google_toolbox_for_mac/UnitTesting/GTMGoogleTestRunner.mm
    -third_party/grpc/src/third_party/upb/python/message.c
    -third_party/grpc/src/third_party/upb/upbc/protoc-gen-upbdefs.cc
    -third_party/grpc/src/third_party/xxhash/xxhash.h
    -third_party/grpc/src/tools/distrib/python/grpc_prefixed/templates/setup.py.template
    -third_party/grpc/src/tools/distrib/python/grpcio_tools/setup.py
    -third_party/grpc/src/tools/distrib/python/xds_protos/setup.py
    -third_party/grpc/src/tools/distrib/update_flakes_query.py
    -third_party/grpc/src/tools/interop_matrix/run_interop_matrix_tests.py
    -third_party/grpc/src/tools/run_tests/performance/bq_upload_result.py
    -third_party/grpc/src/tools/run_tests/python_utils/bazel_report_helper.py
    -third_party/grpc/src/tools/run_tests/python_utils/port_server.py
    -third_party/grpc/src/tools/run_tests/python_utils/upload_rbe_results.py
    -third_party/grpc/src/tools/run_tests/python_utils/upload_test_results.py
    -third_party/grpc/src/tools/run_tests/run_interop_tests.py
    -third_party/grpc/src/tools/run_tests/run_xds_tests.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/iam.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/infrastructure/k8s.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/test_app/runners/k8s/k8s_base_runner.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/test_app/runners/k8s/k8s_xds_server_runner.py
    -third_party/grpc/src/tools/run_tests/xds_k8s_test_driver/framework/xds_url_map_testcase.py
    +third_party/grpc/source/include/grpc/grpc.h
    +third_party/grpc/source/include/grpc/grpc_security_constants.h
    +third_party/grpc/source/include/grpc/support/port_platform.h
    +third_party/grpc/source/include/grpcpp/create_channel_binder.h
    +third_party/grpc/source/include/grpcpp/security/binder_security_policy.h
    +third_party/grpc/source/include/grpcpp/support/proto_buffer_reader.h
    +third_party/grpc/source/include/grpcpp/support/proto_buffer_writer.h
    +third_party/grpc/source/src/core/ext/filters/client_channel/resolver/google_c2p/google_c2p_resolver.cc
    +third_party/grpc/source/src/core/ext/transport/binder/security_policy/security_policy.h
    +third_party/grpc/source/src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc
    +third_party/grpc/source/src/core/ext/xds/xds_api.cc
    +third_party/grpc/source/src/core/ext/xds/xds_cluster.cc
    +third_party/grpc/source/src/core/lib/event_engine/windows/iocp.cc
    +third_party/grpc/source/src/core/lib/gpr/string.h
    +third_party/grpc/source/src/core/lib/gprpp/status_helper.cc
    +third_party/grpc/source/src/core/lib/gprpp/time.h
    +third_party/grpc/source/src/core/lib/iomgr/socket_windows.cc
    +third_party/grpc/source/src/core/lib/json/json_object_loader.cc
    +third_party/grpc/source/src/core/lib/json/json_util.h
    +third_party/grpc/source/src/core/lib/security/authorization/cel_authorization_engine.h
    +third_party/grpc/source/src/core/lib/security/credentials/credentials.h
    +third_party/grpc/source/src/core/lib/security/credentials/external/external_account_credentials.cc
    +third_party/grpc/source/src/core/lib/security/credentials/google_default/google_default_credentials.cc
    +third_party/grpc/source/src/core/lib/security/credentials/jwt/json_token.h
    +third_party/grpc/source/src/core/lib/security/credentials/jwt/jwt_verifier.h
    +third_party/grpc/source/src/core/lib/security/security_connector/ssl_utils_config.cc
    +third_party/grpc/source/src/core/lib/service_config/service_config.h
    +third_party/grpc/source/src/core/lib/service_config/service_config_impl.h
    +third_party/grpc/source/src/cpp/ext/gcp/observability.cc
    +third_party/grpc/source/src/cpp/ext/gcp/observability_logging_sink.cc
    +third_party/grpc/source/third_party/upb/python/message.c
    +third_party/grpc/source/third_party/upb/upbc/protoc-gen-upbdefs.cc
    +third_party/grpc/source/third_party/xxhash/xxhash.h
     third_party/harfbuzz-ng/src/docs/usermanual-install-harfbuzz.xml
     third_party/harfbuzz-ng/src/docs/usermanual-integration.xml
     third_party/harfbuzz-ng/src/docs/usermanual-opentype-features.xml
     third_party/harfbuzz-ng/src/docs/usermanual-what-is-harfbuzz.xml
    +third_party/harfbuzz-ng/src/perf/texts/react-dom.txt
     third_party/harfbuzz-ng/src/src/OT/Color/CBDT/CBDT.hh
     third_party/harfbuzz-ng/src/src/OT/Color/COLR/COLR.hh
     third_party/harfbuzz-ng/src/src/OT/Color/COLR/colrv1-closure.hh
    @@ -10848,6 +10841,8 @@ third_party/isimpledom/ISimpleDOMDocument.idl
     third_party/isimpledom/ISimpleDOMNode.idl
     third_party/isimpledom/ISimpleDOMText.idl
     third_party/jni_zero/PRESUBMIT.py
    +third_party/kotlinc/licenses/gradle_license.txt
    +third_party/kotlinc/licenses/rhino_LICENSE.txt
     third_party/leveldatabase/src/doc/benchmark.html
     third_party/leveldatabase/src/table/format.h
     third_party/libFuzzer/src/FuzzerSHA1.cpp
    @@ -11232,6 +11227,7 @@ third_party/material_web_components/components-chromium/node_modules/lit-html/di
     third_party/material_web_components/components-chromium/node_modules/lit-html/directives/async-replace.d.ts
     third_party/material_web_components/package.json
     third_party/mediapipe/src/mediapipe/calculators/audio/audio_decoder_calculator.cc
    +third_party/mediapipe/src/mediapipe/calculators/audio/two_tap_fir_filter_calculator.cc
     third_party/mediapipe/src/mediapipe/calculators/core/bypass_calculator.cc
     third_party/mediapipe/src/mediapipe/calculators/image/opencv_image_encoder_calculator.cc
     third_party/mediapipe/src/mediapipe/calculators/image/scale_image_calculator.cc
    @@ -11314,7 +11310,6 @@ third_party/node/node_modules/@azure/msal-browser/lib/types/network/FetchClient.
     third_party/node/node_modules/@azure/msal-browser/package.json
     third_party/node/node_modules/@azure/msal-common/lib/types/telemetry/performance/PerformanceEvent.d.ts
     third_party/node/node_modules/@azure/msal-common/package.json
    -third_party/node/node_modules/@eslint/eslintrc/node_modules/globals/package.json
     third_party/node/node_modules/@lit/reactive-element/reactive-element.d.ts
     third_party/node/node_modules/@mediapipe/tasks-vision/vision.d.ts
     third_party/node/node_modules/@rollup/wasm-node/dist/shared/index.js
    @@ -11327,7 +11322,6 @@ third_party/node/node_modules/@typescript-eslint/eslint-plugin/dist/rules/prefer
     third_party/node/node_modules/acorn/dist/acorn.js
     third_party/node/node_modules/acorn/package.json
     third_party/node/node_modules/ajv/dist/ajv.bundle.js
    -third_party/node/node_modules/ansi-regex/package.json
     third_party/node/node_modules/callsites/package.json
     third_party/node/node_modules/chai/chai.js
     third_party/node/node_modules/chai/package.json
    @@ -11394,6 +11388,7 @@ third_party/node/node_modules/fast-glob/node_modules/glob-parent/package.json
     third_party/node/node_modules/fast-glob/out/utils/path.js
     third_party/node/node_modules/find-up/package.json
     third_party/node/node_modules/glob-parent/package.json
    +third_party/node/node_modules/globals/package.json
     third_party/node/node_modules/html-minifier/package.json
     third_party/node/node_modules/html-minifier/src/htmlminifier.js
     third_party/node/node_modules/html-minifier/src/htmlparser.js
    @@ -11401,7 +11396,6 @@ third_party/node/node_modules/import-fresh/package.json
     third_party/node/node_modules/imurmurhash/imurmurhash.js
     third_party/node/node_modules/imurmurhash/imurmurhash.min.js
     third_party/node/node_modules/imurmurhash/package.json
    -third_party/node/node_modules/is-path-inside/package.json
     third_party/node/node_modules/js-yaml/package.json
     third_party/node/node_modules/json-buffer/package.json
     third_party/node/node_modules/lit-html/directives/async-append.d.ts
    @@ -11435,7 +11429,6 @@ third_party/node/node_modules/source-map-support/node_modules/source-map/lib/bas
     third_party/node/node_modules/source-map-support/node_modules/source-map/lib/source-map-consumer.js
     third_party/node/node_modules/source-map-support/node_modules/source-map/package.json
     third_party/node/node_modules/source-map-support/source-map-support.js
    -third_party/node/node_modules/strip-ansi/package.json
     third_party/node/node_modules/strip-json-comments/package.json
     third_party/node/node_modules/svgo/dist/svgo.browser.js
     third_party/node/node_modules/svgo/plugins/_collections.js
    @@ -12145,7 +12138,6 @@ third_party/perfetto/src/profiling/memory/client.cc
     third_party/perfetto/src/profiling/memory/sampler.h
     third_party/perfetto/src/profiling/memory/shared_ring_buffer.cc
     third_party/perfetto/src/profiling/memory/system_property.h
    -third_party/perfetto/src/profiling/symbolizer/breakpad_parser.cc
     third_party/perfetto/src/profiling/symbolizer/breakpad_parser.h
     third_party/perfetto/src/trace_processor/importers/art_method/art_method_tokenizer.cc
     third_party/perfetto/src/trace_processor/importers/common/args_translation_table.cc
    @@ -12154,11 +12146,11 @@ third_party/perfetto/src/trace_processor/importers/ftrace/binder_tracker.cc
     third_party/perfetto/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.h
     third_party/perfetto/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
     third_party/perfetto/src/trace_processor/importers/ninja/ninja_log_parser.cc
    +third_party/perfetto/src/trace_processor/importers/proto/android_probes_parser.cc
     third_party/perfetto/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
     third_party/perfetto/src/trace_processor/importers/proto/statsd_module.cc
     third_party/perfetto/src/trace_processor/importers/proto/system_probes_parser.cc
     third_party/perfetto/src/trace_processor/metrics/sql/android/unsymbolized_frames.sql
    -third_party/perfetto/src/trace_processor/metrics/sql/chrome/rail_modes.sql
     third_party/perfetto/src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql
     third_party/perfetto/src/trace_processor/perfetto_sql/stdlib/android/startup/time_to_display.sql
     third_party/perfetto/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
    @@ -12197,7 +12189,9 @@ third_party/perfetto/ui/src/frontend/sidebar.ts
     third_party/perfetto/ui/src/frontend/trace_url_handler.ts
     third_party/perfetto/ui/src/frontend/viewer_page/wasd_navigation_handler.ts
     third_party/perfetto/ui/src/open_perfetto_trace/index.html
    +third_party/perfetto/ui/src/plugins/dev.perfetto.ProcessSummary/index.ts
     third_party/perfetto/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/adb_msg.ts
    +third_party/perfetto/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/web_device_proxy/wdp_target_provider.ts
     third_party/perfetto/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/android.ts
     third_party/perfetto/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/record_page.ts
     third_party/perfetto/ui/src/plugins/dev.perfetto.TraceInfoPage/trace_info_page.ts
    @@ -12225,7 +12219,6 @@ third_party/protobuf-javascript/src/binary/constants.js
     third_party/protobuf-javascript/src/binary/decoder.js
     third_party/protobuf-javascript/src/binary/decoder_test.js
     third_party/protobuf-javascript/src/binary/encoder.js
    -third_party/protobuf-javascript/src/binary/message_test.js
     third_party/protobuf-javascript/src/binary/proto_test.js
     third_party/protobuf-javascript/src/binary/reader.js
     third_party/protobuf-javascript/src/binary/reader_test.js
    @@ -12233,55 +12226,33 @@ third_party/protobuf-javascript/src/binary/utils.js
     third_party/protobuf-javascript/src/binary/utils_test.js
     third_party/protobuf-javascript/src/binary/writer.js
     third_party/protobuf-javascript/src/binary/writer_test.js
    -third_party/protobuf-javascript/src/commonjs/import_test.js
    -third_party/protobuf-javascript/src/commonjs/strict_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/arith_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/decoder_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/proto_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/reader_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/utils_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/binary/writer_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/commonjs/import_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/debug_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/message_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.0.0/proto3_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/arith_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/decoder_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/proto_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/reader_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/utils_test.js
     third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/binary/writer_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/debug_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/maps_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/message_test.js
    -third_party/protobuf-javascript/src/compatibility_tests/v3.1.0/proto3_test.js
    -third_party/protobuf-javascript/src/debug.js
    -third_party/protobuf-javascript/src/debug_test.js
     third_party/protobuf-javascript/src/experimental/runtime/int64.js
    -third_party/protobuf-javascript/src/experimental/runtime/kernel/kernel_compatibility_test.js
     third_party/protobuf-javascript/src/experimental/runtime/kernel/storage.js
    -third_party/protobuf-javascript/src/experimental/runtime/kernel/writer.js
     third_party/protobuf-javascript/src/generator/js_generator.cc
    -third_party/protobuf-javascript/src/generator/js_generator.h
    -third_party/protobuf-javascript/src/generator/protoc-gen-js.cc
     third_party/protobuf-javascript/src/generator/well_known_types_embed.cc
    -third_party/protobuf-javascript/src/generator/well_known_types_embed.h
    -third_party/protobuf-javascript/src/gulpfile.js
     third_party/protobuf-javascript/src/map.js
    -third_party/protobuf-javascript/src/maps_test.js
     third_party/protobuf-javascript/src/message.js
    -third_party/protobuf-javascript/src/message_test.js
    -third_party/protobuf-javascript/src/node_loader.js
    -third_party/protobuf-javascript/src/proto3_test.js
    -third_party/protobuf-javascript/src/test_bootstrap.js
    -third_party/protobuf/CHANGES.txt
    +third_party/protobuf/.bcr/metadata.template.json
     third_party/protobuf/CMakeLists.txt
     third_party/protobuf/CONTRIBUTORS.txt
    -third_party/protobuf/benchmarks/cpp/cpp_benchmark.cc
    +third_party/protobuf/benchmarks/benchmark.cc
    +third_party/protobuf/benchmarks/compare.py
    +third_party/protobuf/benchmarks/gen_protobuf_binary_cc.py
    +third_party/protobuf/benchmarks/gen_synthetic_protos.py
    +third_party/protobuf/benchmarks/gen_upb_binary_c.py
     third_party/protobuf/conformance/binary_json_conformance_suite.cc
     third_party/protobuf/conformance/binary_json_conformance_suite.h
     third_party/protobuf/conformance/conformance_cpp.cc
    -third_party/protobuf/conformance/conformance_nodejs.js
     third_party/protobuf/conformance/conformance_python.py
     third_party/protobuf/conformance/conformance_test.cc
     third_party/protobuf/conformance/conformance_test.h
    @@ -12290,11 +12261,57 @@ third_party/protobuf/conformance/conformance_test_runner.cc
     third_party/protobuf/conformance/text_format_conformance_suite.cc
     third_party/protobuf/conformance/text_format_conformance_suite.h
     third_party/protobuf/conformance/update_failure_list.py
    +third_party/protobuf/docs/upb/render.py
    +third_party/protobuf/editions/generated_files_test.cc
    +third_party/protobuf/editions/generated_reflection_test.cc
    +third_party/protobuf/hpb/arena.h
    +third_party/protobuf/hpb/backend/upb/interop.h
    +third_party/protobuf/hpb/backend/upb/interop_test.cc
    +third_party/protobuf/hpb/backend/upb/upb.h
    +third_party/protobuf/hpb/extension.cc
    +third_party/protobuf/hpb/extension.h
    +third_party/protobuf/hpb/hpb.cc
    +third_party/protobuf/hpb/hpb.h
    +third_party/protobuf/hpb/internal/message_lock.cc
    +third_party/protobuf/hpb/internal/message_lock.h
    +third_party/protobuf/hpb/internal/message_lock_test.cc
    +third_party/protobuf/hpb/internal/template_help.h
    +third_party/protobuf/hpb/ptr.h
    +third_party/protobuf/hpb/repeated_field.h
    +third_party/protobuf/hpb/repeated_field_iterator.h
    +third_party/protobuf/hpb/repeated_field_iterator_test.cc
    +third_party/protobuf/hpb_generator/gen_accessors.cc
    +third_party/protobuf/hpb_generator/gen_accessors.h
    +third_party/protobuf/hpb_generator/gen_enums.cc
    +third_party/protobuf/hpb_generator/gen_enums.h
    +third_party/protobuf/hpb_generator/gen_extensions.cc
    +third_party/protobuf/hpb_generator/gen_extensions.h
    +third_party/protobuf/hpb_generator/gen_messages.cc
    +third_party/protobuf/hpb_generator/gen_messages.h
    +third_party/protobuf/hpb_generator/gen_repeated_fields.cc
    +third_party/protobuf/hpb_generator/gen_repeated_fields.h
    +third_party/protobuf/hpb_generator/gen_utils.cc
    +third_party/protobuf/hpb_generator/gen_utils.h
    +third_party/protobuf/hpb_generator/keywords.cc
    +third_party/protobuf/hpb_generator/keywords.h
    +third_party/protobuf/hpb_generator/names.cc
    +third_party/protobuf/hpb_generator/names.h
    +third_party/protobuf/hpb_generator/output.cc
    +third_party/protobuf/hpb_generator/output.h
    +third_party/protobuf/hpb_generator/protoc-gen-upb-protos.cc
    +third_party/protobuf/hpb_generator/tests/test_generated.cc
     third_party/protobuf/java/bom/pom.xml
    -third_party/protobuf/java/core/pom.xml
    -third_party/protobuf/java/lite/pom.xml
    +third_party/protobuf/java/core/pom_template.xml
    +third_party/protobuf/java/lite/pom_template.xml
     third_party/protobuf/java/pom.xml
    -third_party/protobuf/java/util/pom.xml
    +third_party/protobuf/java/protoc/pom.xml
    +third_party/protobuf/java/util/pom_template.xml
    +third_party/protobuf/lua/def.c
    +third_party/protobuf/lua/main.c
    +third_party/protobuf/lua/msg.c
    +third_party/protobuf/lua/upb.c
    +third_party/protobuf/lua/upb.h
    +third_party/protobuf/lua/upbc.cc
     third_party/protobuf/objectivec/DevTools/pddm.py
     third_party/protobuf/objectivec/DevTools/pddm_tests.py
     third_party/protobuf/objectivec/GPBAny.pbobjc.h
    @@ -12325,12 +12342,15 @@ third_party/protobuf/objectivec/GPBUnknownField.h
     third_party/protobuf/objectivec/GPBUnknownFieldSet.h
     third_party/protobuf/objectivec/GPBUnknownFieldSet_PackagePrivate.h
     third_party/protobuf/objectivec/GPBUnknownField_PackagePrivate.h
    +third_party/protobuf/objectivec/GPBUnknownFields.h
    +third_party/protobuf/objectivec/GPBUnknownFields_PackagePrivate.h
     third_party/protobuf/objectivec/GPBUtilities.h
     third_party/protobuf/objectivec/GPBUtilities_PackagePrivate.h
     third_party/protobuf/objectivec/GPBWellKnownTypes.h
     third_party/protobuf/objectivec/GPBWireFormat.h
     third_party/protobuf/objectivec/Tests/GPBObjectiveCPlusPlusTest.mm
     third_party/protobuf/objectivec/Tests/GPBTestUtilities.h
    +third_party/protobuf/objectivec/Tests/UnitTests-Bridging-Header.h
     third_party/protobuf/php/composer.json
     third_party/protobuf/php/ext/google/protobuf/arena.c
     third_party/protobuf/php/ext/google/protobuf/arena.h
    @@ -12346,29 +12366,53 @@ third_party/protobuf/php/ext/google/protobuf/message.c
     third_party/protobuf/php/ext/google/protobuf/message.h
     third_party/protobuf/php/ext/google/protobuf/names.c
     third_party/protobuf/php/ext/google/protobuf/names.h
    +third_party/protobuf/php/ext/google/protobuf/php-upb.c
    +third_party/protobuf/php/ext/google/protobuf/php-upb.h
     third_party/protobuf/php/ext/google/protobuf/php_protobuf.h
     third_party/protobuf/php/ext/google/protobuf/protobuf.c
     third_party/protobuf/php/ext/google/protobuf/protobuf.h
     third_party/protobuf/php/ext/google/protobuf/template_package.xml
    -third_party/protobuf/protoc-artifacts/pom.xml
    +third_party/protobuf/pkg/test/test_lib.cc
    +third_party/protobuf/pkg/test/test_lib.h
    +third_party/protobuf/protos/protos.h
    +third_party/protobuf/python/.repo-metadata.json
    +third_party/protobuf/python/convert.c
    +third_party/protobuf/python/convert.h
    +third_party/protobuf/python/descriptor.c
    +third_party/protobuf/python/descriptor.h
    +third_party/protobuf/python/descriptor_containers.c
    +third_party/protobuf/python/descriptor_containers.h
    +third_party/protobuf/python/descriptor_pool.c
    +third_party/protobuf/python/descriptor_pool.h
    +third_party/protobuf/python/dist/setup.py
     third_party/protobuf/python/docs/conf.py
     third_party/protobuf/python/docs/generate_docs.py
    +third_party/protobuf/python/extension_dict.c
    +third_party/protobuf/python/extension_dict.h
     third_party/protobuf/python/google/protobuf/__init__.py
    +third_party/protobuf/python/google/protobuf/any.py
     third_party/protobuf/python/google/protobuf/descriptor.py
     third_party/protobuf/python/google/protobuf/descriptor_database.py
     third_party/protobuf/python/google/protobuf/descriptor_pool.py
    +third_party/protobuf/python/google/protobuf/duration.py
    +third_party/protobuf/python/google/protobuf/internal/__init__.py
     third_party/protobuf/python/google/protobuf/internal/_parameterized.py
    +third_party/protobuf/python/google/protobuf/internal/any_test.py
     third_party/protobuf/python/google/protobuf/internal/api_implementation.cc
     third_party/protobuf/python/google/protobuf/internal/api_implementation.py
     third_party/protobuf/python/google/protobuf/internal/builder.py
     third_party/protobuf/python/google/protobuf/internal/containers.py
     third_party/protobuf/python/google/protobuf/internal/decoder.py
    +third_party/protobuf/python/google/protobuf/internal/decoder_test.py
     third_party/protobuf/python/google/protobuf/internal/descriptor_database_test.py
     third_party/protobuf/python/google/protobuf/internal/descriptor_pool_test.py
     third_party/protobuf/python/google/protobuf/internal/descriptor_test.py
    +third_party/protobuf/python/google/protobuf/internal/duration_test.py
     third_party/protobuf/python/google/protobuf/internal/encoder.py
     third_party/protobuf/python/google/protobuf/internal/enum_type_wrapper.py
     third_party/protobuf/python/google/protobuf/internal/extension_dict.py
    +third_party/protobuf/python/google/protobuf/internal/field_mask.py
    +third_party/protobuf/python/google/protobuf/internal/field_mask_test.py
     third_party/protobuf/python/google/protobuf/internal/generator_test.py
     third_party/protobuf/python/google/protobuf/internal/import_test.py
     third_party/protobuf/python/google/protobuf/internal/import_test_package/__init__.py
    @@ -12377,16 +12421,25 @@ third_party/protobuf/python/google/protobuf/internal/keywords_test.py
     third_party/protobuf/python/google/protobuf/internal/message_factory_test.py
     third_party/protobuf/python/google/protobuf/internal/message_listener.py
     third_party/protobuf/python/google/protobuf/internal/message_test.py
    +third_party/protobuf/python/google/protobuf/internal/numpy/__init__.py
    +third_party/protobuf/python/google/protobuf/internal/numpy/numpy_test.py
     third_party/protobuf/python/google/protobuf/internal/proto_builder_test.py
    +third_party/protobuf/python/google/protobuf/internal/proto_json_test.py
    +third_party/protobuf/python/google/protobuf/internal/proto_test.py
    +third_party/protobuf/python/google/protobuf/internal/pybind11_test_module.cc
     third_party/protobuf/python/google/protobuf/internal/python_message.py
     third_party/protobuf/python/google/protobuf/internal/python_protobuf.cc
    +third_party/protobuf/python/google/protobuf/internal/reflection_cpp_test.py
     third_party/protobuf/python/google/protobuf/internal/reflection_test.py
    +third_party/protobuf/python/google/protobuf/internal/runtime_version_test.py
     third_party/protobuf/python/google/protobuf/internal/service_reflection_test.py
     third_party/protobuf/python/google/protobuf/internal/symbol_database_test.py
     third_party/protobuf/python/google/protobuf/internal/test_util.py
     third_party/protobuf/python/google/protobuf/internal/testing_refleaks.py
     third_party/protobuf/python/google/protobuf/internal/text_encoding_test.py
     third_party/protobuf/python/google/protobuf/internal/text_format_test.py
    +third_party/protobuf/python/google/protobuf/internal/thread_safe_test.py
    +third_party/protobuf/python/google/protobuf/internal/timestamp_test.py
     third_party/protobuf/python/google/protobuf/internal/type_checkers.py
     third_party/protobuf/python/google/protobuf/internal/unknown_fields_test.py
     third_party/protobuf/python/google/protobuf/internal/well_known_types.py
    @@ -12396,8 +12449,10 @@ third_party/protobuf/python/google/protobuf/internal/wire_format_test.py
     third_party/protobuf/python/google/protobuf/json_format.py
     third_party/protobuf/python/google/protobuf/message.py
     third_party/protobuf/python/google/protobuf/message_factory.py
    +third_party/protobuf/python/google/protobuf/proto.py
     third_party/protobuf/python/google/protobuf/proto_api.h
     third_party/protobuf/python/google/protobuf/proto_builder.py
    +third_party/protobuf/python/google/protobuf/proto_json.py
     third_party/protobuf/python/google/protobuf/pyext/cpp_message.py
     third_party/protobuf/python/google/protobuf/pyext/descriptor.cc
     third_party/protobuf/python/google/protobuf/pyext/descriptor.h
    @@ -12426,25 +12481,43 @@ third_party/protobuf/python/google/protobuf/pyext/safe_numerics.h
     third_party/protobuf/python/google/protobuf/pyext/scoped_pyobject_ptr.h
     third_party/protobuf/python/google/protobuf/pyext/unknown_field_set.cc
     third_party/protobuf/python/google/protobuf/pyext/unknown_field_set.h
    -third_party/protobuf/python/google/protobuf/pyext/unknown_fields.cc
    -third_party/protobuf/python/google/protobuf/pyext/unknown_fields.h
     third_party/protobuf/python/google/protobuf/python_protobuf.h
     third_party/protobuf/python/google/protobuf/reflection.py
    +third_party/protobuf/python/google/protobuf/runtime_version.py
     third_party/protobuf/python/google/protobuf/service.py
     third_party/protobuf/python/google/protobuf/service_reflection.py
     third_party/protobuf/python/google/protobuf/symbol_database.py
     third_party/protobuf/python/google/protobuf/text_encoding.py
     third_party/protobuf/python/google/protobuf/text_format.py
    +third_party/protobuf/python/google/protobuf/timestamp.py
     third_party/protobuf/python/google/protobuf/unknown_fields.py
    -third_party/protobuf/python/mox.py
    +third_party/protobuf/python/map.c
    +third_party/protobuf/python/map.h
    +third_party/protobuf/python/message.c
    +third_party/protobuf/python/message.h
    +third_party/protobuf/python/minimal_test.py
    +third_party/protobuf/python/pb_unit_tests/descriptor_pool_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/descriptor_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/generator_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/message_factory_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/message_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/proto_builder_test_wrapper.py
    +third_party/protobuf/python/pb_unit_tests/reflection_test_wrapper.py
    +third_party/protobuf/python/protobuf.c
    +third_party/protobuf/python/protobuf.h
     third_party/protobuf/python/protobuf_distutils/protobuf_distutils/generate_py_protobufs.py
     third_party/protobuf/python/protobuf_distutils/setup.py
    -third_party/protobuf/python/setup.py
    -third_party/protobuf/python/stubout.py
    +third_party/protobuf/python/python_api.h
    +third_party/protobuf/python/python_version_test.py
    +third_party/protobuf/python/repeated.c
    +third_party/protobuf/python/repeated.h
    +third_party/protobuf/python/unknown_fields.c
    +third_party/protobuf/python/unknown_fields.h
     third_party/protobuf/ruby/ext/google/protobuf_c/convert.c
     third_party/protobuf/ruby/ext/google/protobuf_c/convert.h
     third_party/protobuf/ruby/ext/google/protobuf_c/defs.c
     third_party/protobuf/ruby/ext/google/protobuf_c/defs.h
    +third_party/protobuf/ruby/ext/google/protobuf_c/glue.c
     third_party/protobuf/ruby/ext/google/protobuf_c/map.c
     third_party/protobuf/ruby/ext/google/protobuf_c/map.h
     third_party/protobuf/ruby/ext/google/protobuf_c/message.c
    @@ -12453,15 +12526,30 @@ third_party/protobuf/ruby/ext/google/protobuf_c/protobuf.c
     third_party/protobuf/ruby/ext/google/protobuf_c/protobuf.h
     third_party/protobuf/ruby/ext/google/protobuf_c/repeated_field.c
     third_party/protobuf/ruby/ext/google/protobuf_c/repeated_field.h
    +third_party/protobuf/ruby/ext/google/protobuf_c/ruby-upb.c
    +third_party/protobuf/ruby/ext/google/protobuf_c/ruby-upb.h
    +third_party/protobuf/ruby/ext/google/protobuf_c/shared_convert.c
    +third_party/protobuf/ruby/ext/google/protobuf_c/shared_convert.h
    +third_party/protobuf/ruby/ext/google/protobuf_c/shared_message.c
    +third_party/protobuf/ruby/ext/google/protobuf_c/shared_message.h
     third_party/protobuf/ruby/ext/google/protobuf_c/wrap_memcpy.c
     third_party/protobuf/ruby/pom.xml
    +third_party/protobuf/rust/cpp_kernel/serialized_data.h
    +third_party/protobuf/rust/cpp_kernel/strings.h
    +third_party/protobuf/rust/test/cpp/interop/test_utils.cc
    +third_party/protobuf/rust/test/rust_proto_library_unit_test/empty.cc
    +third_party/protobuf/rust/upb/upb_api.c
     third_party/protobuf/src/google/protobuf/any.cc
     third_party/protobuf/src/google/protobuf/any.h
     third_party/protobuf/src/google/protobuf/any_lite.cc
     third_party/protobuf/src/google/protobuf/any_test.cc
     third_party/protobuf/src/google/protobuf/arena.cc
     third_party/protobuf/src/google/protobuf/arena.h
    -third_party/protobuf/src/google/protobuf/arena_impl.h
    +third_party/protobuf/src/google/protobuf/arena_align.cc
    +third_party/protobuf/src/google/protobuf/arena_align.h
    +third_party/protobuf/src/google/protobuf/arena_align_test.cc
    +third_party/protobuf/src/google/protobuf/arena_allocation_policy.h
    +third_party/protobuf/src/google/protobuf/arena_cleanup.h
     third_party/protobuf/src/google/protobuf/arena_test_util.cc
     third_party/protobuf/src/google/protobuf/arena_test_util.h
     third_party/protobuf/src/google/protobuf/arena_unittest.cc
    @@ -12475,47 +12563,63 @@ third_party/protobuf/src/google/protobuf/compiler/annotation_test_util.cc
     third_party/protobuf/src/google/protobuf/compiler/annotation_test_util.h
     third_party/protobuf/src/google/protobuf/compiler/code_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/code_generator.h
    +third_party/protobuf/src/google/protobuf/compiler/code_generator_lite.cc
    +third_party/protobuf/src/google/protobuf/compiler/code_generator_lite.h
    +third_party/protobuf/src/google/protobuf/compiler/code_generator_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/command_line_interface.cc
     third_party/protobuf/src/google/protobuf/compiler/command_line_interface.h
    +third_party/protobuf/src/google/protobuf/compiler/command_line_interface_tester.cc
    +third_party/protobuf/src/google/protobuf/compiler/command_line_interface_tester.h
     third_party/protobuf/src/google/protobuf/compiler/command_line_interface_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/arena_ctor_visibility_test.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/bootstrap_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/copy_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_generator.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/enum.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/enum.h
    -third_party/protobuf/src/google/protobuf/compiler/cpp/enum_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/enum_field.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/extension.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/extension.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/field.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/field.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/cord_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/enum_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/generators.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/map_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/message_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/primitive_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/string_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/field_generators/string_view_field.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/file.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/file.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/file_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/generator.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/generator.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/generator_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/helpers.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/helpers.h
    -third_party/protobuf/src/google/protobuf/compiler/cpp/map_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/map_field.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/ifndef_guard.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/ifndef_guard.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/main.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/message.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/message.h
    -third_party/protobuf/src/google/protobuf/compiler/cpp/message_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/message_field.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/message_layout_helper.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/message_size_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/metadata_test.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/move_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/names.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/namespace_printer.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/options.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/padding_optimizer.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/parse_function_generator.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/plugin_unittest.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/primitive_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/primitive_field.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/service.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/service.h
    -third_party/protobuf/src/google/protobuf/compiler/cpp/string_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/cpp/string_field.h
    +third_party/protobuf/src/google/protobuf/compiler/cpp/tools/analyze_profile_proto.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/tools/analyze_profile_proto_main.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/tracker.cc
    +third_party/protobuf/src/google/protobuf/compiler/cpp/tracker.h
     third_party/protobuf/src/google/protobuf/compiler/cpp/unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/cpp/unittest.h
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc
    @@ -12538,7 +12642,6 @@ third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message.cc
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message.h
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.cc
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.h
    -third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_names.h
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_options.h
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.h
    @@ -12554,6 +12657,9 @@ third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.h
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc
     third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.h
    +third_party/protobuf/src/google/protobuf/compiler/csharp/names.cc
    +third_party/protobuf/src/google/protobuf/compiler/csharp/names.h
    +third_party/protobuf/src/google/protobuf/compiler/fake_plugin.cc
     third_party/protobuf/src/google/protobuf/compiler/importer.cc
     third_party/protobuf/src/google/protobuf/compiler/importer.h
     third_party/protobuf/src/google/protobuf/compiler/importer_unittest.cc
    @@ -12562,95 +12668,125 @@ third_party/protobuf/src/google/protobuf/compiler/java/context.h
     third_party/protobuf/src/google/protobuf/compiler/java/doc_comment.cc
     third_party/protobuf/src/google/protobuf/compiler/java/doc_comment.h
     third_party/protobuf/src/google/protobuf/compiler/java/doc_comment_unittest.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/enum.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/enum.h
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_field.h
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_field_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_field_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/enum_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/extension.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/extension.h
    -third_party/protobuf/src/google/protobuf/compiler/java/extension_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/extension_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/field.h
     third_party/protobuf/src/google/protobuf/compiler/java/file.cc
     third_party/protobuf/src/google/protobuf/compiler/java/file.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/enum.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/enum.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/enum_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/enum_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/extension.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/extension.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/generator_factory.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/make_field_gens.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/make_field_gens.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/map_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/map_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message_builder.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message_builder.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/message_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/primitive_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/primitive_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/service.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/service.h
    +third_party/protobuf/src/google/protobuf/compiler/java/full/string_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/full/string_field.h
     third_party/protobuf/src/google/protobuf/compiler/java/generator.cc
     third_party/protobuf/src/google/protobuf/compiler/java/generator.h
    -third_party/protobuf/src/google/protobuf/compiler/java/generator_factory.cc
     third_party/protobuf/src/google/protobuf/compiler/java/generator_factory.h
     third_party/protobuf/src/google/protobuf/compiler/java/helpers.cc
     third_party/protobuf/src/google/protobuf/compiler/java/helpers.h
    -third_party/protobuf/src/google/protobuf/compiler/java/kotlin_generator.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/kotlin_generator.h
    -third_party/protobuf/src/google/protobuf/compiler/java/map_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/map_field.h
    -third_party/protobuf/src/google/protobuf/compiler/java/map_field_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/map_field_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message_builder.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message_builder.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message_builder_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message_builder_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message_field.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message_field_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message_field_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/message_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/message_lite.h
    +third_party/protobuf/src/google/protobuf/compiler/java/internal_helpers.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/internal_helpers.h
    +third_party/protobuf/src/google/protobuf/compiler/java/java_generator.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/enum.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/enum.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/enum_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/enum_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/extension.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/extension.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/generator_factory.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/make_field_gens.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/make_field_gens.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/map_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/map_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message_builder.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message_builder.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/message_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/primitive_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/primitive_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/string_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/lite/string_field.h
    +third_party/protobuf/src/google/protobuf/compiler/java/message_serialization.cc
    +third_party/protobuf/src/google/protobuf/compiler/java/message_serialization.h
    +third_party/protobuf/src/google/protobuf/compiler/java/message_serialization_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/java/name_resolver.cc
     third_party/protobuf/src/google/protobuf/compiler/java/name_resolver.h
    +third_party/protobuf/src/google/protobuf/compiler/java/names.cc
     third_party/protobuf/src/google/protobuf/compiler/java/names.h
     third_party/protobuf/src/google/protobuf/compiler/java/options.h
     third_party/protobuf/src/google/protobuf/compiler/java/plugin_unittest.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/primitive_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/primitive_field.h
    -third_party/protobuf/src/google/protobuf/compiler/java/primitive_field_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/primitive_field_lite.h
    -third_party/protobuf/src/google/protobuf/compiler/java/service.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/service.h
     third_party/protobuf/src/google/protobuf/compiler/java/shared_code_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/java/shared_code_generator.h
    -third_party/protobuf/src/google/protobuf/compiler/java/string_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/string_field.h
    -third_party/protobuf/src/google/protobuf/compiler/java/string_field_lite.cc
    -third_party/protobuf/src/google/protobuf/compiler/java/string_field_lite.h
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/file.cc
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/file.h
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/generator.cc
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/generator.h
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/message.cc
    +third_party/protobuf/src/google/protobuf/compiler/kotlin/message.h
     third_party/protobuf/src/google/protobuf/compiler/main.cc
    +third_party/protobuf/src/google/protobuf/compiler/main_no_generators.cc
     third_party/protobuf/src/google/protobuf/compiler/mock_code_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/mock_code_generator.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers_unittest.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.h
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc
    -third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/enum.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/enum.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/enum_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/enum_field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/extension.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/extension.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/field.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/file.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/file.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/generator.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/generator.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/helpers.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/helpers.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/import_writer.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/import_writer.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/line_consumer.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/line_consumer.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/line_consumer_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/map_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/map_field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/message.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/message.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/message_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/message_field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/names.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/names.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/names_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/nsobject_methods.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/oneof.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/oneof.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/options.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/primitive_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/primitive_field.h
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/text_format_decode_data_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/tf_decode_data.cc
    +third_party/protobuf/src/google/protobuf/compiler/objectivec/tf_decode_data.h
     third_party/protobuf/src/google/protobuf/compiler/package_info.h
     third_party/protobuf/src/google/protobuf/compiler/parser.cc
     third_party/protobuf/src/google/protobuf/compiler/parser.h
     third_party/protobuf/src/google/protobuf/compiler/parser_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/php/generator_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/php/names.cc
    +third_party/protobuf/src/google/protobuf/compiler/php/names.h
     third_party/protobuf/src/google/protobuf/compiler/php/php_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/php/php_generator.h
     third_party/protobuf/src/google/protobuf/compiler/plugin.cc
    @@ -12662,25 +12798,70 @@ third_party/protobuf/src/google/protobuf/compiler/python/helpers.h
     third_party/protobuf/src/google/protobuf/compiler/python/plugin_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/python/pyi_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/python/pyi_generator.h
    +third_party/protobuf/src/google/protobuf/compiler/python/python_generator.h
    +third_party/protobuf/src/google/protobuf/compiler/retention.cc
    +third_party/protobuf/src/google/protobuf/compiler/retention.h
    +third_party/protobuf/src/google/protobuf/compiler/retention_unittest.cc
     third_party/protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc
     third_party/protobuf/src/google/protobuf/compiler/ruby/ruby_generator.h
     third_party/protobuf/src/google/protobuf/compiler/ruby/ruby_generator_unittest.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/accessor_case.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/accessors.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/accessors.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/default_value.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/default_value.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/generator.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/map.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/repeated_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/singular_cord.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/singular_message.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/singular_string.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/unsupported_field.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/with_presence.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/accessors/with_presence.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/context.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/context.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/enum.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/enum.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/generator.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/generator.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/message.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/message.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/naming.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/naming.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/oneof.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/oneof.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/relative_path.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/relative_path.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/relative_path_test.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/rust_keywords.cc
    +third_party/protobuf/src/google/protobuf/compiler/rust/rust_keywords.h
    +third_party/protobuf/src/google/protobuf/compiler/rust/upb_helpers.h
     third_party/protobuf/src/google/protobuf/compiler/scc.h
     third_party/protobuf/src/google/protobuf/compiler/subprocess.cc
     third_party/protobuf/src/google/protobuf/compiler/subprocess.h
     third_party/protobuf/src/google/protobuf/compiler/test_plugin.cc
    +third_party/protobuf/src/google/protobuf/compiler/versions.cc
    +third_party/protobuf/src/google/protobuf/compiler/versions.h
    +third_party/protobuf/src/google/protobuf/compiler/versions_test.cc
     third_party/protobuf/src/google/protobuf/compiler/zip_writer.cc
     third_party/protobuf/src/google/protobuf/compiler/zip_writer.h
    +third_party/protobuf/src/google/protobuf/debug_counter_test.cc
     third_party/protobuf/src/google/protobuf/descriptor.cc
     third_party/protobuf/src/google/protobuf/descriptor.h
     third_party/protobuf/src/google/protobuf/descriptor_database.cc
     third_party/protobuf/src/google/protobuf/descriptor_database.h
     third_party/protobuf/src/google/protobuf/descriptor_database_unittest.cc
    +third_party/protobuf/src/google/protobuf/descriptor_lite.h
     third_party/protobuf/src/google/protobuf/descriptor_unittest.cc
    +third_party/protobuf/src/google/protobuf/descriptor_visitor.h
    +third_party/protobuf/src/google/protobuf/descriptor_visitor_test.cc
     third_party/protobuf/src/google/protobuf/drop_unknown_fields_test.cc
     third_party/protobuf/src/google/protobuf/dynamic_message.cc
     third_party/protobuf/src/google/protobuf/dynamic_message.h
     third_party/protobuf/src/google/protobuf/dynamic_message_unittest.cc
    +third_party/protobuf/src/google/protobuf/edition_message_unittest.cc
     third_party/protobuf/src/google/protobuf/endian.h
     third_party/protobuf/src/google/protobuf/explicitly_constructed.h
     third_party/protobuf/src/google/protobuf/extension_set.cc
    @@ -12688,10 +12869,14 @@ third_party/protobuf/src/google/protobuf/extension_set.h
     third_party/protobuf/src/google/protobuf/extension_set_heavy.cc
     third_party/protobuf/src/google/protobuf/extension_set_inl.h
     third_party/protobuf/src/google/protobuf/extension_set_unittest.cc
    +third_party/protobuf/src/google/protobuf/feature_resolver.cc
    +third_party/protobuf/src/google/protobuf/feature_resolver.h
    +third_party/protobuf/src/google/protobuf/feature_resolver_test.cc
     third_party/protobuf/src/google/protobuf/field_access_listener.h
     third_party/protobuf/src/google/protobuf/generated_enum_reflection.h
     third_party/protobuf/src/google/protobuf/generated_enum_util.cc
     third_party/protobuf/src/google/protobuf/generated_enum_util.h
    +third_party/protobuf/src/google/protobuf/generated_enum_util_test.cc
     third_party/protobuf/src/google/protobuf/generated_message_bases.cc
     third_party/protobuf/src/google/protobuf/generated_message_bases.h
     third_party/protobuf/src/google/protobuf/generated_message_reflection.cc
    @@ -12699,17 +12884,23 @@ third_party/protobuf/src/google/protobuf/generated_message_reflection.h
     third_party/protobuf/src/google/protobuf/generated_message_reflection_unittest.cc
     third_party/protobuf/src/google/protobuf/generated_message_tctable_decl.h
     third_party/protobuf/src/google/protobuf/generated_message_tctable_full.cc
    +third_party/protobuf/src/google/protobuf/generated_message_tctable_gen.cc
    +third_party/protobuf/src/google/protobuf/generated_message_tctable_gen.h
     third_party/protobuf/src/google/protobuf/generated_message_tctable_impl.h
     third_party/protobuf/src/google/protobuf/generated_message_tctable_lite.cc
     third_party/protobuf/src/google/protobuf/generated_message_tctable_lite_test.cc
     third_party/protobuf/src/google/protobuf/generated_message_util.cc
     third_party/protobuf/src/google/protobuf/generated_message_util.h
     third_party/protobuf/src/google/protobuf/has_bits.h
    +third_party/protobuf/src/google/protobuf/has_bits_test.cc
     third_party/protobuf/src/google/protobuf/implicit_weak_message.cc
     third_party/protobuf/src/google/protobuf/implicit_weak_message.h
     third_party/protobuf/src/google/protobuf/inlined_string_field.cc
     third_party/protobuf/src/google/protobuf/inlined_string_field.h
     third_party/protobuf/src/google/protobuf/inlined_string_field_unittest.cc
    +third_party/protobuf/src/google/protobuf/internal_message_util_unittest.cc
    +third_party/protobuf/src/google/protobuf/internal_visibility.h
    +third_party/protobuf/src/google/protobuf/internal_visibility_for_testing.h
     third_party/protobuf/src/google/protobuf/io/coded_stream.cc
     third_party/protobuf/src/google/protobuf/io/coded_stream.h
     third_party/protobuf/src/google/protobuf/io/coded_stream_unittest.cc
    @@ -12721,12 +12912,18 @@ third_party/protobuf/src/google/protobuf/io/io_win32_unittest.cc
     third_party/protobuf/src/google/protobuf/io/package_info.h
     third_party/protobuf/src/google/protobuf/io/printer.cc
     third_party/protobuf/src/google/protobuf/io/printer.h
    +third_party/protobuf/src/google/protobuf/io/printer_death_test.cc
     third_party/protobuf/src/google/protobuf/io/printer_unittest.cc
     third_party/protobuf/src/google/protobuf/io/strtod.cc
     third_party/protobuf/src/google/protobuf/io/strtod.h
    +third_party/protobuf/src/google/protobuf/io/test_zero_copy_stream.h
    +third_party/protobuf/src/google/protobuf/io/test_zero_copy_stream_test.cc
     third_party/protobuf/src/google/protobuf/io/tokenizer.cc
     third_party/protobuf/src/google/protobuf/io/tokenizer.h
     third_party/protobuf/src/google/protobuf/io/tokenizer_unittest.cc
    +third_party/protobuf/src/google/protobuf/io/zero_copy_sink.cc
    +third_party/protobuf/src/google/protobuf/io/zero_copy_sink.h
    +third_party/protobuf/src/google/protobuf/io/zero_copy_sink_test.cc
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream.cc
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream.h
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc
    @@ -12734,12 +12931,33 @@ third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.h
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h
     third_party/protobuf/src/google/protobuf/io/zero_copy_stream_unittest.cc
    +third_party/protobuf/src/google/protobuf/json/internal/descriptor_traits.h
    +third_party/protobuf/src/google/protobuf/json/internal/lexer.cc
    +third_party/protobuf/src/google/protobuf/json/internal/lexer.h
    +third_party/protobuf/src/google/protobuf/json/internal/lexer_test.cc
    +third_party/protobuf/src/google/protobuf/json/internal/message_path.cc
    +third_party/protobuf/src/google/protobuf/json/internal/message_path.h
    +third_party/protobuf/src/google/protobuf/json/internal/parser.cc
    +third_party/protobuf/src/google/protobuf/json/internal/parser.h
    +third_party/protobuf/src/google/protobuf/json/internal/parser_traits.h
    +third_party/protobuf/src/google/protobuf/json/internal/unparser.cc
    +third_party/protobuf/src/google/protobuf/json/internal/unparser.h
    +third_party/protobuf/src/google/protobuf/json/internal/unparser_traits.h
    +third_party/protobuf/src/google/protobuf/json/internal/untyped_message.cc
    +third_party/protobuf/src/google/protobuf/json/internal/untyped_message.h
    +third_party/protobuf/src/google/protobuf/json/internal/writer.cc
    +third_party/protobuf/src/google/protobuf/json/internal/writer.h
    +third_party/protobuf/src/google/protobuf/json/internal/zero_copy_buffered_stream.cc
    +third_party/protobuf/src/google/protobuf/json/internal/zero_copy_buffered_stream.h
    +third_party/protobuf/src/google/protobuf/json/internal/zero_copy_buffered_stream_test.cc
    +third_party/protobuf/src/google/protobuf/json/json.cc
    +third_party/protobuf/src/google/protobuf/json/json.h
    +third_party/protobuf/src/google/protobuf/json/json_test.cc
     third_party/protobuf/src/google/protobuf/lite_arena_unittest.cc
     third_party/protobuf/src/google/protobuf/lite_unittest.cc
     third_party/protobuf/src/google/protobuf/map.cc
     third_party/protobuf/src/google/protobuf/map.h
     third_party/protobuf/src/google/protobuf/map_entry.h
    -third_party/protobuf/src/google/protobuf/map_entry_lite.h
     third_party/protobuf/src/google/protobuf/map_field.cc
     third_party/protobuf/src/google/protobuf/map_field.h
     third_party/protobuf/src/google/protobuf/map_field_inl.h
    @@ -12747,6 +12965,7 @@ third_party/protobuf/src/google/protobuf/map_field_lite.h
     third_party/protobuf/src/google/protobuf/map_field_test.cc
     third_party/protobuf/src/google/protobuf/map_lite_test_util.cc
     third_party/protobuf/src/google/protobuf/map_lite_test_util.h
    +third_party/protobuf/src/google/protobuf/map_probe_benchmark.cc
     third_party/protobuf/src/google/protobuf/map_test.cc
     third_party/protobuf/src/google/protobuf/map_test_util.h
     third_party/protobuf/src/google/protobuf/map_test_util_impl.h
    @@ -12758,17 +12977,27 @@ third_party/protobuf/src/google/protobuf/message_lite.h
     third_party/protobuf/src/google/protobuf/message_unittest.cc
     third_party/protobuf/src/google/protobuf/metadata.h
     third_party/protobuf/src/google/protobuf/metadata_lite.h
    +third_party/protobuf/src/google/protobuf/no_field_presence_map_test.cc
     third_party/protobuf/src/google/protobuf/no_field_presence_test.cc
     third_party/protobuf/src/google/protobuf/package_info.h
     third_party/protobuf/src/google/protobuf/parse_context.cc
     third_party/protobuf/src/google/protobuf/parse_context.h
    +third_party/protobuf/src/google/protobuf/port.cc
     third_party/protobuf/src/google/protobuf/port.h
    +third_party/protobuf/src/google/protobuf/port_test.cc
     third_party/protobuf/src/google/protobuf/preserve_unknown_enum_test.cc
     third_party/protobuf/src/google/protobuf/proto3_arena_lite_unittest.cc
     third_party/protobuf/src/google/protobuf/proto3_arena_unittest.cc
     third_party/protobuf/src/google/protobuf/proto3_lite_unittest.cc
    +third_party/protobuf/src/google/protobuf/raw_ptr.cc
    +third_party/protobuf/src/google/protobuf/raw_ptr.h
    +third_party/protobuf/src/google/protobuf/raw_ptr_test.cc
    +third_party/protobuf/src/google/protobuf/redaction_metric_test.cc
     third_party/protobuf/src/google/protobuf/reflection.h
     third_party/protobuf/src/google/protobuf/reflection_internal.h
    +third_party/protobuf/src/google/protobuf/reflection_mode.cc
    +third_party/protobuf/src/google/protobuf/reflection_mode.h
    +third_party/protobuf/src/google/protobuf/reflection_mode_test.cc
     third_party/protobuf/src/google/protobuf/reflection_ops.cc
     third_party/protobuf/src/google/protobuf/reflection_ops.h
     third_party/protobuf/src/google/protobuf/reflection_ops_unittest.cc
    @@ -12780,52 +13009,23 @@ third_party/protobuf/src/google/protobuf/repeated_field_reflection_unittest.cc
     third_party/protobuf/src/google/protobuf/repeated_field_unittest.cc
     third_party/protobuf/src/google/protobuf/repeated_ptr_field.cc
     third_party/protobuf/src/google/protobuf/repeated_ptr_field.h
    +third_party/protobuf/src/google/protobuf/repeated_ptr_field_unittest.cc
    +third_party/protobuf/src/google/protobuf/retention_test.cc
    +third_party/protobuf/src/google/protobuf/serial_arena.h
     third_party/protobuf/src/google/protobuf/service.cc
     third_party/protobuf/src/google/protobuf/service.h
    +third_party/protobuf/src/google/protobuf/string_block.h
    +third_party/protobuf/src/google/protobuf/string_block_test.cc
     third_party/protobuf/src/google/protobuf/string_member_robber.h
    -third_party/protobuf/src/google/protobuf/stubs/bytestream.cc
    -third_party/protobuf/src/google/protobuf/stubs/bytestream.h
    -third_party/protobuf/src/google/protobuf/stubs/bytestream_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/casts.h
    +third_party/protobuf/src/google/protobuf/string_piece_field_support_unittest.cc
    +third_party/protobuf/src/google/protobuf/stubs/callback.h
     third_party/protobuf/src/google/protobuf/stubs/common.cc
     third_party/protobuf/src/google/protobuf/stubs/common.h
     third_party/protobuf/src/google/protobuf/stubs/common_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/hash.h
    -third_party/protobuf/src/google/protobuf/stubs/int128.cc
    -third_party/protobuf/src/google/protobuf/stubs/int128.h
    -third_party/protobuf/src/google/protobuf/stubs/int128_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/logging.h
    -third_party/protobuf/src/google/protobuf/stubs/macros.h
    -third_party/protobuf/src/google/protobuf/stubs/map_util.h
    -third_party/protobuf/src/google/protobuf/stubs/mathutil.h
    -third_party/protobuf/src/google/protobuf/stubs/once.h
     third_party/protobuf/src/google/protobuf/stubs/platform_macros.h
     third_party/protobuf/src/google/protobuf/stubs/port.h
    -third_party/protobuf/src/google/protobuf/stubs/status.cc
    -third_party/protobuf/src/google/protobuf/stubs/status.h
     third_party/protobuf/src/google/protobuf/stubs/status_macros.h
    -third_party/protobuf/src/google/protobuf/stubs/status_test.cc
    -third_party/protobuf/src/google/protobuf/stubs/statusor.cc
    -third_party/protobuf/src/google/protobuf/stubs/statusor.h
    -third_party/protobuf/src/google/protobuf/stubs/statusor_test.cc
    -third_party/protobuf/src/google/protobuf/stubs/stl_util.h
    -third_party/protobuf/src/google/protobuf/stubs/stringpiece.cc
    -third_party/protobuf/src/google/protobuf/stubs/stringpiece.h
    -third_party/protobuf/src/google/protobuf/stubs/stringpiece_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/stringprintf.cc
    -third_party/protobuf/src/google/protobuf/stubs/stringprintf.h
    -third_party/protobuf/src/google/protobuf/stubs/stringprintf_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/structurally_valid.cc
    -third_party/protobuf/src/google/protobuf/stubs/structurally_valid_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/strutil.cc
    -third_party/protobuf/src/google/protobuf/stubs/strutil.h
    -third_party/protobuf/src/google/protobuf/stubs/strutil_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/substitute.cc
    -third_party/protobuf/src/google/protobuf/stubs/substitute.h
    -third_party/protobuf/src/google/protobuf/stubs/template_util.h
    -third_party/protobuf/src/google/protobuf/stubs/template_util_unittest.cc
    -third_party/protobuf/src/google/protobuf/stubs/time.h
    -third_party/protobuf/src/google/protobuf/stubs/time_test.cc
    +third_party/protobuf/src/google/protobuf/test_textproto.h
     third_party/protobuf/src/google/protobuf/test_util.cc
     third_party/protobuf/src/google/protobuf/test_util.h
     third_party/protobuf/src/google/protobuf/test_util2.h
    @@ -12835,11 +13035,10 @@ third_party/protobuf/src/google/protobuf/testing/file.cc
     third_party/protobuf/src/google/protobuf/testing/file.h
     third_party/protobuf/src/google/protobuf/testing/googletest.cc
     third_party/protobuf/src/google/protobuf/testing/googletest.h
    -third_party/protobuf/src/google/protobuf/testing/zcgunzip.cc
    -third_party/protobuf/src/google/protobuf/testing/zcgzip.cc
     third_party/protobuf/src/google/protobuf/text_format.cc
     third_party/protobuf/src/google/protobuf/text_format.h
     third_party/protobuf/src/google/protobuf/text_format_unittest.cc
    +third_party/protobuf/src/google/protobuf/thread_safe_arena.h
     third_party/protobuf/src/google/protobuf/unknown_field_set.cc
     third_party/protobuf/src/google/protobuf/unknown_field_set.h
     third_party/protobuf/src/google/protobuf/unknown_field_set_unittest.cc
    @@ -12852,49 +13051,7 @@ third_party/protobuf/src/google/protobuf/util/field_comparator_test.cc
     third_party/protobuf/src/google/protobuf/util/field_mask_util.cc
     third_party/protobuf/src/google/protobuf/util/field_mask_util.h
     third_party/protobuf/src/google/protobuf/util/field_mask_util_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/constants.h
    -third_party/protobuf/src/google/protobuf/util/internal/datapiece.cc
    -third_party/protobuf/src/google/protobuf/util/internal/datapiece.h
    -third_party/protobuf/src/google/protobuf/util/internal/default_value_objectwriter.cc
    -third_party/protobuf/src/google/protobuf/util/internal/default_value_objectwriter.h
    -third_party/protobuf/src/google/protobuf/util/internal/default_value_objectwriter_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/error_listener.cc
    -third_party/protobuf/src/google/protobuf/util/internal/error_listener.h
    -third_party/protobuf/src/google/protobuf/util/internal/expecting_objectwriter.h
    -third_party/protobuf/src/google/protobuf/util/internal/field_mask_utility.cc
    -third_party/protobuf/src/google/protobuf/util/internal/field_mask_utility.h
    -third_party/protobuf/src/google/protobuf/util/internal/json_escaping.cc
    -third_party/protobuf/src/google/protobuf/util/internal/json_escaping.h
    -third_party/protobuf/src/google/protobuf/util/internal/json_objectwriter.cc
    -third_party/protobuf/src/google/protobuf/util/internal/json_objectwriter.h
    -third_party/protobuf/src/google/protobuf/util/internal/json_objectwriter_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/json_stream_parser.cc
    -third_party/protobuf/src/google/protobuf/util/internal/json_stream_parser.h
    -third_party/protobuf/src/google/protobuf/util/internal/json_stream_parser_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/location_tracker.h
    -third_party/protobuf/src/google/protobuf/util/internal/mock_error_listener.h
    -third_party/protobuf/src/google/protobuf/util/internal/object_location_tracker.h
    -third_party/protobuf/src/google/protobuf/util/internal/object_source.h
    -third_party/protobuf/src/google/protobuf/util/internal/object_writer.cc
    -third_party/protobuf/src/google/protobuf/util/internal/object_writer.h
    -third_party/protobuf/src/google/protobuf/util/internal/proto_writer.cc
    -third_party/protobuf/src/google/protobuf/util/internal/proto_writer.h
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectsource.cc
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectsource.h
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectsource_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectwriter.cc
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectwriter.h
    -third_party/protobuf/src/google/protobuf/util/internal/protostream_objectwriter_test.cc
    -third_party/protobuf/src/google/protobuf/util/internal/structured_objectwriter.h
    -third_party/protobuf/src/google/protobuf/util/internal/type_info.cc
    -third_party/protobuf/src/google/protobuf/util/internal/type_info.h
    -third_party/protobuf/src/google/protobuf/util/internal/type_info_test_helper.cc
    -third_party/protobuf/src/google/protobuf/util/internal/type_info_test_helper.h
    -third_party/protobuf/src/google/protobuf/util/internal/utility.cc
    -third_party/protobuf/src/google/protobuf/util/internal/utility.h
    -third_party/protobuf/src/google/protobuf/util/json_util.cc
     third_party/protobuf/src/google/protobuf/util/json_util.h
    -third_party/protobuf/src/google/protobuf/util/json_util_test.cc
     third_party/protobuf/src/google/protobuf/util/message_differencer.cc
     third_party/protobuf/src/google/protobuf/util/message_differencer.h
     third_party/protobuf/src/google/protobuf/util/message_differencer_unittest.cc
    @@ -12906,13 +13063,268 @@ third_party/protobuf/src/google/protobuf/util/type_resolver.h
     third_party/protobuf/src/google/protobuf/util/type_resolver_util.cc
     third_party/protobuf/src/google/protobuf/util/type_resolver_util.h
     third_party/protobuf/src/google/protobuf/util/type_resolver_util_test.cc
    +third_party/protobuf/src/google/protobuf/varint_shuffle.h
    +third_party/protobuf/src/google/protobuf/varint_shuffle_test.cc
     third_party/protobuf/src/google/protobuf/well_known_types_unittest.cc
     third_party/protobuf/src/google/protobuf/wire_format.cc
     third_party/protobuf/src/google/protobuf/wire_format.h
     third_party/protobuf/src/google/protobuf/wire_format_lite.cc
     third_party/protobuf/src/google/protobuf/wire_format_lite.h
     third_party/protobuf/src/google/protobuf/wire_format_unittest.cc
    -third_party/protobuf/update_version.py
    +third_party/protobuf/upb/base/descriptor_constants.h
    +third_party/protobuf/upb/base/internal/endian.h
    +third_party/protobuf/upb/base/internal/log2.h
    +third_party/protobuf/upb/base/status.c
    +third_party/protobuf/upb/base/status.h
    +third_party/protobuf/upb/base/status.hpp
    +third_party/protobuf/upb/base/string_view.h
    +third_party/protobuf/upb/base/upcast.h
    +third_party/protobuf/upb/bazel/amalgamate.py
    +third_party/protobuf/upb/cmake/make_cmakelists.py
    +third_party/protobuf/upb/cmake/staleness_test.py
    +third_party/protobuf/upb/cmake/staleness_test_lib.py
    +third_party/protobuf/upb/conformance/conformance_upb.c
    +third_party/protobuf/upb/generated_code_support.h
    +third_party/protobuf/upb/hash/common.c
    +third_party/protobuf/upb/hash/common.h
    +third_party/protobuf/upb/hash/int_table.h
    +third_party/protobuf/upb/hash/str_table.h
    +third_party/protobuf/upb/hash/test.cc
    +third_party/protobuf/upb/io/chunked_input_stream.c
    +third_party/protobuf/upb/io/chunked_input_stream.h
    +third_party/protobuf/upb/io/chunked_output_stream.c
    +third_party/protobuf/upb/io/chunked_output_stream.h
    +third_party/protobuf/upb/io/string.h
    +third_party/protobuf/upb/io/string_test.cc
    +third_party/protobuf/upb/io/tokenizer.c
    +third_party/protobuf/upb/io/tokenizer.h
    +third_party/protobuf/upb/io/tokenizer_test.cc
    +third_party/protobuf/upb/io/zero_copy_input_stream.h
    +third_party/protobuf/upb/io/zero_copy_output_stream.h
    +third_party/protobuf/upb/io/zero_copy_stream_test.cc
    +third_party/protobuf/upb/json/decode.c
    +third_party/protobuf/upb/json/decode.h
    +third_party/protobuf/upb/json/decode_test.cc
    +third_party/protobuf/upb/json/encode.c
    +third_party/protobuf/upb/json/encode.h
    +third_party/protobuf/upb/json/encode_test.cc
    +third_party/protobuf/upb/json/fuzz_test.cc
    +third_party/protobuf/upb/lex/atoi.c
    +third_party/protobuf/upb/lex/atoi.h
    +third_party/protobuf/upb/lex/atoi_test.cc
    +third_party/protobuf/upb/lex/round_trip.c
    +third_party/protobuf/upb/lex/round_trip.h
    +third_party/protobuf/upb/lex/strtod.c
    +third_party/protobuf/upb/lex/strtod.h
    +third_party/protobuf/upb/lex/unicode.c
    +third_party/protobuf/upb/lex/unicode.h
    +third_party/protobuf/upb/mem/alloc.c
    +third_party/protobuf/upb/mem/alloc.h
    +third_party/protobuf/upb/mem/arena.c
    +third_party/protobuf/upb/mem/arena.h
    +third_party/protobuf/upb/mem/arena.hpp
    +third_party/protobuf/upb/mem/arena_test.cc
    +third_party/protobuf/upb/mem/internal/arena.h
    +third_party/protobuf/upb/message/accessors.c
    +third_party/protobuf/upb/message/accessors.h
    +third_party/protobuf/upb/message/accessors_split64.h
    +third_party/protobuf/upb/message/accessors_test.cc
    +third_party/protobuf/upb/message/array.c
    +third_party/protobuf/upb/message/array.h
    +third_party/protobuf/upb/message/array_test.cc
    +third_party/protobuf/upb/message/compare.c
    +third_party/protobuf/upb/message/compare.h
    +third_party/protobuf/upb/message/compat.c
    +third_party/protobuf/upb/message/compat.h
    +third_party/protobuf/upb/message/copy.c
    +third_party/protobuf/upb/message/copy.h
    +third_party/protobuf/upb/message/copy_test.cc
    +third_party/protobuf/upb/message/internal/accessors.h
    +third_party/protobuf/upb/message/internal/array.h
    +third_party/protobuf/upb/message/internal/compare_unknown.c
    +third_party/protobuf/upb/message/internal/compare_unknown.h
    +third_party/protobuf/upb/message/internal/compare_unknown_test.cc
    +third_party/protobuf/upb/message/internal/extension.c
    +third_party/protobuf/upb/message/internal/extension.h
    +third_party/protobuf/upb/message/internal/iterator.c
    +third_party/protobuf/upb/message/internal/iterator.h
    +third_party/protobuf/upb/message/internal/map.h
    +third_party/protobuf/upb/message/internal/map_entry.h
    +third_party/protobuf/upb/message/internal/map_sorter.h
    +third_party/protobuf/upb/message/internal/message.c
    +third_party/protobuf/upb/message/internal/message.h
    +third_party/protobuf/upb/message/internal/tagged_ptr.h
    +third_party/protobuf/upb/message/internal/types.h
    +third_party/protobuf/upb/message/map.c
    +third_party/protobuf/upb/message/map.h
    +third_party/protobuf/upb/message/map_gencode_util.h
    +third_party/protobuf/upb/message/map_sorter.c
    +third_party/protobuf/upb/message/map_test.cc
    +third_party/protobuf/upb/message/message.c
    +third_party/protobuf/upb/message/message.h
    +third_party/protobuf/upb/message/promote.c
    +third_party/protobuf/upb/message/promote.h
    +third_party/protobuf/upb/message/promote_test.cc
    +third_party/protobuf/upb/message/tagged_ptr.h
    +third_party/protobuf/upb/message/test.cc
    +third_party/protobuf/upb/message/utf8_test.cc
    +third_party/protobuf/upb/message/value.h
    +third_party/protobuf/upb/mini_descriptor/build_enum.c
    +third_party/protobuf/upb/mini_descriptor/build_enum.h
    +third_party/protobuf/upb/mini_descriptor/decode.c
    +third_party/protobuf/upb/mini_descriptor/decode.h
    +third_party/protobuf/upb/mini_descriptor/internal/base92.c
    +third_party/protobuf/upb/mini_descriptor/internal/base92.h
    +third_party/protobuf/upb/mini_descriptor/internal/decoder.h
    +third_party/protobuf/upb/mini_descriptor/internal/encode.c
    +third_party/protobuf/upb/mini_descriptor/internal/encode.h
    +third_party/protobuf/upb/mini_descriptor/internal/encode.hpp
    +third_party/protobuf/upb/mini_descriptor/internal/encode_test.cc
    +third_party/protobuf/upb/mini_descriptor/internal/modifiers.h
    +third_party/protobuf/upb/mini_descriptor/internal/wire_constants.h
    +third_party/protobuf/upb/mini_descriptor/link.c
    +third_party/protobuf/upb/mini_descriptor/link.h
    +third_party/protobuf/upb/mini_table/compat.c
    +third_party/protobuf/upb/mini_table/compat.h
    +third_party/protobuf/upb/mini_table/compat_test.cc
    +third_party/protobuf/upb/mini_table/enum.h
    +third_party/protobuf/upb/mini_table/extension.h
    +third_party/protobuf/upb/mini_table/extension_registry.c
    +third_party/protobuf/upb/mini_table/extension_registry.h
    +third_party/protobuf/upb/mini_table/field.h
    +third_party/protobuf/upb/mini_table/file.h
    +third_party/protobuf/upb/mini_table/internal/enum.h
    +third_party/protobuf/upb/mini_table/internal/extension.h
    +third_party/protobuf/upb/mini_table/internal/field.h
    +third_party/protobuf/upb/mini_table/internal/file.h
    +third_party/protobuf/upb/mini_table/internal/message.c
    +third_party/protobuf/upb/mini_table/internal/message.h
    +third_party/protobuf/upb/mini_table/internal/size_log2.h
    +third_party/protobuf/upb/mini_table/internal/sub.h
    +third_party/protobuf/upb/mini_table/message.c
    +third_party/protobuf/upb/mini_table/message.h
    +third_party/protobuf/upb/mini_table/sub.h
    +third_party/protobuf/upb/port/atomic.h
    +third_party/protobuf/upb/port/vsnprintf_compat.h
    +third_party/protobuf/upb/reflection/common.h
    +third_party/protobuf/upb/reflection/def.h
    +third_party/protobuf/upb/reflection/def.hpp
    +third_party/protobuf/upb/reflection/def_pool.c
    +third_party/protobuf/upb/reflection/def_pool.h
    +third_party/protobuf/upb/reflection/def_type.c
    +third_party/protobuf/upb/reflection/def_type.h
    +third_party/protobuf/upb/reflection/desc_state.c
    +third_party/protobuf/upb/reflection/enum_def.c
    +third_party/protobuf/upb/reflection/enum_def.h
    +third_party/protobuf/upb/reflection/enum_reserved_range.c
    +third_party/protobuf/upb/reflection/enum_reserved_range.h
    +third_party/protobuf/upb/reflection/enum_value_def.c
    +third_party/protobuf/upb/reflection/enum_value_def.h
    +third_party/protobuf/upb/reflection/extension_range.c
    +third_party/protobuf/upb/reflection/extension_range.h
    +third_party/protobuf/upb/reflection/field_def.c
    +third_party/protobuf/upb/reflection/field_def.h
    +third_party/protobuf/upb/reflection/file_def.c
    +third_party/protobuf/upb/reflection/file_def.h
    +third_party/protobuf/upb/reflection/internal/def_builder.c
    +third_party/protobuf/upb/reflection/internal/def_builder.h
    +third_party/protobuf/upb/reflection/internal/def_builder_test.cc
    +third_party/protobuf/upb/reflection/internal/def_pool.h
    +third_party/protobuf/upb/reflection/internal/desc_state.h
    +third_party/protobuf/upb/reflection/internal/enum_def.h
    +third_party/protobuf/upb/reflection/internal/enum_reserved_range.h
    +third_party/protobuf/upb/reflection/internal/enum_value_def.h
    +third_party/protobuf/upb/reflection/internal/extension_range.h
    +third_party/protobuf/upb/reflection/internal/field_def.h
    +third_party/protobuf/upb/reflection/internal/file_def.h
    +third_party/protobuf/upb/reflection/internal/message_def.h
    +third_party/protobuf/upb/reflection/internal/message_reserved_range.h
    +third_party/protobuf/upb/reflection/internal/method_def.h
    +third_party/protobuf/upb/reflection/internal/oneof_def.h
    +third_party/protobuf/upb/reflection/internal/service_def.h
    +third_party/protobuf/upb/reflection/internal/strdup2.c
    +third_party/protobuf/upb/reflection/internal/strdup2.h
    +third_party/protobuf/upb/reflection/internal/upb_edition_defaults.h
    +third_party/protobuf/upb/reflection/message.c
    +third_party/protobuf/upb/reflection/message.h
    +third_party/protobuf/upb/reflection/message.hpp
    +third_party/protobuf/upb/reflection/message_def.c
    +third_party/protobuf/upb/reflection/message_def.h
    +third_party/protobuf/upb/reflection/message_reserved_range.c
    +third_party/protobuf/upb/reflection/message_reserved_range.h
    +third_party/protobuf/upb/reflection/method_def.c
    +third_party/protobuf/upb/reflection/method_def.h
    +third_party/protobuf/upb/reflection/oneof_def.c
    +third_party/protobuf/upb/reflection/oneof_def.h
    +third_party/protobuf/upb/reflection/service_def.c
    +third_party/protobuf/upb/reflection/service_def.h
    +third_party/protobuf/upb/test/editions_test.cc
    +third_party/protobuf/upb/test/fuzz_util.cc
    +third_party/protobuf/upb/test/fuzz_util.h
    +third_party/protobuf/upb/test/parse_text_proto.h
    +third_party/protobuf/upb/test/proto3_test.cc
    +third_party/protobuf/upb/test/test_cpp.cc
    +third_party/protobuf/upb/test/test_generated_code.cc
    +third_party/protobuf/upb/test/test_import_empty_srcs.cc
    +third_party/protobuf/upb/test/test_mini_table_oneof.cc
    +third_party/protobuf/upb/text/debug_string.c
    +third_party/protobuf/upb/text/debug_string.h
    +third_party/protobuf/upb/text/encode.c
    +third_party/protobuf/upb/text/encode.h
    +third_party/protobuf/upb/text/encode_debug_test.cc
    +third_party/protobuf/upb/text/internal/encode.c
    +third_party/protobuf/upb/text/internal/encode.h
    +third_party/protobuf/upb/text/options.h
    +third_party/protobuf/upb/util/def_to_proto.c
    +third_party/protobuf/upb/util/def_to_proto.h
    +third_party/protobuf/upb/util/def_to_proto_fuzz_test.cc
    +third_party/protobuf/upb/util/def_to_proto_test.cc
    +third_party/protobuf/upb/util/def_to_proto_test.h
    +third_party/protobuf/upb/util/required_fields.c
    +third_party/protobuf/upb/util/required_fields.h
    +third_party/protobuf/upb/util/required_fields_test.cc
    +third_party/protobuf/upb/wire/byte_size.c
    +third_party/protobuf/upb/wire/byte_size.h
    +third_party/protobuf/upb/wire/byte_size_test.cc
    +third_party/protobuf/upb/wire/decode.c
    +third_party/protobuf/upb/wire/decode.h
    +third_party/protobuf/upb/wire/encode.c
    +third_party/protobuf/upb/wire/encode.h
    +third_party/protobuf/upb/wire/eps_copy_input_stream.c
    +third_party/protobuf/upb/wire/eps_copy_input_stream.h
    +third_party/protobuf/upb/wire/eps_copy_input_stream_test.cc
    +third_party/protobuf/upb/wire/internal/constants.h
    +third_party/protobuf/upb/wire/internal/decode_fast.c
    +third_party/protobuf/upb/wire/internal/decode_fast.h
    +third_party/protobuf/upb/wire/internal/decoder.h
    +third_party/protobuf/upb/wire/internal/reader.h
    +third_party/protobuf/upb/wire/reader.c
    +third_party/protobuf/upb/wire/reader.h
    +third_party/protobuf/upb/wire/types.h
    +third_party/protobuf/upb_generator/c/generator.cc
    +third_party/protobuf/upb_generator/c/names.cc
    +third_party/protobuf/upb_generator/c/names.h
    +third_party/protobuf/upb_generator/c/names_internal.cc
    +third_party/protobuf/upb_generator/c/names_internal.h
    +third_party/protobuf/upb_generator/common.cc
    +third_party/protobuf/upb_generator/common.h
    +third_party/protobuf/upb_generator/common/names.cc
    +third_party/protobuf/upb_generator/common/names.h
    +third_party/protobuf/upb_generator/file_layout.cc
    +third_party/protobuf/upb_generator/file_layout.h
    +third_party/protobuf/upb_generator/minitable/fasttable.cc
    +third_party/protobuf/upb_generator/minitable/fasttable.h
    +third_party/protobuf/upb_generator/minitable/generator.cc
    +third_party/protobuf/upb_generator/minitable/generator.h
    +third_party/protobuf/upb_generator/minitable/main.cc
    +third_party/protobuf/upb_generator/minitable/names.cc
    +third_party/protobuf/upb_generator/minitable/names.h
    +third_party/protobuf/upb_generator/minitable/names_internal.cc
    +third_party/protobuf/upb_generator/minitable/names_internal.h
    +third_party/protobuf/upb_generator/plugin.h
    +third_party/protobuf/upb_generator/reflection/generator.cc
    +third_party/protobuf/upb_generator/reflection/names.cc
    +third_party/protobuf/upb_generator/reflection/names.h
     third_party/pthreadpool/chromium/jobs.cc
     third_party/pycoverage/coverage/htmlfiles/pyfile.html
     third_party/pyelftools/elftools/__init__.py
    @@ -12996,6 +13408,7 @@ third_party/rust/diplomat/v0_9/BUILD.gn
     third_party/rust/diplomat_core/v0_9/BUILD.gn
     third_party/rust/diplomat_runtime/v0_9/BUILD.gn
     third_party/rust/foldhash/v0_1/BUILD.gn
    +third_party/rust/hashbrown/v0_15/BUILD.gn
     third_party/rust/heck/v0_4/BUILD.gn
     third_party/rust/itoa/v1/BUILD.gn
     third_party/rust/lazy_static/v1/BUILD.gn
    @@ -13020,6 +13433,7 @@ third_party/rust/serde/v1/BUILD.gn
     third_party/rust/serde_derive/v1/BUILD.gn
     third_party/rust/serde_json/v1/BUILD.gn
     third_party/rust/serde_json_lenient/v0_2/BUILD.gn
    +third_party/rust/sfv/v0_10/BUILD.gn
     third_party/rust/stable_deref_trait/v1/BUILD.gn
     third_party/rust/strck/v1/BUILD.gn
     third_party/rust/strum/v0_25/BUILD.gn
    @@ -13077,9 +13491,9 @@ third_party/skia/fuzz/FuzzPathop.cpp
     third_party/skia/gm/addarc.cpp
     third_party/skia/gm/annotated_text.cpp
     third_party/skia/gm/bitmapshader.cpp
    -third_party/skia/gm/blurroundrect.cpp
     third_party/skia/gm/crbug_224618.cpp
     third_party/skia/gm/dashcubics.cpp
    +third_party/skia/gm/gradients.cpp
     third_party/skia/gm/labyrinth.cpp
     third_party/skia/gm/pictureshader.cpp
     third_party/skia/gm/png_codec.cpp
    @@ -13120,6 +13534,8 @@ third_party/skia/infra/bots/recipe_modules/gsutil/examples/full.py
     third_party/skia/infra/bots/recipe_modules/infra/examples/full.py
     third_party/skia/infra/bots/recipe_modules/run/examples/full.py
     third_party/skia/infra/bots/recipe_modules/vars/examples/full.py
    +third_party/skia/infra/bots/recipe_modules/xcode/api.py
    +third_party/skia/infra/bots/recipe_modules/xcode/examples/full.py
     third_party/skia/infra/bots/recipes.py
     third_party/skia/infra/bots/recipes/compile.py
     third_party/skia/infra/bots/recipes/compute_buildstats.expected/normal_bot.json
    @@ -13239,6 +13655,8 @@ third_party/skia/tests/BlurTest.cpp
     third_party/skia/tests/CanvasTest.cpp
     third_party/skia/tests/ClipperTest.cpp
     third_party/skia/tests/CodecTest.cpp
    +third_party/skia/tests/ColorPrivTest.cpp
    +third_party/skia/tests/CtsEnforcement.h
     third_party/skia/tests/DrawPathTest.cpp
     third_party/skia/tests/DrawTextTest.cpp
     third_party/skia/tests/FillPathTest.cpp
    @@ -13516,6 +13934,43 @@ third_party/speedometer/v3.0/resources/todomvc/vanilla-examples/javascript-es5/s
     third_party/speedometer/v3.0/resources/todomvc/vanilla-examples/javascript-es6-webpack-complex/dist/app.css
     third_party/speedometer/v3.0/resources/todomvc/vanilla-examples/javascript-es6-webpack/package-lock.json
     third_party/speedometer/v3.0/resources/todomvc/vanilla-examples/javascript-es6-webpack/src/helpers.js
    +third_party/speedometer/v3.1/resources/editors/assets/longscript.js
    +third_party/speedometer/v3.1/resources/editors/dist/assets/codemirror-521de7ab.js
    +third_party/speedometer/v3.1/resources/editors/dist/assets/tiptap-95a40ba8.js
    +third_party/speedometer/v3.1/resources/newssite/news-next/dist/_next/static/chunks/main-2ba37e62325cc71b.js
    +third_party/speedometer/v3.1/resources/perf.webkit.org/public/data/manifest.json
    +third_party/speedometer/v3.1/resources/react-stockcharts/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/angular-complex/dist/3rdpartylicenses.txt
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/angular-complex/dist/styles.746a93d9fd4de753.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/angular/dist/3rdpartylicenses.txt
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/backbone-complex/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/backbone/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/jquery-complex/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/jquery/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/lit/src/lib/todo-item.ts
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/preact-complex/dist/app.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/preact/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/react-complex/dist/app.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/react-redux-complex/dist/app.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/react-redux/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/react/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/svelte-complex/dist/app.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/vue-complex/dist/css/app.319576e1.css
    +third_party/speedometer/v3.1/resources/todomvc/architecture-examples/vue/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/big-dom-generator/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/big-dom-generator/utils/app.css
    +third_party/speedometer/v3.1/resources/todomvc/todomvc-css/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/todomvc-css/dist/todo-item.css
    +third_party/speedometer/v3.1/resources/todomvc/todomvc-css/dist/todo-item.module.css
    +third_party/speedometer/v3.1/resources/todomvc/todomvc-css/src/css/todo-item.css
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es5-complex/dist/helpers.js
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es5-complex/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es5/dist/helpers.js
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es5/dist/index.css
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es5/src/helpers.js
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es6-webpack-complex/dist/app.css
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es6-webpack/package-lock.json
    +third_party/speedometer/v3.1/resources/todomvc/vanilla-examples/javascript-es6-webpack/src/helpers.js
     third_party/spirv-cross/src/spirv_hlsl.cpp
     third_party/spirv-headers/src/include/spirv/spir-v.xml
     third_party/spirv-tools/src/PRESUBMIT.py
    @@ -14214,7 +14669,6 @@ third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wpttest.py
     third_party/wuffs/BUILD.gn
     third_party/wuffs/src/release/c/wuffs-v0.3.c
     third_party/xdg-utils/scripts/desc/xdg-settings.xml
    -third_party/xnnpack/generate_build_gn.py
     third_party/xnnpack/src/CMakeLists.txt
     third_party/zlib/deflate.c
     third_party/zlib/google/compression_utils_portable.cc
    @@ -14324,6 +14778,7 @@ tools/clang/scripts/package.py
     tools/clang/scripts/update.py
     tools/clang/spanify/evaluate_patches.py
     tools/clang/spanify/extract_edits.py
    +tools/clang/spanify/list-required-pragma.py
     tools/clang/translation_unit/test_translation_unit.py
     tools/code_coverage/coverage.py
     tools/code_coverage/js_source_maps/create_js_source_maps/PRESUBMIT.py
    @@ -14527,6 +14982,7 @@ tools/metrics/histograms/metadata/dev/histograms.xml
     tools/metrics/histograms/metadata/disk/histograms.xml
     tools/metrics/histograms/metadata/download/enums.xml
     tools/metrics/histograms/metadata/download/histograms.xml
    +tools/metrics/histograms/metadata/dwa/histograms.xml
     tools/metrics/histograms/metadata/enterprise/enums.xml
     tools/metrics/histograms/metadata/enterprise/histograms.xml
     tools/metrics/histograms/metadata/event/enums.xml
    @@ -14717,8 +15173,6 @@ tools/metrics/histograms/metadata/ukm/histograms.xml
     tools/metrics/histograms/metadata/uma/enums.xml
     tools/metrics/histograms/metadata/uma/histograms.xml
     tools/metrics/histograms/metadata/update_engine/histograms.xml
    -tools/metrics/histograms/metadata/user_annotations/enums.xml
    -tools/metrics/histograms/metadata/user_annotations/histograms.xml
     tools/metrics/histograms/metadata/user_education/histograms.xml
     tools/metrics/histograms/metadata/v8/enums.xml
     tools/metrics/histograms/metadata/v8/histograms.xml
    @@ -14756,6 +15210,10 @@ tools/metrics/histograms/test_data/example_valid_histograms.xml
     tools/metrics/histograms/test_data/histograms.xml
     tools/metrics/histograms/test_data/no_allowlist_entries_histograms.xml
     tools/metrics/histograms/test_data/tokens/token_errors_histograms.xml
    +tools/metrics/histograms/test_data/tokens/variants_inline_histograms.xml
    +tools/metrics/histograms/test_data/tokens/variants_missing_histograms.xml
    +tools/metrics/histograms/test_data/tokens/variants_out_of_line_explicit_histograms.xml
    +tools/metrics/histograms/test_data/tokens/variants_out_of_line_implicit_histograms.xml
     tools/metrics/histograms/test_data/ukm.xml
     tools/metrics/md2xml.py
     tools/metrics/structured/PRESUBMIT.py
    @@ -14855,6 +15313,8 @@ tools/perf/core/test_data/test_timing_data_1_build.json
     tools/perf/download_proto_trace.py
     tools/perf/generate_legacy_perf_dashboard_json.py
     tools/perf/generate_perf_sharding.py
    +tools/perf/json_util.py
    +tools/perf/json_util_unittest.py
     tools/perf/page_sets/ad_frames.py
     tools/perf/page_sets/alexa1-10000-urls.json
     tools/perf/page_sets/companion/basic_companion_story.py
    @@ -14908,6 +15368,7 @@ tools/perf/page_sets/tough_animation_cases/mix_blend_mode_propagating_isolation.
     tools/perf/page_sets/v8_top_25.py
     tools/perf/page_sets/webrtc_cases/codec_constraints.html
     tools/perf/process_perf_results.py
    +tools/perf/process_perf_results_unittest.py
     tools/perfbot-analysis/builder.js
     tools/perfbot-analysis/bulk-download.js
     tools/pgo/generate_profile.py
    @@ -14995,8 +15456,8 @@ ui/accessibility/platform/browser_accessibility_manager_win.cc
     ui/accessibility/platform/fuchsia/browser_accessibility_fuchsia.h
     ui/android/color_utils_android_unittest.cc
     ui/android/java/res/values/strings.xml
    -ui/aura/native_window_occlusion_tracker_win.cc
    -ui/base/accelerators/accelerator.cc
    +ui/base/accelerators/command.cc
    +ui/base/accelerators/command.h
     ui/base/clipboard/clipboard.h
     ui/base/clipboard/clipboard_android.h
     ui/base/clipboard/clipboard_format_type.h
    @@ -15087,6 +15548,7 @@ ui/gfx/icon_util.h
     ui/gfx/render_text.cc
     ui/gfx/render_text_unittest.cc
     ui/gfx/text_elider_unittest.cc
    +ui/gfx/win/hwnd_util.cc
     ui/gfx/win/physical_size.cc
     ui/gfx/win/singleton_hwnd_hot_key_observer.cc
     ui/gl/android/scoped_a_native_window.cc
    @@ -15136,6 +15598,7 @@ ui/views/view_unittest.cc
     ui/views/win/hwnd_message_handler.cc
     ui/views/window/non_client_view.cc
     ui/webui/resources/cr_components/certificate_manager/certificate_manager_v2_icons.html
    +ui/webui/resources/cr_components/cr_shortcut_input/cr_shortcut_input.ts
     ui/webui/resources/cr_components/help_bubble/help_bubble_icons.html
     ui/webui/resources/cr_elements/icons.html.ts
     ui/webui/resources/js/ios/web_ui.js
    @@ -15159,6 +15622,7 @@ url/url_util.cc
     url/url_util.h
     url/url_util_unittest.cc
     v8/PRESUBMIT.py
    +v8/build_overrides/partition_alloc.gni
     v8/include/cppgc/internal/compiler-specific.h
     v8/include/v8-callbacks.h
     v8/include/v8-context.h
    diff --git a/update_cromite_patches.sh b/update_cromite_patches.sh
    index 0a55c13bd408176c6d157864a471f20f52c5e3a6..8eddb250aba20dc464972804f08c6b6ef627e5db 100755
    --- a/update_cromite_patches.sh
    +++ b/update_cromite_patches.sh
    @@ -4,7 +4,7 @@ root_dir=$(dirname "$(readlink -f "$0")")
     
     cd $root_dir/build
     
    -branch="v134.0.6998.89-f13b33b73e22ecaa1ae9a567a8e0c74caf446678"
    +branch="v135.0.7049.100-0ffdb845a6a3308cbd9826bb78269d1d05cfb8aa"
     if [ -d cromite ]; then
         cd cromite
         git fetch origin $branch
    diff --git a/update_domain_substitution.sh b/update_domain_substitution.sh
    index 3841320b6f19a189eb6c911a1ddbb131e270bced..c73395460764097ef63181da16496f7cf39d2b13 100755
    --- a/update_domain_substitution.sh
    +++ b/update_domain_substitution.sh
    @@ -4,7 +4,7 @@ root_dir=$(dirname "$(readlink -f "$0")")
     
     cd $root_dir/domain_substitution
     
    -branch="134.0.6998.88-1"
    +branch="135.0.7049.95-1"
     if [ -d ungoogled-chromium ]; then
         cd ungoogled-chromium
         git fetch origin $branch
    

!HXC)xz9 -z<#;6U+s!<2K9DiT$2@3Z50%-m^VAQd++wstF8!z;m6kBFPoqK(nGa7pV>3}D0%4e@ -z!Ky-el5~6YoY@@XKEH7qxK~&#XT~<>r*lTdoRuUwv~h7CksqTaYqA~XVFeG4WYL{f -zrR~-b`nHROed|55%gGt-o@4TnCT=m!%tL0a0@ph_DWP#KIFjBoxAPBz3HFNzp#uu+ -zR%$6j?h|`Rl0S2d2-1~Jz%Qx?3gq0|5-`3b%Ckibb!DM)I~+RL>&b;t;ZC9G4K6!k -zJ=A;MjH0xe4uZfW4sUaDGrYQGf4vG4bPb13;wuPG)LiV|$;?r)$s!cwfET)xnXCOzK{fT&a_C8ub#mI7S|E2(rJ7@pdaFRn3ppQ`ADa7n1P$i8g(xE+kz -z$Wo(6U_t)`re`fnW3R~*atv}EXJCglfpM7G2?_Y`@5wm6JgRCV$S&crU0@ZUISMD8 -z7~%^Go9f8<ZrWve!z+k=;Y_ourNv>i}arBb}&Aob!PaE -zifkh;UQ%T{{iqO9KoC!-lzz$}YOjwp;z(?JQu@hA+ -zOr>&&pJ^`Ho7`{w3-l{y$+Fq!e<~Ix-x-kOw-20JFzb2wBDzp2+-_S)&FIP -zd7i{ugp3Ap5@uI446+d-*{Ww%ON$05VgS$#;FLa?z6PTnsVPB++?Iz)+l%rc(UQe}5VyXcq;QJ% -z&_Oyj=Y8gj_SAF4Uh-`eh*Op12rM}V%g!NRX_XrH|&hmj|p+f3x#fi4Q -zb+)Ra!^3IQREd?SOh(1;4r%PjJf`f&qheGI5<}}z21&gH5JZ#9kr;{Dd#2Czch~)P -zDdD8shZ&mq4cQ)m16?_J;`e#xL)B0rzMkMdHaX$P!eWB{VYSANFrMxcpz~$%Cj?Am@UV1 -z@1Ti>G)0c|6J!rppjE9%DAozHs6HN*OS%P4O@Zh88mmb@3#vWaLxbclyWW5@*oWH* -zl-hA@b$x=B*g{iO%0hdfQ*pM8!u^*dD#s6VDdT?G#ips?!2GWUE5Ty>hsO`z|&tQG=fU;FP -zr+JnQ_dF^L`L`}=p<44A#G6lSWH=ZGjJuN-4zf6n -zgtum887qP^rnlCO5;kL!NS0n8Syk6`6UTowBVW|##;hlktKNZ{YiJEFF -zervT1hP{Jdl8*4FnPZ<{x`7hvG@v6tNvA3M)MefUdt^7pndRheJB&?+ONvp?^HHM{ -zwJO6AG*%_~EmY+~x|j2WBXq!KlWcCTh;|c>e$Kz`IDsb)WJ$V^uv(}(>Hu&qqf4-; -zumeX%=5j+SwMT*R(e!gjoFqII!^F-`grY!Z<0^j>`vXw`^E;v64$L6D4Wznqq69In -zS&ywSp=0B?PdOj4XY*>)g|(q@e*IggBEjm)L1LWB0SjN(PV$M)$`OW)g<7~$^onmh -z!zT<=0{yb1|Ib!~13vxgF!eNfMipk7f?#uu5(N@|vqZuKasD0D1?1Cx$dyV8jR3(= -zQL4oN)_*%{L&ATM&SaHRG6rHKrrd?(ZSa_Yzk#Iaklt4qggtj3q9mrsryj#{pN72Y -zR|R-zzwwOReTCuF5^JCV;>|7TxpPWybRRa^#MSeV$(w3922mQ*H$(&)SL=nsL`JU~ -z#uij6ve%-=Cv!5ho;Nsv@@NTkY3z>TK%4Tl>`i`yIDd|ffI&1B@F$f>1I>z~hV=BS -zs6${cz#PGf;P@nwK!rsKVa#3)73AUE4x$2wy@l#&Hs54i3?xN(D61Ouw4Rg&@hIK$?_}DLC}7!QI^2G0BRL$$gAT9rQ1J${QAo;$3A9OLd7?41gK!B -z6lPLoia+~WvA?0Bvc*bOa}e`1IKEygnt%_!4zSTh$#9hLL;@XF48sbMHOJPi?2}s0 -zrj7%d;$(-r^Qm{B=vvzVeNfATWIAtwW^6_;WVfxXNp;ajbVNx8wmf~bHW(pfrjov* -zlqA<|P~4jWo4mUA)glMUV>4%0*+F}Jf>SarIA~C|=@4J;WyMCBq|1ngRn{y*h_gF~ -z;pQV+e|BEK%@?pRK+9y@v1oMt5}#%2>v8P;l%1(X24~w=)eY!aV)c5 -zyQ8?g43}-M{TRyAo)OVDN2eT{wnQaMP(jLS?-IXv2Vd4KAu5i6x2YsLNc+X|PZhLEG{we@`tddKF-qt=9`D%d>g57LqfN+-a^Zs)+s_eNZoq5!dL%9z; -z5YK5(Hot4INtVWfE<%T_0VD2#tWvK*_L@`5Y!xGHLz?xZ#+px9QxJnUR}E?+4)A+E -zoL~*%IRRllSo`QO8I3-K6+H}|&3+5;+WB+6h;AU(6F0BJcA0mgZYmw+!Z~||CKU*= -z6hLw``EUjHSbYjpf?Z`o!gWr~4e)SXRSdK@?dV;qY{(D!*lWmuND1gUUEev<8`Vmb -zzO$KEZVJGFT^r{PI(FkuG=H;WNZL=P)t|)N`uz7+`M$Pe*j?9HV`(ZMdO~Qt68QMg -z>|-u!G1?CRz{g}okvxSN{3}-iBC5BdK)7Fzy(H51Zc{wT3_7>F6tz -zDwJ~h1fdmrIqmeYW5ha=geUC|&K@;ml2(FoG$Ypb1Yn2`m4(q&?B|m?44x`ez3JTf -zj0XX-oK8JXuXPjkJ}?q9bU^Pz%fGE -zfRGc1US~J>-GJ_EmMbg$k0F#b{;OkZ;g+Vhz>${Vnxmj3_FgWxU8q1`!C!P8Sr=uc<|!-VNGN@JKauuNwlH(5|S -zvkfTeh)n@e)kH$6>W+>bXK|rDjkg#uFjHo$jEchJ{jmcAAfa?uWpn>AR~0A2XqKiRGHau(db{*3B)+?EbpBv=7sGx(VbCD-+7VdE*F$4z9is)z0Jd|0 -z-{8S2eyw4^*bR;4vD9$oln8k3-9@Pnb#5-YMJE1KI~lnMX?&)Dyey9W$%== -zi99cMiil$hra33+oS5_GFzFPH(?C)kduux*{9^u}G(=@hjFFi=T(d0^U_`J(xDg_q -zyDJ-!n14^l6sMB3+9t8lc1%PE7>q7AhRYrrvSFwO)*UNJav!KdN!45MXa#e&{h(%=Bc@RzYDmi~iWFD@KN_Lg7cn{~ -zXrb-IcGXAsm%YFtixU2R-othdlOoxk0br#{q3qi^7dPH~x`oA(FD;@02SUugMR2Dr7QOkHog%iUBS2qJwOR`7})`&kSdLZnnH%@{w?IbN+y`t@VPv!4GOw824(O#UGuVnFR -zi1ahQ?sHi2+f50H{$*DCDR=Niv36;+t`YU`2tj;l=|Dxdv|8HK#e$4BDYC -zvl?FDb-`s#tx(=z=j9Oc;-A`5$-wcn~pkYJ~OxmlsOeun3JuiO1LPH -zYU&PHulQ%Sz}Z~JklV~k)A~&|^yhU?_X9&Z;uSN}$hepg5+MLJfss9J{WvTE58if; -z{B3ef02Y=E;6=e&nWA#rkYPobw!=vQ7_#H9>a%`z&N2;UhRWVzx^oHza{`YL(wjaUq -zFCMpns{F?4ryVlro9fQ=>Lleylo{4+b&vB_q*9@A#bNctY5eN*UzB)X_EdzLxh -z5gv)xSjBRG*}`&-$3DsbpNwL-gL&yONqw>>S>U@k!rk)6uFVIF!4Uk9nlc)u)oE3q -zk~J{RHHS$o;SzGMU|(g2{vj)pqv7-`V}9gA#PuM2%|S1RAy;&~iE) -zXUriAw2`8ArBAV2N*q;+Z>t>Ora?3kQMpBPh6>Vuquil-lY~emlu!fdDu7cLLIvc+ -zMoAZjpvX_JLAFlVE3LJ$Bu+$ArF}lNV0@64@}FXG&QJ7SpgKV%5{c|-?<|8yLCRe7 -zbY$B38Z%Sq^0>*#P-Ss3S*DNR`#-+_>`&IXMA@hZ!aj-V^O`D>y@0V#$%obd#G*6& -zA1$88i9M5vDn(X!rR3U;39R)+jWY -zGnn_@3v-ktp<>Y5z7;0$#B(ib=?I6iwS8QNT#B_-zMu6bLx9H>8?3bO}zD`GFR)4GF4n&mz8nT=*nlN?_rKwZe{(+zXp -zpyF{6gnG`5++Ci7@jZ|~XspyBR=FFVU5a^_T@F=5?hhe7iLs3d6d%DkL)0X)RMl;-!C|*8-a{AH -z@-ZDoN?}=!Qqh?WlI$c$2PuX-!RO}9mh`K&w4Sg>rkhQbmVsoIvElH*8 -zkl3hI=y1W$o6Sda{p>K`h-^g+wNW@*Lfu_nu>cgJackki6$yCPx>_%$7Otn74lY3~ -zFDTPT5>qfU&&nd*U7~gCt`9B9_l9rNp>PNZ0esvQHJ4+ -z$IXscavM29yPFF+UYE-dD+#%M5VpNvN0XnU(&rY$u0|~MqC$23sKwEhYzOrAy1l}0h-%#r!+dNd-c&rQ!kdJ;foxmCHMSre8m4m=A``A -zTm1Qpi^FmKw#a@NXj5f_j19$vlDDEHuOq9BK8D7W4}&x6$p#lA<#V7OC#`RJgBGT5 -z2Igg}w7aeq^%vq5gAuK@Ypj3)jfAtdHg;_T1sA6NFV^iHt5y?=8h%QhuY8bbRl(kK^^Z -zJ@6hpM;4nLS7f`_h&_Ao&gQv_MIuRgI`FnkpsocO0diOtbphD?e9hSFx2}>q@|lBS -z?bib#c@au-=A|7n0G@`r~){73lvzig19 -zn6bj0z@Q;W2q>)GV|O6izBc^Wwr$(C(Xnl_<8*A>>DcPnNym0NwrwX*)>?a=v$6Kx -z=N{vEQy-wls99D2dClK-d4oABd-gCt0+YVG?(hs#T`CNBrfZ--0s|F38%U#70wzIy -z&5B%DU0N#8E1HM}DeZ1+)`_elc(@1?q -zd}*)P@82q3Z*+Sm_>M8`x4q>JAW?TfX~?+|#zf<$jwi)iyn`e!R@p)tap=t;3bZFd -zApfOkI`C(bSLGsy{FWp(meWL!2m0)>x&|rfF2OORW>kLPq5SNiRdL8x5z99`an99k -z#&Y($Z}ffn8;QFPT8JHJ1OZLob) -zYM#eju;UTOI62wdMFlD(8yGr^u3rSwk>YZS^h+HyEb*~Lbt&BN?XR#%y=bxg^qr-4Pb-j9R4M6?79dtypd;rn3v_!AtvUmmV=$(-f<87*KXRnd@$+v3jj -zSr7omY0)EMckmp1U2W*xG#5AVQsqXIR+k77ZsZo>6IN`Ny@t#prru>D=obwT_YY)y -zcT-j-0U2OUcH%xyCGL!s;d;p1UpU+$1BtLmL>lj%O}~MrU$HYZO(;#41iAeN -zlt-Bb7n_HpoClAy? -zZ)O3h4HdVmG5`T02n}p6ZeABa6kvgC*+mS$;NpNm#-n4~Ei6ijd+$Eq@Vt_1`>@l` -zlUOJy7qce|bg%*+k{0M~(koJkN-*dcMQabKmla%O(C -z>0NK*POu_o{ZuWTsEt`_j(E{0m)$OJa2B%8mzRz&v>2N11b0S4XVdr!6^U3Q+*n%B -zpoL0(M}!mh9BEB9;QX_zGn7Jg4Y!!bfLL;?gM|Q*Rh-;d-zo&Cr5AM#QFJQhnJIQ4WP#z}in}{U{Fj -zpYSflw(WVp)M81N+2RT`HeWF6BN@P)%7VQu6)Fz!JV~IEVZaIn19*0gqHs#!%Fcqv -z*!OZ#?s;M907fX7Fjc~WH>YcOu9Y{qRV2 -zzxSNt7Bn@q1j!es&Zct*mnqJ5;KOPHr{=l@e4|l5oYLZ`8!5y_fc^;J)iRrb;4+lt~WU3_LY>y?|gUnHz}m8J{mLMy+7RF -zuW?5gPq5Z~b-UU--<~nLDrWCSH-4r}%Im%RJbCzjUf-@CjvlXvGA3h+vX-V^7SAXL -zuk?I3wE34_RgU6r0d2qoUw2@!h@pRURc8PJvmEh=6!g1JRU)ZN9e<^NeDQ;58fuUR -zurRfcuSMs{_cRXi$sTHChnE-y!J=Bgq&esH-BRR~+L~M13wg4uTmf>_gPu!5>luEn -zm~X4t@(|)?Xr{!gf~(#P;#T`1jTzY% -z{`EK);hkaaW&b>jal=LA^jbBeB4a(oWcW3MkY -zW-r}l+fQ>%8zlKGg7A#7h9J)F6-UfCvlJ;LmO(bjVkL6hKF&Gm= -zGENz$475nuyUPT}Fp^JIStLbx5bSyDggE>l{#gXMNjZq2;Bi;3y@QPVAQTQf#~2%T -z=MDXYYVLY4strzBe(PFGBqU#vbrn%SrB>E08pfqg0gxy%H27X1cD+c#F99!JA83!^ -z9@#hFUu4l1!$UI>;fBT*7lpoN%lJe^GBa0i+eG3JOPfyK9qhwWk$kr41xyoUAZ7)d -z_4S-k6Jb9=IADsGn%9uq>S?;u0>^1FBhh@JrSa&5#653T-rf*-EpW^}fJ7IKJm%4` -z5%bI{Lq#kjp8^X|*D+xIY#ig9L$j9{s=IHwKVno$M_78lV9NT!&Ra_-EC68BJW^(t -zp)AqDA&^{4Emaam2^99Og3lWN8;hu_>Z|(s3#g=~;!gV`JwQ$mB$whYHU%c*>V7ju -zi`fc@Ihl};NM;)80t)2gue!uI;{Yr1#|=qNsQWn6o5^mjj}qrO+;_gV^(E64?g>G+ -z05KcVDUGbplvak9VURX*X!Hh4he<^Kk?Gt^Yq4ok=%jI!Y;Hx34oSLkZtA5@(gz0L -z*UMnca+`q#eWDtNbcCA8oP439bCBpPsPa8s0+b;z3e~3)GDq*qWuzt!0H2vg)wO3| -z0=H3NU~)6A5{k!|51l|gV1f9h5I1fS -zx(uBo8yK7>-!|>OQjOT1a2C##K5cVnu+oQsnuJ4v4PbuvXRUf_X}g(msR#*>#OeSz7Jw^V*{n=qKQAz*_>Stjn%zWX!QUkQQCl6%A%iTWYOr3ZNQz8w)h -z78FzSpX@4_60>rAI47PZK5MEU^#@SCsb7-Ordlz;3kr-ucEu(r`XI@KGO0Z4-6{1! -zEuEH+bfPjQ(Ud#2W0z_pUdjuv>CEI -z1NARQet;qeEWtpY^gj>N(p5n6`X`9yA&#Ju!2FP4C$V1_dKFKrXKaKodbL`Gpq4{p09E= -zm^OEEr2e+&`!RSrBrLmjW09HQ$R8}*w&?1;V%^lL&MdwDakBoaTy?ZJ+RJZper(G7 -ztup6sgH)G$Zd_ic(i49Y+L5FAT{J#5=h^xAR#XC39uQNrUS;HS7ISew -z_$V9#v)}r(x5iPzWShF7K^s2Hbj)V_OWTGgW^%x@)nZtFJH`7R&9>1J&7JpTyvl|p -zcIy?!uA*=BCB{6}S)8-nKjewn6SYOWt!Y|O*q+wi{Wj`QYz)!l-CU;bTL1JBC(!<& -z=D4L&Q>rs$J_sGWbN8%OZiM?PxIzA6gC5M;y+~gRtv3q?v@DDAofJQFBB{gI*m=yJ -zvFq_(W%9B8rSXDo=OGQ-m(I|=is$4$^kjW<1X742bot=Rq5rZ5vj9#%*+LJ0kNk?T -zOkmVJUg{;$E7w!w3n`x}|G{VVG`Q%A&`Ktb)}bJU#QRy%a?$F|V{?wrlGxOow+IN= -zUPjRlrsb241N>ye1cX)?=`FpTUHsd%uwcNQCeO#a(KtIfym6pNkxeOW3ZTLSuFbDiT&@QAv)}*gwko-#4r*qB8-y7G7cgaJ- -zF7&@PdX`iTOhPl=;#|5`k4|NXzX(|HO#<B&+y1@2x!0Lh%OHtytu9jvene#yzHC3#@>T%U%Vm5=XJw86z5uNM`u -z&`RQg-nd>P;;}C$JHDa?8h3z^f`k3kPIypi0+}1O!#7`b)#%5@y&Ccb1Oz`e@>sDN -zb@pV6qES@v#Hh|6Uhh=+^4cyGJ9Ku%{mtNx)hl=l&p2kq>{{n%3rChnC$l=rn_cLA -z&F&s{EN<1yN`9Q)5FgEjHlENOTZn7>NyZ~`S@^B>=}i*BukOszmFouU)|3beFtcjT -z2z$pAKwzi>(%kIAxz#6#hIyZ=6V>WH7YC|VEo!?RRQZW=LCDNZHjh_s<^JekO9sE@ -zgTa%&bh7Y*7l3mpQBk5658z$4emJUk*_ky%Rq1!rypcy~Q-GeAh>aKSsk8=o;RMLT -zM}WscZa%+Cz=E^PtWA0ShP$s)(TVi(e4sLFhFoHm$fGGs=3$iyb;1Yu?GL3Q-wR4h -z62hUBX-N_MfjUpW3gDFNuHJ#AyVT9{V-Y`nwkW=KXX6w%y!gpUJ~{nb_qDCc!N5@P -zW*ZLP;nEk7?Vx=J!aQ6ZQHE|xZ0JaE;P$DeT}sn+bz^f@eGSImK8F`tgD@9nwI{{} -z^^KGdQOeb184Jy{n$&At7|;}}541V~Sn;ffT7c7EZE09R%e>bPzy4YBF -z^K(n)Zx1d-Hiq(%lMffl -z(2fA&7L?kGU0qM#t!XA>gdZ+{Xwq|N41{9$w!nSI*WO>OzNEZeUk#FBH&ei#8u^Kyzjij=q@8J -z1{|}s4^3VqeuPWnDyf4zHrK^Q>qYg51YvDVrWA|r~Cn0q6ZGLyZo}X((Jw7OILdNh#)-;{t86j -zuFnI)|7#R46k&%6j@uX7eh>VeP!U+SKKL!KS}l-)ea$=#oUrB5FM;awCi#S#{=|(} -zd8N?T(|WbSA#raT7YR^)ezT9H -zx!@MJxFEOEpbupFSQ8+!b~S35ruJ|tn8MjD(}sbJP`Kj;#^z)_f63!iy~1LO#FnhK -zL<+-M{OPqkI!v(sBCAJ)cYkb)szO>3(UHSe2MNgwL)t#bSY4O`!M0?XCWw@jURT=< -zd<;x@Hj?yw+CR=ip0U*hNG0}~0s~2zS1xK8ng}5DgFUXRwO-6iyqys6s})`hAgvXg768xpiNwZXyK^M1xi0m;jIZD8R+}g( -zda0;wwm{HCp^kQ)Iqwzo4m*NU&P3 -zMvrnjXK2Xbagb28cZ!8&^22VgLlOIUes|NBqDk;tuU13B35}^|^(w~zEs1-MO46k5 -zJLMtCREU1^Yn)Tbdx<q)`o3uF;MPF1UQ3p&ZfgL2!AZmhY7~+>qkQ@*R7O;xyBG3 -zFMv7GoPi!!c)WCz@!va)2THy_H(u|9FFE~mZafyOBc;=$1Hq-e@!M{yn=peLdCdjF -zYhyW>I3oSF>+1g^X5*Fos}G(b=7c?SQ69~WiX)g-xc`g30%PA~#5N{Ec4!RA8L(~5 -zwo)*LIGb%YzcwbTXxK@ko~bIiJvAa5wvb -zH;UF%!LN4FwN60qA5om-;wRax_~N=_l`Awg=e7)xms!T)gs-&KXt`z6hs -zQ{TyKWNk}u5yzo4SJeo=pCTo)4SoTrW=UDmLKCmce|%qOX8zqt773rp@gm(%lbn(S&q?fL_3F?6JaLL3F)$`B=E0$;peoV%6$KCg*mC{nb? -z01+y4?!tM}eV162;OS#GkCou{zhquKHge0lKNLA`a7%LW1lS})d#%82EhYlB8cT1_ -zkV2}oR)#SR2Q8JQ#1H!`P%`+44bc6&Bm>Fd?fSSELKWmyIxA6-M8?4{3aB5T}_i%6%EZjjcrUY@qa0KqdT#a?+JvoJhgU+>`G -zDn%}|y6{rb79hYiXlx^$zdOnzHOS%u5iP-B=`DQc<3Y*VT;?2D1i-GiWAyrHSUGn~ -zZlaS%i*_YeB0slk-a;SR-%9{gH@Q8yqNImDEQgyuale0w3E42_1}2#_kJ -zPPoFzCiTKK=_ADn6{8)EvKDZAip{MuSvokts!N;$Jt!bZxXU`6C!hi1upG+%lsKpA -zF#&w94PWdA*0~dlfHZCox!?fMd3(-@o9lu|@Em`6BH390aWw_)Uu4GzkvsOa4pUB) -zf|R5B8FG=bJ)E0c#VDDip%L&z^!j0>chHHbv&jT57}!tCV&51s2&2%NZzB}$TXl^r -z^7c|<&}ePLnK>CMdO{xOBnYnyDGl>~&1v0~F}jq&PQWKcQRwHH665j$gCJ5ZQNY~g -z%QWp>rZdKcnJC?(xp5;kDVkKw6~MxamEEO}@GJ{)Y0HDl&k0_u29g__LOjjpmzqiI -zMRE$;Lt=U%BjvBHIgp~vTrqh7D8iaJPF?`QFVt1Urf*@0z}ymeFRyN6wzlI3O2RXS -zPsQ>0S?p5E+=3n=ju^n9EBQm=l-Ss|m6gMeX9RdpxnZv~)7?fQ?vu<eb -z`np^kyDP(1bK`=NGB{X~2GLc38$Zsi$+hWab4KVIHS -zl`vil07f)(DLDk;^edH)o#*EFsX67&+!msPbfIknw-4koO4DKKsdC?Q2o#t0E~TVL -z?4)e2+t_V}_~QrkzCCjRdyh_3d@?zHz0w_{(Z%|ofWZg;1VcbJ1fztMitWiYd`l;W -zStJ)&>Ao#8pFpqsA`KjB*Fe9vn0*M<+`%ixYk%~?O4iT?8pQCe7UzT+KK+BfGK48U -zV#%rW2~iSK`8N=cCLe^s>LkFTLqWQeO8|t6lZ(=kB$N0EeilBcnQz&kd-t#8mn43P -zGN@hFS#b}3x%WMk8ZQ%7gR{1K?BgA$E?4M^N9!XSFDo0n*sYZxI~^97J%Q<6Hy1jL -ziMpDuoFxsewea^&g$An_Ortn?+e;v84wu|4Dm$kf!Ac}}n6%vnV|H`u$EH`V(XYn= -zJ(U#kfmeC91-@E&0hyGTk>EAyd^rzoVDh42B`;SuGm-!u(m+=*Y&au9Z$AU2H=ziE -z3~Wf%86^twjEe0+mpk#|m3+gxG>jbYO4DUuDSdW}z$l8vfJa^O7qf2jU0bS%)y*vU -zcQ4Y?2GBflbL(t6WDXJCU?AfT7U?$7#PIpGA2i-`>aNbTCv*e?kxpR@M&4#G_@Oet -zslaMHYbxeLD$xF%Q@o~zHU_JIlmPP9V`m$Kh51QIozk!j93q(Zd5d4$9#$ -zH7BXE5Cph^c=UQ@6w2Mn6NO8p7{oL0TzD#9;?#A}g=Jt`$>6A{6pmf)^brU1`Ke49 -z%FZ@MtC4(lNU_OqTTc6Nz?)#7g(04tqm55L|0yt3O-ffEu|0JdK=J&ruT!zb*t`c -zbQ>|-uXiB-7MQd+;U^P&icg*HBp~uqhO0Y(2rNI*^gx-)lEs~Wpc4l+ -zXnHfpj!^=IB_NMia}QXk8uF?<<4 -zIyu|2S%?kovoA(<)vK=joIWRaB -zcn{>FvE*{SCUCgM3*`hc#twOEg9&+2M01YTQriE-@J9qm^z5XXznHfw~8Wc=Y$U<8T!R5{o=c@7gNuiR)grwV~e -z^;7wi1teR*Ei3~-hHRd -z6>UK55@C>axhj^`IgEc6rE8xm@L)4-I$cMD(`b+u50h@%Zah0~34vM*9A9=S5?DwK -ztr%#0QyDEal;$019-x`N%)ij<#9+c4YVAx!^p+yuR?B%U+_uB8XxXNU<`>e<7sR%- -zG9C~aSGXEoQ#20AE~c;+oe9(@ieRWQT&k2%2m3P8=oBw{lueyH{3^4>WblAMLYowP -zRF`o8YS&8E%iI*>9~ObmSh0-H`b9Pw$A?g*sc4AUOPCXrIY@zhx{dV^k0oQVE?jgq -zSXm9uB_y=itC`haRsxdJ5vORmOBTf%+=NBlcYgGSE{32xCymG19b&=~3Z}1*oL@bo -zjeXyr#X0#{YhLc)Nh4_;F;SbW%shdtb5|IJZjh|dv(RCLbF43|1)`h1G#IlRrV=+){1bWLHr_xNK=qPx%9x-PTf5vjb?7Fo0|)EsiQ -z6Vx-N2Y+^~~lootKkE~>eGMVW(d)$`q$1(7FzwRwVZ39@pj7-ytarJWu4I>T(@y63 -z-MUYIuXEJAtBoar7Q*t>3RYS2s^2F`fJ@R167Jl#;V$vQP3HL(3yFf6*u5(}LJ!o+ -zUGKfQQW%sVZk1D}a^6^2$Pt=uDH0GvUrtSY!AYsBm_ucnvAkf? -zn<|G=_r)A4OOJQBc3BA>)>S=639+D=RP=NowN=<+WKqZtCdICC@7d`~$ -z3tCnZ@(+S>m1F=D6KL_N3c-yP4vOXTk4OHGn2(c>XOb>Cb6+2&IS;NrZk8vl_muZL -z-#6b6bgsUSCXlBOc7o9ld?O6=lcn^y!sZ -z^15gP$KeWhaND3rFBu{x$#=)nD9s#Zut|FqBW5EGAD<7<_D6DJRMd4mCPIMj3<^u@ -z@D}CjC=k3zXL{yIqpqxb5fd^;uYzS{U=LuW(sMF-dkfDdKeLTI1Tlk{b`9KlNQeD! -zK3cW1pIq!k)?YFA;RCUGTNYEwF*9g2XrGmZTi#1FS_zcG1KV#TxqtzLdlb(igY-3X -z*3jihr-vXkgPv2fsF{B7F&9W#33ldqzDM7;MU5use(rTM$}|D6ahf07Pp#KraLqIM -zdn*NGYPkjGaB4{)E2RD`$Cx0O<|SBl$Z;}=%mk1_3iURuQrlL5{W@(+Apz$mmL#bf -zl<$(o+R(!FMGsGk-)px-s`H`X0pqgWc`A$vV{SPLR%zsiBq&fnm=Pr`Ej9q5Ya+c; -zAy=744upp43d~~oH!CESrJy0*o_$_W(}i&E@2#FM^645J9=CtWtz??O}Z_ -zlWRdK{1I%o7sip|A?58q(>6NB=c@^;L+;*xU*mCPrWGT<5Hjs~vd}-U^2z-mp2)0o -zwWtMgwhCnhGBm@pt`(e-6E|ZWi}yNb$2?N##-Dsr-aYXQ!Vok<>_ig}V%@>eD~yAt -ziy;67fYH?fGiI**N9gq>ZWRa7$dFmXHz`IlXJKA$}{HrkN -zA@pwVg;kVsmPkDpWSF&C{#*;=ot!@~6e7vv4)uKH6UGlDefj -zSpWrsohoI+kp;s5nnZ -zJb(BjAs|F_X!^i~ggK?f06)(}US`Luy>uS(Ex^+%t;`sh<2ogakPTUI>mpEhE`##Q -zct_4sNx6fGwR3og2*Yw^nT`Wjt;QOh65hsFD4ncGsaTisEXM=~G$(gU0#@fBa8Ou* -zRwJf=`6zG6KW6^+wtOqGzvY83b087fhjjuZ_w3#iy92`MM}L$Vr7XwSRij66+F;`B -zJ^3_+>X;?!QCgsxVa-Inah?(Ub;mR@zvxR|TMlAL9*WbFcxj!F*N5VvNj@8A7$vYv -zmxUSI^~PZfx{Wf*x!`9wohlN)87~lVfL5D&@kg*8Vd!ySPvbYBme-Inw#lAq!NG4H -z)R}ZsvskgnllzFJf`X_5+*WnJ2%ZaA;(dU^8)O>=9MSnS~0>PRVZ2YW_fQ2Hq5iEje1nBp2DR8*LE|^lSaU%E)(=QjMN{cQgJsy -zkWGc|SKD53O^O2ow`vPU00@b5r$Gl+go;~cdO|;4I|R&yYTkT2XIw(Hz)scLx=(tC -zPR>xIQXOiVUv^Jyn>ikyFj5`^#WU;$jQ3IRk!5SQ9@meq^X&jW(k60X(i{&HRIj5r -zQmY0zq!6y~E|8FFG`teCbjbs9f2-+nOZongiT>PciH9~Yg(I-r3vv&E$(+q)8VbAmq?G8aJ -z8-^tvL+7h==qZ5)C|)e<*PClSo|Umb*K@|584s8>Uu#GyJ%V@3#%wjJI1 -zMYM6E4D{HPtIabJKB*#CWR$BuZE*K#a{&7dabaIcDZyBb!8}F% -zdi%%o#mdxL#WK9%8mWn(X@g3l1Zv8m&Zj073XSn;Tf(dXv>bt}HF2@BkV5VCiG8a- -z2569$K3kA}vx%U9;rE-y@&>810vD0BbB6*x{9bzr5?@FLwygzIls*YRM9M8(zG@17 -z-aTwz#Cq}4x>(d`Ehc_{d{Wrp(!l^JnHjD;<2L`rfTA?RuOlc^YaYBr#9IPNr4S*? -zorurZLiY&@dBiEx0%kRGw$8oDT<_9eA+_eDWsoGTlyaP4r1X9X8Gp&LZ-?=Txlq)S -z)l7w7hd$=HER&KpfV&oh6J-Q@M^b3}!*5=~Q1v>WCo -z2$PF7Zb@+uzA|ac9rzMFncprPe(hMUJ_k%G$i2+iv5hCNK3`zU2*QjAc3bNEj2hcd -zD6$o2Ipn-(g7bD9a!*6c(<&}Bq&B`7tVd+#8h3t^k8VzpjY02j0u^q{hv=?nmty?8 -zJa{VFlBBUE4e}8;W#xL^$VV -zjUx6h_9e(L<_e2~lH1q1Sd^jnN_et6|0GD&xK -znVIP895QKTqWJhg`f}w8Wm&;6K`nB*f+(sln71%1uU^OEUU6TjwCE%ha*zae9ktf+ -zru=L_VtJE|(C=pPa{?XsaznGU7nabVI&Vt(FAw@g_nvH9kHH;kujhq5Ynhs=?r+wA}Hvv%+SXz(eO_NWtMbWJ+$Y61PF4*OcMYU%1X_2 -z)%&if+^F^sJg7herUh^~&SNnB!s6L-??m`9x5n{x7DpERD}ZYQFv4(r0a~ljLF5Og -z^VJwE_hZ2@wyH#xQ>{>iuL_Ffos6zz5)Qqsw^*wKNv-OY0xdzf>XD2%`421sJ)>0FVm7#5TVC^AI6&N^{LA%DIHf-o5$ZJ)AK+U~Kf -zqsc|p!6Q_*Wj@ywzwkZaMEH-WXHr9p?=#Wjb7A$9s~=Qx_Iur+SSe=lt-UHeGC(ZP`Yea2qHoMof1J -z)gQSh36wkqw>dw9)~s>4`ebx&o5VxZ$eV|W5fq#&1Tigwx>u9_5%OyFR(5B568qP5 -zOpkNA;<=6_()rbb!yqL1B~&of%`0Jl>dCvQIZjFywd^S} -zfHZ9G9<#Uu7y?Ol?=v-VKp873uSECO^ve#3c9Bi&l#pt_S*R;e*jvNK7Y!RQ5p3YJ -zdAFJ3yE6N@g(@yhl}V)<6eRwNcd&&D@Qb%7y1jng+p1Uwx5a$eq>ze|M%}f|L}aUAGl!rfn%V3djrG -zzDQ7Pu0?I%_G01J-CN}B@GtjB$B5BQ7dy+1B{)-1Hz-D}Q}{XVBk8s37iW+%ydllsWqZ*??JQPw7SidOXIx7(?%{ER2sx0*3l#g)(lcVK$kamf< -zASB|>JGfif_dp$p1p%{pFbpWwcblUTA{rEk!Nas?ipm}gta&%Zpu^yBEwZlGL$pNFEuqhK@MiGN1}jjaA1K0U*OyBBt+QOH=fk3=g2>{q8 -z2pT@{DY^c=!swcKlo7>DzFj()F+h6~h(YqcAMLb7q`%LrLwHLUm;${fj=*HT_9n0| -zCP0cHW@3moa4tU3q+d6z&r^n88jnXYL8%ZvCq4i(j~#QhqlL_ra2?s__0(0^+!WElHsLG76I!Qbnm{NXy1_cyH>uM95bCZ92uZih@ -z`&GWI-5SX1%q=Ge!t;1ZhV5-&lWzr#LQPX~$N)a$tV+`ilYL$@j7&U)uiu-gLH(eF -z!F%YKjq4H(JQxDkAaJNf+)tYe;tcze8o}+s2uswL<{vuq*;koGJE*XDDv|=P+*IrA}IR_GZ(W&dOv5X -zSW*rThf(k=C^~^=%DHNVfOw;-FVwCB-%SdULy`gJ?Bva~NDC10Su<%{YlV^gj{P|t -zH(^kYQ5o8-AX%d6_(qK}Uy|ffk*$;{AA2=rh#)!jib@||50nV>=KUx!JMT!~aGz5C -za;}*H-p=fxq$KX@cB-8+vS5`Jq8M4JXZEs1WRGMz9wPjJofn4cw0g -z#v&m3B;ubf^+iBFt%n#`RLtt8`bt19yxWHM@kFSLw;EF$?eYv_!^v(kriFHBCT2n( -zJvgx5qg>#ygjF%UyJ+rpRR9W%J>y5H`+B{c -z4hBblN6^s`wn*)&#H8sBu;tx+7_soI*hQLbq{9zIcCj$P31qAEG$U34G2vUfPLOGi -z#xBtvYYd=m10Gn*@8ZFVD0xT1R4WJYF(9#^Fr7Co}Cu*I~ -zY?1ddsm3s~?!iLE0PHf$VBLc^*SIe{4v4W+D<;rxDB+v|auFgr%BBF`9&`G^q6$b_ -zUQ^k=qn^GJ?FHfqjXcgC$~Xzcq)kRfCC7&S{5c?$)=s$Z^mZB -zKW2)d&@Q~y?=%DmfHfKe5qQE~+?m)w*{9J@EQ43K`> -zx?IbPL#`->KB7$`<`4eU!Q(o|WH22OcxlbaCUZw7xY?MoGQK2Le?cXw^N#)+1L&97 -zUR>lBW1z7+KTU%x#H7mnB?91A#cvW1TnpwC8ur_MFV4e2_RBkK!JZSE2pL%n(m6%w -z^dG9L0L9dv@QP0UMma^og94-0*ajPY+H!fQkP)8l}OaN7Sma1eEc5q -zD{b;Uj3*{-L;`B54f~bPI!)33CUu45bY&%#YAu`ETsAbI%0!j7#d*FWteo>;TA>Za -z#xNnw$pnNpm?%HpdTi0~VHvO%tD|gFUoEbuAb5Sy~gv77N2Al}bSWC{keb3pT$j1&^ -z$W$%bLct3G3BnBu(n0G!$z{sd|LSbR1^wY{!(aS`v)!`&hqK-MugHm+ZzAH*+!uI&DplW`xj^1IVOoo?l))K;omyjW(xno*+&2IyR&`uKhAdP -zpUyTZ-`_ji05$*WY`6WRvwidLob4_2Y!cc3b++yPaJGf#=c)grv(1q3hqJ8@2K|rD -zb~4(hv%L%XkIr_P&R;s)Z~w*F{uTM}o$YxKtNf^cbhg!PKAr89-<|FBzjC(oKmO9$ -zR*3&sXM4B&Z_f7h%b(7+JM!<&Hp>r$M+JAPo0mc93Y+JXqfF7v-m9wp&1^-3{FAfIS@(ytO~Cp0 -z&Nk!eAI^5{KRDaO*0d-f0!jbX*;a`8duO{i*2-yBoOt)|oNev5U8DbTwhIBxe*Vqb -zR`&gCXFGk3$_AW^_&+(@1X_RZY^NdqduMwb(Y)*rXB$%o!TSI1Y;!LB=4?-8{;#vG -z{hPC$s`u}lZGFBrdV=2n2WQ)~_0!qz_zPzn1>yhVZ0o1};cPF%3>p7FI@__xfA4I^ -z*Zt;fd;aNc`^W#IvwiUw&UWPgI@?g%e>mIW>1kpAj-^!B!Fd!`bGP_}$rd?fH++_G9~} -zv%Ru{69O}8t*?w{;ji3W&NL>?aJSsZ3@QH>wj{#jk|w$win_5(%F{V`4?yV%gm>< -z4dwek&Nhs-CPF$UQz$|s&3s7menwMn|MYd&c%r-ucDPTQHH)14QeT1g)Cq(+HG1$W -z6cwsL3WxL%S`sMs}dNOK#2Zly9(llwU!ZktuU+kMELkUH~E793Z_B5N-7w-7_2e46KrkG1W -z>v5^0avXkxrg!Kw+uSI~abREO$L#&20t@gKc@7>=k3EOH%EM$C$M7^hzGF;@ja%1? -zI2@?%=LRa-#63$I0>#1=S$8Fk(*Ar&)Ra*LaOS-V4AM)9l40fy1tSMf8_n9Joe--Z -z3iqSiZD<=+`DQ+ewAo-m*PofKp0C<@oGrZc@c?4KId0_sJotd`TIHwsbGS>}TgFY# -z?ixmpnV}M2o!SYWXH`tbHM&qmjxUHmV4l29Vz!r-2;ZJ_fJS3NvHxatxzt$GoF+}i -zW1WdJ{alS9EIewj!E5V-=V-&(L2)$;E@&h35ck%6Balh;wAGL7S`QwFGN&62q*E6f -zte;tQLVsDqtdihSziM0&AJy@Lidfb2ngl0_%kt)4?I08mgS@!1Qx0IQPF$GZtVnyT -zc3M7)x4HQS;&z2&MQdtrPfbLK8>OX%7PJ2D=SiWFGDTJJ^e~rT6ek`ej2rW0L5%g- -z6(hXnv+$YgDGw(|ZBy!9Y=m%`MWJZ)>0L>k@O|Gb($buAm?B1zMNO+>HtQN5Z!y;i{erh`!KZIX?Wjg0xvwJR@nBQh -z*q3e -zF)$A^{H6I>QyT^Jdc*0~zz513cO$H;_-sSU+3MyFiPSY2{Ae|y -z7k^n@2jUcJ4`Ln~$~xeLy845?zj!jCF~qbjLvYDFI-^^FRzz=0laD<5&lIkrb*^d5nwtu1a!yY^d03e&D=zKccAQEd~SLiAS)b1K -z%Wuy1Izr|KTx|f5H35GsQ@^DQ`XI2t@6Pr?5yaM~vyEVi;PIQYjRGEr^66|V)DaSY -zI@@c~qfiu10M{_)NxwVWeV@)Y?ckrzw*Q!kax;(zs8Vplr?YMEz~n7;sS}s^KhAbA -z>8G>pzWC{EgT}-lt%Jse|L$zxy755?sv@i>JA%ete>&T}C={0kq%j@8Ioo&S@wswk6LC$<~s$QSY_TBJM2~ynT@6Psc+21+arVPJ3+tMP4-ea%W1&2akjEkVN -zbT?6cceV>)ps&D16Mu8Il_uje()EM=MSGG!w8X*Fk?hbBvRm>jBHjpWz4lrhdq&Eh(YcPR*qsfH2N9gqD_R7dXKYrgx0q5ib-MP4EzUfR+U4C0{DKt>4P -zbwVPONzf%jGBL3PiuJU+nJ=tJd~|v2eyu}_=-{7b)4ZqjX-b|P))$?V4w1f}g}O=7 -z&jUq3cdsICa%tk_bwtl+ZA$5xq+U_26Upk~_>3=|l-ge)VgTgs0S_C7%pHd#+jyUN -zsfxmf-jtLgQ7fjQL~P(9t;MCuE!B$4mST|vFXY4Ic|sC|vZoL;BF#U@{7@d9A9IE6*JHRYhrE^kb03iXsL|sF>4hjQN5To-s@A_5ps#yr^ -z_M5}GP#!qi+Kch>hU%I!2(>`mdUZ3a|bH+kmKN&(wTP -z;90ZAYBIP@Zm^s5h+5-)*^jQWO3p!T$oQ~_L&%Z+&^FM8%0y(~aD-rrLhzrE1Qkxr -z`$i=OxO#CLp{{bmObv;ElqM!8T{L_RhKOBMFc;{r2__h4+SvUlKxUI@X4cK975)G5 -zb`Q>BcJ0EqW4p1_IE`(yjcu#3?KEy|yK&OkXl&cI?R;r_8_%=%o_D_g;hMG1nQNWL -zZ)cfL3HD={mO->at-$wsxne(pndli)z>d2M)H*$vFt}YJ`3y=QMsK9AJ+1>9 -z6gfzOP+^_kEaz74`epJLQSvdm`};ea0w7O)q4|1g=|0VWx6KsDl0Lg{RR_cBOiD$i -z?~Mg%In+ -zKi<6usqp6rM@0!P|kh9r;^v5@emQRP3*W -zAa&JmaMg79rFtSWZMbzuuBX31o9b;m-q&gh`%K09cCyA2qPLW$ZoWy4C4}LC?y@F6 -zLs@b1mqM=v;Wh;4_Zor-_;f#|Lv3>YnPQL;^cx8dF%%K6Q4k2y`Xk37^lbh^glQu3 -zJHYPDti=$%uP+Yj0iRwna{J!rZ6bG$iq}K;9j?*=dIGDA6Ev66lDQvq8;VUvjtKd0 -zal<-KltBTGg>7QgKR|U95WoH>1j$fCa@vVmhTt>14l3Fqh^}3-fyolgvrK@js+orF -z3jOTG^qHzxQNK(mTxwh2kjv7FcW;bwcYGG7%bWSiY%ci`%=#Id60paZ0zj6c66`H% -z56a%2(p*5Bg*TfI%C|Ey25+bk+3tj9ObXG#dK^JeI9KSaDYKToK6KIKQ|VA7n -z?-eI@@1hecoy(|W0j=@2KPnP}i|R2PZmsY|zx!0Czix$)Yc@g9vN#YNY9kSk2wPOF -z(u-i^2-h(bQ1^T|v(@1 -zvXxGetn)+*B-{wAXO?H#^5Ic|_feTR9-_I#-Qs)-NC2d9ZrE#&a%1Od$3%bLR;v9X -zpCF)u&qs|27bfEjtk}RVU!UKSa5Q~2648?%1TKY*`-crchsukB&tomr_i_fZ3+F(9EF~yo` -z1OUM7m=akm<<>c*+&g6Z`8}PUKzkxcsd=1YZj)}J5)jgl-Bm}Bqlh>h(0WFX4;Ot@ -zU{bMtt9JD6-M#>+=Rp|L?=47%BlM66!koH&mzB;iN%+~$mv2Xht`ejvsx*aKdX5nD -zY0I|W;NxVv^GXN%I;2m^uzfgU6q7^R_7(ROghJHh~)5!P` -z$XPfD&zD9!?ue}8N_f{7*7g?w+1oP?FE$~Uw75+j#x -z5X0q*TvRe;e(D@pj=f-+SHH>IEqQy9p0J6A(X#GW$TC5!TESQd@bY;U5Gg{sp{Sz% -zC7~#|@-=gFIi?o_5D5Iuwt6PCA>@cyGH*rW25a!4c(sgLu=Wi7LzZEM@2a-HoL|c} -zQz4hFa2C(@!Dk3R@E!nMrT3M=1@WQ_>pW30Nrk)hwKBqi_!Y_`4AB*$Hg|^~4BLFW(>WCxYYjkc2r|_acRvRq;w54&s*1 -z#X;1)y(L_Ph&)}Y4i{Z&&C?5(6EiihoHynm$pVHkj>qaSfQOxi7|`{o;rZ7!owmDVfRibMos7JQ#n=nuq}8<3l1SpkRdW{?x7e# -z6?@@@jvj`fr@pBgb!X@)12Dx9i?`n_2VN_bSFPzx*avVbIl!z<+k!effSBfs8Pm^g_)B>atp~*yH(CwJPc061jiSZ4e&X0Er8~bj}lIg -zP42U`yM2V0tQV%&>zpi!EcV3*ck{Hp((F*y1D}7B(c`PnX`9vtaf5v=A%er0j-gK6 -zpAu{`+BYr7q=78TWDkC-^vIyLYDTRGV_}&4@reZM{-Po8!J7{EEEwaXzL^9>5Q0|{ -zNMBlw2X>qnxFv_kGcy|r0Q85j(=6>0b=i9J-mfH4WbD4?_p|^ezK_!vf;G$vQye=A -zNPGuysh -z+7Qno5*V-(`<+EneV7T`NgYISR*>~dU&|^4UJ!8m#;opR>3Gfw)?E- -zz)|F1p>0+a3Y_1dZNIfwXdCU@E3_T=3T+1fCw_;v5lmR|#D0OctF!(LZBIsjhql?& -zgYo|gZEtuIZe_Oo4sF|`$%5$*{by*~#(G%$pP+5sdrUcn-=Xc^@YBb?L)+8M01Z-o -zJ##ODMc<)qBHJI(HqrXLK@>1V{BO|qXIBu(XjTmdjPX}!`{3`;HZoP_m;V#AEn4#z -zXxn~p`YJBd_Xo7iO7{wF!eQ@;umP!i8&U$&eXi(m7QAKgnNay$!2$f`v(6EZ8NQMNM`>7wEd-z -zEq3mAXj@G171~bt4sF}~4sENwLfcVO{|0RfFPXa27{9jgXd7<*AE9me@6h&} -z{A|?SKS0~8&p)BuRdG%%?=>qZ_qaK^FKk`-ru3^55GX$gGD;?{|IfbH;A+T1=`M0dWE*7 -ze}T3MQ~n9s&gXHZo%l1ft?}QX?HH-=&~}*tJPrpFh{un=@?ZBEHHoaG9o1f}8XnXh-+8%j@ -zwugWWWbmV9N#_3yZIAziwsWa|hqmQL{{n5Zd(%wjti*tM{)D!3ZVP~(euK7Af&T_= -z+y4pLM*2cB^9!^c%>5JEKKv`Ro%$2nj{G~codmxL_X=&twEYTgTbqA}w%-ypj-CB) -zpzV~uL)#L6g|>}D`6@8lX8!IQ||l6r~U!d*#pV0Q+^S?mbr@uklVt<0RqyGwRvtIoUZS(8>3T^NIGqm0KE3~Z=`6p;w -z>i-OF7tj0#ZCA$qgtpiJ1=?0v_Fv5Z0c}%`{{n3nZvPH#PyHF%77PCYZAVr99omlZ -z{|Rl&bsc6n{uSC5{~g-ar~VFY=lwgh?eYq3XB_34N5xX#njMKkC+b6$4+ouyhpl#&v)c0kt&^BW~n6c{L -zp=}Ak_#P<9W%~$)Q@oc(>nnYmbrN@{ABSa-UIgHbK9Ojk9Zxv~5Pk@xaI=NUH7h|v&v -zwYWCGMZHs0bh;HCKr_>dincI#x1#rTA6d-UP1&zHOs$FRv&;!rZ-NMmhri1CdRCun -z={srqgaB)qwhJ1YOtY}-i~|Q_MvzYrNY9dF8xKVY%^puUcZm8_zVN1kP}oU$q0T^n -z7ec41QcB^|b6JDNCSEUlWLVT!)n7M40@UuywG~xq?_Re(BNE?a5Rhg+5}{)lG+wsu -z=LM!#>?y~Fz00CjQQQe)nXi9KoZ$bul2q}C1q$lZ(})!%1&d+1#1I`avZIciGlQez -z*mZgdT3Cxg08+np$@OAx#l1ig_oRVHg=9YTWF18(v|p_Z83VVWtkz -zEltO9MqIrSL;L9&ak~lA@Hjnw?%CcFZ#s4F=*s=^*77NOgYINq%JXi;gI)L#17^)h -zgkd6_VQ*og{C?o&IeacMAgN(zLYaKa1E$%b?C8E`&ceRjA-?Hlrny|HBrXr<4k*1S -zG4tVGjpv~CgP>!6a(aX64R;#arkC|oNDdp%)!yV@Kuc13CM?@Sj;qaE2t1o+jh@GO -zyp^Nyvw$#yLHmn=2YYFUMcTDW)s#HHY4hH;hWP~i^iW6liKz5WnukNGMYAbNX4mOt -zjRj-c&O40dgwXgKyfN9mFqZYNzL?o+L|2++K)8i3M3T^c}f< -z@XC#eYd=p{c4ev_A7nIN+k36MlA(*T)WE27x5Y_De+xpU)a6yCyP~$-|*>kO*Tf)zlv0vq)0IVkRw|`E&dhb9l%fJS2Y)+~9VK&oR -zvG#pIRrR}^ULxtoJE|uA1Jz#RP#inPSp-j*0|RvMXSJqxbPhB^oMIqZNn*b1nYRlr -z;Spc4Nm_iL7LGILv3Y2E*KFGsnyCdWagTtz9yqBU(HY|l`lyqXl2E{_Slsi9p^oiS -zF>lQj!B24m5G#o! -zNocq~AN#e#esCmn|Dc;Aw5_X$90jB0CtSR{Yw)>fm|g{myXc4A4@IlIisMw8~f_EFW^=1L}o -z17l189!GY0KsKM7&Bb_@*O)+PoR^MRyms-H|KNm)pn!_Iul}*mO+0hv(y+K_LtQq -znOr2|NTF2ugbWk%Yy@N^!&YDtK7)f&7KHNXwdXN(*j|jVZSd>Owvr|ID#`xzj_YSz -z+ow)ZZ<8i5X6zY2LbHAIGc-}L9m71;y4>RD)Re_DSM$E%%ZiT5%$s$VMTA681Q?NC -ztbIQ$$alATZtSZ-S2;Suo9eF571hkQ^jRO()xu|&v+XM!07!4xEYPMQA@E?V^j}<; -zXq0G~RU9OvDZvkrDcE0wc38+8PwX9|1A1~Zc@^DWVV}Qb2CqF}exk3&(tCQ_HUD@f -zq6X>tQgaO7S^2oeBwjsxHiqNQzP-v7Zv*OF-H`xIHtWbBRL6-~aqYKo}P|l(vuCk -z(5Y)DtS69fdv2PJ0%w}m6?u{!Cy -zw;omsx7>7{Mvoj>#G^Bh_&iNr4OHYGoh{HDDz#Wz+whE>G?XOSt?+OkF7=LIrhCQ~ -zfGSti++NT;T*G788vz=gdxSOAc%H{U+k%~~b8$Mwg52d9BJuVG1<)j+J3+om-Ln1zT8`izpMa=uc_gp*%o(wh99Gz0( -zR{e;?S^6P@!5a_t^JUL@h%j9-;-l%k$@(@0@bKEs(<$4VXR?$1Ny9s%I?EGbvXdtF -zYoxv`Fy@%rht7c&4IoB{CWbE -zgSl7HoYp{riEh7XUh{rJkGGz^Q){E)IfW0xJk4S{M00P}4Xoc29^izS?}AS;&^fob -z5~w0Pj%Z#N0mNBY*V@4ETQJ9Ehf&*&72EfPFFT7?E_H44ysKak=iPOxZ+o%8wTrC{ -zJB08(`(V*jxpTGgxC42({%|f;<578eG?JR&*NKmaW_$1w~FhNR&OWdVFJDhKP0-s0-lg8Fyfu_HL2 -z;&Z6LTWo}xmhvLIBz&P$CxxoFMU(Bkp$u1=bhlYKK**XO((MvWE$;yCcW5x>u= -zu)+-OY7kn-A?4e;MaIC>J$_b1HTG-12vc;+Gw1$tfq{keVs(~xftMpg|a7N21a>)R| -zVT_-+^_v2>DyP)!Gu1>@EJ%2p&PB?nx%I#;1oMKUANv>vnB2JouP0ETZH9fOdu(E{ -z4h1TDK2jAhm*w$GwhMmcF5g>Kst`fya_u;DvammgjB**S7*{zqnDWNB5_qPB;=FAIiDk`6dIF6A&^n>xvu#tUh?C1t$Cd+$>DNWIJ(o!=BPQ?j|(g}Q-#*?iYGxH$YS+-~GEsbbi8g#1%-kUNCJQV<-G+?2V -z^US0h%-Br0X#d5Bwm8sK#Ajys^!Tj+PnvoLlil``raQjtMcUL7mr<*rfp};>kpZ`i -z+md3GBwu=-e1K8vp?X^vNc{TL)fO47bYkW+;K7Z1*qy6r&@`wpodN9%WOR9$vx_Vz -zuur7xgkmt>ScjD1EX*<@ZmzYkwIM$59AzUmn#LP|*e0zX$0%O{l>_y+ -zp&p4iK~Vv=#9u2k!{@-Jrlw6tsXCb4S`U)}2ZbjMhu~;9j5YZ{2$-q$;u=82w$!&d -z^SO<-s^ZIY#XS{5R#sso8H0j+SM`ny71=^FBl2nJ(VbiyXx*>^g$AzDA*f$N$k^tWk{Kr?WFZ9I&; -zJa&}5>#mYFQH7gbOuTn=8f6kur|6xT6`_+Q(Yx`j2KdUXAYT;MQ)X4=ti;WZ>R4y< -zIWu+>DqXHPnKRL^-&W~@^^{DMtr04?L*l++bln4pY$zyoSYa&{#clvfZwd&IH(hV7jW-IIBJ>(p5zYdaSBcc0+$J!9Xp6{ -z(H$|L;%o46!)nIT!*2EU9Y*7Pm>EuEqi*<)DFoWlo&Q*Fw>~-eB=5mm3_))?_%odI -zK{y@{F@s-R2z#3@iv8QjT_n0D;RSD!3r>#%KyK!VmSvV*>QhhTQ#WvmeDFyLq^#3< -zZ?z-_;&^ik_r}i5gJ}mF|JicP*h|D<`gfp3Bciiio0dqKeH~b5Vbv3$%C#Z`-!u?R -z*O?=jlr3c(%yeLg)1pR)bJ*&DW2p1m1kRj%*q{VAF4zp;_;8oHCZFhPU> -zpkkdLBq1_2nfv#q56(R&m*2qU?g^rDHG~ni3rT%_fGBk*2nxWyPMcIu@)|bJglfvu -zUkk!6x3EF7GwCuB%1F=(8fxa4pWl#I`2q?tl4XVOZ&(JM7zfLcZI7-VHvtBSF^FIU -z{gzFz;BLO`+&lHfZM7NnJB&d8nd{rb| -z`s;IB6bgB;e)zn^uQkxlkiL}`Z17x~Vg>uQ%48Aw;C9G&1M6w~I!mSb9lLotH$|Oq -z80*RutKZ -zP4yQrrtWPu7M{!0Yw{evXxPKgxLsRkbfzTjG#>->-;BkrGFtLa@On-^Y#oG)Ctig2 -zwFU4IJL#5hP9)g}Z=wPSAR5(d(Sbf_08KA+q2NJDNNhnTy|XDLC&Kl=;rW)LJKqG- -ze7_UME7SA9kr>n}16eEpUk;aD?%~O&9tMV-zoT}hvwnFI%g$ZnCc%Nd>P1cViE018{3zH8$N~+|3F&c!0B(=d=b5718=zfsKy0 -zun_9f79*ZO(MZMv@^1#GF`V(p%pSue1}jPWhVnOiaz4DA_%MUTVBfpkuw;1fyy5mjl-=JZHYa*nD@^7O1WH -z!NIr2Cv%{z(k+i0eKGj07&^_q@O>>Ydv9UXHz$Ciy$k17Q(Bdnzy>@XnpBX{Mk0g} -z?Tu{pSsw8^kWv6y^l3#O6kqv$I)^BwjH3erHnxP@WG0fCyu!K8^mP#n6R#K(#;@uu -zE=yuOCh-UCKK60f0?%pnre@_cfb5IY1D|xgdmBqnaH;63II86g4~-OIHu;}N^C|5h$HWK}K5 -zQ*(C9^s>d5p?Mpm=O4VDgbG3%?lsbbM@Ym;t-8q@WOL}fZJN54l)8bkGX+3c*&MX7 -z$WBslbl|AJ$}*Vhlcd+N7-fJF9)<)j&^mwaxy&FjbN9`pZY+s2Rx|HQBW40hK_l1J -zIZl-3?rb8WDy8OlB5g=W|dPxH|;MjJfD{Lpxc^h#obOc3Xv+@OezV$2Ql5;P}%~xRh -z8wu`c_uEG)Jv3_9ojML(-y@ogjDZLYHoFl0`~GgVM`UJ_cFZ!=O732WksM%ga$VZ@ -zXAZ+9S;EmbdWQpRGU%|+cc{LCd<>$C*$UkFpvefWE|4$62rGoXxN2{)T@c!xi^I}z -z251i=L#yI-2@pxJPS!8;3Xgl&H7#Jm1n{_tU_1!ZG~xl_Cvh<`BMTEyD@nnP -z`EZs&))^`>hY8#`^6T-&`~oXX0FfEYV{nKF!WB8YA&$N>o3Z&tCzu2H0}MusNMMqV -z@Kbq^qxeMOC2hLn5>qoCg@|9*H6rs7Y^VYO} -z_J;vU=rkQ;6)+BDm6klU2!T3*{g41NAi{=scFRooL}hC)K_t19RYO!q&DTw@_B8{Qq!UmBsVE+KPJ5*f+T$1779xN#?{IyBNHsuH_rTdLM4hM)sF3NZ -z$2ocQ#75^+k{oE%T~cdOdYm?c=n4Sv*SQ!Mg*fBY6ExXJr|eZAx;x4h=#K@!h02Og -zpk?~5tfYRQrr9^+K3bM)jKGyb@9D@8g}(u2xjo$iYVXDAJX)q3!PZxh+3kww|4LYf -z10@7$@Kvr7dZ>qe*a*PV4HVv5J^KhsMF0(uaq7&pl-~(1jje1hHkQF*aXoSk>1p~9 -zBN3LZ(*AS|WQY~tUf-Hx6h_j)JE(V^W)#KiAw=HH?gra}QQ(brJyWtO7 -zCca;DaWHqL6(t{cxRa5&gZZkfqz5eLA|{e799RU1kqvJ+UsDiPni>2Nu{<($?eUSE -z&*aeHLx@-SJgiFJ22jzM%7*~eiHdJScm1_sF;!K)7Jx~q4oTH-bfMJo(1{iEG--t` -zi;m|^89#v(bcd(g*6xU2@05AQfXV|mYQ0Er&c2P8v2JvzA#DR|z#(L$DR&r4?4Z5u -z0~^_9%scpe$1i@|mP{x)*tSS(GK)^6orF4k>8Khy;rQjuhS+o0pnFv58MxpOVzK1` -z`s5BFv)$2ts`APM<%XO)s2ana^PQMF*x8u%6+CSE1)+unrgxV5gIm^mK -zBzu-~P_0t7D+jg_(+aeHVHm~Z#?RM{9~kC*GP+x)d1H~a4wWEtqCdrJ?;R_uN!^9@ -zGx&yZYmnPh|7LVJK%~9rM%%Sc_Dh|%<-@y#HHRz -zFc=Y#G;CtUY7@ZRg;g)U--HSq9rU=m0i?8IjTOic=4EqeZr0Iji89&@##4;8XW&)~ -zp!Ve@#dr;Z+6?KC@XB+qdP^(NjWC`+4*9JU^(lp=Tr=PSgQXN+4ZDBeRmC<=1oHS9 -z9ZxUJ-9Rq0rh6DXXNQb<=jXNCyC<}a)pQtsd3)F1%I7wNEX{KA6j>a48{5&b=E3u@ -zCx;}csf6Cla40DlvInW$s$|ZT+@qzYRH`6;6u+YrL;E(8n8eqny80)$gT;j6C4&SW -z1hGpZO|4Qxks5u2Z8@l|`)or6A4YPdp<&Vun6HqaeAN>uK8EfNEG)5S -zl;O&BkJFH^c#V48hnZ-?9ykQ>uY;TwAnBN*gh(@2X;#nVFjW*nr|qYUxAyVfoXMU} -zBY{comZR(S+AhX1NKCY)jbB3#s3XsBX5V0mr!X?AwzFQ_MCKcE^ZZU;gb?Iv}ASo_oKq-3}OeHnke6P&+U*>SGgxC -zqASzwR!w~IAblOVo3o`wOT&4ndidI52n->X0uh!?AF-&-dIFO)@N`4K5SroW)8{I4 -zv=Fk^%hNdEujcSg0UOF1Go2|w+*qk*15H&o!>1xg(YtohiDPgi^nw7MoKc-s$ru00?nU@)U$UdG@2Q!D!tJ5 -zJR|Ny@`;QM)dzx17j`Fi02gcBie=H6du$V(H+?GJv8#z0M7w_2*M^|P`1pe^)st9o -zZ+7VMz^hU)nz)D!uHDDni}6&wYjQg{*@ej<*&`+#nlj|ws-ayHF44hdVoqS}oUAVc -z{ekS*^OKB}>Iph#1-6J)-b~lVzMqwMHow;7T(xPWdj!SW4)M@3NG975UOUq7wSH&JOD05fA -zJg2XixWoc`(mix5ERiMX$m9C~6n{H7tU~+g_Bk72KzPm?Ne*n;kwujK8e;u{V8FHHDLsY8Ua -zUOq@(4%7PO-_FG}h@Ll -zY8m4nns&t$$kKpp@vEjC`cu>9gEgMdXty@0+5@3Tu6Wh7^?uQ`y-(}aUx#Pr@0zxJ -zF$9xIiXwEuskQI$PffcQvL4pzcTF2}$C>d+xDM_s%G;d&; -zeg^P}S{M(5dZiyBZwXaDP*_+u5OIV%5E>Pr`iz*glIMbRI+f8lkna)(-V|%()VPWEEq3-oLX^T1if=b;#;XNxh{hNA}3rtb);*yhb3%-SFdp-`#_^k9IydT -zMB!%~MV0h0xtp~KIo#L)?g1d$naOCxkdSDMd`+Y%8w_?{SCD*0O#M2=O}DW#t9U^9 -zhb7@Zn#8^#Kxts$HMH^rS{&S?yja&IBDRPeErl{&3Mwz|;SB>w<>f%Q6JeLKA;exa -zv&jSju`32&7!trLWkI`f6d`&41=P^Ek=cDLEQ$=LTR6E0RY03^(K_)iQdVn{2w}VD -ztI3OikDbDE|CM0ybR_qT>73`&jrp#HVlQ~naa09nNOKS5e*Aad~AX$ppBUv -zYOCQJF4+g>%;J9eC#DA3Vm^k>>FR3yoy)<6LU12yT?pvzz=gtRLDz$Yry_Yfb*=j2x)&6RH -z3C?RB@TIr!P>=^wa|t!USiJ|a)WCy3o`T1UT0HSx`|jb@cZg6jkPX9Lka`~>RJtrI -z1K7e(<VvhO{dU9ip))(66&}Bpa-%6UJ;=YL3UM -zbM(d=JIEFsex;vc>MF0_~aEnpR=wHU2eIH -zJ#YQr^)zRE@d}PoIMk`qh2t~}2D_GU@X2lKU6I0R|_tfvB1L^PMo-XVF#%yIBvtOajJgD&6qWW@9j|5A&p`& -z$)Ao(|4H~T>KcO~8KoOa94ci;lep00njLU1O0+`AcSfd-QNJZj9n_zcrHyj_0D -zw&iM9O&OnWFeyF8(w^hbgozsStw)85Iui%VgTRy`K1n0Prlx>QIq1AE|F0n=_ -z7dhp7hq#|n8rv)w6D)*=Qj5ucIZ`L@H;I-+MpHCUot{L%R@tKW!x_QE5aV!5W^WhX -zJOe3uvhtRNOn(m3}QkjpwNqvc-!Kz -z5|;Hz$CDojqr$@4AxW{;2FnsUPk7GAcUnWzeRje<-Bps4PnU8y){pQ4!?h#&=e! -zBvR=!BPAI!s~MQs#3ls#sY%#Ui$*1UBgPd+OuiYe!qosR)%)3AR#r}Q^dSl7^U5z= -zYCH){%;`iYJlVdW2u#6 -z+vGT*P8BkH2ZS^5X*0&l(ZUBOv&i3^Td5B0dhB=+l8et@>7FnNk -zd9DkJTQdAB?FY(?zUriGZ_rtf0=~1&l%qHGGZRezRkYg}dFhaS{7cs^PaS2aRLXGK -zgK+~Ql`WweW``}cu*LCc;xdk$7ZI$Jq$~RT##<8Va4}*=Y6PyB_OTgQy@Y(|+WG|$ -z=4=Pn!ol_Tuq&v3Rsb2+*&=n?b;`pI!vte~Hvkfu!@!-2<5mq0bfK7#F6TA6Abt6s -z7#Fm?-CF?f#Gd8bI8BB@Zq3qtHa`a%Cd^^V)8iTi^Hl@Ao$)RA^>J76klTbh@0(RF -z9FW^AVI$Kb;Si|S2c$Jg)U_a_>}C*R6CACMWK!${`Da`^wvjM)Quhsk9fgeZlE`&bX`lvw_dz~vY*_{e -z3<16JR_E2^6t?vI9F?T&s_?wGl%KX0`-{?n8S@|Cvz{@B3S%Nek7>+rNx-?4=#B0xo&1MA_ybgsvPz{19f`+$Np-E{gyMwMG>rjb2;_!M^ -zE&)azkW0m7UZo94UCzQ<2K7HMLd_9Cq5kM{WiP$ZpI0pRA{vZbA;$0_Ozq~<+2BD -zbEsWsgtLYL09#B6erCj?9$4QzyO087wpGRIU<$URP -zn$fKYfB4`dg-+c>Z>FLhF|BA1H`LOEFtXWFp^x3I?rwsl>k69G!&^03fMLntZ6n_U -zmMp6UkEN+!i@W(GYY0hZVQdZ{;h=FeiyIbAmJLlzj70d&ca4?nZlpqQDA4=xxSn}f -zj95UuB0*71UYz!zkbdPJ%^3zzFzZ~j`xKqOq6sffmE+S+p@~zVS0fp3jw_BS;|?Y3 -zS(|%XYI|VW;XM@=Pt6F?znS~z_t@x -z64JdPvsT7zV7O4cLZcS=&^g!Lac(DIGGH9kKl0Y-g*!DXP-w^c@F4=%TIYLILoe9-Z<==R!>6B` -z_Q=*hXxfo$KQ(QlziZm>FA{EINP*~o(X>BpMxreIs%gvrQPaM&=av0M(@vU_ad!Ui -zn)db&O*Bs)2Y11eD(6q;2HSMrJYT6MsKQ!&IS4}&@e{SVl!gozO|I2^Z -zv?rH-)3ot_)wBt^{;Q@fq4Nh#8_D3$ns(;O4^5l#zi8T46YT6nR0~U)S8}onAw2_{2RDaX75dldXe$lksJpNhJwplDx1^@E)KWW;lGJn*x -z#o+&4(`G~L&Hbrq`>9(Q{m(V+y@~IdHf_T1nl{UKOGy8nfyJznoo -z)K>Tpn)W2v|3uTqkNBx+N5DAg{x3D{Q@B5B+UF(THEqRTHEq?%KWo~#f7i6%yZo+c -zn<)LzwBI`=i3R*c)Bei)Q`3%7{8iJQ!2h3W+MC(`P19!3`c>1GF8^KA-X!`LO}ltf -zH`wksO^(si~QJrr!4_tS_r-t?Rsw^JDh+S66U;PM_xA -zns(W{`#+ktjD}>UeUIVMU=Yt{b0YT9K!e`(rsy8lhn&TzZJRR3?9c7XhUXxeQ5 -zXxa>T|5MY({#(=D_xoQp?FQC=Y1*4||5MZEvH7=}_F&L|YTBbq|6bFMFaJL^ZGnHM -zX(P)0PfdHY{IhXD-R5DU5u;sarBQGp1Rh)9p -z(+|BcKvZyjcg48Y2E|pJdw)-0tso9lwuX!2pTNUFpRZX@fKHp!COS#6)Y~|rt>*zq -zE*47O1bdzQbO@&SijzOkN(4ksZ71!crmDJvB?{A)k^9eMAMnBeE$1hM#F3q?3}MAs -z7EnvGiAe#nb;cgmO;>++NS6*8fbuLMNvk|Nu8c6m`DX-EQ -zVc;bSMh>e%lq8#}?KAQxkFN%hRteej_fDXCFa(uqfMqBq<* -z+^rR?s&gD9DuS@GJ2bTJp-2CvX(P#%qzBFXWa2gX*0lGX=%dr6_5aqi7x-1bHSH^4 -zoMx5tm@}?2fx?II^XZaE{y+V5Bg;GrklcT1+Ho?tMBkb=IMZL6b^zEVBC2@GUz#?y -z44pIy1}u!mx-jwZ@o<`su&W%eAJI<-V-nML#(~ZYJ-RKosd+m)p1(BhtZz+w?l$IU -zdCzSW(|7qDXq3Z~#QC?T?ftRad+2l8wxBxZ@&m2Na#?A_^oLJ0)A`S0^u}} -zHx_?s+8OJx!BP)yM!Li;^5mHQ<54_tgIdkGCFVu`5VVkeYZ@#6(zHJ{^mpPPpyH7J -z(zG${6U^)0gE8JlG>qdK!no>=6AYVtNq*_1RKafqUKSjDYueWm|DkCQsrr?;!DoMW -zrJq7t8B8UiLC=rGzU`H$@p_y&q8IRt_n!XT04FW}OVh^QEXHNK -zQAaHPN7EM0`_{Bg;8g~=efLz&XaCl;eZDpA9^FYTY<;I)z`rzYKN`5wRfr<-7V~5! -zP`#))Q8J%rzp^4X+rKq!NTs2T0-T5Dzcg(^ihnfi)o0gV-I)76$l`dxvW8rOTc6#tb$#H7NMOIc^Vc>$Di -zxzdbn<0$b{Qs;EB3V9Y#Y7&s*Dt?umWsH!Zs);O2`TCn>sfuvc%BNWJJo3~*)Tu~bRK$O#zJXBJmDCNk#w0N+i}kRxr@5eibXpN -zlu#e0o&*HSw&e=Z4B{+AW#xU%a=yTP&B<0KJ3`~L>YXX -z4q;K5u6WORoDU$i$p%;pI|?MMK;nPIY&OIaHu0FMX=?RaF05x&h_To`C`&wqNX&t% -zVmze!0(fV}1z;;C*Z{;XExXzP67AYx@z5oe{?t5?tqO@#4PGtYvSI{>$Q|2^dZ{6F -zAWAg!EE5Jcb~Ii#6@wq>H5@+ves(n!7E3Cj4=Z9tg9r`owfE9R1i-MPH&}|k#{6g% -zDx?E_GIxKUVG`y~Nip~gCBzU48ejxm%A6r5*^t}y3C;$?ZjPkO0O4Nv({-r{z_>i- -zqg7mSy5t^H#Af8@sBX7@tAQ_F{ -zth1No5=h1peSpmMqsS&FAzO^rZq_;^VYx{+Ph;gTdx*_f9XT&5>iV -z@P{?a5cqSm-C?nwmc~SoNF;Mg2G<%!JumDQRN}KTQ0;i31MKDysU1Bk0C3~7K+&H{ -zoM@*~RdpzAJN%^5I^SXvohXiMIWRAEOOHVXk&tGwts4!il~=B;U(2O*5!VK-N#UhcKHoF+~V!O7!rN(%Vyn4HUUcFqTs`rjv -ztf_0r_ynJ>bCb{dscjSKZng*x$t>Bx)FS+4_;wnjl;EGv1fp0Rq}$f`q-+IGC}8SS8)W2=bJ_@_qY5!#qxLFWM0O)|E&%8OZR;4v)@EZoJ6vYY(`lZBl_V*zPX -zG8Yyl+*5+lhrvZzF`wCsccrh-Vq%fGJqBl3Th3VVWtueqhvCk^0Q5Xg(#=-DhQX$U -z#h9I|4ukcL1h5C8^xF&P1wuhJ^rv%YM1~KtXAgnGenb^39`%T~qKiS;sY)=DnRSza -zA3;M%klmSUrjPpoOT(ycAsese0O!4G6i@}~2P#plAgGMON;rIey*Z=l#(qmtT|w6F -zK(O)4m5C6Ief~H%AN&mRt3#!HnCKmo-+6W`H34F2!PCjF|1S*ptr={E`U-JlkR%59 -z?7-g)H?Gh0LKEQX-wgLE(1J>&)?k&nYJA?Du!E2&*dv2mgHj5fK~Y^Ne**DRh0J<7 -z5PL`4;~*jUs6KtM5^ImE2Z1~y84HWd;|a3%rQx8MX~A7ckm4dScSt>Z@J|0x{ij8{ -zS30{4EzftSRgHBcu1K3HYY_WY*T*yyF3bA#eyJ}NxeSb -z#qA_>X!q}X-12?I+@->30WR|%e;wvC42TTF_qGp>NHTY;QA1qDeVJWgxB>aq_=zh_ -zi-^LyobVQcv#t<#{K9$rn9f6w-|oE-YdS{MuC<5IbI1eRlY)V1A^C5(&c@38E>a9B -z(hHkd5rX+}x`J^1M)fN|HBPS@G;ZA4Zyyh;5BdP||_Y -z_WX*l2S#g0AWQ3Jr6sr^n>&@(v+{g)r}5BUqqBHrOj1efBg=xvtQO$hWPD4hSBrQA -z_PM0vl_i$e1$z;bW)QfMLxyPn&{ -zdPMq50E`;o;HWBQqOe2`mG}yqI4gS -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 -zx@z-$)E=z!#Humnc}}Q^pT5v2yTyz}{|pt -zzKi1qJTzphCsb3{L%!*h?4doJo^pN?(u{-HFxI_i#5y%tW^SKmeX$9c0a+DvL}1Cq -zX+QYnTg=cz6~Lrr>m7#XLg%eTz9^db>~&F~=_n^RHr|c5zMY)i8RzHw;NVV$SEgb2 -zkA|PMylp`ie52~JaZ9msLU{a!b-}$S_Xhsqv9|;=(eaDP9lX$UcwmQy -zh7B%i0YS@RBP>!jDF8VkMWO|nGz>w1KoC#B45qN022XncX#lfQxCtj0Iiorp)S_d? -zq^F>yLG)OTubu!7$pnbC01VAB=sP-_w(Tfs9-XsoEq16Qi_mUL>L|C*x}oPR@v!bp4yy -z8M597?mcB?1H7NgxuOMmJWVPF08{n4Yk}xh6%{2PLzuc}TzL0SZ|%)X8+s_d>{P|z -zVa7oz5aV&Y2&UD5sMg6zQ23Z+_fzt7#)WWfmQODg8o&FA2V3#nfe{$IkggbX_<96W -zLzn-oX0O_o4pP^$RhI4?fVXXPpz$H69XoVg45F0RqGS}FZ*X`S^89)}PQJBs+^`5N -zvh)=&Pc&yF{P@deNv~h^c1DxF$y9Aa9NsZGi(we?-Uj-&J7smLcn{Lu^rTC)ec(Fj@l?M`JlIxiHTG$ -zs_PAF&=gIX8|G>XQ`J}l)Ru_hKB)kYxP6^_m`di?N5Fu-#y>S)d#rsb+LUB8@??x8 -zLRQWNov6ohQ>pqt1~`n69_%YiC_g@N>x*=|3d>}ncNwGk9K?LEC1D|i;NSz_Hlbfa -zntTTQ@MLpw$uUB_Nw<8St`2x5k265>XE2#mRgc55hZ_86cmNol6sY*3+=upN=1aFt -zHT~6f*r=85^n-aO*fqH&tm@702r{Yp-jK+;G=c?FwDAy0xi`FEeRNguq6KCoF^rWi -zrrrJAqsz15@$-6Betb><{3iXJ%g}aN^+KX=Pc=vt;Bg+oOPe?Wz!~Fxc;V6clWdW` -znBsFK7nDYBjTz73Qc-5)_&{;Jcx`v>8)rI@P#Cd;dywK6(aeCqElsQv;yvF(?UaOi#OK)#ps`Z0B#l1X)rOg_DGb9GTL1_lE -zKRk4SW=uAbJGCt7@w0#ls)hKaBwAj^d?C<^d5)_EU_M}q?nRE}xCffpgeMxiT1NK~wry>7y{}kq)Kn6O -zABEgZD;lg4P-41`pA!|)Nqd?xWiH_of~ilG;3xKZ=NE}C0xoi))Y)FXDB*(e(tMpP -z#S0D529ORE>dBGw9$8X&e@r~uMO0@$Qr%!o&LYV^Z%93=h^8ajc9<&H%%%r{6i?t3 -zI1!Lkb8EVTb7R=_%-y$6Y|v -z)E?eJHvhrSB^nM2;h!0V?eDZ-m5%B?=jOg`vxF@1*^O?=jQ3~_!2apGJ7d>Bw2u2P -zR(h(;DWa9)6BAM76K}E!Vf|wt0ZG=64`IwnM4x0;Js_~M-D+v#Bb}wZF*UXL4WhOd -z7Q;hfMETFbm&}|f8kzOzWgpk{x`TLU--mh?Qj&X`qz3A@@kv=;)C_mxAZwYrj -zwM;r4)J9^uJxAsSy}i7Ea~Ugui!%1GD^j)t_QekAFNBRqLU+onZxQ$x!ENJ>&|!gZ*eK3r8i;FvIRbI< -zC_7f7N%l!ZomIelGiQt;A{KUll}SNAmD=6(G7u9 -zp=adnJlM9GsJ{qu`{xn5jN3!G#vU-vlSJ@0!95-GO>keJKsXhD6WoSne-qp^6`42T -z7J_98z9EQ?H8FtXp)ucwi`=hXdB@+tnL+F`-KRrTHcl_H;+m^rq1laH5n$4CXdj$Z -z)+LgZyl@gn1YgNB1+p*^?l)V3m16FZLpN$zoOU_F4FlEfVqt*TsGlWjs!zhZNFSne -z>>Ld?$}~^!+9Z~Cc!iX*$V-nww~84U-z`AUo?y!Nw5%U9u{RJT>JjI{cY4#kcfzLL -zK>*d!v(!6Elk+SY%|I^uc&MZ)r?iC!z)p+}%@9ibO&xV@a0W#ms!M^NlT{FW0UT2_ -zG8Tp>?%9nmN@1k&y!}*ySIM&~F(P9_!vD>0?CLAUu$&swWxfGsSs=F3Qxx -z6!4|#k*|YS>h$%a;x3v`cw^@({w&k7sdEQAfZ1eT2`F=+v^m14e>ZufOEi| -zAOXHxC*y|i)|?$^EiB9my;^O#+m3YCy6mFCcDIf#VQnbJcDT6;DcT4j%6z6k0Fgg; -zrZu=92qkV7Ty|Kf%Vg9mEUMz-wZK$%F!+Y?26xqvIC@dsBh!cTFNU%!iXfFfXf2{<+GREL8Q;YxL1is`Qk6FiOb(4q_O -z^?B18bV_>HfoEvZ9{Hq%K1FNr#$->t7o-u84E(Dpo|8Zo>MH(RT1D8YHgLl{_*>|o -zFo!(>zd6xcaKRfSI7ACTa%mGu!%H|%Yecsm!>TnxzC&Yz(W4{Ig`dQK3*7K+;KiW= -zXx{?&52GY=k@=3`=V&_V7ti%jO6ia`u9?ZpAi-|WAlAxnW?Yzgt=sBh1Cyq* -zvnw+DE8p9$hrey%iL+ds_62DwokMb54dPU -zti8*#NnNudp5*GwvHV=Iyimuut!LQG=(y=~aq2Z2y8>tIw76=qGI9x&6;#PVa -zULvzYGYMy?_T!5CbB$ts&6(__lVv`^k0W819U?6)Z-M3l);nGljtRWc@)6F}&PZ4c -zKXcLXd}{$py{M8K;k}qdaQ)#UVlVbVg=o(Owp;3oU#%?HBs5EoHyfeMRGtoBix^`E -zwV-QtG3Wc(GAGzK(l6T=YAb8TenUSpUj~z_-3Epyn?38XTBkH#>=DN2?kd)ZeyMX) -zq5;LYo;+PTM4;s(^oia9cXlAu!3i>ve=LpG`Jz(A?C%vUZo>vydyJot_dd -z^8xcA+|n>BD!*h64}aW`GdUF<(i4u2ZZTv=%Zy#sxw}*t`Ynl3<$Ali#-AtSg`ra| -zoxe0^o42vL;u8(fS&CoiX3+TJMg&T~VVOTV)WJuAj{X8Wy5~NrT-f_t;FkOrxD$WE -z+&mLkT(_0AIj3&UL_PH15pWPQbm8Duz#EqM7Slx|b+`Y{*Oz^93jWP&Ndq04K)X%` -zQvPDO1hZs#Q$Bif6%4f(4flqaGeO3$i~OWJV>EUYWY>yo`_eqT+X?GnY6n*$w@k}r -zWEtbnF+0%?+=d#S3<*mfpV(MOa`6BKNC%1R?FB8p(uVq5Z3=7>>ap#re-&o0$2;7a -z#N)D5hcuK&;lxCy1j$<+7ojh{f55qhacx~ucF#EYo&&UFvK~wA&Lq2JyqI_UEyq+$ -zba%R8mWnjT{)F97alNX1yvLaSX4oAYr-Mf1W7gs35bVZ8L15-t@ZwDh)LiF4L#!Hl -zQT(2%kjcMI$0hUU!|V|pv*W=-JDJ(YSw5Wlvn&IlpnfA_35rs*8L!2p5QEG -zg%oQKsa$4<$VVnY+O5s7kp;?od5z?uLxGjdRIypD;*xiJg&rgJ5#mva9-A?7^BiB* -zyDk!iv6R^z$$zMI-7DIO>OJOsPB}i*|IahS5jlndJkS;IG1~3CBI31rgub5nY -zHqdp$7xXVxJ8Bt1gj*QMVV}$b?kczMFd{HZdj~TkGzNHw&cEPtT`>6AwG$=ZAyHJ_ -zn3_(+;4O4U4h&z+oYwCIk^SuxumxH|#sL68ClFbtGz>1?0#=Q?7aeJq-rHCZH!1?3 -zuItE-3UgorGMJp-*^yVGg0yI5Tb72;TGB|v_Fn;xp-=?-TGaUWlvZHJJ|%}XEM&w# -zjqJvr#q+*;-s-pB*r$^k;rN3gr=J1NhbdWnJpuR6u`{-%a~d9;#i9~^NppcRDC@Ba -zQ6r8guV{o;#>?>+<8|DWQswTKkTWMDk(-6#fe5;0<-z@L!48_kPPi;~g?sVU?`It`OHcCY~L&l8Jb_gW_+0tE!S74Dp4+kbFV5 -ze;{em6Js8F6@9BTCa6J-gSQZ=j;LczgU6Y8 -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 -zZm7lE&Cix*)!g~ZsWj-Ohx*%U_U -zr>l&8DN+V$awbrk+MJ`cY)zz?=QSNggi2^wf++satNo!6zFroZ6+Qw06maoq8EE+K -zm#`QGq->OAM!aQONdSh1(8ALdyjyD!lc%Pd8r}V_*EEH>Y1dz34Je -zUwaME#x|hwI@&p3;&{Kl_^`2CY3q7q+U-e{UDKhURhC@6pN|(N?b!2GvUz(-5ot?0|a@~5zD$>5J-Lk7lxh=R&9$pPL0g>vOh7fb2Pmf -zvM5Lpg7THo(&a$X9$;GCmL#<~>N_)%|9(MY>Mvi|NsrO_b=)B*%2r`-`0BVp>rGf? -zGFsDhZ>0D*8TKdnQ1mO`Akxo+9C7UrTH}uxC=8^=J48QePp4w$0|!E)XQVNEC!vvn -zi-~1Zb^5T6`E_TNt>Kn)ta(tdX16tA0AuU~dlp&i9B3SEjNaPPnF-;R;Ow8&%cMQj -z!NyF1(!bnv8u#dmq0ocBGNIt#20SW2GBB6&s$*&~u4yT>E5`WG*vw()*on1q#n?TH -zu0^|^c5l$HI~?*t_0SaNBT?*!%dtSy2B7Z9P$bb~@SKd=OJy1rI;96l%PoVt@axhX -z6r2d^lxtjRFj5=0rO4UruuC;DR*9iuOK)Rk9AK;#VH7bS6`?9bfu=nI><{siGzA$u -z)F?PhcZEIt)(*o -z1716vEM?x*-`{;=MXA2rNOgViyyee?{qe+}P(2g;GwGnX!^M#WMay>tTnVcRvznaz -zv)aQ)xxCbBHH4i8A8RB|jMby1<$Pz{%@+p`m;Ha&BFsxTv9&4>(zn584p6kfHO>$p -zkzF;b?Y{-labzFJ>JTptXD8(o>IT8{1{ewOT%mm7pgHO2Ojg0g3W4@$0dr368#igo -zWPCf^SW&nPq<#I;UD@$OGm@C!4mT0N8qP8wR)GCU+r16auWyI@CiyidG9WI*&RNv+ -z0y&|!B5LEMA3(X|nD6V=J?n&tSUx(s -zK|YK6mSvA0+;T$D`?&YC+adKv0O6+d>u-l!n1PyymSUaMA}#?SZ@5v9@)U^`va+=U -zRYbx{b{FfQgh`_>5T1t`W>+9zxvp6(!dt#Lz`;(@*K(yvVnHj~U<1H~7N>dW{G#2W -zN=dCPZf?!JzHGT#>m}6%Z)XtzSWO(+TMjXBuJeM!TX%VXAMT{X_R=VLMBa9iAi>V` -zj#}){5RznH61*Cf2VF{H5xQM19f-z)kd?9qkppS|^ISy9T1f^7$gfISd|-#x_Sc*( -z4{%!UIzOVx7TV;|Yw3fDI-5kXeoh=b2&?50QrtkN=JW=`5KB_tfYEkmc)l=$LfTyI -z0>kJS48{x{@ihx4yIbDCe1DInES-C5a9-sQJ0sVEI^&DF(F!T| -zbIi@ic7*UW)X+<>pfo69i3gm6f!CW4;i9le!hPbRA0Qt^F{-29`K?(G;7u7ch9O>_ -zuYs*r6s>2W866nGg_=!e`E|!^i)@F5uemXo^@f~XC`;S;r!jBaH*;{xU~EZ0 -z4h4XiUJN`PWAXc9vpXpp`sMx>2EN@aGj32*^8fg{$L_$|YysD?Dzs3w!(t=^(i)7>v}0l5oH)Pb>>y1(ulBg{E**JAE1xnbHn|g~ -zt&a!?9L1V&Nae9@c&ykaN(u=yV9u9Lp}VC{fFCdHAkN6-zd1jbXmb24+0k&aeWaTgWxBC;kW8hzXm -z+8BDqqX8-2ukemosSgs2Oih@dfB3X!lUsOO)5*b)tuWJAQ$Mt+w3#iQJYvqkx?$0h -z0VH$)=XV+|MToG>nSK{sxvLPdxLQQDvQ8l<&bPl-_p$ut4Luo_{2-9iZY|-eVS^GP -zKS<)~b%@UP55#>IQuhsUXO(MdOQwB8+{{G${4V}Q-w?Nzn(sOhWe=Qxt!R#m^(mmj -zyi|4{fM*FccL05A3}y4GKe0U{|Mz?SW$YYt^5pS|t`BcxcoiBc -zK?G8aBxZFa1vH5ZQ%yviWBDzJL5ZADo;Py-cZFh@Zjvg;KoBRU|7SpkgQlz951b#k -zuuw>@Jk2c-qWNE)bu>4X -z4uB`D)|M@VH0PReq_}SlLALS9lwrSl=P}v=W$_UPQUb~_-US-tto-6G^aaH(uMx(f -zBR%_>CH0*KD*pH?^KT33Qos?&q}N8Qe`g0Q)qB<2b&P`kLfq3wL%8U0?WLOE5O-`n -z$jmpyy(TxxXTSXoabwXlsKl-n{e`%X{|DlBT*4Y`!@$!`xezcF*!PiR>kb0h`-v9w -zBH0=%0<9M5C7=x0N?g9xZ$60Zz9J2Co^cK$6%ghCR7K(rI)_|HRw;dSwbsW7vGXS`2H?O&JiS -zqbTGyok+iE6pIJFsvmC2oJ_*)C3+gp0iOzDzA!Nfq~2V_CM}jqA@?!H -zwx%+VBthk36>?`I{oj<&VxiQhuevySJDKnvV%~ypCYQni%hGD@EoqTKB8?7 -z#I>q7^1x{8=NdEnOS6Jaa7~@W5^0ZCze{pt92KZNH77gpLXS0m{%Svoj{lpAp{cx7X(`0X%=w_mb6Au>rsBTyy -zx3;{=W^+uE_2U|vknkUwJeVFNY7n=;%W#?6+S{Q`jhU;v$IHbQeG>PPu&?fuk|KyLo#8d|``*q8zUzDec2{!Y*(nx16~^W$9>oz;J?LAoMuE -z3`5q~U2o-RVTw|pL3)6rg&eYU#)B^n3L@iPwxmK(hsUxASU4SQM$mVn#zXV65)DQa -z`i?(R#@LcmftFA{{Dm}HyTCY`){(i;p5WPv@)B&Ar_uyn{6A*rBfB6{@>*5$+tXQB -z9S9t*t2wxusr@4W?G#U-$SiP3IiHXhX>s8NCB1TSt$!7KGGnY#-R{4+s{Bjh{*l*A-M*uNTYMz^?hnc0 -z^zvG0-NB1_hNDYdpI~umari@kW~3QY1{WZjAF-xAl_mzr0%@_SfZo~x$#M2+zyrH6 -zxSS8>2NWo5zQ7za2QCc0{<+jzBD&1~lBoypL*#2qMB7WXZ2>r=(KN+qSLJ&!XZNIqe%ki&LWB2Pp;aLs3lmEB257Vln=&RI8hak}On{3CJq{!8N4{gg40e{!);`zvw#QvA$OAT103 -zN8-k({3~%A7JW(;J<5qT`?ju;>9y4rV~t_R*6VptnBUp3z1Kj -zIDmvxpaEV{3Ngjmg9Oozp@`b!TKGT)h22{m)^uib(*8oFo8iYz_Vve+U63Ld-Rz@Q -z^&@bvt;n&MAIhzzM3Iq^-{C@dmS(b0q6IPh%% -zx?d%DYhrD%z)ANAZs%a*Dcp-5JfQ!o;QPSAd-`-4#w;V-YY_wYp*6q}n7~Ot1E~0% -z|7Am19uF -zO+K6>smNP*KqUm|4nuP|j%kc8f{iPxsmbr{5nEKR&Bfy0G)N>9t>Nwo<_J+eEvEOJ -zIq2X#&3ub?%hCx=kx@(!SC4at5)O2wp`SEccX9F|QNV3`cM@OK7fJ%`?6mO>W@P$_@P -z_VFMO&H!$qpS@CvULF|9ur`azd6ZDCq}UG{Kytn8sV+%zB3{cDO=qJZ;_o{@hy5N5 -z@**mI@nuAFY#s05Gq>6a34!lO#OS0V$-2i(^Cy6Dn+vs~((!@a8lv#+`FfNDh$Pet -zL^6A*;3dcnU8(cnZA5>fKP8qy0d6VM`X!>o1QxBP;4idO5Jn^&5gwUd1xgMjVHvA^tGwvuu>krO;v@6e>t{ipOh!_B}1}@;YQ0x+VJtqwECi+}J -z$uTjwd*y^P4@qSlNODHa48LeHM@0}@D^X>%wU1dcdySrEB5%1)9=^jSd?_wY6&-Ze -z!bLB@L($5JIS^GDpnSfj>H~l%ate?BYwwXaX0xrzOxlyS&;cm%-e6M~6o%@Ir5h+< -zvjsc@&u1`&8P>V_)pvhQAD7*cnTA;}6C|Szo1$n1L;t&h?`1J{n7CQ^&ImdI=9mk+ -zS$3>Ye|Pje|6!_-0G6lrI+iuSKmVb)Oa4P~r~E^4$Lzh2jgP}s<9SM`-bQ_BXDmMZ -zLrk_}+qCN(c7wtKy|Pb2r~Mbjtsik~FmZMUl`k>-O>uir{~wCmHWQ^+`!B^E^-Xa% -z;UUgBe*{#@uJ7*sjzkfpW4O%ARsJM4my7%W5H(7o~S3CfNUO?Ls$$a(oG#$>$rB3%(zq~|79wh -z`6w^Z57T)7E0@ -z-QH)yHd5%1YG^M-#KJ}u0ik+2xdEoqYHEzI8+E7>tOI)=+y+n`F1Pp<9!cyb -zn8{?1@)W3Nb?4%c>gg2f$4e#q3C-0ih#3^qN{@2iWicU>e^K0R|4`h~|Dm`o{!-jZ -z-xT-UUy2)2s)oP@1G#_2;4j53ep=Eun3?xYac{)@o8m^PI5NZhhvKGI{13%Vg9wND -zAh8Ix6P-F4ASdjf&UAA6a3l0BZS?=B>d5 -z@7JWN4l3{L<8eA_j=?4qoc$HnvsWX3b{_h2Di~HZjOyHj+8Rt#RP)%=)d>0?rQkyD -zBEEwN|8yXCka2X}IO0~a4^|DYRpbm;c&z?Frz+RS_~$ -z03P%OJlwzsidoZuyQ;dM__z>@KTPMNfRuo>c`dFt^sU;y_w}r{28UA-Ek4SY!c-g8 -zc}lz+XoX(&$i$^=Ee;L;C-fghNgNk;h!C^gx4=j@iD -zZQ0jhe7O)=Uisbhx^2m5i^@E#8^{@5K=0QO9eQFhO+J_y!x(Z`6tm=H{Q~VfNrnS) -zFMov}%wBVv`>B8&4Fb)Y_~H#6^Vnkf62j>Okpj3;`6CR1$gtR}*t&%!P2MS5-9;X1 -zv4gIt$2{;I$-hC^ms_&NU`2T-i5{#v%N?!-PM@WxA_-U=SlHcN?RSdGX9Dto;eiBg -zWvQ)2ArGn~O;&ykTPSuljuy@8rP}V~s*x;=6f{^gOB4h@aBZt>GCc_MN;^>zJi1x@ -z-~e_+r*I{WY{`bYh(Ep9uZkz&6*gq2WUM5d!x*=&0G|9M+Wjn){t(SdvKjd1bW_X>V7=(hxR&$`_~t=w$5?W@X9Rd^y#ASZ2h*V3s#f -zl|=8B7i#}kIdnVohY;}9*-3a^^u?17HXp)pfj8woytv0rG(Zqpf$k@1$Rf;jbs44V6a@+kplGm~z`L%4!`Eq40qjMwZ2 -zbTCw9iIxQNAxDRdYrD-}PqG$daAxCcsVlIKxqHPqO)GVohQMyy%GEZZ4)I6YIv*`0 -z`PrUoNkcz;V3ZUP;TQZ;#gk$BE_s=r(_BA%KVRR6ZR?}glfO=*_++r7#ZD3EaSR7T -zN*~K*zPfclpYU76#K0?~WmcUbY`3}JYV1HMr9mSo7GlO%1;Y}ByHp`N!aBvDWCB6H -z@^9Ta(O&B^Ee!wk$?ryxuold?OIPq=^oNL+YT)?-w`%bWZ6?r1G{t*T_&f79_(T9E -zlqZpR-7l2Mx-R3Z4`op(3U8JSgo6c;0gcu$<4xX_ifXBj4Id0a6yAiEu@k;qs*{5s -z939#wiV!9A1?PGC3(q8|>bxKj+;HQWyYm=1F%?Cl65KSIVF3&6AEIVTE9`88Y9VgP -z$Ii)`00P|G*CU!M397D^dW**u4w5NHPAEtLjsJvmue-p+;_9xNgg?QiQQ_>8a5kyW;&G!7wNz -z!r^8^w$)p<47X%#-(|>w5bh>>a?+9glOCu@4fHuZ^%+3yCW>XA2+<2qU#j0Ttc!H<`$J2 -z!=h6H<;Iq8I_J30(hNCa7bhez(MzTIHNX4d$#nyywx&w&7388<+83#(8)Apcs%5>} -zu)KkCHqq$+tgih`Uej1Kw;alS%R$u%P+gS{BIadL9nbwp*#1 -z)oDlDUSR`%-t)@)dJ>AaHb~i@*>%f=q6AF^mtW}){SwdE7vwH)AxsHm$8yk)aRJwL -zlZ_P;_+xiWK}~f`Oc@4v6GK1+yo4@i#ko3xUG+Fis{FL1M2*K}HCR`*w>r$?2Ex2v -zCuZKc*f)%McvaSZf#+MOv3bbJ93pkOd=OMB7ss& -zuv-3=p@HkroRwr{4Hqh1m;Tt|sr{)%GR39bD`uReSV9_mc-ATDl9WV{80RP>6CV&B8prB9m -znkl4v^YsZgdL6Vq`uH{l5pyLnXfBvhtk~jk3UtW(Bwt;cr9I>Ld5%?D|+;H -z?A4YZ^(*ouMWlr`tU83h%ex^7(q>Kk6~hBEVR*YmBNoZdUyPgm8{^hEW~;DHLqYMd -z%Cvpze66Pew(dwI=2-j}#vO~R#%5c`Kr_+BlvlTQ$IdOl{13)G{}M+t>p4}{40&_f -z$w9*KY2V23Lg}EC!&~F6P(WaBM1PNvU4kZO0nU(>7c?sR%qH)0jd7gSwwq`{p)jL` -zO$8mZue4-x+V5kdYZB6E{?crU41zUsXWk!0n@21+abPG#BR&4Ujv5o_>4x%SHs!qx -z*1q^bJ|i9yzat&fEEpKFX@~5O^^I7E#g0_I@?HxxmzFW2wI>#ND2d%v&8j}_oaCY7 -zC&z)~4$%H_A%kyVQ{@D(^&_qdT)`n43h|I@|HZrm^@GQ689@)cGDU}nC6wkF6OmNv -z(YSLGhL-esJhqXNlGcg=^XRwI{XR8!;oJfWenNJM8=A=vqKzugj6xKgFgaz)e=u(2 -z(5gpQ7Jx1{7<9X)c84}=FZs^Fd1~I7UQdBrs89VR?meoh13d6 -zFVd2g9+ZOQUnNWT33K!$l2y70vjhQ2cu4G5L_t{k-sNo30~Dyg^u>oq%Aw6jzjqOAO$F{di#)_v2U9vm9>AP$RbFsxZF^cN_pWb^}1Ku9cHLDdLm;DSfSP_ -zBZAy)vG&WxXK|AG-`|7wfQk7W3;j3-heIz*;W|}2z7V{T$PXGg2Wx;sA5%UWovDSD -z<8PPvRM7@*z^fg2+P1cNWEb=z%2MhhRz0?u^crF@_LRIL^$P?v6l_z -z8u8MC$%#4g%A1(WB-1(ayJRChv_YK$z~0q4RxnV1%FKkn=*)Hy+x3uMer2u9-JxVz -zfW`%Ulq8uQKzyN-UHCUWL -zKu3!NPk;mmZhM01y*{E0aJ;2IMx4@-+K -zPssmZ+)D&!|BG?+On+nCrhhSR_1mkYv{zm7jv2USWoW -zbNprL=(O??aSH` -zt9B$dQrEn{Q4>)BbmThpcRuP8El5FUxA*ZyTA5l0SJ6>47-mAwGcvS6(I37s?((wn -z!}|fu#aoqTK(&u2vDc+8e!edb5t4n -zO5Nh5x4TD)C(mZFFvPxZ1R{zdo(@4RxLSn=o(&TAJfwsl?zgbUI^3O*+A_$@2@%!+ -z0duEQ<~fG2EAB8&-oXX3JpKf42DZFVbr~(V`VlJL#djAUzMQPyLstfhzrVJQ$i+*t -zT`vM{VxZu}lgYVv<`IgpFH2yBL<`=>o8-HhpFh@`H4r2ru3OA$=FU2P<88WdjESL# -ztsYlE-nvq$lv&`OSST=YFJ{V{NUU+6skZvN`zWqMqls|$M?Ou5O -z51ebhEw+NUTY&PI?atsk*HxA6Bl2SB-UzM*_Qm46VHX`SnPx;NGcNgPs|!yts?yv1 -zbwDcK+|rz)fj)8u8b+V0^-=kVrL=hN$ogb=uhI02`vP@w4FPC-XwGOrl^6^6^gq#B -z90dxkZ0sN^t;szRZ8h9>bqRPrOqKyZ4?-TA4`}?*^7OsrNxgjCj!mbdA$P&N; -zv&pT>CX#HpClM|T#SfhLA{D0fWt8*^LXvbxlH{}?XW`a+f;fjCtt}80yJ8I2J@mm5VXWB^IeQVrd -zl!_*qptyx-D)v7et;bzM%;|O<0y=nPRZZ4kXHka5vk>E3JVuD8AT&x_Z}7GTwJMi~ -zC}^SR(Pi0I(D{WMy`_b*_ICvzjf#HI=l~${eq_HYIR=IB2T@~xZKw?usM*NK&~Mo2 -zYLFx@t@_6fya5duR2@$l@`Y>h(fYE3`W{Q;yxsGDig=x#7*dt8Sq^H|jv}Zl#sr?J -zB0fAeocKhb^bg+cdJL5lf?EU63*wlE4DZxLi41?JAF5b9x)f -zYhVoM=9uHqOd@w{jU5py6G9aT{d#ZM^&m+t31|@!w9>F%J%@vv9W!*SqATw7uf|=S)k10YXn*ZYH6V#hTD9vI7UgOznb>@&z)cAj -zk@r9z)TRpr0W^Lf9u<7W(^UQ7HvjG#up`q9m&kI1RmbBGeaaEM`{eOkbb~!~iv|3fqjw0(~jFsT+IhjYCLH~Wkh|JIN>3@9m)RgJwM8&|lR4Bbf^M4DIM^4DS( -z`gpIIhx2@R5UT>>&nT{|W2Fm#6f(B+Nn$y+v>_k#PPU8ndTk>osjk+%v-AVAkfS0# -z?ZiGcH@@VAU(oU(HPP6C18BjyPKuE5MdaJT3WbkU(}b#vm}6s)LBd4|^9j#29Y_OH -z4ajhfPcbpdsNENzBO+3GC9fLr?y}k -zO3<&hLx$fPcgnQyGJdPXx5iEQc;X7A;`Wcmz1SpgB2q7(W3H*aSz!P6t#QA6YuxV6 -zVJpnZ1gT$(`ZnJhx7%^DgE0^`(2d^j1NBj6;A{rv5U`W3JS*<6 -zwNh?PNC8vx>(Lvz7M~mcIu$H4?-ZElH}j_QwvZD6Dgps8pHNB<8*wC+jA{NmI+#W2 -z@k5Sqww#rDdu`|uH?m3s;!#XUyU8&|RS+*}D;?|u+adXQ?p{*k$`8z-z65xWlckT& -zl@T0AC78!PDhxEgv8FxXgGJ5vB})wGb%<_aoEhAjDH;ebVmdVhXTDVkj^# -z<8Pt*j^K|cdUXdF&M2Xaj2HqqMZJrygn&0>GN}8WvBr*&zFo9 -zJaN#}EyxRBL+l+B*2M>8l5htSFY6GgORU?>=o3e|qp-fOAgv$N6elVEIHdWrHFL2-Z1r1^jq+ -zDPlwTqpceq0D;X=<%Ys{h#qKAP;F?dk7i(Y;&<>b={Z^w5BwsdTODyI@L0%aEQn~> -z^)slT_M^kQq+qlc0AqFA`8d{(GIcl`9^Lr%YWmEzAxCrnLMNG`%cME1yD%6IhX-!s -z+fU6%fuz}cQ0xHQr8R}BkyO=w^&l0IN6q_Z<3AG>#W1iF+-<;l%MY}qKvx(vWKXk#%`&m1!f{>6jFMQ0W49h#LM@< -zesgsBzQ2ys4fC3++76=Gfh_2gBReP(H}Sn`)^_LRjtojxI@#YVvv=uIrURf{g0CD` -z7_&>d69&Lig&Kt1r}v+$Hc40dC=GpUt9AXGFG=74E{!(+tR)8mj9E?mEUE>|4QHB5 -zg~#^@SqcdOJTQcnC`FKIrPf#eB%>@;$WKK*zSUJewIc$zD-#i_u=MzP?8yW0u%bym -zoP)o-G-?$UPq_09l365>cF(5_p06z``7&y@4q=0#<5)1@s4;cA2h1WwS``=K9}2N1 -zKEhaS*lRgAq4z6La8||{+Dw+L;E;d)%3F_}i_PP{z(BBZVU=vQkV--VGkLFgq`mQC -zlQ)}0%-=_r8Srfd~Hb~yn~n8ws6Sef($ -z!!^U`w&hbZn#|T>%n*a&NCN8xzDar{zH0}=vMi-OCX5{MWZ1gUuI6Z;(53HV -zozaj=sdy1&2W4<~Qph9SRqr0&Y+ZuSM#4H|YI*__nmZO!pY4xZv+e|F%i>-Bc^qoi -ziGR=O_KTc+tZb%~HnZNWqzRh33G^CAhSw~BM^)D`8DBFk&Rz%D8JJsJ$+-SPg~zj> -z6602hw+Rjsivo}b`~nO7+t-Sib7Q=dR00&P8Qz!UNs< -zv9;_9o#7izl2Ua2LKZwk#SrH<$$MCVTJQm&?;~xEJgSr`FgS;VrO<&4+E>XJo2j(k -zE`yO+&7G!zBB7kL?D@rCR!|*hr8IVc6q_P(v@pLJgK#E}n7sgg?6nXjp$U0A3!=-# -zsxR!^MIBXT!>31Vc&!}qAR{(o#tm^zs<*9QxlbAj%|VLb7^cR3?1NHmK$t|-8!fkd -ze-x~k_dq$vbNR$S=}waaFb5G2oG*u+^_I)nCIJ&K_oL>em5SJjqAUq?lnXq9xTLo) -zp374|@T`R%d-C*Pbn*dqJ%iJeZ6Do+cyb*xBxlUw7r^CGS(2 -zq5=S;cMzO)a+$-qDW~Cj6X@sMj19anX-N*!1$w1~AJSKdmIkGd(gwKskev{JV7o5v=FJv{QXA(-r3Fg!X90OE)>@hvt=} -zk>yLkgThXj)`sZGqS9i7F6(k#O{r?;5sHAO$|5!t2VroyVh#l3G3^+l^bMy~MNmY> -z-i*L8`2|mk#1B86XrW*uM#bjDVikb!Wk06|6;w}YAB)+u9_>l&g5P%`JLfe*0K=MZ3cMj)g4aP8 -zC!#?_naHI83&@06yC8IQ;oQPK@-xAkpq30P#k7=Bww`mWY*|W6=(+tx^aw7&jp4lc -z2Lg<3HE=+(H_v{`5`$(BbQG7_3k*d?!U`r>)TF$I?9ZCC>hZy;#TVnk&g*Qw&5lES -zg>hoEM}wnxy2wRyRf$O?0j~lO*+TDHC1G(T`WFs~qk&Ad;?ffHOt7xg70q>w9kjbI -z86oAR0ra%hewKB_04WfKU@QX0_Vy73j@Q_W%-y6rwRL&cwHT~fz|az@=$($RP-}Fc -zK$CvS1Z;7STb5L?Q -zqHv3(aLm9vC~{EMo>f~>( -zvV+lRIkpD1#?Y!?6U!o3ChN1U1O3;e7^Sm-P&chcG<9l&TS>mgI_q$}CB3a8Q8 -zX$vMDd3_PBfqy3b)g|?K9VpTr -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 -z_)}%B>{LcVp$e=$46O�W%bDOmx#-V6z%uqQQnxqMrYQ!5qT?`cN6>O*v0}n#Xmk -zM28VQ2@K{oE{4T1m$765i*btv^monG`nG0n6d{`t%`ALoegy0v$tMG~UPxZ=V6~{L -z3fP|^iAe)4v9T`Rx5Sh)M8j0dshvSk&8*GWr3)^aO2hI8k?G3?0#*P>4VN=nOX$-J)oB-De!MRw+2pUo4DA7)t^t&=1_$H`j&QUgU^Up7}F4|eJI -z_eI)`uC9Zv8Hh_<1UzQ;DnJ$0HbGkqQ{)=h^um$kG46ZK1LkdLcb}95fR&6n)om`y -zm-X!Hq9gr)@Azw{dMb|T)DA-j{lXMo9QF7hBlg#?Qad}lsj;iOwT6~+!kT|(^6L40 -zs_XipqJ4fgPG8gMwRdf5z6{gEWwCrME_HV9e<&U`FL(RwUq3p%4MRB$yd1ctZf|bs -zhP^I~Puoc?bkm&3cYkts`wZXu>hzyYpRJ#rJqddV2j|t2N=ta+Vk$VdxcM#gEy%%fE8Ps^eqOpm|pt6Z|NqE10 -zp$|VKTboC}CdIsuGAdr*P_gd$xba@re2!@LGJM*eTjPVFp^zeSdq5J&#I&jX!=+K; -zJ4%xtuhS*(J*h^+9KC*R{vr+#R<8aG{mm0X)@O?cGQ>&448Pn12?tI1JOmhaa=)-I -zm_NcG?dQxj3CWk27`SJcMatHM_iC^o-E`zve?UjLdk&f=T^1_iz8&7J^G#097QY?L -znLou5J2(|7JI{;aOXP%^A^JsZlMEkyce8<{@iiRyJm8xz&yA5+{8puSprtmb!DbRI -zCyV@7`*62T7k6~6BjtPwiiH1o^R*PV3tdF${-bNIF%@S!1E=?pjUcLPgc7yhVlxgF -z*Radw`UNNjI*1#AIKn;ihTc9M7KGMCf}Ca^cp4a^V7pHeqorE(m-tUI3;FkZ4KT$d -z!xvGqsR^gUY8ze#V04KiXi@u5@Cgm;+n5v$!(%PqtXURww -zV(I5|HhwCJ4FVu$VoX@9@LT#P5%+3apR{2r)JQ2Af#DlKAw^x??y%w(o%mcNrrA+9 -zfHxS~v0(g_zS)3%#-8DRwhdBl8UzWVgK3Jo65Y0wT$yMBOcl(IAKDxFidfkuAMn|G -z!Hk02^V`0sS%!@rJNOO^W@9qH8OvptzwK=VilYoZr*HgI3QbH=M(y=kcDf1;?+vSJ -z093zgym>-^VV|4ItI;!O50)uNGo)5fM9-LoAiuC*G?We7k)yS;5zPY-fha3~$CwyP -z{F&8aO3NVNbz)o7>C%^5RWmc6Jws_w@?TXu@B>jjL0EWzi>T&X)n2#}1h9Ss&WN(K -zLvPYtnRHC~R<++696rBQ?UNyU$G@ufm+4K+*+O9hB2oUVM?^nRNW$E(;(t`_`fpX+ -z?OWBZW&5jYL;u_>(`Fk{`M0Vq@U3bCa%F*#mi>>aUH&gs+g0pa)!zC4R<)D!Mp*Zh -zD5x#JRqdN^RolPkU#d2M+2+Q#svXw5{d4^NA5|Nz0Ti&=D3<_oHVW>2N@gHP=fsmQTtZ4-)-L=HO~L4+8cjW?N4APw{KM&*BHblRv3*} -z4_F>v$qhm$mf{4Kvk4LkS`my14{_%wLDILXjr*-?#|g`QtJ=b*DIVg`o!_eVt(8$A -z-uquwn^Eq$jaOmzGF19ss`l!`zg6uu`u|b2w-02nkN&FK;6Xfr0vLA3n%}B6!Q1~; -zwO_wg?V!^CQMIf8qiT~F|6f(R?Y~rQ#&1>o<-b(zsee@MA@Cg2|5CN5u>PvrSWYvR -z8w_Lhn*7Bmi$3BIZFA&!c*)If|E+3&S0(?d+B-0RRc*?@s`k~ls$Kivsy6<=Rc)hh -zRr^_N@7KRn?Q|{?^>0PRBfR3 -z|5CN_{#(@!X8M<^eS-@f@vUkXe5=~j|KF-M+h0|C_N{6={I{wN4qo}gwd;RX?bOTC -z8Pac68`MI6`yW+1Kk2Wkt?;dC+kC6q#ytP4YR?tdeyiFNQWBW6->NoEZtP!G8@ZZl -zy2q>@+W(1N)m<+RqDW(J5@vU{q$j^NdhIC-YO`mobwU@dqW)M$$_B=zcZN&pv|<|1 -z_H^vbO-|Wdy+9~hixEGX@?IdD>bY;ilIw6nKuerlvDTeqX^k3LiFC7HGD?C_oBlj4RtKOS}q`5XiwJ|~P -zYB0_Ubg95r^R%sh2GWigH{x}z9PJ!z#c{(IyIsHbpISXb^wW&sHc#)jhgvycMtGaS -zQ@(D&Ul@$uvEeId$Nmx{>W=eC-D>3y-Zde<@K)iuAyYO!SzRfj5~vA?|MlyjeARCn -zO#ymJ!I63=p8*&fIaex|*9oxg48d~wLvy)P`|fPl-?<+3&+K@1CR!-#jMEzO?UlSS -z7p7+4z`ibCb-z_QdBO`YJoN$^X#76pf58}|!xoNmMkdZRJ5o~aUPldL3imhUxd -ziDPI&ZT^O`Wo`*Y6g={qNjLB_f5`DG9DP+{{iiO*)-Xy;5%}Y_apfl5g4AtJ|L3dI -zqMv~)zsfo>R?L~d2FpX!^N)c%B{e0Qjf7t>w6_<}oejojo@+a+v&J`ARS>;=5uQX% -zFd@BJ6Ugrjyx1B6eY5mLjG -znOR)66}rr&<~xI0)ARFAQSMMdsC_yI&r)G)0NIxZkjDZauqw7hH>Q$Jwz`7+hh|qx -zc^~>PQ*TVrqD)OOz#r9I$smgqTz&Q%#6xyliFegZ0C2v#l1IMvtEJGVf#)b=4vmbT -zMvh=eyF`1C_B|d}Smj=(%_;dI;xw6n;9?NqwxZZ;KRsuH%sm -zBDTeQLQ30sTk}ms^!3A)F@D9yg(b -zoO;o&=0X32*bmcR=&sX&ABzX`btNC4>vZR8ZFUZheAQR4>MSQ+KE7-1@xM>_zQ&Y( -z%otqU9O*q?MEt>KOT?Zr5$lSD0IV%{QOQ)u(+vX3hO{RSJu?=t1+0=P~|%s0~TF?(1ZM0qYriu -z`lFGYGJm!F5`?WwkTZcF7R>I){EJ30tPwFCm;1!aUw#5aB-9^b`gK3=gy2?|Vt8CmO<>^rbyh&Gy&b-d``1S} -zBo!X-Vkz2yU*LruCPnarz3h$%F)rh9@VCHOe&QbzHQ+^SI68g3f`z&1M$0^i&czQL -zT4C~T^+WU>ZVW}$WI>8u-2T>2cu4UDce*NUV -z!-PWX&9MOXc(WvH?BunjGk7f+h8aHXAW@CHTP^cy?mqJ}2bgJ>Xacx&9IwD;jozY)Ag)qSIk%H!r}>5lLvXg3D^!)7tKw5Z&1G(;_`A2Smv+ -zn{I}&oF3P7`5NPGqu^u8gCUF!7PB$$+@h59N=AVRL|2I=$3fOh2f2QXI~+DubS1Fz -z0fX?AwB^KC&ZwsQ*s-{2a9$72ud=QurYh&`5+uL=eKd1*68)P!8}JMIFdI;D*CXhn -zL@Eo%&RDElB;IkhQ_e||+99SkZ()JAZMRR&xJO2?Fht%?;Dylw)S6EYp4iI3;Jn~9XZSJX=hGXKs^QMLPJqa$85#ki)hm&U$ -zNx<2K(1rvIv3$08sV+1Nxyj80h*$p1*4l^ZK!WI;aFRcHX1Q)(cf$H~#e$r(p!Km- -z~UI4>c{J;kdrVGl}`{lh?MM+MDgA*JDyuz#DV0Pv#fNMds%fdM*WaKi%cOM -zoQqTfF0z#zwt#2OnpA~}e7UZt?HNoXNWPwEvg9=XPBV=1@tAa%9g2P*GZEGE$cqGu -zIM-xs{1SK3?nbd8M%s{86h+q^7aJT}Q-~~;w1aP!f7>6$bCjxR34y~$jO4Qf?g_zd -zclg98vs8< -ze{(rJpL&TPRFY`6(Vsvl0I(hAL@Ar1gfZYw#+)4VmV7i10X_zE{Y2jVbV3(JDVTxj -zKILef6DOM{J1zh&fr2!(ezEx1@@^72Y_~%KieO!(W-3}mgmsN9<$Rar?DZGoYe37g -zhQP@;BXH1~f&4r*5ak}7Vr)F<6j?x6C;_W}CyZU{0w^=C{0Xfah*|HJ-^CB`A?j|F -zpl?3#B3=4x9-E4$bDd1p9kTW`&m2U@B?X&M(%36P>E_-Uv0{^w*JI%jb7N^%S^9N5o{=H%yJ#jir!3@VY%5oKCLJ&>bp0!$V+S29G$ -zJv`*0Jvmdz5SO`SW8o=4-wg=9{N+?%gn&;XL>-4tUa(!lE&Tdp+;~;#$N~`X7^rTS -z4%K{=s0=k2DdgSXsk=y(GyfApp1X<_Fnx!*sRk!M7zYUoHe4E%Slm -zx03p23d;PT0x7#x*Uo7rfaG?9`$lbime|A(FpujuoYyZFcnUa%J)y?7z@TD?U!CF^ -z5T1ySBm)F)uuTGya?a~v8L9JL+2|aB?9D!Wbc&)p$Tg1hO6v-UP{TP#v-~4sA+nY$ -z!jte6SOAnW<=but%MYxrt?}b%4Ab!SJf=4EtG=r{PA)q5YEcCs20D?Jy4Fb|SMZUv -zuDNk6Ox{_o=dwqTZH^|1%tS#Iw-4L0qasCtbHKn#MUsCfxAo=W_Aq5_n-g$ -zhhLY?W2tzw9I&}DJ@0U*YmT^7u&l9a}l9=75h!Lt=6^-dB> -z=v-F5xr>a6@IEY;HZtI$4d;V0RSV?>@Bjb@kmnyQJi9)e&OO{XB!+2u)N<%_h=5k} -zOD3$`FyF@Err&&TKu*cmH>DlfGB!5_8P9zPo%Vdg=$c}(lmlv@<#6c@q|XCUhrIR2 -zkDrp6LbOF4V77`ndPsDM7+&M&K$CeMzqLRan`vKfI|LT^KZI-tIlmDL|M+vwP0LtdH>roAy=kMrMQ3nVcW@z -zH71Aflmi0yy>IM{(7TS7p-ALD#?Q~;eGki5C(^+yV}|Wf1C6SUSXtDPnx^Fk%8fkU -zE)J7m*H01wXCcvW!&94xC^r<7k;L)hJ}^2P5ND=cg^fl#)q#47KlV@&lz>}*PKSRO -zeFU$8-vH0W`}SH!bii!5IZlAOL#KmV>ZoIc%~Llmfv%jF=-%4T7dHy%*i1a)=f&;B -z*yHD~f9P)|df(gi#Ne<-ILKmgV`L7MYm%sKY7h{~X3+Su4CU#e=mgi!iM|RjVXcqh -z`}=0U;aPq(X~=N28<`q$U&9A}foxtr8Q4`ue;%LmvVgv75WQY!zfCweNLKhl-;sjo -z1o17Xz`BwekKvIc*-=JZ!ii|qvfRi9gqIHq>ROg#eyky>N_99V8LzDGM6U*65kOZ% -zB3(102CPk3_=_Uje>1k;p)^6rtVh>y7C7q(HlM*oE+p}g!`_mEOB2w!&g6fOZL{!1 -zj#x7}+c`yHwIYxWs>JHnoOS7Osw~q2glASb5JPj6nI6lI;m|S+uD@v$e+*^_s0Cvj -zqSSQly?ot0T-w31C2nvw7Z$aZp#CvA&)5n3J -zXN3xtq3^?GCamp30>a?*T245RS5kcXnhFYJM;6g*!`;I#0(w5 -z?)9T2om*I9DzS7S5ph!e6AOE-lp?!bfe&D+;i_(FE%2U87uB&#g;|UX2U20Z@6NEl -z{0M-0kUd}`=@p+=S0&XFPpalDddq1Z>r+;nSj^mvp9$^mQ3Q#t-xkuCXf`fLeK^`a;Z^k;c=?>{nSPCsr2J`Ae0 -z6SF0s)+9(Iw*VN5II}&?BiQd$(BAgIU7@L4{(`DU&NYRNhvD^4FsB$R&q1&Lxg${2 -z@%GAE;%`)@>d-=!!}zLSUl5nG0@vyFR(zN^{dA>{kIi3G4=2c;Gp-;Dw1F&#xHhP(~Y1JRjAMPG5Cue&zC!q-!0ITY4?1x&pux6 -z!B`Q@J|a*#mfH0yB~}3+^kMxVPGTz=OxA;3*5M8y-uv$<(3KREvp-Zm6;gqz1?Bei -zCr~DVpG?KSY@Lz!fvfh;fm!>`rp42)ZN_DkTuqY6XfagTIERr|5RF~Si7~COmFSZKkgPn -zQM$y}@01&)M&4+giQND{`2x{>~EiB8g|BrqPpYWjR$?;4a9Iil{z&#sXG`j7hLBsI3!>j1%R>t#Zkz+*`0JwFfmwlMl6;(E;T|5T8#=X~x0dXyk)2vh2x -ze*cl-1gvVv($|U_O@M$I29P$tvOtnRi_atg&@rX)D8I<()#2EwIJjD(ANzRzn*Z`3 -zP%r$#F+(nK-4+Be_khpGA0xCnIxTXbL^-xV^VLpe&Cc-u0d+-OZ+lCD$L^af@{>TXXte}|+OI$vhY -z6^oa2CJY^Ga;Q7=`yT%Wjexpao1#$VhzXJksi$&$RA#Ga|tpdhi;KfUuk!WSmV`f)(08km2`j-MGQu+(#`=@)M(Ov -zjBP$83Zub4f5aeB&%zuXCPLm2qToFEs|op$(rB2EQ=OuFHT2Y7Y&K$SoJF8b*14b!-mMf(*PV!6yne -zoJ06_aB$zFD-I=j0!U-p%sSXW?zMP~5h7uA*i#B8G1~wP0VpQVl6*~`go4&v0%=}x>=0!@)7wf4TNsL%46h03_?+joUzo?1s=uu8!`}J68o%i -zhR_gkpq${t78TwbmT)sHA9Z`g1yD9(kYYE;|%?p -zQw6g2n!lPKmS8hT-vI-#*_3isI%weU7jpS{qo9LoeA4NKhSUl9E35v3K(aoad_qEN -z9db+jHht5wgX@ZUSQN?Q#J~+ce7S%b@&H4-cgz#nSdkTa@ug7aNNa~e(S|bECC-KB -zV;ScwfHz`-yEv}Vc7?2=tNoe0c%P~eo`WZHYj$PjjGZ6xOmK;Iua1PwhHlw9_-KM= -z9MCkfI2C9HXSKjYWcC*b2*7<&AZ$yw9_ECm(A`c{M2j-&zDJj^Xx1L%)oycT!Iqeo -zirCGqNJGFR>Hq;|B7~FlEAg4z<@Cr$F;rmgP-#Vsj#Djh?9#nZi+72BxE@WN9}IGb -z)J=L+lsE&d*k#v1iJtS$ZX;CGfQ-=xGwu6SMi)!Pw20Wq#&CZAl%Og0w53JBN0Ejk -z+0nsjG}h9gsI+WwbH4Je0VORhvRi;=r=4c)f|vr=2kMB`ITnn%+p2r^8Vw=Ap7G=7 -z+#D&A{rT%3rTf&t?$ZdO}Q`HuUf&#xkvGBc9btLXeH -zSj&6+O7(%fAo@M=p#kFtqPn*Hzx8G6U9i-XA`CbqP-x20!+E0n>?zeZr#{j4T?+*h0XA4b@(O(nF~6vRFV$sT$!3(H_V@iD_ZicFTGin -zeY2wTrGV1^C|RQ*JBq}%^F=fRq(a6^?JR|0SD)SKO*y&92>yifoSh#sF+DSO+SZJ4 -zKQ;Fq*7q?k`v$Fn!L8o*5x^h^9UV^88|XsES4J>yYBci^?%`)j3$_Vx%i)<#;ggvj -zSnYVssRG8~e=kU@ -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 -z7(FUulwBge`qEVEaq457(eDW(J$$A)b|h&% -zuLgwi*J0#NH?;W{O`~KR9k!(qae=c(Bd_ENL4M$Bf|E-P+|ag`Gg}<}`ZR?&iC)n` -zAJ4=i1m6ce8Sc%X48uCr^I&^>I_6f*yNY0~0Yxih3-Yx}qO&=q%UQK*x5QV&_sH;>I*ovW><$Q)m#dKYCix7r`R=)CmSs0Q -z%j5K%h#xSaW-ec!bdJ|YW4*(RWL7fGJi_VL_q5K{QC3Cdb2oWpiwfy-z;u!-(J^KxNaZDqDuxQG!Uu!NI~n -zZ0<^rpQP;B`oZL}`6PM&6&)-9mY;q|B-@dPTdK+MTt -zU4tO!HOF_5G?UtvlwI(8=}PSGZWA?%mj0AbI -z%h3cpOp8$L$1&yA7u0a}X8H99Hkwm?zRVb*uvYL$6_O0e4QvI{Ziy1}3V)dNy3s!uSDW4#*`%wo~XmA -za)%Ge!3?^)8LqwRH)bK;6TbiaZBT0GCE9-L)qWt@tqA?-uR>lej}!+|p1h?giY&I` -z!8hv2>mC6tgzX{^g(Utk5kf-1^t}FSc|bl8Ew5D!$pT>165-rVV1U@G(p17)<|&=C;|217*h}j -zhg6MC!q8akZyxwV&YaIz(kxBKc8r%0EcE48s;Xb(G^qSaIPo%_AHa$&0q5LgWyUMi -zHOm{t;l>N#`h;tEP&bap+g+ -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 -zuof-<){KH+19={8n9(Jas|RG695FQ{#+q -zAi*~L#G5Vth&L^KNt9|Fmtkj%OoB~s1I4i9%^5Zu%tczsT+TnITrOOI9Gx`APtdlN -zd4SE{qv~PQ{Gx)y*lxIeI*z42ue3=h??Y88uHL%h7P_nmlO-9Fy(9)#bNb^385K2` -zl^CsYKb2hg3XceMGzCZmCn>(AxqpP)Rd18DqT}$1N`>izM^ll^@cY{oyP1Li{`jG8 -zW~hb{DQ>0ssX=b$s7$E?z>i0TSkT2T6`WFJNrvR?wc}?n)R6b^+Tjy)cU9_i>6VaH -zMd2(fdFdws#D7d4Gg5l!wl~_+pb29Mm+bv|<`g|HSC3jb3X@f1&jb}4xMH;O64jbs -zQW~a2$<-?&IwX+tV*=-WWN-tFZ}p~;IK|=;4CxqomEo-909e&sW6WY;?a4+vAt>cN -zSeSy2L;kjzimSB3DPjq`E`B9-Y0qOS7_arseAB8{k79#(%|ez+f*}6eX4omRQ|APb -zp1uIs;HVFMXaW7;0YlYtNBs)DT_`?DbdZ?cfc61Hnd&?lI@aL{XgA5fife%OBbN9c -zoUP3sM`&-Pb4r-R3O%iP&$x!HPx_NBRee+?v|6%ydJ9bD+JJdMul>+-lLp^>#(*wy -zjWG?(i|t3tf5zci93&NRJ>yVK7J0-L06z&Kw2`TU!+`4)r>yrS2fPq!I@JiMqF> -zUzYHz&zauYZnKLQ)9c05T|$7dNRV@Qh`so`r0R -zWqj>Tar2UU9tah3^?TK6Kx*iiQ}bjJt!_}184`#Pwrnd;EzMUiL5rj1f14?v$~t8I -zcG+m0^0J9ZxuEA6&{ob2xfN6j!Y*-^kWHTDnGrn>vjPtt&+|t?h@K0p#(Fzkp;Iyq -zTJ6$u8t+%N4s|_x -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 -z6aj3h#J&c0aodKh?$)TWn@_cO2uKwidOg0SgH -z#zj26&clz5}T`$*GaTG@#d-l-|(P! -zog_@FY4_XlmAU$y_pdYbJF4|Yw&twGFJ9%}oHluQ&(?=#ZnAv#OF)mR?;ft{p6_JS -z%uNAJt_Gr78hmDw-yA_NceuG+6G0w}Opttg!$KxoP+1{KUU}|Xbyt_3c(n>zoV_S` -z3_Q!Yp}e*oJ2iE5Mpe1w3I#5>3-sxb%bp*pvchycJHBx{C(c~q$B16cL^t7~v}_A| -zdl!+bt<5n6ITK`1Ssp*kV@O#PU*SdpM$qyd@xPT%gSUdePK(K;c4~M-_AHPNrFOwX -zo7@||3Fl!fK;$xeF#|dT5$;`F+duI^`h5MuY*F{ZxYo!@{M|{1$0ICX68LO*)N5y- -zju(&!=@_2jwI%C|fE|@20~c)(8U4j82Nl8M2HfVr>j^PVJXFk=8Wig;x{DsT1fqv- -z9T(9+jAVPZ2hA;N-b?r}tczS`oQw{g?w&QLJYzhe!yRzA3VOH3C&DK|lv -zm~ChczGdgWLDb#CA!i|BOXm6Y4GzIRqHA$ -zzPO^aGBeXH8{$`hKqVW1D9C33Hmu}OAu(?*>G?4nJoR=el$>-%#>7;6K%8?IWyShO -zkp!fkzVCZp!ZM+XKp>U>^jnd0Hjd0Z{87LTE -z?YP!3Bkq_x)ujjDdZ@}v_`xBnjquz^OIYqv2EFl{f3}$uddvEcS_QHNJ)Z9^ -z3nAIL5IPY83sPrQ=#)a+ld(3Z0$h&Z_iH@UZ8GRsmcSN^Dm4?vnK`hKOsf(Mi -zsr{6PVdQk7!k1L@3Gdi@+ky=)Y7tn=eLKY05Do$J@e?KbddK`iz_n#WaNCY#_Az3~ -z>Va+L^PwUgVxR=lxX3GHAP~T+zRcHJ3JDD0bEC0ge%oQuYlJOB`bF8jb=gl&@?ZTi -zQ_cmW2N)b`&A;ygZ6T41Ii8?L_|X2OjO%q@CC!FdP!2boryx2-GNveMr9M>lA?yJA -zPAv(M?#T!QUV%9@X&6X;zlCDhD^oNC)FTC{cFU>Xv`6dvd3Pjhs>Ltm*5py!9UMRR -zTm`s9qFshIXbaQB)i+X>4ZV<6}4^4 -zJx4+DC*5*0f7h|+=&39*G><}(L4aPr*D*60?>FVw((mKhNI!7o3x(%^*@wuwjO@7* -z2szH*2;t9$Qw4&X#%S__iU7Ps5j-D9OFAF`v!p^fZqVJj-Ju#UPVV*hmG_CE`MUCo -zTC{nTW_9vDqcA88wZkK*Pw8*jD*kdy^?A6DC&>#2SnEmqX*_wmyIcysnbhRjjNtTr -z3_N^u}Ae_fb -z2W8UtLdcT@lfW)O8!hy!ywwd2`S;vdh1=K7G1^VEPnDzIcGKOe5tIN7Q>owQ>SaDR -z`^V&K??f5uUrm^>AJF(&Ins+8kwKLvtCBY&lYK80%u(*jRJd% -zv@>NKU7TKb4AnT88?ocOdPI~G^Xd{Qz*rL7OpVbYtpbYn@gL%cR_Id#e@Uet+rxIPjDpO$E -z?TJ9dptTkyJRJ_h$zd)Jm(@JCpD9)3aR$lPspfJAM^#Yd?e;8LYojhf;==%_|08nK -z{2!5fhJG{n?Oo)KAt>5AF|-zKkhv@**!_XkBka+kZzs*5K7z+Q)xvuVc6I1iEV%Ks -zJ19FWMYxg+SFI+D%T3ZE1XFeCm_5t|yUCFqIsTol5f%vg1z(tD(--R4*E}JRoa@V0 -zQsG#;ahk*?t^HALz5E0Mr%FIArvQgZ4#+JgWGmN8tB~2%1z4M5SPQ_Yi!FN&G??@n -zSS{EI%}6v&WKN$V(n=~A_?PS}f&LAG+T%4efv@{x>CgkWN%ubLWP2lDOHl5sc+0Wf -zYJyJ@G9CD~IvNu*5ZxEw$(FFSkekbCpY`6W`BbNiu@rreIlep#VimOdd<;(S?6slY -z(SCiT(-(1~Eh3U*TUQHW#Qk7{&+%+JYn&pcz&m4YW!2MWMYP$OJ29%o5E5M#yobG; -z@I3zL+pJxVm(CvJz#MukYw03<>p8RCH1&-X-3KLUNqnjkk`A_KhY{GIPF7@im)CnP -z2B(+Ti@G%@yFwD -zk=yfKAnzk -zrsYt~TJIZWd9|$t)DWe331!z#R-7nHlmqb`tv~Un_o;kN$pvSpd&T8ztLiM`Q&o|A -zuXTJ-5(6$o`?`9a#g_`={TcOFt+U7mc;yNwxXBr(VMx*Xi0wDc<&zz{_|Kh*o%fBM -zMQB6l-R=Q>Sbu2b0L -zCI#n&EnN^FW^V``bSeyS!`GIGIsVXYTaitoq67@@Ydvzz714~-n!9RG{_q&1&TUNX -ze?;zEr+1NCV5^dw?6=4bUA>Lb&8NI^z~g63ikkvDW;tv#r6Onl=$!O(4m?0po&<7N -zcwOw*cOmc}ik~sy^_abQs=mOEgd@q{?;es$0JKX2T|%&Y8VPzG50u=3A_&sACRJk) -zE5I`}bp&1MI9D|VME0r6pARawV7VN;iA%%M%ff=8MhCCr{2_YgpeQWfDjiq(_yT(s$f!UkA|ReMvP -zErLv-7Yvv`Uqu~DGPT193E(7UzH}mc<)&?Ss)VCOGaX~i<}K~xicpO1y^}(i+*Hm8 -zl;Y8KxElkhfs6iI;|5uB`7ARWl$ixYnoK -z@{72V;bsdV2FRCwcjQB5{?>-Cs45fIV|Fq5lR^xI$*+Q2eINK+71fR^-F7E(-GUo8Vdz+^cX60K`?ZSg{8s -zq*QNNL*&N#5V>vMMQ)~li`*{)_=^}J>82B18`{4`Ztm=VMDFqbEplJ| -zx5$n6TjU1#5V_suKSXZR50QHdX#Sym78Cw8f#t?iF -z^}3ooelX1M#OvXGSa}aF*yU%6X>Cm@`OS`G&RE4LN%;AdV#6(7ywj$zQ(w2&4#?RO-#*#ntAivr3%?|A-Wn-q# -z10l!t>k<$E+zb0vGjF=5eEufzg;O38ccgeVNvns5Ry9(!bvzL?O;Lx^fOU})9x$O8 -z7$hYXL1w0E%+w~?QJ5z@2gor*g37=t-6Cw3bQ8MX^>8ob1ab9BFP1Yu>QP>P2d3H} -zSsR$teVDTqPEH>9vk3lJleJ-n7B%WDkpXeu_R9+ezv8L6WQ1=F5v+RcE>Ia_MS-Ba -z`SR9*=e{PegocJuBfz)}=lBXF1861u9AbncHHLGe^8G0@mViI^?%*WFIs1pmP4`FS -z*8MGV%YBI4h}C{ghJgnZ3$*OQ7{NTtTg&N_PO0>|p~JYYj(uU6DcE4J*a09?yJOq0 -z#q(@lha6g!Bv9>A7*8WU1g5xttE@iTH26eH50$jG4QrJ(mhXE#?l#<@{+r~c`XIS= -zNp2X<_T4T==lsW$=_K;Cv@zUd%XUz2OAH!HthE^;e+cE!2Ng~|&gbEjc|{HDh1{~E -z)->IamtyoSp36u=+!!9tfRX^o)uhyJaRDzoeimgGPkcJCn&POE>J27Z9Qhc#MI&hxfW&K|Y^}*wC9b#yZV&?60NveC=^GN#_m0c5?*8$sxaGz(n%o{!MG*q(p3`7V7rFd*HSt46a9 -z@*ndxl^-@#rqWncS4=4*zS_@mdKHb%-1Csdiou(d1pij-$?vv4NAJv)CchALD0{zTxRWZH`!}Nnl1q{ -z<9HK=I`%j>0onP5<=J){{a)b|0&Y5KEab1_-rjtZB*8xHwGv%1yc92aHSo@!WtQ;) -z+EdWHWr~EtnC07oZbTCWc#AecO|4X;Jl+}=TAy6{@2Vw!m}=L(NczRwzdKSI%(cr*Pk&Csj4weH`td -zkikH5daV1%h9B`Yw4fVudEx?wzRgTQ~hK1gIaKd0LcAG*3~r -zZuWk?F^bn9`xGcthA>vio_dY9+JVw>(Ex*lt*9-K^O;tlc;0Ajw55FQ6wVm9ri*@_R1l_4WeD`?BX61S&aX>{iKiLfUT#u -zewF!*kX0a0A2eB?z2%qDKbb$iXsEFFD;{G%U>wA9e4L}%DbXak-D?2&LX+J1v)Wt& -zKV>_^UQ(IG_Jmi1M>)5Ig2kh#*&7e@2|xqbuy*j0Bkd7%oLdT}pyO05H1ZO((U_}5 -z#Im@M^6i`8ofK$Sy -z?ohAENt1%g54`KepvnLV#0v+F7dn`cg|#MgnIEgPJ72X3Zfph-)nabXOW{uDAA)4* -zmWJT6;0gGAL#ts&B;y17ELXSIkt1wM+^v!82<|lqsAs>ii;|zo`aRUm3VsvW7xN4{ -zhoQ&T%6Vq7R~l_=!VnMBB>7&}qaezVbc-N{k-cjMPV`IYP&_vf7(|9P#@9ExU%%Cj -zyvO-q=PhzC%Sb~nn5E$ay`Ma6=6$Gn)`9`Vqz^Yg6`w*`So%S)32&z3Gv23J!&M_UU*Ws~BkyW8k -zPWl*xoZ=awoq5T+LrJGKBoBK;duydUU3ka6 -za>aN-Mli@fE+wx=OflqxcTWwrOmGgzTLqpYLjrmNwI^vR;#0ph!Q`e}WN}@_Rfw$N -zCh#Jw=5slsUCrKJ6<3X<&aAZ}ONMRv&{;`(S}O6iz#2lU<~wUc&-5AMyG+s4Dqz;E -znQ+V$gdh)D)i`Gdg89hl?=9dPEv6mEaeDTBZuj4yk3?F+W+-bsg9Uh)(x4O+n>CL# -ze-+eSzJVUHTy(TRa0anI++SJ(p4Deftc+c{wV&%sP4OFlrg#RQz<%O62LdZx;cs<% -znXkfrEk^f}Gv}``8kuk}aA4i-ovq~-qYiER`QxB5Sgu|J$sQ@^6ycSU)^S;Dh9j=xcr_xhVrOz0>gCNp8AhabrtR$YVjEP%WF$ -zRp6P)Gh2zlc&Tf*mJ4bkbM`D+$I5-BC^JTb?Ddl=)u0cOJK!O^rmg%r5O4yD5sd7E -z4B$E1ABo5QZ$?b4ao3ZXs -zQ5z`&4jB=s2CrqgVx^{65-xjA_9u)}QN&^O;UQQ}bEuR4WR22jwe58$q~aq@8C1dy -z)(#OnJ*8vQEi6J3D$P>*gLNVzGk2QnUUUu7?O6i;N-Jl2V -z4U8Cv1F#?loaIYiJht -zwWB5Q&+e;5(Yvw;Xp)M?+e1zRI?w|{tao{sK8hB4Etzpw4xz7ir6_p9^8NXG-#)j5 -ze?jb(8uLZ=1?awqD1@Fwqs8pJyb7A1fcK7f1#4JT&5U2u~&hn -z2n5qm#*nXaI;QbmE6HiKt~OH@xaY!>)usr$#j~zZzBbBbRUx#AZ#gec)g@?)$Bv%$?t>Y=EIMc;_L1n_vSm;A*x=u_Tr=a^7>A4A96xW$86+(klaJi -z=j1Qd8-izPHhC*QC}1$A`PBd~5)BDdiY0r6KS*wbcaq!eo#dYQO>&oi{UEs|{*c^y -z|020@F<02pgK|Bsc0GlA9s9(k?#9JKu3q{tSkNOFT~`w7*9V54FV}$>C&yvy3Dmsiw^1 -zIBX6;0iW@Q8O#G~v;;sYX=NMu%7zR3v231wJCSThl_{xut%kaDEQ0&s)eP^+>%|Aj -zo%=y@8%HOLeqY!gW}r$vND}QpnDh6bvbX{;0*Ut>Pqem28TwY-fE%PAkro@_Eu9|1 -zFWK~@$eSm(g@HvPo!6_+SIge#)oOx&XTxQ(Pz$uqv~S0tRtmVLM4jz1FqQrx~TY3f~W%;(uuGgoeMVJ>?KY6M4mwMBv8-KyDs(dHn2i>?(y#gQWeHX?7f#obci{_UG;A(#@Q!-Q@su -zFi!h%n2^rvz#_Vuq=|wc#s1}ZTWfuHY-m-=4gfv7Z7n$C=7EW^j90tEw4%s%tP}z@ -z_|Ix8x^lhCJ1-}QR-cO=T`?I1^ecAaN=Y!|vDK*Z*O5q*P~SM<183U7Si%ErErm{& -zSvmDAPg8*tk-&2b@{nUdfITS>w04p|n69{jPXgzT*8zBqP` -z6R?+D`v7W~OSz)ip418qB?ydFfh+Q@9~(uh6a>C-zvVey2>|L{ -zC1VSnG5b1~7PBZK)%dkg<|SIaituR94eeIJrGO*mc9m2}UcD3!zDZ78d;Ju%5BSI@)!ER#HZ0eht;H*j*IroAe16rr1qe_R^bj`t*dId% -zXO@cXF^hnw$l&7`&~kI00z_gC@MWcU3J)F7!Og@%co%3|kOJLnojxI~#V)N?I-AAf -zId9}^rP8^zJOnrv7OQ(8G+ETX)gca&wk!M;%Ke(4f$^E_B5k2OF~95v+88k=JiQ?v -z*m4sNz$fuSs1(t0!1wY-#lG~KnHyxyfRVA??+nE%d@C)irE+o%qqgB%{5+W0lJl2Tor-lu84w&=y?tVc-KD5vzAUdX?%+7p(%{Q{t11<6*V_7!tdZjss+DI`tC -z^8^`eW=48`Eq&s6fWRgG*Ylb;uACtV6krI -zM1zKW6~i8HuC -zizBr{`OTH8H6)SSo%H|-5NVKuzXXmT=@VHJ;y?2pYfjcI(gcw*D%<`J<2j7RtP2qYh(8yhhsfBiN2C?aAI~mhVGqeUf -zp@jZ~YJRKY*CcP5%j73L#&$FK#QxJ4(OCUmoYAUupW7f~KP)fZNUa82RG2=AHH8y2C(6igy)}?2`6RkR -zW_4$_ARJYIj=?%7*Cx52!dylj3-)^b5>93?c^e-mxCrqhP>(P81!kkW;|ynwL<%CR&dEf^zBYvytnnn=dp2FHM~!4}se~ -zRJ}>Ypyt-f>r+EzA5D1sOa1;2d!C#th*_3yS$?7hqY_0 -zy^m%3#CB)T<+N5e)_`~yHMUQp5m&v01W2lWOWSdabtjwMP!hXPj -zPE#HOJYlY%?5FIeY2ri+FE6}60Bt^}EpHP6vK{!iwlG9#E#)eQ1(1s&BC+giHKzxM@(?fGIBcKT -z_^rMlEyzoUrK@^s3q8vYKC-IC>&k^l1Cqo~p#7CLX|FfLXBzK%~*VNxa#Fq7zJpW%f<7Gw;ii&$;o!k1{6d$e(Q -zWwcPOK41%|-WOa5nCSDOl|3p&!kKRknb9ECw&u_DvVi!TJ@;A2UH>nMYY>I{x}$pdZVD?B?9S-E?2DZp2tp#zhIZIn(Hl}prl}LU5U3Jf?2j1#oi>}X}iO)$1PMHdzvUm%(q*Jp9s{7CUiB1y~gC=#UzSL -zA}>OP(YeFVABiq5+N_(Ndd|;%A4wimMNyKbYBHz$p?z1TU+4FHbAiW^EGj(|39k$U -z3=78a%z5OPLRz1`%2b^46Hw68RW914u3pvQ -zYX6tWP4Xdf-!`fhvowY(dl<{nJR3kok`3_!i&^ds27q7(OB+i;8eXh~-TQigs{r>& -zkoq$51%!N2wp8DT7w4x|!P}oPa2!QGD^W1{f#d8+ICQyEGarBEO}dfJbud@mr#Ge6 -z8wkLJkai*k>BSfDT3dwkTjW+FsyWQB???QX$o+Kd4Nw)Q@h_2EhBa37`)`q(O>yKO -zk^2r^-2?8)!oUxy#u>!2%Gn5IUY!s*D}&i$8#Y#=qla9O82$5I)aUY}g!yXVC?N)Y -zd(ND9k$W%(s`yvrWw4U2Yb?$>%yyVOZj48pi8sb~S<)9k)zUJhHlLvXh}=T3Zr8wM -z=lZXghxb4Gfit@KX7csqm2tZP&=8C-4z)9XWd>xv0kb7i6UJ5?41))IMT#t?TV`Ik -z@9ZA9LTOc*VS$8fa@(#@iIf^c*UQEan8iVJsDo7svTkNzc|a^~cmemuXAVMol<3N3 -zC_(#CaaE&xu?+dH{*TBl -z)B0QFPI?!)@igWs&aqxb{)k4nO=A1>HiGX4Z -zqZFJCgvNY65g!3bb~ZYAk+DAw(b@?jY_VbqBZZK)aZ4*43cPA5{vp`?s|zI+K=yN5 -zBA+3wva#tEA#XfdmK?=rQ5r~U560SQ6{*u27vff{@5F1rq9XXAZ12!$N`>P|l}d4z -z$JH93uXO7|pbQ39T^kEpC7PZuq(u^>iSvtKLo(UGfEwu04Z))*&mq>Wq;&Bn{0TAK -zoCznz}?ok6_*%tg)1GPuX&F|y*V8FG*SAh*1U+{IC5EeAH}7eH -zMLv#a0m<_#Dr<8EZ2z^=^KK4Hqr{S5!;rKoa+1!KuGG^3&>|+>NYF@Xz2&%{LOl!C -zJV|V+q+NsQsB(_y4aiIk3`8wZg)`W3`$!8IADhsPq7t=DCK} -zCGM$I10ML*SxGFc3PmCP@gPF_HlXAVJj1iROspDRv^uU`meYt1j-Njye*!<8cKBXQ -zEa;Ta4U~#lVFuWAYC3^xpfbY7KYUlC3AmH=e-}ns_UvVtML9QzHBGjn%EIUm&Cva`&U-3iy+d8w0 -zs>}5f%y7W^ID<=J#0DQ(^LA}MJhU5}Ie-YGZqMC^L+qIfqu#s?$o_jWcglQDP$O)we% -z;D@B(Lki}?LC{JGH!j*4VyBgXY@blo(x3SX{!PwW`4^b>-7Ce0b3Z6DdG3J@ivf+g -zQo8SNk$W*d{axhN`VhGrBw0R0?oioDNCY$j^0~!g>m=VVu=p|ogrfi<4ELFz1!g4V -z0W9bnzOydQ#bv*X+>$N}CTd$V{}H*p964*PB8F#`9Wm%XL~iz2tb%XP+n8mj7j@mS -zD%|uTN~TKnDBjc27|(lHi9@^09j)Rr| -zBXU1|a{VK6^Sq1P93LWgj&^&$Akyv^bnJvGB-Yz);L%TvSU{{`fDdzFG1e}$Y@)kVR#uBh^05%%ulD1Lp_Mv9lDO*A -zqILe-^L?!IWQ&~q%VITx^>Eb)fbJiW+f&uhNr25x58Hbug2r3+kI0RZtZ(>7`nSj(KcfHrB{#h7e~H|s6&uZ4|1EOAf}y^P+))@6`kY@iA7DO2Zoqeu -zoBfZ-t^O`@@APaVD?`nK#}pEUZ8y>>o|=HYUGIjpq;~uO&u@y_DROU4ZD+`V1K`Dj -zHQ--tQL>Xzet!z7YmCn7mJ?j=J$Zt!UT1EeEzlHDK@FhqDL_7R^J4WHIRf$*cfI^mL_K(Q@dG=l8#{0L(ef>w|29316{1Ca1=b)SEuS$ET -zhXAWfTL)5ny>a_A|acUfUxR9UdJpt$Hu -zml7@F7_|FYYi1hP!vQ0hQ<$WLo1Tspuf`iP+~g@iiSG&KlgalXVs5f{b+$Ag6-A;6 -z$BdaIaF?)0Hf#>8p(mb%Y)0Akp@QO-Z^7TyC;;qOoMHrm^<+d1`3kRX`f+r|^s}q$ -zS$^cp*#>AFL!%@sZ37U<-zVLSB6%xw_~nLxI_RFTE(OUhLXxb@Qx17dh!dRN#&k=F -zL3N+-gMps!q<8>LDuo=V4;Q9{EN-B%(v=+g$q_1ds%E(72S>9r%YxleLyX*D3PET# -zEQBN-q$OwfPhY)@-1sLfDF0LBMx0Wi{Sdh+|A^dpxAGTUcL~2mZq5HGa^L<-s@$~KQUeZZ4qP4C+hkNSo0HhPlWI9`Qx%ld52WOx=ZO(mVk} -z@!irx%7pA69UzRwOA0!p=sc4N2h!+>!fTguDdS-?{B(E14+h=m$L3JKF7@J8Aj|o- -zHtVxxx#V`r3o^_XK>XpiR@!dl2?wh_f37FS?~yMNWU2CYo;dWav0vFcfv}_TV7M_g -zsqH_-8lRi;)8vfd=!~pr2T|2A5$>xK)K6QD=~iFl&ElX9BC^0p_e54o?yd3vVY9P* -zJsnB0UZf-GSv(Cloxq3S9PtR(43UqetT8sU5mm}=B*;Y*EiIa;7U+%RQ3k)CI&-#R -z@9rq}eA8pI^`_r&in*GSHDQkGs2P7gwNl{4R~UIvF7Ebq5G|4|URJbBcC~^K!p0OD -zAX)PuHDMMDzxTLTdd=WlUdWg(=-ZbYC4t>1JCYvLq(giV!&7v43-GTi6-NqSo1dWK -zoO}+55G$#we07z;xrUk>Nu4|;+Ds!bjz|0t8hD-h7H47d25|+aNz=awc@@93@C(N| -zazlV8gtmnd9bsW)p{OC`6*n(sAgAZKBn0t~U40_s$=g$NN)cYAR)ilj -z2fKF)lB4TO$SZr5<>$DacQ_JHaGv;K8diMV2Umv$vKifWtF{yB#rxlPhS?6ZMEdc} -z<8Cc=fQcq%VBIY&*|AoK?D8RShO&n#WTHpp_4H_y!vI4^mdYQ -zmnV6u{9=LwR(IS1U$Vg^Ug@)eiu4-t@YZohUjPCr+C2Kn{^FPxfo<6#Op2NDY)qY%B9lD4to}RR2BCbd}X%Sk>=AkYxoNT!p -z8p((ct#TP5nFi+1tv$s2+^I-%+*SGni~&=GZ8e6UtHxuL39DdF_{cCD6is^pf$uH5 -zD5wyS$`(L`^=k<&IM38vTAF^%T3wAaZUK>BbjKR7haR2Dvc|KodyYsV -zQc*f0NCw7GK(WrYv-1TdU$^Rib*0xLg|_odv#2LgcsC|Z4rdF^2|r8T%|d;U+@M=% -zkL9E_&TDvhIT6#}RwREOCZ4^M+-7#tOw>INlnWjpVA-aRe6_BG^^G%PnwuQ9%F`#? -zYd40XN_($yq#V3l-b}P -zSnRMGc>rKI?Zdp2+^LxFB)8`xlbswM=*>IHZTyGi79sLHG6U9ht!(*Cav#f1EU#_7 -zliaaw>3V(kkmQfPIofDn6iPkt|B&1y?<9AJt6-J}cr{mneoP+Mx)oNV_r=0H$?YzF -zndIe|fAVe5RQT|KH$9-o)R9*;{t!qOq&y18UkW`i+ffM+p^!W~P(J&PYMe-A{%Kg^ -zExPH0lMOnw4QHCHS*_TDlk-&oRajp$K6p>!PaE5KPVYLhY* -zdbsoZZibiG-?8yiQ-pZx$hY`9=^sDovPi&gvWP8*@NCEFm&+MlQ7Kz(plKHVP2CLs -zhI-;azUehyX#YW<)_94ob%_a;Fy(DpucBIw$K@zR?p(YzS=rKD)2k18JDc3tAPwkT -zkK5s=QM&KMQJO@6)8-USrq78KUPd4RGRni=gXvO-E8lYIG-Q@B@LT)fEwzol>lCFJ -z0)JMv_y*RLQ2hb)*o!1@y*t)|E^&rqHw6p35tV%o|MOCk&Y&4BHQRlo -zZ2;@)VWadks@!!{QQpH%m-H?S@D&nmF*TYwd}kCk^f^*BB>;az631qJnp-iyyXl)c -zU3Nv+($KIBTu2GOn*rrFvO91Az=JOWjkqZyu9(u!%)*ccT7!6+`a(Yk5VIY46b4xq -z3ZX4`w_l!;Gy9>+sKCSQDMe{Kwe$9nr$-Qj2TFJsxv2@O-bL;vpSOqG5Z!36vHZDkx($fzZaIpr{1GFcyLtm3LE)%~Ac@25 -zBXc{?b|oet3ePkByomU1f`5tJpgoq+wVF!|_2<$V?hb!M?w?(=^$mb6zeVm1pz$df -zof#WV^@QWxm?N=x7%Mk8b8~vBAt~e6aMgINI(~aw7_0l^71cT%l9=&yQ)6(zD@es* -zkZl5?vMrM*#{cN+|QzSuGVxL+V2d>WjzFhR&nH)S**h(IW!9i1rFEo~vab -zDv;J?8Q7?A)?=K30~{MF(NBC#0=vFj$l!-O#Su0Vg88#R7TzsN@y$xo6aIS{ -z?LtUj^|pl)6SRE=jKOxLL(ojCGKKG!Ni(!ElO+`iq#Zs;?x8KNI@s8lSt5B4PsFVT -zy?_VB3h;6Qx_TL}i8rS^tWn3LTjj}wZ#-Ugw(Qz33Kppzze(<=hGH}1^JbFYBzM5C -z#t)KPwYjgBtt#M{&d(BC1s;rTIQrajx?j~0f>O_LB8>p3sY2!JKy%KgS#+Nih1|h( -zeFzvcnCR>ipbY*!d1G&g_&~poJcw27VcF<oL! -z>M-g_6^~%rpBCgQt~V9b56D-ksiz3j0iMq0!t`QzzSiE%lEQuS#>BAAgg=`|{hXO; -z;O{H2ip?RFHO&!7AvorQ<&Xec&$#NF9!-uarxKN)RBCgyWKW6iKIa<255nly098sZ -zw}H>u1%MK+?bw`B7!8j -ziUZi)8Ndqo3bQsRStYHOcM7Y;4FKH=f^*x>|N2}V}X0`)Yb`PF7i!e`vh>BoRwZJ6gX5TSq- -zOHn;iUHJL}V=**(;GGG1Z(yi0f`(u~!Bgx}b3~SF=YqLrjx7H=^o{H5_x?1IxzEF? -zUP~n}5eCdVL?vzqrZFR%LIdcW!a_`-idhLxGGlIzDWTy5=GkR38TA>6-VDEVnT3p{ -zBjZxJwd!aftA#Fvnh=Rc@@p&YF;L^?X~_Flar@hJthO)kIa| -z_xMH`H;I;F@1TNQv~u^H9f@?*e+vqf@}b -ziV0b(Tov>{U(5ncvigtGvx8W|&;CEU?t#4zZowXQY&Ev+G`5|_wv)zoW81cE+h$`l -zNt66y+j-MIXJ*cscm7{uU)SDiuX~lpJce}j_C+ASdvEr;L?;oiW&@cN>E@lTQ(>^{ -zdbI})#WShDHE3gWr8o;8AR`WB7#oX0QVxzpH;ry8Ga%Lq*r;*sq$zl|iFh)A^>OEjI>^r<&d_y4cG03 -zQOkU2Vq@h8#YXS}SFvu{M423%#IFDW%Vf0iR$U*gLIU3wHTN#S)77^&ryz~vWKa_@ -z;{$V{TBDmaD@>0ku+U(RUSqLY^2*~0j5teKM!%ak6uUXa-&=yK>g1=$4uLKPX6)IJ -zifd^P8%;J5$>K(aRarpUbqI%`d>x!wNDPpa8KoT~mQ*Ks&pq_3E!pJ28*feMwz~>% -zzTidc2Nh*f0u}ePcj=BFs-ROu7$mWL8;ft0PvS*GGgQRQ%)<64)Mm9a3~JT1+_gC> -zVr!#EQoNfst1G$VxgSZ)97S-7r*8;W0K*{&_)QKWk_M71PnQ?F`$0N(ND*cmjQ0}_ -zygwKR(RU9U%5*tz-;Eh6)Nr4@vv)sRQ=bzWjhE-)P+8C+eQ!420q4_BG2F^SHRF@fotq5fU7do1|9^Zewjw>8>rD3ax? -zDtVv?l)yI&W4#ue!vkMPTW;}pHZF1?SZVspeEq6Sg*NNaTJks~t^n)ySx}3B*QFbg -zW;T@~?gJGy|H+u9!V)bL8YUeY!|if*bF3Z?N?qw+#rX8=Q(9m(g6Oa_;$x1XN;$X!V9gylJgCsi5Je -zkVTZ50ks@RVMw~SONq{5;^dbJ^khq(dxiIpJ@f%Q8OM;c;{@?Bkge6a -z^JM;EHUe4*;Y)star0Dk3iTfnOT8@1xa4(#j-y@vKjQG7GkPUHQz1R98xjKIYRKP~ -z2p*j1-_LnPO0z6>F~G{?ccHTKDhGKrGCirG_4XHU5plUv&F&{r?4V-HE}%WZ?1OKj -z3pZdb5HnAdP+%@FKHQ&y=t>6ci*fS>$f&-nJtuY*ttb>dHje|_V2hA^Nz!65E46#W -zxW+r?kPKB8f@Y1a23c_?(hzX5N^~-S3V*%DHa-dL`l*Q{y(7Y+vUZ>ZI}G`q>VpdQ -zKt{9%yc|WDIBvB2eD13oYT+bnj5v924HIt=eN09soC-Qd0{9Emz{lCk%)4=vbYheT -z;b}rDUrm|e8mz!e>+{OkWoimia7($fU>YFZXbc$|=G*-g -z(gD1;X;F5v3AlRX0y?7?C@)mD@>i`;LcC^T=kzmusRW~>EXv176&vs~^g}$8^dZzl -z4Z`vd&neJkN?F4+AdT4nnKU+7!^VQcfDjwiOTY=-M42^-J -z36I$xB(gx(XgvN=(`VW;wJD6Te9eR}CTE}#9se^8KK_(c9MTeo-G62(W9p=4=`)2z -zE(hS;Ro!)^iY`t2PfYuvwiKpR4~&ad35CEGZ(8xCpO_66lGLLyEr{(~K|6QyC;?N7 -z>@M{O(I8*0wA(NgqgR(yBhU-dGK|hcDk?KF&tQ{WkRnR5Yl;pzFs&d~#8WA|)Ul~_RQzbu143oW?Z{SLV4nqf9G^WAUW7>k-d*8Sl$ -z<`hZx{gF8G`xq}jF2NOlfwN-q3EkW4(9rK~Z!O~P(9Bt7rr1@ -z)QX5@7C$r>%PM-4Bs+Osw8ZQ_wf(xn=MhKGz*RHuSf=FTW}Mw`3)di)bmzKkmdULK -zrvr9SM|TEZdG(TAIk~8d=e|x0@IFcgC362F<*;9L#tf9bAR8EkSY2S^0IRnY92iFU -zBeywLr9s#89PU)MX!}+C@rBDTD;gHETjiO3M)nGPkOBxXCw2 -zOyDR^H?4KxH`<8sxyS;>?IRNV1Hz3*H=W4I2L88$C-)e2QUDaxr*jEDOnlmU?SbFs~ -ztbeDu$81dd(-sCM5LnY*kzCqIngs0dt};ElP$Wd|2^?MW0S&I+@YH2qB>B_2spCuW -z4)4ax+EkU18LP}HBWD1;q -zm#+bZD2J32^M~+3RCg6*hg}78<(6AC~X#EH1b1p3J#!3rQl``;E}pF*2`2mJ{Y7V$^1Lw(4fd><|V -zf>DQ=V7m*)Y8o1$QKw=Z0doRCEG^o!LeZXuZ(r86FB_}-=oqt-Y1-<93HP(A3CP)P -z*1Sf|B4*!ZVi}eVzU&`#1dhqI0C0JDpgWT`V`&iwHZnhNs9hJZ* -z&=oRHiOcwz_|S$H+R)q&P*`z)d_iVkQP^k=e&lkn&62o|H_1vHkJ}1C8S4Qys-A8` -zV1h9)0Nl{$%yqh2mrYW&Zugzfk7dZZ4ekcLlhln40d(bZT1b%$E*tZ++D?uI| -zJB^E41Z5~)x^5vuQt7%iMR~(&m?aJcmw*frSEHH^br&S!a8cJ0D&-XkMK>CF36N=z -znMXIq(L+#zQ?ade)w@zeQ&?0oZOvItGF-3j;cy&nSb2-;=oWd$F!G|!;BvarC4J0@ -zZNs?6@+aa#O0|Gr1F-s09EbxC&dTh%3imV;$krJWKWQ0#ZZ*X){W(={MjmLim22T!_A2~wTDtaYGL7K|=RNV9;InC1txT>n^6vGYeH_>H#9 -zT`^$zo0QCcb@vBWHcuGLE#(yqyy*pDf7KM77qv-N@&=P~Q~sj|i#fc$e=@vc{cOWa -z;AmfBi4&weN;Hh*Z~comMCgdi+)~fA^9v>#)r*vsak1}vJ&}H}zEtXkIa^Vc>huW0 -zvbv4yEwI_KbirUv)f9*>nMFd!kQG%w -z?w(;~n-wl=%KS-RQk^oRwn7n}>(Ffr_XWVsWA$x3n;1QN5 -z#>J-En4ZB4fVpI%W0N#O6&qqwwXt1T4-^#+oBX|t!i+yU28JjL9s971kE{gT7Q4?k -zz#(;X28c0#Wr%=SPw}RQ-6@wn)%0m30YXp*N)e0~{sH4|0!pXi+qERkE#bU?n0ZT#qS#6P)rI>NDZCT` -z--JN0BJI|8XPU|MYnv=&uoWp0gy!i_4mD`{n6XQyJ+QUKDhCC`+s+wFek&K_6&7F~ -z049i3jdzl?NA9j}sDm#TgnxavEzuyz-MLqqbWLN@CcUc2VOSHhcU;qW5_PR&J_0%n -z5ZRaOGVZdtSJn!G!VVHmA&y8Mv7KYX`Nk^}#E2VW{Ge~fyP_>xnm^D~e=aoIw2<1z -z(xp@MKHD%>CZBD4fzk#Uh*?6az_p!xb6`2qUcbGg -zcF&)evol2b6*y@ZK&p#Q5%Xx+jCE$2p(2)1K#2{kdyN2CcinB@HHVI&1VWLP!N5pu -z4^Ezx%v;sndaiFrh6L`N!%Q1BP^Q{Fg_3KoW=y~whOs!-2wC9+`W{|c`B=B{02|+! -zKkR;`2gDbE;*>wi9?NXlI%Ut~Fw+b@D-(zm#=$IINriD)XQDtd29YB1xFyL4b024V -zGtogd)&8TBdg|f;d2jjUtD4q3iq#h|nwrsY>H=K(C5Yn{qP~uNP%hN~la29+bTaM0T){%%G2Hev -z^VW`(-UVw^q)pKtP`($J%h8N5Waxz1z>&B3bZ8HDX|(QAvvO7p2AVsl?l21H6yBFD -z5RGF@4atoeG^bdqc9{Ph7|rm0;W-N`GEgYTDWrayX#u>p6cRGnCb(tsfq<$^Oq8gu -z-`#03mLGj|%jE>Hi9d7H+N}GujXZfdqjh^Zd;p$Pb__G9K0KjRgNRon5sPYzud?Z3 -z)D~VKK=$!NW*8;kwOAZyoWZdn92w}ZUEf9N6Ti(O+^!?|1ryq-^>09Hc>O@jU#RIJ -zLG-UQxy*|c=@Yt1p_0SakA~Vj`fV}GC7g&Q1;4_NZ(>x`jehU(+0f@lhm&F^$DLF| -z!gd5<+A~hf0W-=!O?p>*L#-2M1FulD6Llh+IPSal@Qw5(e?A3vh^Ft`L6M28%Zja{z}PrA0=B -z&TEN%S9Gz4yA0s{NN4k(WbQmq4>M;6E#BhmPgEP2eBS-K+GBI}tDP~ct6Q4jV~L;h -z{F>bRCgtO-#HbIyFE)~$h3<_bA^Ha|y3bS22NSo0n$m+C=1bv5Ox}V`lg<{~YSr}8 -zQvAb5JL`S<&~+V&-nJ1Fvy;~Q(#*4K*am|0jgs2QQqhe+E@rh)oYQ^@UzW#PUd_Il -z`R)|oQ8U-?0K|rFy^z;6f~~WdqmXDE8Rwgc7YBKZ4aTtsb)FLN#i|*g_BBibf+$zl -z&xiRiz57)yw-%KK%u@wh7wdpN_!4n91L|x&ACVJMe82(>{(j7<${zmNCd3G0U(+67 -zjy<1&i465Mp>AS4pl;U@X)+76a-`W@;Rzib!|3!WKIMdK>|zjSiEqTG19J~h2eDp{ -zbh9Tfe|CB$_q2Vdy}B+wJa&t56TzT&ClQlj`_hUvKeO=Y_+`QopwS|(?dq*6%h50R -z%9kR9u`r!3xJxNecl!?Qy>@O$Y}I?;TZ!SfQeoSnw_|kY_-=E(n;WZRo{?Y;`wJ>) -zqGv;SJ<$`N;9;I&y)=DCTxe9O`#TSQUh)>&!=?IhWK)v5(8u*P`6>fHDxW&T(9H#3 -zvX|qv0K};2TJl;yW7bdJ)Gq`r3|?!yedQeJg{_#G^FDwUwiy2ROP^PYoJc*hwS|(m -z0L+1#^0f-sM+BEjf^6L1M0v31>IOqJ!W29+Ubv6H(#X#mU^=*~p}D+7B?^^y_~=(; -z^jvg&bkTd#tv1?Etf+ -z^_WrQ7EaOM&SGL;Dsg3ZzI*DRfMKDX%NwMFjuk_DCX4}hGB9n}WdXDp;1+d(mL=RI -z-@VG@a64W*It|Z=Xj4dBSJ=;K=80eoEa4^i%ALlH&x0rCsFCT7yf5_HTkghcxwf3sm~BJ*L+ -zGkGO*^xJ+Lx_HAooLdXY`vsQi1%$MRR@O@njO-?>a7l5w{>)7#KAu9612Rp+gcqP1 -z37D`N9bA%h&%Z(31XmfCM^;^^fPq)0Gx_2#)N|_Ei(d-&>kcpQ#JIADUF^X=xZuf; -zmqXq10rV{X`k=P!eM6g{i7PkVIv&2HXrwgVL+cg%=WJV98M+O9g%`zx%d@UlJu~0g -zrPX2GBRspvHUU`Im$l&bZ81Zb_f&j@N`YUcIY^w#**#WO@U3v>2+K>b%@5|hm1lij -zKcW|dOu*tH1F%;=bRQ)$_uwmCZc4TJcdmJlf}@^xgyn1E -z!)s*lNAEnov!U!GXHIYNuq`+FbU7A5p83M?P%+#YpVKA2ygrR3v2G2bdh_}5gdu&} -z+zZn$9;{L|`Q)&w-Hm14m?g}D(E*E7r6oXXs=iVIRLSRr99J;=jFlSCs+m9seBq_{ -zdM8_1Q|Dn9P5uOthMQ47W -zB$By?7`29?Y9Y<{-FGP6wJ*y7T -z?y>xqw|)D4&N1;y$u-G({x!}B4&;{uk#ld#-gAV(tbS(S^uBp%n*edXb`tQ(^%DSe -z6s(hdf?w`A1SAgt4F@94EVbF48x*?l*CQAQs(Mwy;>fUxqIe3hvva_eUbH9J$+zB`mY|?Am4@aupJ(zBg3W$6) -zCUarlZ+mC{z(LcVM!-X85`IabIU4G`TQJK*b(0U}khI67b%`Os)P -z2d-^Z-Oy~C=0ZZ4smYx+qtI8-6wsrc=YWigh-s+~cVMSz0U;c;Obn&$C7XlL!{O{4 -z^dkdKLdobkC+5hzM~;w*ltddE0KOwWoQ73wD!j=qdQqVoJq#RSyBIAMLbxhd>z&ft -zc5PW6jbG9XMqo)H9b~tI5bLj!=%H@`*X^zzNhX|(%A9B^gim){o|)+Z+gm%UzPht! -zt3cu01bt+ZgFn^JsOZwjC^rY_m$LpzqS -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 -z2-MbQaa)kJ$Lnci3T@Oaqpeq=a@w}xY2p@ -zadwxOutcoc>m%n7uXsJb&s&jI4))61bA;hAdK+B$dQ$^02pf`XFpIQspZj-c*zhyi|$U;SdFELS>I1qb^m{s33h1CLPNm_MC9j6OlLWXV-JsddTUR{BT{pyk -zc*B<^rp0yY4h_}#H+3@jY$SJs#7dmw7B5-U8;ClKixd*(f%p?W=9-Anch>1_1n{%dc`8@L^9MOwu|mS!=uaQDpR~ZF9(K(`h&#{%%|P -z)V7NtE77`mz3@*C#Z#u!U2;`NCr40;69AvuHsw91!5?j#k-#jo- -zdszH%aj5j&s>D{G%md5rA8q@avJJ7gc)8$Q0W)JVwe9umr?w62=0XSKhw3t#F~pRG -z^u79U&LQiMwhiO@N87HQiwpSFw#z=XZIPVvKiW1dhOtbXuPfJ>8IT@jYk5{1Qsha3 -zXMY_jE|x&~r?%}VW_oW1p?snkdwCGuWAye%+s@;k?V!@$0#yg(Rt+$D3L75EOaP(F -z!rTAVw%hu-Kmu0zBj&v77eRYJwe6IlQK)TWzfW!3jSOZ{A7d)mf5(?X8OuaP;8WX1 -z$*9=9{YTp_`_#4_{%G4HnUo=dQ=i&)9*>qXp`sOI;?LP?U<5a!274Rt*ZA&+a|0jR?f3K`qZ}lgywX$I4Pl~y}f%^ -zNze5;>zzKe?J|hMFJFd!{MEMY5I(hSHqq}@zcv0#+urGznEKSV!F3MYKDBL^zuNXv -z*k5gX;7qV>E!2tJ=TqDE$JHs51;N}1!M6GNkbtEe -z+W=1-{Xg2a;itC!I0rO@k@uLEHS-LcuKY*a<`H5^`$yZ(w%PfkZKJPP1L3YYWk#%j -zYTJD~f3!wyM4tADiZX+{XRT}jHn+V(u>4@f4+0u|x$e`(v}pW1e0UZIgn -z>_cf-MlkTGC}5Sijd|F~tv!Koj%3BhoOd^j81D7i(u%Q8_F`-SM6kgQr%uvAHFS8{ -zzp7Mjk8pGUR#Ts?jSSi&acHdjcPrL`2dR`)3`jp+B1FTAFOa-f50(0rE-k58T+VDI6n(;m|- -z08ZH%fnveo`#hZ_m#GFhn7k(gkpP#h%o6N}W=a8*6)`7GEiw;^GGtfm^k!u)7?_A5 -zLvq%D!H)L+u5UHVZbyjmVmHIi$aS8mg%($2rO|kND;THs2ul8tT1Re2S**RPXxU6} -zWC}!#d8uegL#2CX63$mRJTN`gllkXnMb->z#QTC@gwI&O|61;U8cQ}Mpx230S6Y^Icy9JuhB>5ttB_ww@VU9tzFI01Z8$FQ+ZH=L>Xn`A -zToeV(#1n~Dp&4cXvKrQ4(4KjD+_~zv8dM&aU@e`m06l2_@s!e~~uOU8x%iPTw#u{qICkDJT?VT1zh+~SDy3>jRJFV=k%!A8;~dKm(EOw -zUrm_7MAZwKc4k|mr7h{ro*Gq28KNQ#REGX#^qf|`D-eRwO4Z*&lvi6s8U5H;eD1QV -z=bcaqb0^RWp$J_X6F7xu!_5na(^_Kb2rgoA6)fLt_@S#~*MaCxk{>og9%-Ze%Yv&- -zAP`S!y~#i2n1p<4!Xrh#6`78ujs65_wJw*{bu%8Io*)p408w77y=5F>o56B{^9{3( -zk?_L8@?@XD{R^d01SBiQEt75DlA!L|jF2=YmJ!4@Mn62E;mDHB)GQHYuP##*Y$u$q -z+|77WFF%ewj&LG#vw-Q0LtuLLJn!&ZtHjh<+>}T!k)#*K$tTg>oDMCwuA8P>1SYn=?XWw9V+xXCxG*ios3I -zjXJ#J;^HashQ(fL(1rCKHu5r_;uI|w7LbZPd(0z5{GHH%C&9^D%#kM$AA*veDn8-qIx=wWx=CD_j*a+n=0xZ?B~#8xtH;fM`WFvo3k%K7w% -zAHOV>bl3_=Jnx -z1%Dahlx2ai8Mfc-RBN1ZX9Ji5E7$atVW3|rVPSOZhF_;1x!l-B4YyeeJ6^k%IAupm@y684O|u#D2qNx*_t)z^a^#Hew|yD_pDB|z=Q+41I9O6;DPlpA!?r;HTdi$9 -z3N83~$QS*TZ4=3p>G}opQC=u3|2=oygfjZCKV956a7SnHFt1wMXyuKwxDRIJ%0(^p(bKB+V2Fel@ccj%JqDU3Gp7pkSwbI?GJWG1S~?|0yqQaJ-las -zGLkHQ;?aphk88}7Tjo=fLi*IUqyJ6YCeE|(`P8=SreP8wi(x*sZ42FhY1@uOf3)p_ -zZZW8`KiYQdKiam;)<4>|%%`>uCHOree(&G4ZHaxtaP6jGxW@m`wr}k`3DrpSB|f$7 -zSqv_U*O|Boahv5o+IIDSYTM)gPi-56{2y&Q$ogyaA8mW1oo!I=KeTP*|J1f2`{&I6 -z(YC>khsT>DfCN9aZDpB%Y1_Lp|6AKmX8d<;`|*FYZD9p@od4FggD2Qs%09JipZ~3G -zho@h6{?)cg{pS6FmLH(V(PxI~zxV!^wypg1|JSx3|7zRD&C)D?wQUx|Kic-jzqIY) -z5*!bof77=2{%YI$B6dN2a{x%2Lz91L+lzm+ZPib0Te{DF`j56fUb_VCEgzyG6cE4NuRYX8x;-ToiicJv=@n{xDzw(a5c -zpW5~okT*fX@ju!&?7wT`8hkQhif3$6To;m4HZCm>vZCjAQ_-ElJSrO(RZF{z3H8rZ= -z)044SPVLFt^8ePhDL=JsjGv#{c4F$MwjGl{zVTPvuK%lTLu7qw+i(?usDHHW -zTV)`&|IxPf`@&`m{zu!!{y(&BA^Y>c+IH#Pe`?!_=bzd(5zZfNo9mCZEd#wUht={| -z+pgUBqiwJL(Y7rd%fWU&we5kMY6zEq)3zTiC}{p$+eUQw4{dwnkG5S(_o;2GfBBcT -z9S9u;`(N7j=!S -z%c<6pDG>9|?xagq{OY1V%mY_%&*`faZ-~YilWqjc_g=!mwOf8f(F~DPGcsl@U*)^$ -zYzvl8Xr8=<+UD=|l`UE77S-JnVfW~4047ve&1 -zuYjS_dXP<^roTTtlq!~7vu^vRXZrxToK&v2FSMg>)rOqGZSI7-%K|4{<~i3-kUrZe -z)D~alS=Liwgq`i!bRtu@sG;|Lzb(Mz(=6H4&(@k3yYK-aN1ME#&zwd9<28XO3?whW -z%B#qBvUQZq*?H9(o1_TrS*6PqaD_z~)PtN^i&=u*V94bH$=ioTbOeJmsX)Jr<$HwS -zrJ~zls#dTV5xJkD~2cMq3f`L -z1mE&h%L2o)hF>(nnEcUeBeLPvP5|uFBEq6bd@CAEUob`rw6}9#cf=!w9%(k -zEpy!kU~2VDUygNTcU@2<>*SN@!<|hFj1fUn%N*1~gww&9`^I0pgYhPj`r2kxfpf@f+cLVsq~nZ%bhtu)r(ed?%4!}mlSQKm)HPfa5@lkDQ2Lo!uZvyWy^ALk -z!$2a)0p|ya>VLy7;pL)>F~3GIE@Z<_>JfqZ()@-$Oi`?~_wBe@vGt4dRBXcc5ZwGO -z_(e5!P_4riV-$FurM$0r5@5vNN5oqAGyzzPUe@VR5v6Y|K{1L&BpHX+$aY*0rABGq-Q<8{KilF(~9&gC|R5HuIkfJX_u8#$!goX?2hT^2F5 -zo6|i*o#pJ~jlTe?qvM*q!Yhf}e1A3BsiF@~O4mN$e;_Vy;rT$tVVY&726hP3u+*v2 -zbHb}wEz{h%DvH)}M~#swy)djOK=xD0hx%PyIRVAGHb~u5YS@m$2wJs}uFDXUVP$G( -zm#KuSw2=wYa*su=X-CBjS87)G0`~_=epf; -z+SctgOPV$ddOWzD1)4rnx5rMnSjdvASJwPWj*uUUc%(TiB7H0Rk%BKQTX_sBLPb|oyVtR)DrZMp59a@q7V_?km -zbtI~^>tnU#_YZb^EY`(~XS$ho=GcMg)HP>H<<$c})=-0%>?J4dPFf^{8ZW|Lt%VTE -z`-SWT-N<}%QvF)Nse=O7#u1H)nuRpsA-HWD9S#vpXzpL!CU11;iqmw4i@NSA6WysG -z1<=`ZtND^;rT-mtJ -z^ej)bVIk>E(!^B~O+( -z<@|1KF9H~*1_|VBFYO}ewJhwf*vLI)bnTpnPeiVAA-brVHb8YR36#IuSsKtI8%c5>CHV}L%Y!Xee&4!sKOFvQbVgCy!q-I -z=Be)U`t;^`C17?L&qAfHtRjBt4=#fB>Yj4YbC6r|0VGQX3u|2AULR;cQ1FHAAf2K! -z`>~ZXyL_1@9is2I>rBRdc$~$m&{;wuljfekN3!3^?38QCFn)iN>sD`V;ema>?lFck -z#iCFy%*>RD_ORahFYY1?cMi+B;X#*J&@8^!#-eBdDo!itoQGvVR0Ge!{^`gR^xZ;7 -zK8r4gb!vEgQtsQ7NO>}n%Y#DiOykMJ&GLwzONNf-%&QvY$M+p`9)}p;BP1GnnI=8! -z=Z{nBR!Bh!E)H|+Q0Gp%Q!v~t -z*5FGbg|HK2(wX1u;%Tm5$`#AHzu7@LtK>^?Itgbs<_U+w!3X2BpWIdu(#oI|PHE%Lw~x -z4O!+dS(0q7uR6$fhEXdz4jNc|uC9wpn7ogcu;tkn7J!;g{L^%JI31Q8+=&IQwO%NQU>__m+^>uY$)s)7R~yjgAP%rwoUj;>86VarAU^c|*e% -zB#fdMkhcz*JP*pP5<6*hCw2&QvdDvu`-8I|v5w7df~VUXM=JjexmXjQ6t$rtDNmp9 -zAV)r^@r=d^j;J_gvhV&jE=cGkK}p447owxOr?7fZ<)x%p=rnnNYO)39C9itI{dwj^ -zg{z%5r?-!Hh^Yn!z^U>Ti34 -zx%d~fhvZb0Y$TY$fLBqu%)k^F(&jXEsq7kO`I05))WIrTMhjtvnn62hNizsj7NfYi -zcS=y%YAN0I=S9W|EyoCR{o(fovV*44cl}F6l;o;mQgXwb@?gqiuKFoofk5%r}UA -z@;Tn^wGIdLZ$Lx!L|*t|bK0T4nXnhs;a$)4U} -z&YHHpAYmn~$&o7Js3@d5_lBP6AB_bSV5khqq_L9OXK^qlwqJm@XYGLkKp`OSn9b6O -zH#Pf$wLcdg&!G8-{aTO7DyIpbmcGkT%jH&&yl -z_Vw}{=dr<{x7{~9V2LD3_q|+xnt{Gr&=n}cjS(lN=8 -zC5GgiN%CYQG<;#w=qscKOG8|p`sRYK7}VV*U)7titg@r@OD8EU5l3ER5-F;jY5D?b -zD1R7;1{u(X_ReQ`b@QqVjvEpRnJ>W_VsrHo0^vxaBKC?#S(~2YZ8WeMISZ^TAEG;)CP!)d4IPL!%Z$1 -zuEY);CRt|W9RRCBuk0A>E=o=aCYQCl3k!k$viq9Sq-OLyD#4^VYeXMq6M)MP3OnvLO-k3Kq>S~?oaL)Qy0c$0xO$TDJ4$*#60vXgBos26y}r2r)O -zn>THY>?Un_icynL0<>j0{Pm2o -zO`N`%bfF}%x@}M9{)6@ni|s}w|R -z_*?J{MRC@T?B1hzoPwiGR9iR~Z22V0gPiRr!!wm@DyAf{Dz{|@=CpIAnvk%-y+)6% -zch2Dr2UFQ)95~R8j3pcg6Kw$o)YH~1uxq?%tS{L;C?Hi@P@n^}#8NuTqQ-^zk1AD? -zvUn&A9~3)^-|C346WdI0?vxLLVbMv73%jCxR_plrX^n$4B5Gr$VQ^epZU8>t)UAjQ -z?F}gR^M0FaX(h*~b9}oge*=V%1HuC8x -zLzmu|L=@gqXBm8V(&Sg#sjzEGZ#yby2GO -zP=(TFm~b&MQx&oBjK{v$J1^a|*A>1858%2Oy`hMg9+D`KNfZ)qM;*+gpd(LchPyh@ -zNU?x#Q%G(VRf5$EKe+ezCpwBXdgRVNS7E9Ux0-a-1g=GYeEQJY6^&PD>00qgW8S=~ -zt!vQ;(591#Rl! -zEGE^1jD#e%a@bW9=b>&l*o>uxy#XK!Qs%X&58wsl=K% -z@z)-k`KIS>Or*!sGO3W{0K59r(WK~=-m*w4W;ci=iNZ_&jDi&X8B-}MnHNdj`%b8I6uAIx#s4Q-pQ3ey(q+IrsC`3);#cwK^ -ziD+wgDp$Cf@0=W{U|$?!-|g36e@Zt@Kwrq#S`vZ8C@f2HQ-mPn_bUij6JkzXpuZH8 -z@3bT`S_#_zaOHrn2^c0u+iyh&kaN~M6-0qjHfP_RWJ)=eI%i3AFSdbHvxJzi3LfI! -zVvfPpOwnO33fw7xd;<3~O%OPLjJ{@rt3mD*HYInF?U0AHBbtL#=8Pn=2RbZI#HlQXr&o -zdz#dWNj~BUc9`iR`1g{)s0YMVP~WhYcVzLsT^BA0UMcwXBnHsf%P=jVJY=$q3VU3) -z;I|dU_A-2i%b`v)KlKF9%tb~k_s~>Iq8rnOnH4u2jr0f)iZ8!1f+e;iV#?C6KCU!7 -z=IyL9j(0)*w;cQ1d3T}JyB8pjT0*}+6f3t4oEnFB`~tHvgK#t645fCXKDaB -zap@!^6K5wJuY-Fy%!c^0JRych(5~+n(*RLPn=b8;NwcQ^tigt -zc~Yfse}X%By!>@NdAHZfg8}GE7>pkeL2!tzNEJX18KQ^W^(NL8qS#1NF9$#nYr5ND -z5JzPVSF0(;Cs|T#2!SkW1Qh^BGy~@91cp_==W>dWW+o!PE5gkKW(%4&9~PpQNmqWn -zBj38KzPW^)(nVW}xmTa_T))OF67}m2EMb^yRVoKLTEF}RsR9HoEiVpr88|bdz7W}} -zn@E=3D@7dRew2nq5)`V)n@NR|DhZB|LoUI6=G<@|azzR6s{cEgQ))=D&o`CmRap#D -z9hT>!8T>U?hwy_9j&5O6f+3JbF7|hfFuwI3(AZ*EG*0N8B1UeBSqvM -zJ~XG`CIt+gw4PrK2wA}wg)Ko^N}#E8k6rWm%)6+>H4XSZhK5w>6$ -zKG+X4aUPhSd}_uay&UqD5Jf{&f>IR4fuAsR`htp*v^g}aVWlE|Ca2@W^meK&g_#wm -zuV*aGr-(mmo7kPZ%Gi>HL{|@PgscUor0FW_hrZY`sDwT}ZL3j|5j|Ji+IaM1d5W$3 -zy)eG^$MFcSUd9L@wDuy|lR$%A0D*BXOBHh0HrC{4K%B%gc-jMAgRn(bi1ZgNiTmk@2Er{T?gt-S&CXPOjKc%qa)?%;2tesc0bVGD^Uht$Z*7;DV@iZ -zL!+%Y0-&(&Q1kN^UBg4@B55|t3(SYM-^nOy=^7y<1vzNZHKc0CD18A@|QAz=6F^vZOF(z>E(V3d+wOn -z+O#NiX86=Dx`|Ra*Mclg(1#1R3jXGy7HMxZmut9Iw55G`8BtYQJD_q5&5Q-KfbGa76E -z;tlukQ)aMaGm&e$g4aGb91uz^8Vw0jT%ghmss9%Ez9!t_bus3ZPNQS$voKtvKYjR2 -z%r!bU63G`^A&bKc2H)1P)qku((qmeA*aH@AOINSe{#j3XNVcqgLm!#Vf}ds)R)CdK -z&x;c*!MAL54&7+?l{SxqM;Yu{Q9;D1cp12sMj5ZMLf^`>?So%AO1DPzMgibf8Ybqm -zHqTD;XySqIwQ54*;kdbUeQ8Bcyq}9b=QJJ4U4_66IcOkF-ymKp5ZC8L%N1P6L3rR< -zC^e>aA160%3PWfjnC6f(mLxqT59v)W17k)22jDuF>Iu5c@S(@+=oFTa%7@bxg!8wq -z#{=ece%GMQ!y3BlpFg3~+Al0g7ZdKZnaIjjg#!77$>YcLdVL!%=Gf -z?$V}~0&G3q>5@i?WjEO4l-Z!C0OU5KD>@29xDm1N!eT$M9f(eMaiZ0?YOMujeB%^7 -z?>^r2j+DNc3$IJDtNIN6=SiFymE<13EJP$45aC6dr-UH2&oV-Y_(UeBN~@7zBU^j%T3UfE-OWej}fBG0ufkgo<$7Wx-nS -zQNEGHj2K;KSGQ- -z4?R(;b-yMMoSo1CAv5zj?1BgI;hA10`8|^bg|)J;&Q*Es+*)aGI;FJNCWbYd%=H&2l{f5YU$&V -zff~BJ6Ry$>iX~8J_FV_Gu=MW2{=OLLmr7QBMfGyF!t+tO=jhRPvfSuu{q8Bv&O)=G -zU^@O3ajsqjwdL4rccP}skM0rTqybkPZSOA{D>ZI(Jx@*!+TrV6nEdb{SlR0!y*-wV -zc}x>9-&4CD`xH>V??dzJ@aW@%YNDASevPKM;g=zWzQqw-S{L!MtJs_BFis+n#)w3H -zG!ue*!mssI#98MD9UjxWt+QBSJ>;kjGIQ}sa$^+!Wu)s1y9GjLTkHn%?IAazCY#1~ -z>UI?0?Z(5zc|zg#78wZ>d3l{q|2blTKmx<1h%qjmswDFlSb}4JmCukF(UyWE|6o*o -z+f>z0b!fH}@V)R#obRzee=+EpzJxc=Ze8OGhzSKrlv)6!w~~iUEEAQl -zPiR7gUOq-n;Ei4AEm@OFUG%CdB>d63=2J-j+lyO-DV1oYr5u*! -zVB#Tw|F6BDHoy4!S)dhrIqKqwz~!<$pq>HU%IsUhI7dN$+)$W;r9rGu0KDeB}1Q1rKU=X=snKfYdh?$z~V@DL0=~#)aKwJ;EJdayf1Qd -z^#RRlpXr^9*qfymGn~nXACO>GV57INab(0#nn{+{7y%~as%M8s#>Pby!5rsmknagP -zt|736ICS`S9Vz`*FfqUwMeMGi`@A?a1Wz5Har>pG^2y1Z>RWhnKNy70^4OzkBKIU0 -zL^&-Ac#)M!>_B5@fXw=mf^6y)giNtXeM~)!i&9pBYA;XHB)s~G?=b*JmAsn$t#CT? -zKz@9KHf7B*Vy6QIZVAaMkuxFt<@PqNeie)@tgeKGB7emS4o5}|fmcxxwz3|dktk|u -zALbRfAY8!ryPu+RKQjx0!j&65FG@LaL&8%r*_OkR?h>iAEMx8P#Z*tElL`XMPD@KT -z_ -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 -zX`x3 -z@qT$fwZ2#evTc8Y-4d5p+FHTSbEA{mNr%sdqA&M%2S=mMW7^f_-fyl?*U$GS`mc3T -zpSU)hHkbF?^~31m%ggn{Npg4eV6yPKhZoaXf6zL&l{9=QT(#Z~PzBc9E1kX&4y>r=ZchxW96g_gk+Z1T%lD-wm*gbl97$ -z6b_7$f`YkK$c~%!lZ@2q;boWDH3mSJJ>7}? -z?tkf+a_TZ6;D5iTkw*J?dVVut)>QvV#UJRQ`;EzE9cG7I5(30H>;MG{+eG7CCo~6)H1`Tcc5a -zz3pGd=pItr6GLgCkOhYV^@)Rcn#-IMc~6^%n^{AkCcqZLCA4*{HC=v;4IXesi2gxR -zca^t7UD*XmX=V6A)tMLkm2?<}8Kt?L@0y4tm -zQr%pvfxkG>>qP+(=!;1Gld(>ww*tp8tarBNg+cvUtUdgCj;#6@2PKCGP=|QwiWqaC -z>8tSlW(!-UwTLwBhq(g8>(sH@F9|qJ;R5l{$Vgq~*LZygu%BsH=lBw)1PF^15|Gog -z{pUy@d#}F2R^uvE0G}$HASK?sKvlJ(`ZlV2kM#0FnfeF!m(6RL-r-i*GRXHG?*za( -zfD{9#uj@3^L64yPv1nyFD@;qSbAf#z3uCCsH=m8Pz>-A%)bXlYRIA6NniT%y*>Hzv*%}O^vzrEjp9jObrMf(mGQIg)g6znHb`x*)(^WkaogJQ -zgpcna<&v>}KZO-rOeJdlx;OHf9{b-a#V!qLn}S@u_@5hHy?ma33e1Jz8zb&-Rx -z0G-nn@GryH*2stuuop|!T#N=4j?ofST$%E+F`CXAYbg1%Xk_a~9&{;~V?`oMvKC1lb~c_#h4t6_)8DVV-y8y#2+_N)f|QEzjOCiE2e)ZIZn%wv&&`f; -z;dTlVUg%GkE1OPMNZRLrrD7;qmA2rohz2F=DwYOg|;00MY^U`?0bo@_;3=V{j$^kM*lWN5KwC6<=PBe7KOF% -zjv9X5FxGa5p`?rjx0`>Jb-{5!dNs=B4t3FFhMM~Qc)ZGz4u1O6%aU#!u9e{e47sH6VK#zra2G<_$>>&T*Xe;GPVeb@84+AGIBvc0jyW8L{k -z#q_4pbFJb$xeqy+m>l^c#};yYAb04utjgGfJwTDP&fP1$qA3v&xrUnpK*;HSXZaxR -zQQBn-M)>BE1rqa -zf+&8dt+h_{vKQY}CS?pV9g8+F$wPr^xb`xA$^g3QPy>_9XIxcggt -zv8NJ(i|{wz`f7;K1gF(ti1ee;s*}mlZDo0k9yk;dBD{NIfV^n1xsvm@inpX_6coqr -z+t=1`i`5)Ag-_g561oEcsg7b!b+U}ia3&UGTB1w2)=JG{B@K;G217(LuTRvRh9_!6 -zCYCt2j7zZH52J;6Ztv -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 -zEGqMXj&qHgCidR?q5x)$>uf -zJ4i_`?->ONI&vfR;<6tar}z9RpD5EjMV_WsgU{%ofQStB -zUV}W)y`2iLN8wF%ZANXgbB@t$mKe>5o*#QC`p(fg=xk3oXTkpzFkasJv4O^sbs%Z}Q$=mgvaM9S);CxRoFQ|)L?e0#TV%RP!06&6p(;fln -z%Nk@wvpD7Ye{0+I`~T9mOC>(FZEYM55l8gUKecUqp8wRgfgO19|IoH$GX7KBE|W&)^R^TuKW*eo1^6qZQJ!9+BRL`|D|m!Hh*f{6~DFZU2^=wf7iC%{?N7y -z$3C@f8R~y&+Yzzb|E+Dy{Y~433j4dZU9$eEZP)%o+cqivt!>Lx7XSnM{i$tx{V#31 -z_;1>_{=aM6LBjt-+eWVbL))JFQ`_eFr?#D-@ISO|qpIK9c8SfWw(SP`scnNi|DkOU -zBK@IlSNx%E!`!f5wCx|-_RW82+x*5t#(!wrM0Nkrwr!yP(6$rf -z(R1tnsckbDP0yM9scq-}scnlf{MNQ3M*gO4$NZbNt@^2LmjM1xZTk~!{D-zZ@SoZ? -zC;6wgedHun`Iojm{V#1>rscP`4dL{k+BWROziHdPzqM^rZbi#~)3(q4rfs8?|3lk0 -z=}YJI*jM~R+ou22wxMMgsC=}FVCww8{;6$a|4rL={?xX&|3lk${!`m#{y((s-cM~C -zU-|!`ZKwR!wqxFtQrm=PivFo>*M4f-ViW&|wjCz@scnN_vHSm1+aCL^ZD0S^wuxvy -zwe7s?|DkOY4gN#hE<;UC_=mP_{10ur>9@8`q5YrQHo|Xh+wTu;n>c&Goq)=b2Whmd -zTPM*1&bZ)DZTsj?Z9C#`+ICL(D(}i~ZJXy$ZF`sFAKEsB(?7KB%71Fxh(1N^e`(u0 -z|D|n@{iSU~;QXO&%WM9jZ7YC&YTKCqrEQ<6eQMjO=Krp3*E##3FxK~fYTKd{|I)Tw -zg(C9*(zbd2scj>M{noaRvp%(LHH%GA+F_GLz?pw(+mt2$(zYQY|J1fy2~J%8(zcoY -zFKt^|&vqMh=jJbMTgL2f+BSC1AKEtJ;D2k|2QYtX+wmFyTic%gr?!3im$pq*_jhfZ -zdGhbtcEKky{4Z_$hsifV)4#Os(U)nY@aObeVU)uJs-`aKv?Eg<~ -zJL+%R_Lrppm$qH?hqgWRFKwH%;@`CG(U4DV+o1TrwCzR0Keg>v$lu!bo|6kg+ -z#eZwtwhF(s?ZuBh?-%Zm<}42959N(_?vKHbvYxvSn%r0K1M82)kC$%j-Z{y2Kd-vC -z>J+W_^2q1#OA<;bV$SvHbda=fJft}*?)qRl(2v%m9;Jz_ACbU7(Kfs6$*c-C-d-P| -zH4j*Q(2Dcto@pO2L*fcVET9tC&Y!(ER$+)4ES_q07BK5Ff>?hnNb!z4veW25hm~}y -zkJW30&e-ndbl57lYa^I*BYKg2tb}Ar4kE`YYlC`og{Jef8EHI3$__^&rR?ekOH=kVZVArDJ?^d~ -z#7}s;$*Axq1pR)wXvzvlhBxW!Wem{IHC;?9u3Jmt21?4`iv^c5(_co>Q1y8WQXX)* -z&UJVTrUvh3wYMKSR^}?4qi#yGG)sZohn>Y*LckhJU}6&Iu|H(@Xpcw?D>(N2x~^iu -zs6m-mILv2=_SGFUowaUIyZT)blL@Yp7zTxCMa)yU%*7vFWeLrq5gUX -zwb7w{icTn!A9|ntA^0%sno<#V+;l#!bqKUieme-7)1*^|DQQf$TXn?mezZz3gLR&4 -zVvv^`BF%KPCT#r-$J?TPrM -z`+Ips;B}G4yiEir!hu}0c{ATn%@Y{w%}8hGAnQ4RqZdO)pHO4etg7qAkn`+E`t{{MjS~HU!Xb+w;~A3W#V0D1+?7fgio@JiId9z! -zy=s?EwfL=Z*KE??Mf1&9@hczvjy)qiDgWcoxiHa*VZDrnADT*Xe-X_$6nlAovFIvq -zCSO_4W+V;mGH2GcjITBal8pcrwZJxD7M82OdsI8GK$ -zetA|^)6>t{L6!*M1uWHT-A{Uxe+lbd2CAiyH7w9Pe$v1%?I49zHyy~ei0}HbC65?jDBzwR}kI$jso^_mP?fiW60>Yl3~_Z14{4RYle(YYNNMoYjm=#l4X~!zEg@)<`LhxKm)Ez6q)u -z>wdf6=+7Uk7e~{cd93L2j34TdnUIn`KZg};Rpo(zfJ)G0Do^O`Y47a$l~Q+BgyIag -z&~6N0wqNcF`uaJ7iE9`IWY=y2k!}`fk8NfR&{^D-c$ua*3IH7$P?^fRoKRTgiX4Q3 -za{|K0EUZEds&bTs2;%Uz~5v(IeVNoO>$t{q{m -zGfy8Cfla>&O9KhJ^a+lJVyI7>NWankc>df&>GKiNw?|O|`{|?8Srhvu+V_6$*O_w) -z=R<6vpzqS%kGmNOYjdcXs?M?}M3~JO{3H5q{?RSZVHV{b(ko^DRkfSqlQ;v%V5HnVKEh_wj?7w&{B8m`Bhg0>0XT|2+e8ZVtjz*_}s -zAANtKAkyV|8-#=^vK3u;g{?y!zF0gAU5_#v3bSpte!%EpNzWi`m9-iO@-g9^+SLiC -z%uv{ldz*HTIY6T*S1OZoDFms-`G}LaOSJ28XfeyzQdYm{u(hwH^O)zQkXMpu&ogBi -zlAE6C4byulPCQddvg6`@z(l$E1KfJLc58U`*e35D;4{rU83y&~2)_9ydVBP1VQG_N -z)&(wfc$~TBCt_Mx-S@A_G6-i|3=MIx_fLwkt=@6;w7CH7lx{FQ!$Gg`58_A!2wAGvAc2)MEH)X`7VopaZgu`4=Pwt3Y?L@8|7$%ySJMN=%&OZKjbB`#eux9&@Sx6V=6~~{$%jV -zBgZ+huh9H>9O~M7d){2@aU0%KuUoBMr990Fmzo!6k9w;nq}AIGd!b3VJt(}=-k=;h -zKr*(*Zi1MWxgan$FWGP?>9TgXglh>``DtRyuaBfKCzaU?&}vJ~?T1@J;r)ynP;z(& -zd_FumP1uAX@XkgljxrOx{^VRGBe@b8b-r+<1ij~>j}(VKb)24mLz&PkugC_B1hB;_6F5$Slyc!c-A8Sl{JCX0vk{-)8ZyTUd4MmrJB@&G?``lo)3a`=bHsZteHMA(L)zB;uis_vW8YYGQZ$nCnA9Q722 -zm^Fi&BR|Rwa*C^9hGl`zx#mw|B8utmw6!x-$=vMzY;Sn3iSqx_y#M6q>fQ$bFXtV5 -z^jKC{lTqV0HAifMAu;yCEO5t06KNrQM(^9?QYEDO!)9JE3yv{Ba)a>FqF31@s0T3d -z)n~6oEsVCK(7OOqd#WaiEr|+E34X^|$U&Lt14y;v0K7FOV>JN(DI5u=aGhH;pf%nF -z-Cn`)tw@K(R>jIotwBD70|F?y&&6PJ8*=I{D5W5(4`oO~!epQV&<)d%u$H(a$V?u0 -z`WF`&Dek6qK!ujr87o7}#OSB63x(8St}V;uwkxx1|xW^_O)d1UiB`XxWFI-y*h;>`4wY -zoa*QtN-&u4FM$l}a_N|3SO_GB<*h=$H(6ZWv5)5Zp^o%l?Z`gr(1)}X@%WuuoY$;bqhW^nUtVUq%%~lyL~(5NM4VQeE5zv -z*=txW@Z@`yTgTY@iDO$`lU1H4fRHwi-`g}sk8mMtF6*rgF#$ps@-{WMgcl3wmkVG%ib$lsARO)Wc -z#j}H;Y>G|fsOy$=lFj)jqQG;)brw!s`w3;?GNK)$~bvBgbo`c#`0uKLS -zzDG|>NGjXGS2tZaPUZ5DnrmU)bf+Y1jT&lC^-$|s_ty%3Exzq=AoA%6PiS{;W~_tO -zLa)MmafKO@%&mJxK~I^3<*3f@1KNQT1E!lV=XSEiLn&BuLG_`s5ukmfvBL)>**z5b -zh~{+rGDxS1_BFTraX5y)xlHh$>x^t{WiuNi7pV)n7x(xQ2-*6n0zo+{;d(8VFD=3s -zhE0%-yiFbJfeoYX6RFa}eQVzX^Tb&~p{!Zulfr#x0`6N|0K5i%DRZ5zU;>kthymn? -zq$1QMRof4iVo%abnjRNc_P -zI_^fUV^$t|dzNMS4vYXQF$=&M>C2hN=~85$HB1i99BFe9RQ5*19q~F*q}#GH0*e)o -zHRYYz!F2;KOF|>02YGy>Ejer`i*Gvd!VP7%S@d<61G@~Sp;$@?}Qz>H_K3mi6b -zb*lu6-vF6_(G*N;FS_{SqXD!o><*BhX!4U*#r(q7>4hL;J2%8C>2_9C$R! -zdVb~3+Bf|juy;z@eWRNiZ1dh5Nv>hHxbc&TGCd@gGZBlWFx~H)O9dcM~1mMS$G&t%(`|9gtB~g<cpz^?VChPK|9#D!(ktyVF7RBwM$pLlxc=z$);7TwI2B+1} -zZE>GQhgfpMO=CADuJ -zfyOWjb}LV^DQ!J;QgsD;K|0O_((unqZuCN^!oLkj7yN)?A?G+OjW;mG7F6M+HG|~; -zxOHk25yZSv_*lusB8-DjM(d9SHqB?#@vM-1^ivknu`l*GSxyh~L6m~AY3icTaoYLm3P -zJZMc@WDO3}>PoGsc5*;w4N2(!Xgx>_L=xmk4aJ~bx-X0H)f+tQ(X%7LuM%0H5Im1* -zT-)$-Bg1Y6sdb+h)q~v#i!;LGaFZ)69$bdxpt8Zen+utZ#o1T4V!5R<;2RMCjs^J( -zi1)PpZ2uP!=Lfl%>@NNW#K+wi{RdFNnE54Ee5#csJ`YDu*krT6Hw8agaEB!jn=4v? -zWQ|7d(t3;mx1z3jo*v&**8HhI%#NgKX$UGdDXSSQWD-cLPZ*ouQ+bnc=yR#JVf!58 -zpUfvaH8^x;onga%#Q2wt9a0-)vQ-cvgH4**>U+F`Dd4lt)~&qzYI-~G4o}z5c$y#^ -zu3O3eRxt3r>rdmnI12$AHVfSxNXEw;dTcVwfED8BjwJ5hOFXrx$PHnnUj>xi)o!=u -z8dFsuE*8b;Z|Z&p#yc$%ZafPjdL3;TgSXA{kWjsiTxww{vk#~quQ~V5lD*pk1_zu- -zw+U`#{}&MN2L)xP=fimnI$BZ@4$W;UJ#^63Q=+*-JfoJwJ|ON3-{AQ2(`cJnr%<;b -zhCa7Nn$@o4YUH$zh>oOGh3M#S4ho0TK`rVhX@yt?4{mG&55*h{%Y?@c?gpmh!u=CY -z@%Uea_*wC`^&5{g9Zd3Y`n>gk9hq4g0ofcJhp;DB=~XpY0V|Fxq -z6acWzf-nj*7VX$BaB?G_2@?$YX3q64$36hS?*lQA-iM98-?rLNwrZKb4(cr#+J-(z -zddOhVeqykqA!HzV+)& -zGvG6%0g!pcxxvtrT{bt8Ho-+a5(GYY?|z?eEQ*B)thViFmEANJ#(u;=lSF}`r@U;` -z?M!!{0YpA%lnA5G5Mt!kN8REAA+xM>hXCj@7a;c1xqK$0Zo41&>16lR8f<$#*mF`7 -zA^i`%6kZS--IwY*00EUJyn>^@VOF7#Df^femi|`1wruVX$Osxl`9tNaU9GB~Oc2)i -zh_w+DKda-pVJO}NKKmYnDlG+caGv>r!4tVH)5J*+c0Twx|MWctVwY&7kb)d8Cffxu -zz@hNV`sg>#zXPsooz<(0yMfWn -zgf7!J@oOy3`~v~ZEz&|4$v2ibm7NZN+rt=4W+aMRITGBEEE8KFpa<;)%$?1IPv|&- -zs)opey23AnT)UTeZB0+O5Ur5v>dUG)E<#OuB(@xgy6@p$x4Uzwq9 -zgnuk?XZC+A@oc%jmUt+lrrBvrLSIMGG8dT;Di4Pb6)p4K(ZVrH=FndNkD#A!_}_icv!uU3SK+wxWPkq -z>C9hCTsX$;+Y(p&w!|GKoJ#(+#9zKGaj7@|KUIRMsxPF9>lHe6=5KA)p0iB`!odvhST)r -z#seyQMk-)7H_4j;R1|qX3oxJMc2B_{ -zwy%(}Fp$CRs^7D_1Go&ZF&~K^6TiH#t+b*1HaQ<3l}gURT*vXLwi}hIAYxKsi0kY( -zS@MX*h)Wle?IfuDJnUlmRR+p -zrkJS0pC61jD;jR7n*b&IT#<~-4g_=4y_*|B&K2U1*s` -z-2af`MSC+kf9`|+lH&Zn7Jo9G-r#hHss^fZC8#}(FPQfy;8U7I?q8m!vIc;4_^D?5 -z^%PV#{1&P3N4KZixW9P7dHT@oD1V-@SD28#?DC2dS&gvBx$)fCN2s{S*GrgTf`n{! -zT&_}ymKsAhOvMeF#hwklztY>`w7y)m9Xt?}c!&Tp#gLE7hFGv%`hD=y+iDNsQ?5Yn$LLWcpfY@lIpMj9w?wvjElm8Kk$-B{H2vAIX~NK@s4j-SQ0ynAns;>hU;7<gdqHAVj{spb9pk4-<8Q+pXhW^++zFg#MsBj{uCBL|Nr~ -zXpCE?D|7qLnl>uWU@w6F*+v6HJ^J-^e?(#*a_n{ksa^0>h1HUB0AJioRiFzCaS|*Z -zctdq6-W)}vC`}~wM-!tw66y0A7os++S|aZEfCvF7+ecKIQk6Io`(G?KJO8WP*)VjA3uyPo8Ok6ho`BBYJ}A -zBrBSEhJF5L(qAS6wSvZo9d-jnNF&3F3pDoF6t6cidq5}PcQ%?+4c=3v8QeYyKqtY8 -z#~$b9+(Gg`HyaW~5q#3?4lj3CS~I=S6JL(SQvvE?-S&$aokODcrRGUeY1V{AUe%?p -z6;#*v8(C4N3@@TX{MVXAlhPc3TOiNyU+74^9sSN=zz+lu##b8-`Aa0fVR1z1lmCIm -zJACio7_83o8Wu5%pE}_U=K+gXV{>bxUGZzi;R<(fJJP8z#?1L+sGaA#LAg}n#tv@8 -z_x(+kcvEzL;Tb?`U~`ye+ir!-nQ;x6qYa1sps8p$ULNK+PQ#5{h7MJ>07GL?A -z`2Ur~kN(Qywtr=Da>9RPae^Pfr0D-Gi&y-QEIzb{x%9uXIG)!3k;Ng$d&~Zn#hnbS -z_7Wp0MaW9ke;4BVFH5>S?c=X@!0mW4s!E;=_(634Si4EQ#I#Ve9Qs3V%zY0cNkP3~Adm -zkDvlf500Xx7>WEn7l*-RkTbOG++Z~kQy%VoYDy$4mqUc%_ZS>BV0Dykxlt}8)(fJe -z9D#pix@wz}0gY1t*tVGIUtHW)edQY$cbHKRs(kzTAu$GkW!o=?C%e^v?hFQVc^6)W -z6>lXCU56)KT>i)nM;9|BX<|{a?!{{)b)Q*xYAqP!aRw(5Y1@+|+bOv|pA1i(Me47s*Cc5!2|`a8db~6Kr&YXFOD6%Ul;r -z9{w_arWl@y6C{bd0WF&6sg~y9){`B|e6^)|Xyd+GgEyTfd;>-*08&{AQ&JN=f)Tdexcey`;O_L#Z}XXll0 -z60(on&`f+VtxtKR=l?*1%PM|P=5=HKG4j~yoo>L!biFkt=gcI;6e(_xH6RD;z~q^C -ziiHQ@!;RJNPpW6Wolq*yNNj40!xxYfTpO|kubW+M=};uq6j)9PWhycv#RrN1>3y3L -z>mc}4F_#1;gD%x(O -zy9l*@a)9LA%SvVVi10B{^&8Qn^tHF~n|{b?~2F4r_talsPm2EbNCIc$#cH -zZoOj7%j4G3-e`IIxa004G5L$FZqCA0ncygAH=tFS+p1IS?2ZAb;)Um=DrfGXg;4Wb=>{y(b!>_#Y*-UaB-@$!>X~FgC -z6aaRtPSJwFdXr*?{DpTm1K2;u^|PxJG3g+OZ_EQUPshTA#8!ZOg*UWj>7<@jPC&wO -zQO(>6@T#HlXg|eT^VDOWGUEA{&vf$=V^I9rd|{vh`*kiM+YNBi)JW0|Ft$9E*4y{v -z!3Bff-+nq{#$a+D&U2>b -zfp*Ucv^p9@(Bv;EvP?3GR6CN#D|;)7u#NCn8Rz?NW!!xFUuC?Uos?mPzB}`h?`iFN -zb%UTmtV35CxqDa7s!EbExlyt$z^B@_dn|@bCZrLE$9P-4TbJ*Sw|f!`6Ly@j7POs+ -zA^DFTQ1rwp-G03*IVmA*erf6sP$Gl`Po?|h&B}r+U5l=v!GAO3H2*g<9_dH1W=mo) -zcFkLW8vg@8M~+*6XUUjoP8u$^XGCE21|6r%lsc>&{aEm4TbnlMHwO;*GyPC;{3MLU -z;-Ku3BjMvKpdGf{rJV2A=X-qsGy-PSOt7{d)42zyl*3j#ZZ>ceIaMZX}!?M<1GNvcJ@e#&RlY2u`@#t9@ -zgtN=BvEvp4F`y=?Ob!~GPODTWtiw27UR*l0xX&<9OCJ6@-IgR@<#-nZ|GKP5ILC%Bn&rYyEjS+Y -z+DVQp>KtXB<0)JOvj=9jTF0>nP?!B-9f$c!{95N$MRLK@4D{#JbC;guMFzC`9o^}A -zy*UzH?Y+}U_lqEM+OS2`m1G*crac8L4U2k6>dUO|gYrc4a~5|&IHAftnCI$4`Y2{D -z1;DR2CX9(>a8+xNEs~7J2hq^)jw8R-UbFm+lO=mb238N90 -z;N{Q0CO(;P(0To}!`Ce~;6lWjrlQy6qpZ6JBwCGg(S!sfKn%?c$0%SZtc5$qt1LDL -zk-Q4;R=5Blw|b47iC*#ydwG}r5aE=Ig^C%1aO^H -zA(leTJ;cnv*7#L`Lcf_y8Ra|UD7V}$jUah|Jmj}E4!MHz*BY-ba0Z@d@HGc>`D=|w -zWv_l)6&1Ucyb%S~=YLuQe{WW;Tal4mrSo7LPlh -z+b0&b=Rbt@X3|sjDG=cD>dRfY%W3vxB{VUHyd}fs{iGpl1$kQ#m!!owvB2m -zC&ejL*Hqms*ojM4R#|XncB+q;FC8Yn%x#2Z8fZ1Y{(K~Nqk7h2UmE`k9Xtoi3I?x0 -zg2&VY#?c(}ka8kOg~lI9*Hv^zz|kYcbuHEtltkxh6RQi)B}tgab%wQs&zl58cFLZTPmvn=kIx -zq|wQT;I^PB;}u5Id>M$d;_3CZ?-^qusT|>bJv`H^NSTetEBnyE5)wJiKf)SkW~FZ0 -z8oXphiE{BdwQI9fv8zCkX<{11jc7b8+AdCVTF@(6I$(T$t|Z5ioWc}^$%?2miBgK` -z@w+9#6H6f~N>ItxKU5&A{*b|i%P5pA7d958@NpeB5-VghmckXuvxq_0jAk@DCh^73 -z){g2IU*2T$F8^}+1IzH!4knXz39O4?4LCHLDn#y`cPb$iYsjvx)J#a6kRHBztGG(f -zm7Ca?@o`(o6mz+a2^O@xGtMsidf#XQcCxq#eC7;d5FHIL8a`#wScPoR;~E2ZgLyaG -znaSPA>qeG05n9h(vTsx7)~U{XJ$S}*wv*?K#pUTdovXD{!$(!!=ChB3z{};q5z?LF -zds}a1m|&P&0D_Y&@lG!sh!$PMtm}@-K&2wKoSIME2$O>*6?}|~|2#B~3Hj9o<3Jd9 -z1*jhwu}&fL4?&+5N2VwE0#iPPLw?q_XERW>g|lFdEV|eOWQq0yO}0fi&(@N<%&o@s -z!3r)WvFkTBuH4)Cjg7BH{ez84kAXcU_p} -zAQ1<5!Ej&)nf0C$n!;OXU?8Lf_er##o(zcC#KoMK -zp|6Ow|7zoM2`b;(c$qQ172bQ&w>CbJ@&3Mu@K+lrdKux8(J!J^qFW|vW0<54ab455 -zVl?>H#&KgY|CFgnL}lkvj$D;sfnQ5-D6$KlSo)-jv=gq%u)lJAHSU3wvR{*&2=-d! -zTo*HSx5Ux*SCcnDzC&Q9xTbIxpuxF&N?o~ -z*56h?_M?)jdLCr*>g6T@py$E|IA>))oU4Khkc&4})-R-hU>zk_z(-`4B348=+OpRH -z;M)k%F3(SZ;D`A!pblgB(FDv!{0@E>Y!poS|P*m`xbmhA<0_nm_S2x&$c -zKU*0DR+m>70&Mhz!8MXe=lAP=m+XSAcH51GJ>xoS11(rBPvw!idVc7Lk!wZ -zPQQixxQF8N5E^2?!p>L|Q``sKD*Dwi*;o}fxBz&gs0I)baIs1`K~)r1g3Z_S`$w{F -zl=pafM!4-JgjtU~#ccj4^{>YUgI_`-_7OiO+CgkG!G@NkM3C`&mjGE?$uXjkGR3p| -z!u8b&@(!~JIyLw4-K`EmsJK$1<-8SG^rxyIi@cx~Bh -zIFm6#HK;hNLkyY6%9k$5Y3cfm*DU#hZ|hBk>nCO?CJJd3Bv6#EYggO)uQmiyS)@Y4 -z4Y&VLhQZDH)VwP>GnDU$2%9zO%U;}1xQ2B>!QhqcBjq$Tz>9Wj`UdK;tzocd7{51v -zY5u11yc{vZW!aa11%~f;T#cW&!n8^$sL75vCOGQ}^~A54w~XmHEN9djfLQA`;aRCU -zL|#GZ-=0tjNDDQ5&xAix7jl!8PMKQd<%ALYM;sS2t2+Q1cKJsfk9r84JZsk}%={L| -zi(2d^f96Plic^DLW6SXB`thimo{YdFluL^@PxiC`;#QW0r#=?qbSNakz7XJf!eSs( -z0P=vJbAfy2X@S$NwS6yPvtoySxc_fkDR-Q@-Og$q_D>4Cz|xJJyuxpGV^{RKkQo -zT`G>?136!zFf6@#I4VMgsE8d`PxOaEorxjkR&nZiECz`yKu*lSDlQ$Y19^>*k*ia+q+$p~Nd!9WV8DCm4p^o@9Ee-(DX}aW3YJWJ{CL4_ -z66?OHxrNM*L}0@|?N?}#E6qsnM{utPNY0{5G@@0Bufumg!jA=MP-B-ophnx4)!=uY -z%WL8&HjVXm2_p+TI+#U4h+hZa2|=K0rnUm!->a9L-%NB^#Y441a4UWA1zA+IoH;s| -z3#qK(Hr2=qiw}Q+ndm%ly3r~z#!z=~Nw&vZ&CQj`IKot57))Zl{oos=rQXp!KpNX^ -zmo4rBt+W=$g|Q37*;vZ;t{OzZ?iNjD6PmS^QcW0a$swCV`;~f`GnAnZ17ijgl6?lW -zz`w6x>;vHv7~7c#v4%Aw7o|Ko;Aw(!i$7!JS7mZ94}r9%wy=Xr8LEjomuM{4DXO^#!wXw<1lh#=m6F8cw+$hVf&wtS_P -zh4<;-#@^2uCIEyYcR4mJ{cer8AdxNFl1Y36o_{UhUeg9^2XseJWW%fxot1zs*8Q2c -z%2b5?YUn$eBI{-c(_>#?SDNAxR!{Ru1YKh8lj(_N90>7^s4+89L=Y!e0RekB^IHI) -zVbta%i`xSZ-v9*YiVO=}`gkN8kdh5UA`|;_!29yEHEaBy8KC9vdQmb#vuAeiyi;4gT;U%;`C4AgYe53K^f} -z6opRlvp$09A_!F3)a>)E!NMFfM`XQo4zg?E$nq~o+q~OjtRi8bXB-LgUT%4hFkop_ -zQM{8L-=wfdF8O(mf;fsI?IJtN!EiL8e@cN_KdY@$VY -z9f}`nM@s -zh1P@4T<8c1zAxPkRxB?@b0RBAi1qosm-_(2!wpI&M;F( -zBAK}=ear!qyeg+W4*!sI6ouHYdJRX{`?yC-P*t7WWZ41Wg~0TE8)8#U?JmR727)L| -z>DUU3Ve>Yj5M;*wsr3Y83F%?#5kf9?qR;GOzv`k*cKFfy_$IrD@Wv}1m2O}*1|?uI -zPkXb@xPdYnMK};b%a4(`dig}2A1Zo^02zNUJ@U0#>_mfTH7xh^PYPIDXk$bkCQWOK -z9=PF#u>bVHamQ6w1!I7uV+HJxf(cUqWwX)b!~lO0kD!}`*$(2Q{Q&I^#)22$$DB4! -zub68wp@(-)UA_jQ%R*$RcB1^1;*DF&!hycBNxcmPWyWLT-*vifU##Az?EFoRPYwi4 -zmtQP$qZ370Y=E<-TSOcvjKuJe^fbA(21S#MPE?|&*#r0!JCmb#CAU@!k(l4>H>V1V -zhQL2>Cs-0QiAFA9MsT9r@gK9PU9m*6xNNfka&wChG94jC*iGqgIn4{)rS=$SkCzk= -zXISwpw2JsCE4ku(4)E21P98EIuA8W)E#lSY@Kt)Y;-$iVg+FKmZn-N1mIX -z4e3L=T{k-J+r}FLRaX-O(m^dsq9(2d!&nlgW-~1p!UJcs4%_m`8Cfc^G&%lsCTV>a -zC!y1ctLS&Ch;i(tf0F>cQpPf^<1Z3=u_4jKz$Muj;3J5~A4)QCG2$xy`J?VY(stP9 -z@axa@+9Q$K=fEE8^zVPk@vwi%aiFlczHf5e{$FyOtUQzb_m(#0nKX^W`hXGZ|5vyDv%!~Ev>1; -zkY|+5v*c*d@$w7ai*PfPP&&|xW#z+!2|ZW2#aNj$yt+pjR~4@VtUc-J4xf2LML% -z|A!pMy~Fm~Bb*$PhWaMQWvNzqd_JQ;jl)@>WDa2tJDz&6RV2tU>l)Itv&>=oqxw7}C#5X?*(4od_i6$6onT+Nb>C@8&Ev?*d0YMJEP7l`HB! -z2iy!_9y_nU%#Jg0hxaxyOq(MfN`{n2Lwa-S%U~;Z(+0`OWFm4+KGI8o&qu(dat>9w -z3^q+Rz%@x0`VspkBqg^?U9Ra7EJfRT_{{jnxLgI}hygTL)G_G0%OaE^dA-oY(iw2` -zU>^Z=yO9TC_2E~f>zFh{@uYhKhmf_#zL$%AirPE;nFhLfTY304 -zCwsyY07tc(7w}_1%x)fmC9GW4dChWq**fBBdZ58eCges@Jt@!1$s~Z7Yh*?sd`I@W -z#l4Q?!^WCpfHX(5Q(;+iD}uo2g1UJVd)ZFv=Lshd7Q|*JL$nfFQGV&1O3hpv?x_<%p7?Gm7E>DTFcT#rxQGl=1PpW^vbKvyIj>A?VMJ -zogpW3Y(XH7u&{U`8GawSHj9-ItKbN?`keGZ_ -zJEa+`Ky~D5pN(Izh5PLFUS@DQp%^Pm2Y#zhPf|-I|(|FwTVe@QWlao9WjM(?=>-G}YMWd!K4+W!^4jB6-1#Pl| -ztgn`4P>#tTTR>~d6vo2LI1lz2s2F}m^Z7b4u+#6OWz+>zPf;&08*Csr -zqNNbG6ssr4{zV8ek?J6O?0cJybvS -zMX@_1&&%t1$kozn;lY%`rz8V9iY^98&d#TyrJL7fTsQbkq6nENs@e_9^-P{((>v_} -z!q|Ln95*de`6d5o01oPiRsbuXc9Zi5EwvGxm+_wJ$1}f}b_f!VyID9406AH*-a#?6 -zX~sW6ww6ML!qq(T%EP4^S3Is@E=9t2pG5B;x~#+UR}qgp`snAEd*Bf6~$;Ta1rJk%Qax$D~51;6<_B_VeQ;RBbp;VO>J1tsN0NZ~FwfG8{ -ze%<482a^u=9MML@&2$yJrV$gAV0T+~A^8r$t*zXVeTjI1{^1rQDC&^y=7tAQdhQ;h -zKIxi^1dW>%qBwQZ+#j!OX(K`GfhV27Z;m9Mn2^f6V@INU#xkkoHXA&cF;~4gP|vBV -z>JG^f>fx`tN6zp%M|}I;7&TMCW)CUAHMxzS{-)c;dOC(bXvvD|%{b`;I`b~=zVZ+6 -za#xWUE2VDiHeN?_A>5BA2G6rS?wDzU&HiEI9Q*)2g*f=?!QmbxT5`hR&_;C8z@Jk7;krSv~N@KA!&N;L#BPrtj4)KjQL~z0M+J6M5ZJ`y&tl -z5q8s{+6+vd@h0o=y?5*}J!QqH*BA+q2jAb!pa^)L99-oD_&0}cT-;)c@0DU^8o4cI -zow%D-nvF9oRrS)f!?jvL%)s4knLGEs9;5qK#5TU8b6M2LzK&( -z8Pk(D4w(3#RqQ_v_U2_W-*pXynFe_rRYj_3qizgATD9US9LM^f@Xa=+iyUt~|sk|L=`%pIu0r{8AaF` -z&ehrTu7q;7SeTj_{r9*;o2UFmAz>`fap<7d+u+p(7dJPR#o4hR!eE#(FE3?ac==P&E_$h12w#v4PZ_xJ4>ErR~`r>Y`sY|zWYwP>%`tauLFA`z5jIf9JPO6+G1ORXZ@w(P3$eO2}tne7BD{Cdw%s+*bN}V36Gds^G%LpCw-IS -z&`|%7;}=N(A;+hV|3i*X{3XZ3{+k>xaQP28zV$CT&ihS{(^~#7IZpK-a=fyfTk~IX -zyvP6lkmDm*i;{_#d9}|7th^5fTclWc8K2Cv?NNWpai<^1ShaD>IQi82KF$GUE>{w_ -zYRf(^<1Y?#=|(e>eZB+Xs_+n&zWxGz{l6ZM>xVY#1jUPjz&uLfN6ZCMMCrGSu}N>4 -z03yj)>_9!7N71NH_`9H2uzg$*sxd%r} -zq_tFu0cqEI>Fnn_h%w{V*8Jisj>`v+uQ8e+SC&X*YrIsp8p&Lu)@Ga5pdIKTCIw>% -zkImiMM}E2>)UOg})eFKi!Wsp-ypbE%SEBwJiy$+Xf4|iLQ;auw5hWX^c07z`#zU`- -z4MaDqfqs=a?0AbBxn?*7Sv9V3P=If5uR5c*aWPtE2IM|K2bQezNwoCJ-TPT=@5>1$ -zS=(uw(+#w8>6Lnx?UlGmt15gP*1+TwtNcxlACvzNIli3rmmJ>*KiLUQLVzQRj%{vT -zX10Dx{7a5c{f8VU)eRC(OIy51B8ICKxdU0Ajt*X^#mTVsEubGatH`;VQ#n -zXf-VmvofXOTV?xgczoYO0$tW|D$42Dfi|Jd$6qV70rp0{<~sF@>* -z2vN6`O-y?S#t4p>l$$VMih7%yy>W@9*+>RT+QmnOZ5fq40s!{!*f4C-L`IsZYojwL -zrZD|d{Jc!i&3?L;Ywbp#2En*uR?tV-~DF>v39C1psi?2+w_Mm^wGvN!hRJ`D($Wt*HQKT-r0M^ -zOOAMEiEz|TCM}9bhy6V%gpEobH_scz>2fO~b))3ub#ajPK$G^&yjaM`E0?ZkubsFD -z#v%E6=gsp)(=xQSMl4_C*iF3f`dG5y;{Am>ZuZ)RcQ;h56PtsjQH|5NvFGqjj&JdP -zSF;bxZ*n~2iwBH~FV=XlNI`wPc_3mJ<)IVM+udnTEQRETrnUK~6$mWA4=wqD>x&R= -z^wJc**LsLo2&kFv=AxgnvkPkHBAHUcQhkE -z_&6)7HcwF(TByT~y8sXRG#@k%_K-LtnjT}ccX))bwt#pEJd_(#= -zg4hmp{+59t;~X)&QSp1h7Jp#_RE&`RNv+T?1zv0E%K*ABOt#!Hrpn8-5>qyz{H?Cu -z47I-VmpjI_uFH$QRl1Ij&S>4QvR@9}2Rhh?wqI*>;C?&-0^frU)mM8`Ku=E9UoTr< -zEL^wpN>5&x7y0YaJ#&{0I$Nx36|{>}aZg{JY>%Z2NHs*d+m0+uPP$(+Q?FY{wK%7T -z$(4&8(MO;hgxX((?&EacoX)TFYIjWadnQ>06sI -z0jzt$h?xsO92NOCLZXukq7Tnj?G@=s@5DZ9rnWmKWhOmKb(`m_qJ0>XQ`dts?d@No -znG&uEdz5Y|Su173JgXhQ;jsNJN5=AOg@p{Fitokt;uFCQhmP>Wxx`ebtQRb9dCF~`^@qeUN>|v+fJ3|A5_3b!4WS5O;sL)M;lGv -zO&9}H>(7peV~z(JO=8+lKB}_Vy#m|3$%1I}lWF?9k`#&PKd+$nD%cLHoT*|XDf9w$Cd|cq`gHj4%aKn3I -z|38%719M>ezV7?j>e%Yowr$()*h$B>ZQHhO+qTiMPUf6z@3rnZ``laiMU47ajrx5* -z&+}#~-etH%D`yVmn7go8!ND#M>B_{m^QNs1YIoZEr&kFb$V&q);F1RemB<4+s#U26 -zG4kX?$apt5VfUF@a?vvYRbT)CgGJEAL4wI6&P=;nhgywbVTE-fqnU+jNT_(u^SP+Y -z=u%_MEC;Kpdd>a^`qDTZ+P*?BD0rv -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 -z8U}RNnMi(pfdD^l -zGUwJ9=d53F7P2Zc-UD{0R|BvANw@tn>X+;Jo$anED}&QsR}hw$ucZzTMvAhCH*WS_ -zoln;pLTA>m?rAnQG393g-Wb1=gB$LWoVGZO=v{Tf6?qY@K3~&?x{VRAfMj^cGmRuq -zuPTCaacx@}C?56gP#xGlLZ6}_kTw$`Sh;li8&c+`FO!Q-K{J6E;pp!J_iu6hqRTH_ -zQ*4hQxRdlLi6qBCbL8`iT-}9Gv?4v@FvFncTO0>dGH|$#YIWt -zS=UnRrq=u~alCBzzr}HKD*9wK8UiF+g$E>P_o@WKHf>jUy|Ud3YcuK_c?s0U+c@ZY -zbR@Wfp4TqU{>R6kKBxSTKB0N69vaCU_wIIH$n+umiA>OgJOW2Wn*KkB^SR{UR5Y{3 -zYL|nkM%%S2N4XwC`pAp4^kWwa}U{2JtKr@SKY7)xB`(+6;ZS_Dz!NPDJv2 -zytRiP3etQ0;XBsVP^Cy-3+0mY`yAX3NK;%d4ELZ84j$mP>np2IOP##+S#WggXaAs@wW24v-?B3H+TJ&cPl;V -zhfxVR7U1XO<@(nL@hR+*K_$tK_wDe82^#f|D_Y7z^hG%$&jKTAyf3A@!o>Se>TK^p= -zIZxb2km_;TE*13KTWV(EeC5eCw0`?z3O&Zl0T^vhkRtd|=^e`x#s$S~4~(9Uq_cmM -zpWk?Oy+<(*`bHR`I6Cc_$ZIn(hgf=CuM0r=>Ah>Wg`9FiNZYh-vJB9PHe$%Hnin~m -z3p^4nAXNZz7z@hYFn)>$bk1u0Bw&}Ysr6`+Y`zs)h1bkBn -z#jEVkcPd*T6=tJT3*kr-z7`mN(a~zE80?unMo0ihF0+ku_&XPE+Y2RRE$Z207ToUR -zZZ3!gXkNRC7;&w6KY*hjH!shI0V2b~;n-yIQQR{gm5OTh4jp?Pn0iJ{tadt@$M}t& -zA5k#xzP$>zHkK)xbyWMa^8JAI3g0^vt%|(7YOt&t@x=+}3y!M5^@L8%#adm0bI_TT -zj6-3Ak{n1uk<9E1nJ*6E-u^s%o0Zb?B?bc^3lzELs?e_fY;Ol -zzRWKAjIXhOi(1xvfMPpfF#HU*j+N3qbq>$&8xm66Uo=<($JkAE^nntF5hQ!1W -zz4MCjWXFNZ@8#V#h#@ltlXPzw18>kl+Eb<7-GZB8zU*oUZSvdka!U^Z+RodE7%+&F9xf{(^s$>{w5#*K6T!;QZkc2tiV -zBs52p>6+m?zA0n;R5d^-GdZv<%%Zs=o#Ku01&DW{#{2~V`vZIoa?477w^JW7UjL#0|LsjTRiRMBQ$nu~G+9*zOc6C|ZG(qz%{Du-Tp5YQN*X!iCRZ@=9ynN=X -z0_xm>x_gv*ewaQy930caj>QeKt-ruQ89!7jq&&R8_4_W`Ylrh@%KZfBN;`D84<=y~ -zxYFQ%apShAqjCS@#_`qviyKepYT#&b}! -zA6#>BocJEc%DVBREa6Er|KY}~CmYXKdz}iosKO5H40gcqy0m&ATK!WuIge6}z6;gP -zX9Y6*gGRwzvRoMFF#B(AeE0=sCo*P-Aj+aao513P!kdKhU)*@5PAL{R*eSo&Q$Y7E$OZ^$M$ZhMPVJGq;n@uNkD4TAB -zYK`k!>NUhig@n$G%B~P8AUOD46GFCeUdFf%?HQDJ@-QeVoJt%&@^_Uav1wm)N_yLc -zc(tahsNuob1=nbJDJ0Urxp8}o90A%FlX-{-CVay`y;AEei?hNC^wfkbb&MI=yu_xV -zot9=lZsaovB%QvwaSn@9IVla67Ixj2C7-w?)KzM}`gxgpX8|eSb713vvkfSbRjWmA -z_sF0ay~*cy=HKp7M&2Kv(Q_W=dQWe>O7u-5#!4GinU|0?zShGKladwMj^=bQo^`$T -z?9rzjhTj9~oHs`yNQ`u>5tuAskSRrhf^V5B`+qXcI<&+Q+*0ZPB!2N#aR4=6fA+ca -z2$kGuYcE-L>JEB_$r@OR;7v;;CGmSWYJCXy55J44&hxC7aw08>{QGvTH8Acaq)S0$I`@yf*up9E@Pf1msET{*s#Wv;0TWym2-Z~a`5nC|{QdEZ -z!%s35UX!6`Onegk{(9v!9U~ZQM$a| -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 -zFC*^MMIZX)fVK@^z8U>k1()JtO(Cs?LEq -zEVhw7qo5#HrV`_+m5=AI44bT>IOPy({b5}YNx9|9`M=XxSH1QuZ>n4Wp}eo{PoYT7a7Z|wuor3Y -z+vAzc{lKVT4&`%KNeG&)JL@GFEHK*L27LKo0B-5+qlSFE_Y2;$H1@{ab8sw^^FNs| -z5-INvL^&u&;}*xt{0(QY;*xijuY-?;-hCjM9bR55bUJuA&tbBV>*CC;UGt#%sY@C; -zq;UZ}_z-Ja^{AtO#D6R{<;!U8W%f>xr&-MbfP@9Trk?uir)@5W9 -z#p)c#k2#WH>O_wL$f6@c4`jS8GDFzwNd5sMr!Q3#rw6T0t`a)3W}(ez+XP(rg#h9N -zmDsx8&g|^TyYI;0jGMo)$KbM=PT}0-uMVh#aWeLkG3wL}K%%Qjg5np59H=<%&c@&) -z+d|dis(0RYN83i~JVa;+!`${LjVO{WYao9u-kXw&u3Z0W+u;-A%8vB?0}FpZpT^)% -zWpM@~))R!&?C%oO0nG0@z<{qEghI0ZQ)tLvyM -zX+i!CF}Vy9co8)yhHt&wNcX?>ZclX0zxD2DkKhIY4}m -zWM8;Ubmrd2ULcv$;KJ;TE12GnYumEaY%&+fJ$#zGeZcx%#FgFO56g&s+JHF1b)5dh -z&PY9gRGeoms5^~3J-zPrOxZHPSfMGAn7Wf27s2g*#}Bp~MgVAnMk0N6cU8h$T)~kz -za9O2gr62vp2YO%=(m@(FRiWx3PXTz{pY%Bm^>BsWf6Q$loUwp7t9}twCyIsvlgn<+ -zArHc8-tK@e?m9&S{ONU$4HiK3PHDgg)N#}KYUIMl3)x+Aj^V_xKuv(B^ -z$&C2tz-q81cJ`g&NIxcW9FQhXtJKGJVc_cxI~!;(!qFsU6;o*s=T{(qBT(3)KfSM? -znYx$a0o2{-iW)_o;z1=lnJx*ZV51Qroq4K^^-ns!3uP-B?LN#loMUvL3U?PIpRSsw -z*+#&=@5a^<{n(U)5>*RK_V>y`=nrf-I(?a##5eK?Tu1ilD2`&ceZgOf_^Xw%d#G|i -zZ9$$B{MgtEjLsN>hPB|+zh}fMihyE_0g?JmtXR0Oqqx7nS -z$ejCfg>$|9k^pDT#r2hlb0N`~Q>dFO1fhIR{MhWaU|}mz=)H{~n4)NSyO`Qk4hjwE -z>I%;FN`1~r_l}!@Ay+uz3(9UU;+lS9S)S?ip?u=G*fEI1a-*5}pTzQ2YCLu>h;k^v -z<_p+PYirVkq3pYP#Ez~%Mewg@)E5|^w!|~IOex5JS55+NrM8ro{v73Wt_s&VZ=C8U -z8(OMn^th=)yM7BbPCkLq<8NoQwHWb*x@ULtHGl@j%qj4w;gcm!%_x={i1j7RZ+uyH -z!6y~oX+mXk%-zmfhM|Feb`H31Z9X2}Ks7oQCC!5t6wv5Y-Z5TCBa_yfH%@tB^rYg{ -z6H>3l3OXjap3m^Cw{I&r$3&#W2fa_8R3G57(Fi7m$(Y^h`|}4jFUGRqSN#iMKjky -zQ8zdo&>8C{47uF^!dVUEdbTOZak -zcepFVyAUz-i1RiO3X*w7z)v05a -zRD_`?yE+i254!4%u!j^ppQR9(<^vLY7#VS^-Eq -zYhSM5M#YomLlfg8{_OSNW#fLCt}~nr@x8ceWtl1^64a_oQ4v>?qB}23+k8cHg#j$c -zy%QU{K^Lg&z)RBL3^*<`cM0)nC*vz{$FX2KreeG8@oY1~VZFSy?&tnR9Wp7CQV2g7 -zp6pF&?UR<+8C=5NTPr)CJD*|P6j@_qKtnnk7BH9uK^9#0`?hjfPZlVhoN|?!lSv?3 -zMl(k$3>wvaGpvs-B#(wg&HI(7+;OS@#dQYW(Q`xC;EBvm4YQ5$UZoJZrt(Av8c&y- -z{cN*%C*`3|`$rDtgvPzfN0p}NCjIK137T@&Qn{8bO(shc5JjxYtKth!URnmdKlQ+x -zB12F=hGa}k6HJ(XhaPKq^l;B<$=*OlOzgbY=F_a7m6J>bzH-Ge8b<_K`-n+}eP8K_ -zfru&CuS$k-3}*i21N%W`b=%&?Wfl8HEe`YnGr6J(ncRFAr2W>IN6ZMP9Qxl_fk;P& -z>PY1c+5`S`00@J03`1Z>`Ctw?Udfhi(oq)hz3$mkg5Z!DLujrdIAlWu)r?(dPpj*p -zro=J(kPnZ+YH_sLy@|gAtARaV0*Q-Po+|t=W;$8N8#Cc2&LsS$rH2?zU1=(UQbE`9 -z+?)wvZjcGEqE}^H3GuW3fwnr7_~3qv|_s@pH0 -z?#MCMnqlC>Z`Bw7%F!y@XXa}g8^;>}&UNdxOq18M_8BLI&B7|UisPT#Ui*E^ek7#P -zUG@~KM6Ci(@uTwl$VeI_ljM=t#Fwuv~6C}T{ei6`*tDaIv)L_ChyCUC7oY`!)u=ZGPbGVD;fVWwo%*8#z!y*S~O)XcxZG`A%azT -z5Oylh1d-j%r^RVSuQKa)w@?fws0|kR -zAr;2EQh@Ry@1_CfFkf!TdV};8F%}oG+sJuujAcQB9 -zwl-9?9{WD1k=r>&adh2oyIijPkG{4OVE{o+uh*&}oUa|Kx)fFHisZuDUcgwSG6;l& -zqq-Zf&vKMPrM-V$|HkdqZ(G5JwLp&=gBH2cc?x|SKDbMKI5c(lsatacU&LA`5Q}Oj -zBjiPO0;KFrV$)sN7>TZaGQpo{46u=iB`5KVHNcTV^!l650*gd=S6vi2pNvCT5+1wa -zR}PE;q#{lnxSP!L-8r5VyYMA)+5vlyI9*Y?;kv@{YL8=U-vARHogMt?{dG2PICz_n -zUO{MYURfJ(uPR?cZ}xB3Pj@)CAleG}2A9M|wIEK2%zR;*5j~6+xJ{4W<`)E7;7_nHl3=)`7lr!5P72f@Wj7mIA`6C-;^r;-ak$m`i%P)0w8!T{6_iLg{aMcc&AR|Sm&KK`g -z&yY2q^pnapHyUnGLB^Y6l_mp;9c!;AqvNwns@I9aSDqpNhJ7{KNF@|rh?IyhMQ|aE -zfsu4Lf23tzt5e{iVHLrTL@L`0xk=a0U5U?550*xa6oX<6I)e=3lhklJ6b%@-@5tYM -z2;1CrVR8!?Fr#Dv6B(GKW>e1V>!oZW^2)(2%p8LPIN7x(o~a5ltwVZ<`4kJ6EkRDr -zPXbBm#8@?jL~^g#9HB|SfPnwnF^K6)?-ULn~#G9AM27NDKdK5FvuAZEiO&efWjb7W-U!@E9@w -zJj@U;_;rPAVSyf3xD-R4r?;4=#d+#1-0T|WfCujj_XK}}err(x0wAK;^#2Ri{sHkH -zu(lj<>%fpFlTIaUZbyd&WhDMPOYxMfMxzf+d#-6pbQCg-t%Br>fKqDPPx#~IlCD~5 -zrGn^25Io~SX=@9?Tdt)~7qZSuC?`E)`}x%Xr+@ -zk!LO7Y|Aa=(9on(ZOMtN%8nWbaI)#*hlWk#O>N){=ef0;NN9Oh4}FKZET&V&qZsKB -zi_U1Jf@7QnR4PM@f%Aww;)y_B!C34JV}cjlM-Y2j!5{V0(wtLJ|19V3brjXnIGb2K -z@?PpVrOmjj5#lx7QpFFH4)=EQRx=Xxpklr+XT#Ge!&j7;U*hjQ&n4Z6z?^t;$|%7{ -z`P+tURWhzEqpF+jz8~Oea#qS;fZC5A6x>NQ9;{d_6Fj))&wG#3G=5$RhtjjC$ -z-Z9-T(H9kyH9duIb~~w-=m#(+E5S!zM#`v)+5a}R@BepGyQ{?DZ&UkGoR}ikTdF-4 -z#Ow3@rS(LSshF(DZdIQEVCgnIQHn|bC$GU3xzLRLy!mg5+Cb|WzEjzMliGE2Y^hgI -zXR;b#D=x)+_Jq}gC5(Eu*{WKbDMp4GDIa| -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 -zhlOetXJJO!IwJ}VuZ -z{vi5I#LUF+XZybF0UDP(%bZeS-DG!4{(6Dh_f`4n_l>#A-!A14QK0JP>}_)t1tL#c -zAlUHKn(g+W)tt*{5RHT}B5tK68dpHLrEIM+NlZnQ61e(lles-nllT%7+^to-x!1;G -zE#z@r1paQK{#3FYCOs>ze6Cz%K!1evlH};iHi>faI)4{0nfGyfCrT+wQcPw?y@=gJ -z|COZJc1g#Afk`gHJuMI^Ggf9Pf*_N4&>Ry6eX27B7lrVYCLP?``foj3h7IB8-+Fdm -zNq<+X(YKy00D}$sm!6G`44U<~o^5`U0EooN`NJ9LGNSxl`T{=XbMiXCi<8dghQ;H| -zJ_K=~1%bP%FW@S|kn7N6AS<#=8f^)z=jFjhI~1gx5K%GrYQWjv$g4C0Csa>%n?T26wbfxY?%MDv+dEZ>He{^%NxJ#?0Ufe!OlJi -z{kF4fdgRCp3dS70{;{)B!V?t^@psK^Jvx<`0HHl_^zy^wcFA@_K{zVVgHXoIqVhFY -zks3dxaKP*OM&W)RD_-+WG&TXg<|KiSI&J|?u0-fGSZk`spXEiLgv7yEy3Uze(b0@Z -z5qpXfiC3-Ww6KD*I67UH3&X_?n@knd`R_f0#XduTWP< -z`1KpEe{#6?^P=uEg;q~1qz--vCtPHi_Ex<}+!|TMNctGg|08!AOQvGV7>sEt49;>1 -zJ)7aYkuz+3+;;VGEjpL$661%JgX37SnWPc}5DG}eBh9V*Z(GAj#JoLp7fFdc{{~&E -zFC{>jbITvEt5)b@oSygEd(}V{!xXJTCsvn8G1aK1_$!mnU^^xlyjR^S`GZ;R3dy3d -z>6yIC;U{2f-1K)GYRT**XGL?h)EV`v -zrV74bl7Ims@$d?I(Tl}vL@7!nL|VrI8vyXj%7Zs<^HE!|V?pmR(VamsUI{)~@qaS& -z+vF>SPC3(E4|4$ymYr;`a&%a;gzi~6!SX(gG(N;9DyQ9S60a>dgEpNanUjj{07(Oe -zDEXt{CwY$YQ1d?kd|ag{kw+C%dK>MDpL5+Pp#t6>RwU;vuUSx(I`51`60zl_wJolI -z!=kzb$f?PbWqZ_fBj;t{IYladio48R?HtQ9l9&;%2s3h6$pZME0H~vDr+M+H_wAH2 -zRHjd*OuHc9i}1{LYm81JquWanEHG|)7c5(S%ZniEc -zd+8b@(_7N~piNIP|KjZ)*u&7;HetuMZQFL**tTukwrw?yZQE>YH)+t= -zNi*5qc($IIdFS|!?^mp~?seVg6&b+>m~HuC5JPiS?e2}Dy(Y*vS&}C5nk(W5T1yk5 -zJ427Uvy*NlUniDu+$$FQCJbsft-3tcVMvaY?u)TPD4t^oBSRizkAD?(5Kz2m@*|-c -zg2+=0k(Dh8Kc@mGztJ%!v+`NN9k3lXB@6A_*EFqkMBTk$4 -ziJ*8E-;b8vXH#PhG!@~F9nWZHD*_a(9t2%XgWoE -zGhCD(8Z+9w4_(RkOnlrmg -z7cMQldBM@DxNq^WH1#Z?=IYqS3u~Wj^Vc?B^RB@o+cEB*A>F~;mtuM(y6`+CPB3Wo -zz{0ZopTIy>K3c*8580T}eEzsRv6QTFaO?%8qT@ClJKm8(AvaEfey4wUMIW(nq#{1a -z4D9(CMta|=R`J7x0)scF_~Aend+2D9Sr(Yk@v`O`<_`MxTM;3Zr9srh=}v}qH~?M% -zWM4Ew>6-Rn7|Dw#S20I2$O4;lbdwE2GxkOdRZZ6EM7$^ouH~Y!_%U0Pg}P -z#mA|Api1{-7GP-*TT_4^GXj|tgq+r-=F(-G0ix7t^=IJWUvLrKxbr1`CpdMx+ -zCcDCBIR&Au$(zdg+PY6c;bfC8?)UJT9PLm>?_g@EP$7-CrdnGrzs}Lpq(t=|=N_~j -zbr!*9OY*?jCd5DE9Bk{er(jmV4OEU><8r+r_dIl17@F{<^JCzUfe -zl0h+8%-yb&7ipxbW)V&p$Kq2Wsd+w?Clys=kr+ES>y22e#r8a;cHQDo{})MX2kpF| -z@vsyz}W<#zrfjKe}S`=gMPxD!r20DzropHtAB>G -zF-CsE*+cC=;q376f56#B&va0WV^F`s*_63|g|m&Deu1;2-r;P(U*YT->%RG?(Z}yu -zSnqK5u)w2@vC=!7UHuMc*W(VSlsOl%{u$0*d55zfUf$vCxQJijY}#Mp?D?>|ok-a8}}!iy`178@;5je=xXc8xn%wi -zID30Km;&&z`hO2+Ut~HW{u`Wa&^6@sA2>T@pLphK* -za5k0AJDgnwfb<_YI}qF7kx=G+D&QDoK862JIQu}f1n&O{&fc~B8=QSu=X_v9q45*W -zcKHR)Harp}EqsTwk>BBL%3tAZ#r?m-+1pV+;q1bs-{5Sfhrh$w>u~!}|Aezy{sCu~ -z9x#5-{}s+2k}|$I)5w$`#PQ%z2XMQLdtoIKaG?1soXvV4<9P5da5ki^O!S}N?BaJg -zn?~{-&c0aq1I|X2dxx_d9RDkv-TMw_n;X8v*)=$3wvs@_6aa>bc+G>{AW13iSH+z -zjj-_toE`8hoW0;ZqGrG7_n-Y2IJ-EeU=x7+%b(zEIm_)C9x(I2z}dFH!`XQ6 -zaJFZ7qW6E`Yzeo2fwM`Ne}S`Ye}l6dE*YBt70w3LE{6LXoK5r&XNyGt24~BQ{u`W4 -zlEcek@>e+9?f(pCUy;4T*{Wkqg1^Ao5+7!`+6G(q58n0yOBsHuw>YpK$i$_MhSGvEpCgY_dP#YjoSA=gtLou|Nn)vP2S<`;h%7J&Y$6I%9?jL+bH=LIJ?S+ppVw! -zzrfiJe}=PJoqvO~M}CE~clrJu&Zf)%9nKbX{WF~X{zvjRINRk&hD}iPWcti=KCGa_Wlo?T{ZnzIQwSs -zC!B4*_)j?7;Ws$@o2H9g)!*Q3I_Uqv*$#2 -zwuFs9-ap~&fuz5}*>2?j-@@4-Q2z#J|Cso{gtKRwIx#c<1ZPA3pW*Dh)STpy{|%h| -zO7tfRk8=U>^QufW?cRQ&YpYu -z1JYRKnO$pyhS=fCz2^2un3%)ScKwppY3l6cmfNaXLFD -z+iO3!}xe;gN`nNvQ38OICGNU$ZP;m-d^6mKF1%Z*1?eR8it(EwT!&&1He# -zhfZ5PExe5fM3YgwNSCwLs-AYey20S5sqT^By2+J5p*6uNgxfPo0&cU!{Gu$|7*v@UA53_^i&fp4uSyYJyLecxg_|g6@xd-^rNw$Ruy>5IyC+AxDF!^-}YsOL-(~ -z)&6Vud32{mW7NqA`h(Ua%*K-MP5qDN0h7?S$4}+X=X>sgaIcHqVtrSVxiZ;&w+k;Y -z)|D&d8;Ocs4=u-MTwj-bel+k8$>LI^oqTO}-OOuh(N{5T43m~DUW+4iXtd|z!#>|C -zCgl&`x?9m~St(Obx~*VuZ<+-NzGm*Ig{42|FW~%wd!Pk{_to@{$|2UpwvaezTjT2L -z2z%B&jacGo7Q^76clnYNVrhzS-chsB$_u_6nhMTqbj>;Y#$^}QGIzdP7iRuSs<(*- -zgp>15ksJ6$l8+;s+VgEpEN#cY=klyP=hFS&ddqs~fSLV=*1(mP`{EhmQfF>5vYj15 -z{`99A(8hX`ZT1MUia_6h+@{bp2<&`bhRKg5&R5p=nqCz?Q~c6CI4FhC){a-^`CEB@ -z56zhtnGVx?dp?iG*qDmh32FwGu<7X;&pTvO1*I8^ijSKOYvi$?b3QoA1tKtT_5eBytsoV>2?e!+j6B;UJF$AGne -zBZ|1_D7!l*>L|oL`TUvRP3*{#8%qswbmnPF5f|LYl}zZlVD~geD=h&D|N7%%&;4uN{XBBOcH=97sg4RH{_!yxh~{_J@4+7EqrG+SNEq@o;YzreT|7W -zJZ|-ZbQ5qEO0;UI(;3`yO;XX-Rn#jzB#IwTh&px7v_=hRupY=(*8Cy2Oi{pZw42Fh -zt*Hj7BtY`Gg@Wx+BNA}PTfH(?_i~+4y68rGoZDwQsrl@&cfm|vInUlO8Oihe -zkdxGttAJR^U5YBf-`ZSYyx6Y2I);0D)&ivH;gQMN!#qDDg^Q3|Y`Idf0aG6exu@z? -z3ZF|@oF5{=)AV) -zBuq`;j53ikh@L-7H9CusbYr1cvWneMT6OA&-%Mm{q&GKP5Kc4Fak5>x3r~rUJ9UTe -z@hwzsH2K4EuZJqE1LMa(<~zp@QO3!7VWf7QQb^$OX+ -zGm2d^z0&S)<;ecf#f*XcY8!S}vwwgc2U+#JRv524!bfwig(q~&=IGLKlJbCD7I9;B -zdYw$L+#@xQg%~SuI``wK>yjzib0p)vT!VpdK$)jtgKphZ3^!NH^S7n*3<3m&-!crM -zy1-ba&W6;w#pIcNUuISMwz?~yfiG!%-Cz}IUA&w`b7MH&?u)bpbV8kM|fG=`|` -zHTOo=cFx_vOXN#h#fCT$XW;RLIszaEkz{`O)9ZU`shX!7ZlizNWK;;$?7`X;Ra^B74hfZj3oPx?Q5hVll6MO&aZVe7M?GA=y%*(%^a<|hprnc -z(rxFux_354=1s+upisou}2u$A=y%3b_YG(E~p@{=qE}1 -zT#?~*8;HLni9LB+!FM;+-ut#pIh{kvdks9Oc%nS@E#1|0bR<{16-WhddydU%k+I*O -zxN_cfJOHp}ex(XI;Qv0o{R!8ex5TJF^5scfy*h24G1!?vmpbz6!vcmBP01y0G-QON -zUqaxEdLeuhc$FtPr}3l3BPoV@DuF2wKMlrI>;ZbTff{kz{LR9L9%V59{^{o(Wa1b3 -z&4tp*t7*Hf&1kuP_vcg61bEmEGTZy`)b3Xh7%?+jgC}Qx+ks`a6azOc8D(972WJd{ -zs!n{LsI_6*(*&qk_*xY29{P(uXc57Sz|K>D2u8ZSn(Jl&1)m*!B-ZVZmdo@{9v^`n -z=b}^G5qu)?Ig6$u@X&t_w7n&wg#2zF);0=%ESo -zxNxT}pE^Auw?&U4xY>M#$_r(etQ(5#XmW|D0-dAlPsJrU4%bx~?`R8!Z=3)$S^b#e -zhle}NT$65C9`c?{S}i6|tf}o7qthSZ?0tE@zRuFGkv7}Ixw>%0RwRuH_sC?+g6R5y -zj6E>T-ddRt$<||LAb=5`(osDEc?n5AIh=5JHM#KdbN)sL0FG!$9xO0jd8w!+zoU?o -z;k0bzO$|;Q>hMJ?=8%U+&fcD7)cuFip)7FWRV)@`!td6%O^bU|gc1X>~{j0O&rSDq}?B@s?@v;Prwc(?rrv^#Id|szFCnp8nY%i?@ -z2#)3~{5rE^w~p&NYH+u@HECHPhz3&F4^PI)fF -z2A2r7fVnO!upalu{o6ylQr?t4c8$u{QRaAmPqe)N1Vt5RIgH$^%ePO%sun7q&vey)wQ^k9HbYB-&yGx>^H{ECnVSMUo}bty{$oG=AcGHY -z;OtujD5t65iDBy)EFR&?UPFqK-;yFBxjxZp4uvBF=?Zc9Zl}HrPcv6Kpg57K`Zt>U -zy5pnBCqiGe;2};i)Y1b@RBFy5KhvoUrNDU4)~eJq%qVQJt{nH8eU`VxP?Uo`#xXi> -zlvh3zBC2J1G)NSMs?p^K!tVuJQNqGe4T7Z;%bn3$N_8P--+`lY&M|O>OP(4*PvI0Tu-_2&J?2iK#1-8`N#Vfyo&~!*1R0u -zO;dTwUXVuITvXMMnI}89UnD^c6F$^u{{R^R$os|sA)$?890bV81hzv^vS05Calz<* -zLVmUL8M%*qtQKbGj!<+jjiZfa#U79e5~r~BHgb?Zm0f&4?t423N^hIQhSDw6W1CZ5 -z(zS5Pby+upF7ReDD@aTerf}dQ%>H3%V8z`Lj=`SH=MkVjAw<<%ZvBpZIzNT2vD}$9 -zf1F#BN9_*(3pv+95bx_4^lx8sftThq@!GhNEp&VRA7sZWF&Xt -zbl*US(=526vGsuA3MnDituPDXny?loxM7*r~3rZ!jQhTu}plUY1p}y -zGVuJA7Oz$Wr-ljeh^?`wY)*>nKq!jSFIn85QvnjzAirZbUaA`)^Y`#n@!RR}t8JHV -zV>H1)OZp2ABJk5imo4 -z0uY=^Po5u?V)6`_&eCqqAl6kjQ#ox~`}dI1VCtIzOUj -zzY^GYCiS!xMht%6*L(h#HiK=6gWLnZ$!to8P;hO>flmP<(cUP9s!Tb2YS02kiXTDE -z-Cv++GF^>R4DCayhcPt1m0fTSwv8sLmk_$L#sbF%B-NMa0j(FEL(}3l+tc!N*!3~w -zmfu~}D91t&RWg*iCddhaSC36T@nc`@fxX04R;99Bp@l19159yqWE+-0nEp -z>`YNIMD-V@2!9q1tIyC?&`u6{e*u}FR$lK -z{^$y~Bo8l-RXnWE65PgoYG0$N^yUmHv`TYr6w_eXLg`7wtiJ*!o{v}`-K|?Zhz#C# -zfO}B}U2g-AR@6=~K737JQLppkN^LM>lADAOH}c9hoYL-R8wQvPuI6M6BTnYS0Q=5^ -z*R2%DRwieP!PgUbbK=W|G~7N(IEeN8lx56(S6o`*_z~|YUEThBUK-tOb{2S?R8*Y*=UW -zpz8`Rmr&^fkS(`|UJs$Orl(vFun6j2TRP)maB0%?!;oq&5cYPGXVy_Q?#IQXGs)LT -z8=B9LyCQ&F8KuH6T%(y7o-a?Y_)nVQo6SDF444&&aCK_y7^Q*-86*~&Tp%CnFj=}P -zguUG;xOy8Mjj93R)J_Z^epohdT+-NT=MdqXi4@DMFWL3e$4!ot180pLP46k{pv|b_ -z7A#$#-eZD;OGD`%fh!>3B6fi%i>WT|GV(~4aE*mYav(%&q+qZ3U0e|gYLC}VPqP}W -zlt5333*ww`&z1|Sfn3bSQ7%Q55SlVCzwYv}{>cH^>s^r(5YAex;y`ja6qx^bzMK$+T_)`Rtw; -z1@=_GNx$W5G?wKn1;z(MRg4@-n!8xRLDCF>5y4SU3y-sWu9UEO*CU)?TIwa}_;HFg -zc%c8{2YH&}M09GN>f1UZ#j%_3XVOb{Dpcnq!gl?j`E&ZP@mar#8!u`;sVtu^h2i0p -zYV%NFt0_>-IM7vr6k)ysH(Gt~x}`xb@l95iH;42*`IqY|bsQ?6=*ZTnZAj&k=|{$g -zNZjdW?)WWQgy?V04o=g2I^TQE;3xQqFm|0SO9sU|B>ctj$ft}?YtA_*43dC -zE+@c{tV-Erb4EI+Nk5G53QbC(y?gB{;d?zSnJ+MOv9l&5(jQj&sfH*Ru5FvW&^CTf -z9H!fyAqnQJP{u>eQ>}|&P_%CA;#dnO%UIu+zrNn74fWMKUvCMIS%e^WeH(Py?pT0P -zdGjk@pa>}*#@)E(sLhOuo%N%0%K1Eapr$+tIv<^VSiww|jiZI&q -zRj{uLIwrf!x1988eQ59gB^BJ?V~0Ej(<97M1EGfph!5X43SqCsEFPG2tQI_@+PTWY -zB}O$%6*y|*jttJ^q^Gc{dMn!W}7%G9 -zR^fI>f@fcAxjbBG`YS})zUV{*FQ-~{L?Wk`$q+|+wlZ?Sc|RmDJVXn*XoK;{d77Dh -zC{~EKz&LoF%)!Y6&2~-nn8q>Z2)#HXSn>IgiZb>DJk!;X?=<7hqj8BrtkknwbATda -z&;*D9WG9Bl3YpFW-T087b*C7odI`5@8)VZ>WwZ>`}vcA||n9>_Uz -zHDSAL`IJcf-PBj+yW`wv`{AD%v1>T-6e`;7-a3htWWiOYC^jaP!J{17s5ypv5Xi2uXOXj}XTHB9yySh3%Nr3gGFgk0>WRaOdZhwuuBiU$?@pIzmL9 -zK;pr+Lj;M|Q0o6Y`dLaQR`J48232g_@`r_wM!+P2H!w_(iQ=*HV~&5fuGr#UbO5x` -zPCWwshXS}GP4!!JnE11uQzRsflQ>PXnpPKH?iRZ6(2wyvGBE4g{i)C;_^xs_p2Ix# -z;mRNL(RHD|MR&MRppYi@%G}bj;Gb~h0(LOM^9Z~))Lu{Qww8Y166L;yI~V8_)xXOY##9Gyy?Ez3}S%?_Q+D&L(qP$uYTJ|j--vUFFDnsU9f`Y2na0E%f!1TexqI3XPRC=8UX<* -zm!*D-zB|n$b26!6b*#+t`V%P=)s^{f*_lu -z17ypkdDUagm50pR2X-_eJ-$PCaIiX{vBTDPeK8*>PV75Q@uEc?Az>@1?ZrnSd}LJ} -zm2N58Wv}M~F`0$r`1>rB>JnMhdTHyE1U)HoC`d|{;Hm1IeYLaFNHFe->C&);#*gxY -z1YIt!n}T`xo=ao|h<;_gNuYgs}t%@*^{t-X+~RcokT -z_}`je{PHEjcTa>bPu`XgwTeiVLyzCS*p7E2Jd4nU&`^P#)hRu~bwH|LW4xFWJs&7w -zzw(*_=j>1nX%E@GJUwD_oWqM?P{r`>3;Ouc$ww`tP?v6^g|n;e>6oYt`*5H6NeJ$@ -zh};r_&EI@h@RwHcG52iNHto(wpx3u;SAQQy0UktjQK+W%bFmlmxnh2$4}#=~9Y67wL>y1`x17+wEybl4s3dx@_PeO!A$I4poBYqi^_sQ_f!O8bx@RZaE1;-rFdbOG`1O73ZgT2%O|lbm+g -z@9-0_L{eEEo{d!+RL&%691a@>xw%<9z#42)evVPD?<=n~n=11Sf(FAFGl?X%%bZd7avs+l2mpdanNqN+Y=%QAuqYbkG@5yO -zZF(T+R}%W0ui -zEJ?vnI6LnroDHR)#biI|sf8nAKw~1-?F_{VU+f?pQX7gPSqE2cORbzydL_n(1-h#N -zg!n_}o?u&c5lV-c<&%i2qiCZEfN-OE$VC4uUmDu`Dkke9D9p`Xz|H_<(%aS-2JGN0 -zukU)@0$!HmB@a1{+0-PgkqvUAHEb(JDlQxB%HCM)g`l(_&sZIlob>6ICBMH -zuviDhr7(|frMK$h-$bfA%zO*B;VeibB=oI%Sh1t#t#>q9zp}hQ^~#caHG&2WlX5Ak -zyTkdwa&^sV_B#51UGUR#VT%>ZhM>A#QBNEM!z4e5yg`14EbBlc=3vB<{KZ0YM6XkB -z16}5mSNeG99mC?O#nE(yKV4<2Orey&;c|XPz?Rdhz=o>D5k`Iie|rc7}kNnV)E_KwvT9^ -zO(4DPfcKu1PqI*tD%sM%2beoDchs?ZxM<{%baoUx6H;+(lD*YwK_Ru%dkFYKlR}zF -zZ9Y-(dpqSmT3N&rop6(91*t@W`J<@W56+KY0dl~owL>Nxrw*~N-PACI9qr&7;%!NgokYa!o(9yf;_|8 -zB^x{>o6gpjZf+zICRzsJwZ?Cm1BoBLF|e(2HevuVZ^5-q#3nK26)Q3DhbU)aBNdek -zsM4V+XvWYq>y-RB@wF9pt*7E0u}w|-n7E)KKcu8p;Hr7YNJyu*A3jT~ba_yWueEVJ -z-L^qunHG}wex@yCNFfT4|yaaLod{pG0lRH -zpCINGrqUr3m*b7BKXkF<{6r^{s16f%rMxu@%kYEOkHwBFRI;Q6j!29a%+JQxbkkkiJ9!NHOOCPPOp1zuf=P;J_2hhY -z3t@$ZSEd$W^NLero^3jKx5}J~8epylj7Ssb!4~1R(W|1(`b&MN=Glt@ESJlnxK5E3 -zO83;w(J^kA+aOGxmbfMEz4%I_Ew|t+@MMbnaQL+oc?4`QsUUY;6UWw`zyiDj@nZ;I -zf>7Jjetc17>xXJuagqTnh#~mifdlMmuzFIZfsEA77mL+`$Xw&jZ~WHtD7ZE3-2ox~Qz>BHz^uJ^9gBJ;a#LxN -zipk|73G6#)ZsN^Y*u2G&r5I&Q&m|imijO}!X*4=-+(A#*6wAqu?s$9weqd)Je -z&+IqN3N>N3t0fdkt+gsBb(5)5&{d3Om6sA;)s7iMap$lyCAd5=Iu8Vm>_MtL?*M(m -z@tL|Tg@KD9HiQxeF6bs0Xe!rIh1f=9T+Wh-mnMs)r*lEJWN1 -zdSJy@uIwfCYpR=6%V74!-D^leGAz*LB52k!mzw2QTy~jK@>GKbUEbShNxM3op-C4S -zVV^a9La_G_5GAfYh3YllKK4zN1YVyFPGQzOTe>xlutDl9AmqbKmkS6?Q~#D@v+mI% -z3$b2|kA?2L-lOLOQ~W8);aq~qR&*is2BOinE&8hHbAoOtoDh(nYK*^HK3@#_BY7&A -zu(%gcp7T&X$8?pkQ0#kuuhEbD`MAMa2UD?NX)SCf5stk!L$kwmG>0Sx@mz<;VnoHB -zwgP;+BckR*FOfFc%iS+HyF)BWb9lu9D*Hbi<`k}ip1LH6NXxkf=kriGEs^XmR=TQ)o6s7n#&RII1Mc&hOw3|j -z*k&coC`xL%!#4z;In3vh?b&?Hc@Z-uRj<=fcaDSd0Ka1DIDWmz3d3i}H=)zbbq-%L -zHLdJPWL9CCq>Z&BECEeb`8*072PX!^mRc|VgtNYa>MW#A}ZyO1pC`+aDn0 -zZ)gz5A`NE?X#REiY>oA~CiAjQm|-kLTG<4qFmE6zD$vjuuxCAKicfO7{x(q1;9C6Z -z2Q!zNUp44?7`1?xzGwyuzcC~4e&Col@RQ|U~up3Os5=cZXQLsN!Hnp$Aho{G6sE)3K?)WF*AMj}+& -z@P30!D&hxn0A&QDz*+6fc*Bd0T&J=vnX0MWj#z|iTpK0cG2kdPM9mtfS3%pH0B^q=6mt -z6cPyDC|UIGF$2*Zq8%Z*^7x#(6EHxXYwCYIx5N1G`Mv&aq2PrQ!4NpG5zitOtNF4YZ| -zK4g=6PM-eT;q7)LE@?!~-O~YfgtL^Z%bSl}0B%F!3mQlUqNmfSa -z%bbh7CH{~omyG<=U -zcw|_T0?FxKfr(Lx_0i5B0cW2{?6uKR)QNtrP!cg -z(fpAR=g4IJ2$3!gC#62yQOs$FS3nWx8bCUUC96tXs!${#r=v2Bo=Hb{hzF+5o)bVQ -z0SA>KHp9;se_atIt9Am0Su=2~VULBOL`5LdgW*_Kj(XfSLQ9Yj6I=4YF`|PN{9xS3 -zv?PTFK!o;s*ct>Ztg~$Llcbt(^Inlqk3GEYmnrl`wl+2a^S-_axNB@_)4_o8?Qd|I -zJd6PifMgSxwGRBjvw?JnEAodJwq8dJPy;Qj3hO`;0!uZqoMC~&7DIG~W;A!~@Lq|` -zhkFSul5;tCmnb&DcH&r5&0YxA+ZTbELa$J)6r5kF;tfo3JQTSo%^(F{@0^;Hg|IH= -z`&SCExk(U9=)#D>rvwN!0GXRp1on=0iM6~Vjb+9Vi6zHuQm~d}EOV;35oS6KIh@veDrWD)7Nf^ns8iMF-r65SK -zxFS8iZgPhwf=iPgR5iNya3iy`IvY6;PdRg1b}qVO3@$%8hrn;%?t1gro+9A_;+naF -zF~M@m>GKWK$XV$N04F8MA{~{IIA15FvopyVHL$&5!S5GAtS<(*(%r0GXY?8aZpB>i -z-rv2Xtp(7$9!)svi;0ytLw$G;TYZAGYKx-wEsiw?kD@rS>%Umv_s*!dRgp<`(iS!V -zEz;YrOv2dasVz>w -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 -zat9I1>L?(iRx9%ZS2EWHly~Y{F -zJ;5DonqgIF=xPPv#Q9H+pBx+v@F -zE3Ncs9pkv!oJ5mxl}E@4NRF3OS=xQWXP9&x3vY<5c-=5=;C;624PEG%2^v(L_2-A(lQn8L6|{9jCR&z# -zVGb>3!pt)Zm2_pa3KB5O2n?Iu!nZquh^p$ybRBN1k82lUrY -zGp~{SQ1f_H$4CY3ta5|BoB2WeY+IQ9e@(nNd^cn9JZ&WQb6bw)dxPVJ9XF9@ZnLgD -zNE!*C?+PwCBR=z!l`j)`O<5SXL?E{0Pk7E5Opzy1hV^Aug0UikKl0$qR)pMS5isDF -z_&BG9h~oE0;DL>q#)4v8e*DoD{=q)H>*o95lUGdihwP{I}gaK -zir0j!uZ)Rsmy`Su%)PCKDr6?I6_qg4GzI2;?EF>>6h?+LBaqdv%f;A2hG0 -zCe#TWwBwdEqvxk_cS$~fCTD2tbL3U&t+&p2QDaqA&yoz!NywrL>FRC%#E9OEaS0Bv -zU__KJp3@IAolGr&u&AX#uu(grn_%YETAp{2OwL^K5EgN&nTQ|f{D3McBk{)@Xa*`e -z%hhv*(GXXnKhYIjE;fTvg1V0>L&4Gu2tycrtp6nA;eHGaOfLC!(|O5zI0!)CE*xk{ -z(zc{O&IpN6$$dCzOYIhmOpt?V^#x<$n9ie@cV@dWjgbll%@({wfD<9$7gmQ2)Xl@px@Dlh9-zzvb1VI5O46_=n`+uEq)`wW~e5GJ9iLII%1i;Eat%5RJ3Cx1MGaU!pB^&`Q9}Q0NPBp8R -zvnhA2I7tG?;Sdl^b38_m@kl);LT3i<8_^)DjN&GWLu5o^Xb~FOI=_2I04OQP1uY$C(d -zz|%a##foWFuo!_+@J;5CFs|1lwTo-78B`lKt&@ -z#-VbOik4HWCS2sdfMD4Z(YpU|1$_Qx?<`R*H{k+?st^HhkuZM`0F8VOWB<5jdc#y|JR^Pa;v%kecRP^*WD# -zKTYWE(+Yxn@Sp3lfs4dj%=}f$r77NBr-!0{<=Pt58^fr6(=3ZzliHkU?-{r!M&XhnmloBo=ByaQutT-*mKIU4JpDjf73$ -zZ^0Z{s`ADCPN3YajF|RbFM_rVha~7>r1aacgqYuDBwt3(@n&$ns{MSDGsvO0CxXeM@PKD-MyF)xHV6^nhBUbB*Yb|Db*; -zmqr3wWmQRGj8vp^wLD*JTm`sP{v@H94KGF4GVvS;Qu*2&%}aA;-+uBh1>yZS8gYr3V-Kw${ -zO{`?d1tnzn&^)a-je@P~P*M~y0!g`0-%HoRZ(J&gLb;wPV>(Xn0`O98;&evCm?iUh -z0S{oXoN-z~j_k -z>9qE2sf;UZjdM+3aj>5boAaN&`Z4Z9Q7lYfq`|@v{4&bMa$0Qr6JHrR~SktIH}`q+Fh0Gb7S&pn8=GAnAc?<=Tx55%a#PZF$2ORRqTB9|=7 -zUAY}Ev1fvE!i~F8H;&&>ywZL_ra!W`DAy8qDLhuB(SCnpyf@O0B$H|&c-Pjpsm#FX -z?baI|Pv6ge5_h$GVTuNV=tIC11?g>56;yx`8y#2crXOe7yTSSQNsB4O3)+p^9{HKR -zw0uJ7`rWpnvh4j*<-3d%m{)cyQ6s11D6pc1s9@lBmXNB -z?$J1SA9Ew#IZTxDZUa5RiuX?O?bi^t3s>@I=;2{3%{butqRe#yKa&%e22ALN&vM*6 -z4XsqsM|`Q5Wf_O8KG;dTE6^t%_boI@JR)RS5AvQEpQr|W*9yv$1Hj^nhh>&^ahSeL -zKEDG|Q%ED+tAYJ_0p{vxF$oTbGp*!)64`zuw)`5>3q&M@IcjhLx&l7yT0x&0pVQ1wWjn;(HgCsRP{k8+BMjB&)FPFOkwAhzQjhRl$7k7JN=AIA2K|6S~d -zX%sG)$Pr{=*To=)iBthX`zU1qB*(p2a1>%sp|$>AELW_qb-1o!CwN71-0M~S_6b11 -z_DJgfyIG_m`1pzA7k+ObfAD3FCtb@??->)<>`3ci=$Xz^O&*Mil=ZR=#IPgf#I_R- -zPnk2z9j-m`QzA2%I=IWGU0)K1=UBknd^v-ce=E@w6Gt_6TcO65Q0{8kA?DjCc9MKfSLHm`){3?`+Le6^3{`S=IoZ?&9BAgam(5a?DsFx>DbOK}uEO -z-byRH23{TMr-1A{l+aiP^XOklQ43E}K#}OE!(q -zvqR0^v7v|xKjikqf8@5r-#)B?=pCnf)BCr|mP3@xV9QrgQY#<&l9=Yk9U!N?o~iJn -ziMj@b%Guqh#9!&LXLo<~OAuEhFf0pgT7uh8a~Oe93LLC<`#~Yyl{y6JOVH>_1L5cd -zXz0!7z!MZ0nB^>qreq3S&TF1oA)eo6Zspm7K^&2gYvEV{*Sjt%JZh=7#ZqxQDee>z -z)Zy>}``f3?Cz280k`ugiY}5FqUo>8s#yU-%c_h|TAAWwRBRPj+ZZ9?R%sDqH^21E^ -zK?v$)4Vi=1F@Fi*))q6lJ*M-1ZC0Zp`>hl?n-4EsAgjTeLH%L%x)a9++kB9teWa+g -z@82gIq5cz%lfOCj8>hDB+=!5{6~1SwbwWSEN%?sraAmRVP?#lq?iS1npV%!@fIQ4@ -z`GPV7a5d~4NP*C@xxCx%%zrl%{*c#fu&PIU(<00enjl!8X_xc -z-ZPTV&%uGcK+kNI9@vJ;Dz2C?Q~pjh>A=M};qQoi`kH|~8qeyEVUhRK{C?3PsoiY< -z!(wlzbuwFOao&+&XWndN2&V0^)!dxSkG -zJHgFX+|rm$9x*iMuQ60LCjw}te22RQlz@FWx!%ET&)V1ItO!PAnWRUGFgjh-VH$*! -z;o;Z=d*j3&VR6kXkU{~nfuAHon0PbKF`U88;S`icO&O(_d)Qi9ksP-}Nr<`r{NT;O -zU&p=fb26?Ua}a6qik@r)UAE4sOe0N5Dh0Ni069xQL?8$?Q9$>fiaieQ^0k5bL!8TT -zlIqHVkYJWiVy1h>3%Db(C%p2q6QMy?b<@IDBnr|6tP)(6@wnz>rtfA6RLa-C{cREi -zvl@kZ1p)>uAjlt(JELxE*+Z*pI!p2#GH>QCgX>uK3m~`hCkS_QNO21W5wC~X@=9ct_go)rlA~VibDz@hab)b -z-aPz#Zb+gl%+rXL>6B6mm6Rj5X5NN)>{?uUE(iAOb4zc}X{nZo2QWxuI^=1t-WjfV -zV(MWhygvyI?GL84st<5tM_@Cr0W$B3ZI1ptfS*I_Z?hvFM=t1wI3!u$WtkZKfY3-P -zgoTYl+`<_y4kHrqh|p(1f`J^e&_nOqB;CG&JNYw4JRNS0rjm@w{MIdHh$QrY4e@YJ -zWH`0EX4QS}-{dytlN{G)GSZbsCdpxOL1pCTtZ+z$eA39Zc-JABa{!(g2lkO8G`bK# -zTce_U$t%9xOim^6&@9hZfP(1CA5)c*nyMr&pmh7ZjmU(#Bpet%Lg&%l)!?!A)y!47 -z++rB#R)+^B1w~}Nqn9yF%RoBg>`qYuX(QHgz7?o0#oK;;RTgQs<(3P9c2k82*+>Me -ziNJw>frWR!_I`T{Yw;WQ#g_5HgTM^(C7SlWKi}nvI?D#un>|)m(HM)k60h``698mr -zQK1oz@a%n=pDEuo)%4LaWT#O7sTU$R%C5mDYh7)6jhjZx{|txHs~I4gjI9hFlWWc8 -z(Z^UCA?9;d8V*xp%iKp<0#o7sHUyg -zn!R1e3iFfK&R4rqzXXkY@uMu%F?-m3b%pvYTc(alU14ulvgLz1+N!{fxx#*7BT53V -z0QAO6Cyc7B|z{$7Un -zL73=beQ%D5-^3(6j<@2Q&HR!fgrGpv&Lu0+FPYG=MU$W;1p>_k|%aWC4p9u0Zm|}$Xvyl43k0)SVt&&FO~yV$n}Ki -zrn^8uErCRn4Ut4+@9keJOaq7`W!QJ+eDxU~S3OF_IqW1b*t@tGR>wT1vK(xtZC23Y -zx@)WJg4`$~wL$rsFl;Y -zf;L*%T5rl3-zZA)C$|37$Sp%z0U$S7BSimp+yp;>DPbw*@(;FG7KZ}&y7rS$4+aZ(Uaqmr*5XN`j86kG_c3# -zQy$n>n;R^Acj-2{wWWvlw7fkva6a?7E)ug{%cpm|oICWp$M2H)V`7=ku2uER0lLUw -z%CFhgj^3kZ;q>y+4u7@hbK|RC5XZsy6_?DtwRJh4_wnw29Z6>^rKwiWFK4UU$hW8F -z>+$&T^6~NA$Xly)VtspH{kYc7?|SR{`tmxpwI_CDow$~_yVBE8=vuF}6lcWm)5qmi -z|6c7-bjj|7gWv$aW)?L){jmD61xe!IxM#nepd#Rn4;Gvmc#{w~{cSf`89*K^; -z2{>5nNFG6Cmlv$r-HGq3E-MhBpLuu4g@c -zVzisAAC{D^ka5iRq9-CUc7pQ?Y!QutCtKHq^L6DxZQbWxx{}*srtx3N0sqwqRW}NY -z|G@2SFXx>7BL#w@c|l;F#n6K$eQ{z8dwDqIS4;p=l#Umnfz+grYgpV9*b)t2=35iu -zFP@))OnC4+qCFp7a~?O%(YPL+!_z%JKE6I9*mHl%V|Gw#GIsb^?birtu>z#73!6+| -zeP6qQq|qIGxDw!}Pw&0=w}M8k_{)VhsQv~jdS}bLFSAI$4p|Q@qeJBqTIvLVWsBWZ -z_A^~1*s;q?{xMZ&EF+FeZj%oHfLKlR4^I4;i~*ECN|`sZvyc -z7w(*q;`KEwc$to2Qn^3L1O8MFHVx99$W+kb$vubk7RYC)3!FSy+-o=kNk -zR{)2`1VD;-SI<)(|G`>`U(`XYDaanhExLDRJ-;yJhJ|)4!p)R9Ivi=++V)0ORa9QS -zX)Ccupv5BN|9GjdPP^WB3Z@P<5VeGab-f`}Mj%Zd3YPDs>N)7TbXVvz&rnqJ%VM_5 -z$!31J@*rH6uPmlJ2^_Fj0W*EnK$&v?0z$5(h9MDS7^wYJ!eNCMz#^im>ZN|+ -z89JeXA1db=oL{c?wQ=Jm>@h*7Kw%5gN!9Qs`m79ZyRO=XXbXF -zO{K)~e)7O!DtXBjIwTn;dFhuLNk15TF86_`F*XA$21IoZ83>G@|9k%9QBibsM0v3; -zf%1rFneuBHnWInj8dB>Du&?5>>gF4|Vk--Uy<=gJZ{YV!ZowN;Ui={YpcX;kkGSoA -zD|l*!4cHO>W|LNtvD)IewJWFMsL5fjaVcqG}fp}#-QOzAgBWA0qC%OK=b -z^zb^LA5Wjrm|FmB*&(G^SGG4`p7Hgwf=zFiIGVKuP^dI9 -zQHr*H_^8E@bo$*bhac1;@Mfz2yTz|#E -zZ&VWuzb6cZkYxsAUK)@rWG57_=B8?1Fx+K<;A20&ReoH`G~En7@4=`qnZ+ojuM`p{O`Q6rA;=`o1J==2xVJ@B&Yd@V1y -zv9xonFaU3MqHLU)Th#{W{w!{i1@aO41+*zSNfLS_c`JJ(oHM*ERQM6OTHNde%}|`a -z2lOqz$>eYuFUBps1928N;3-|WwktEl+a;H@1?!|gU;EgXOXc;FoyFy&&BvGBXCg6; -z-5#Oe>$WfF**PLiqq~sTTr4x?(z4NQ>h~vpBe5H+OKj+^^!O(R-lgi=qfO%GnKdkp -z$9JukNv$T$J?9)KFB-E@>tU(N%4HOocy1;HeCGVXNP4D -zo^ZW?;PzUDM$;q354hdu|9hUevkrcymajl!4(HYT2i#V>`w!d>BL2OjIrk6T4oD-C -zeE*AK^gnQWT{eHgNUO=)8#)r2=E-|(!X=xi+LCpMMfXb)~`}u)IYPfRXlM}N}*j`?kw}K^Lk%WGb<{+|AE_YCfJlp_qd1~ -z2XtX+%8dy0iWO5e;SVG -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 -z2w8-2 -zD9GJgMSeerkZQwDhy~!?zOX%hq_4D!D(2fNXd!8%XX=n>{7C%cqr7hm!+iEi3f@r~ -z-}ZLD=vy@c6{Ex)pd>ZK6GP59)S{_Ec-Yxz3bER9oyQLz*in0qlmi|* -zk7`RoNmpRze)R%avlVM6J!8JSrx)>TQJfIK=Yn_;qPg%Nk5}~QM^wR^rN_-`hHfCm -zPZe9N_PJPa(?lFN818uDcV`Iz{t1fM=18FJIf>#n$I41+G17a4!n{Kq!N7Y0`SRmHNNAG@oPzrEgSV%?s$6CTjWEKW3 -znpYJIJ({oX9C;sV0ZVFX8n*&W$#PAtK{A5Yz~dlBCQ@REwW>T{uwn@YlI@=#FI-S2 -ze7>IJA3iShUM=$_(YB15$ZynSUn158n2tb9DOKq>(=ovKH1sucIGi#WbR#5k-5iA> -zBQyMh!D0i0OdC#DdW%-wf5<+c)ft6YlfihC^dsQF4Osu>w@Zslr2IlxW7M)ix5qm~ -zUe8t-Yeq;bMbFDg_m#hE)Lx5@%(YI*f}G;zWGyUM_GH1YXSDzwv5tm^`74O%uM8$# -zkMcUKp@{(YY~};=c~J7j7A}*wFQim(T--}h_^0pd(8`*1iSTB!ci%c|ZMDwye#08H -z2{9P;vrEutO{J(7!oo@_Qdz=9UKiIZL4*EZAyVFO3r1h+S)Jqfff7rIm<=hXp%|!VhwvBhF -zerbga>#ClkgcxZRRS+TIeR=f}#Lw4KqrguDpz?bRdYUfzxKY1lKx3 -zdJCl1*5V$4%n3ERvqJd|PD(kGLwikbHzH3{kz6jlDOby)A4e!Zpt-rzbak_D^pF^w8002l17VeVrL$CYQqi6q1s5`E!Slgp?jr>5 -zmB05-2*X6gS|kVw=n_evam{WTyRC_u!ei}|IDEc4bZ%l&QXfA72tsH+fEgHGlP-m( -z%vC#WxPXu;yxBD!7u=hGyr@!&+{?)Y@6cWzRBacg@{1`SU!E4ydpAz__EfT|386<5kP!pO;A16l!eL+B7O|<$2BX#yVRH=d80 -z;kR~YNv<(;n03?V84^q9@m1Kp(8HfgXEL`&AVliFu+u$Zi!r-{i?;GER5b|w4A15Ui!4p~LG;OxE;*eCrHtKwRy-5_)&Yx!sCkKA( -z9MMs}-7?KJ(UP0r=}8eBAeTgHDG0FZoY169E(q2kAUzdqT;zRh@ehHbmFcPGD|S~e -z515SUKaJg$K13iV;v@LQX;HVleOk~K>kVXHtyITrm*JbhU@5kBYpNaJKK>Gf0REi{M)Ca>DOdIIZ -z`m)s~hwO7)iO4t)^-qMdtzs}7x&{172~qdoU+oC-A*u9CN06;_e$>APBa0PNG6fTC -zJ0OrK8sIwkwd1$kr2-AFYwMbOgj$OCa?B+K>qfPoR9|YM?4YvnWnU)+nJ~VBm=v0R -zQyRrxO1-vCH_74vQAu08r}C|oVX;n-8?CXhk>CWbU9bVo)^2JQ>UE&&d>h=9D?4|9 -zIpDr<{~FwoSoePoZX#MMI#g`!AA$Pj{~FxJ-v;+w1~z2=x4{h*@WXMPRNC;r4Q?UT -zQQ=4)8q#gN33=dt8{FP1Wd!o>zlmG4yFSmls=XWVlNyKf+fu%v+y~PEqGgz5(o;Uxy7AT_P}d -z?0*~FZ==0PCBM_M{x!G(V$Y$e?bBgO07L?!lG?Avw9|C|F}SCSlScnBxLM@B4Q|+= -zzXo>`l)QD2{m_2AFG;u{0cNbA=lg8?Erzh-8e87C!9C=5cR0nf_aB2hqTH~E6g7aX -zc=@M{r(rpQ_R#)RKO%W^M&OHM2iGa3d=b;rWxqc0;MUm^;uhv!v~^!mFy-uRC8*hM -zEga`Ds#+