csagan5 3 лет назад
Родитель
Сommit
8b9c14c8b7

+ 3 - 0
build/bromite_patches_list.txt

@@ -1,3 +1,4 @@
+Disable-third-party-cookies-by-default.patch
 AV1-codec-support.patch
 Switch-to-fstack-protector-strong.patch
 Enable-fwrapv-in-Clang-for-non-UBSan-builds.patch
@@ -116,6 +117,7 @@ Disable-the-DIAL-repeating-discovery.patch
 Block-qjz9zk-or-trk-requests.patch
 Hardening-against-incognito-mode-detection.patch
 Remove-weblayer-dependency-on-Play-Services.patch
+Timezone-customization.patch
 Move-some-account-settings-back-to-privacy-settings.patch
 Restore-Simplified-NTP-launch.patch
 Add-option-to-use-home-page-as-NTP.patch
@@ -157,6 +159,7 @@ Add-custom-tab-intents-privacy-option.patch
 Enable-share-intent.patch
 Site-setting-for-images.patch
 Bromite-auto-updater.patch
+Experimental-user-scripts-support.patch
 Enable-native-Android-autofill.patch
 Keep-empty-tabs-between-sessions.patch
 Disable-third-party-origin-trials.patch

+ 4 - 4
build/patches/Automated-domain-substitution.patch

@@ -4409,10 +4409,10 @@ index de05ded0fa8d8..1938e2116a685 100644
  """
  
 diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
-index ae3adcf47ea15..ceb2810f56042 100644
+index d1ec95b1f7d30..f6802be8e47ba 100644
 --- a/chrome/browser/about_flags.cc
 +++ b/chrome/browser/about_flags.cc
-@@ -3818,7 +3818,7 @@ const FeatureEntry kFeatureEntries[] = {
+@@ -3819,7 +3819,7 @@ const FeatureEntry kFeatureEntries[] = {
       flag_descriptions::kSyncSandboxDescription, kOsAll,
       SINGLE_VALUE_TYPE_AND_VALUE(
           switches::kSyncServiceURL,
@@ -4421,7 +4421,7 @@ index ae3adcf47ea15..ceb2810f56042 100644
  #if !defined(OS_ANDROID)
      {"media-router-cast-allow-all-ips",
       flag_descriptions::kMediaRouterCastAllowAllIPsName,
-@@ -4050,7 +4050,7 @@ const FeatureEntry kFeatureEntries[] = {
+@@ -4051,7 +4051,7 @@ const FeatureEntry kFeatureEntries[] = {
       flag_descriptions::kSetMarketUrlForTestingName,
       flag_descriptions::kSetMarketUrlForTestingDescription, kOsAndroid,
       SINGLE_VALUE_TYPE_AND_VALUE(switches::kMarketUrlForTesting,
@@ -5764,7 +5764,7 @@ index f56e60a05897b..565dd568f596c 100644
  // The maximum number of retries for the SimpleURLLoader requests.
  const size_t kMaxRetries = 1;
 diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
-index 6b489b8614b24..6f29ebd320601 100644
+index 078b188b7e077..2e900680297d3 100644
 --- a/chrome/browser/flag_descriptions.cc
 +++ b/chrome/browser/flag_descriptions.cc
 @@ -205,7 +205,7 @@ const char kU2FPermissionPromptDescription[] =

+ 23 - 0
build/patches/Disable-third-party-cookies-by-default.patch

@@ -0,0 +1,23 @@
+From: csagan5 <32685696+csagan5@users.noreply.github.com>
+Date: Wed, 11 Oct 2017 22:50:10 +0200
+Subject: Disable third-party cookies by default
+
+---
+ components/content_settings/core/browser/cookie_settings.cc | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/components/content_settings/core/browser/cookie_settings.cc b/components/content_settings/core/browser/cookie_settings.cc
+--- a/components/content_settings/core/browser/cookie_settings.cc
++++ b/components/content_settings/core/browser/cookie_settings.cc
+@@ -63,7 +63,7 @@ void CookieSettings::RegisterProfilePrefs(
+     user_prefs::PrefRegistrySyncable* registry) {
+   registry->RegisterIntegerPref(
+       prefs::kCookieControlsMode,
+-      static_cast<int>(CookieControlsMode::kIncognitoOnly),
++      static_cast<int>(CookieControlsMode::kBlockThirdParty),
+       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+ }
+ 
+-- 
+2.20.1
+

+ 1 - 1
build/patches/Enable-native-Android-autofill.patch

@@ -28,7 +28,7 @@ See also: https://github.com/bromite/bromite/issues/547
 diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
 --- a/chrome/android/BUILD.gn
 +++ b/chrome/android/BUILD.gn
-@@ -415,6 +415,7 @@ android_library("chrome_java") {
+@@ -419,6 +419,7 @@ android_library("chrome_java") {
      "//chrome/browser/xsurface:java",
      "//components/autofill/android:autofill_java",
      "//components/autofill_assistant/browser:proto_java",

+ 10639 - 0
build/patches/Experimental-user-scripts-support.patch

@@ -0,0 +1,10639 @@
+From: uazo <uazo@users.noreply.github.com>
+Date: Fri, 13 Aug 2021 17:10:47 +0000
+Subject: Experimental user scripts support
+
+Activate the user scripts functionality for Android,
+as it is available in the Desktop version.
+
+It is possible to add user scripts in two ways: by
+selecting files from the picker in the settings or
+downloading the scripts and opening them from downloads
+(only if such files end with '.user.js').
+
+New imported scripts are disabled by default: they
+can be activated via the UI.
+
+Parsed user script headers are: name, version, description,
+include, exclude, match, exclude_match (only http and
+https), run-at (document-start, document-end,
+document-idle), homepage, url_source
+
+The UI also allows you to see the source of the script.
+
+See also: components/user_scripts/README.md
+---
+ chrome/android/BUILD.gn                       |   5 +
+ .../android/java/res/xml/main_preferences.xml |   5 +
+ .../browser/download/DownloadUtils.java       |   6 +
+ .../init/ProcessInitializationHandler.java    |   3 +
+ chrome/android/java_sources.gni               |   3 +
+ chrome/browser/BUILD.gn                       |   5 +
+ chrome/browser/about_flags.cc                 |   5 +
+ .../browser/chrome_content_browser_client.cc  |   3 +-
+ chrome/browser/flag_descriptions.cc           |   5 +
+ chrome/browser/flag_descriptions.h            |   3 +
+ chrome/browser/prefs/browser_prefs.cc         |   2 +
+ chrome/browser/profiles/BUILD.gn              |   1 +
+ ...hrome_browser_main_extra_parts_profiles.cc |   3 +
+ chrome/browser/profiles/profile_manager.cc    |   9 +
+ chrome/browser/profiles/renderer_updater.cc   |  10 +-
+ chrome/browser/profiles/renderer_updater.h    |   1 +
+ .../webui/chrome_web_ui_controller_factory.cc |   3 +
+ chrome/chrome_paks.gni                        |   2 +
+ chrome/common/renderer_configuration.mojom    |   1 +
+ chrome/renderer/BUILD.gn                      |   1 +
+ .../chrome_content_renderer_client.cc         |  37 +
+ .../renderer/chrome_render_thread_observer.cc |   3 +
+ components/components_strings.grd             |   1 +
+ components/user_scripts/README.md             | 150 ++++
+ components/user_scripts/android/BUILD.gn      |  80 ++
+ .../java/res/layout/accept_script_item.xml    | 160 ++++
+ .../java/res/layout/accept_script_list.xml    |  10 +
+ .../java/res/layout/scripts_preference.xml    |  40 +
+ .../android/java/res/values/dimens.xml        |  11 +
+ .../java/res/xml/userscripts_preferences.xml  |  34 +
+ .../user_scripts/UserScriptsUtils.java        |  84 ++
+ .../user_scripts/FragmentWindowAndroid.java   |  89 ++
+ .../user_scripts/IUserScriptsUtils.java       |  22 +
+ .../components/user_scripts/ScriptInfo.java   |  37 +
+ .../user_scripts/ScriptListBaseAdapter.java   | 163 ++++
+ .../user_scripts/ScriptListPreference.java    | 171 ++++
+ .../user_scripts/UserScriptsBridge.java       | 200 +++++
+ .../user_scripts/UserScriptsPreferences.java  | 116 +++
+ .../user_scripts/android/java_sources.gni     |  18 +
+ .../android/user_scripts_bridge.cc            | 173 ++++
+ .../android/user_scripts_bridge.h             |  31 +
+ components/user_scripts/browser/BUILD.gn      |  82 ++
+ .../user_scripts/browser/file_task_runner.cc  |  40 +
+ .../user_scripts/browser/file_task_runner.h   |  34 +
+ .../browser/resources/browser_resources.grd   |  14 +
+ .../browser/resources/user-script-ui/BUILD.gn |  12 +
+ .../user-script-ui/user-scripts-ui.html       |  14 +
+ .../user-script-ui/user-scripts-ui.js         |   9 +
+ .../browser/ui/user_scripts_ui.cc             | 148 ++++
+ .../user_scripts/browser/ui/user_scripts_ui.h |  39 +
+ .../browser/user_script_loader.cc             | 721 ++++++++++++++++
+ .../user_scripts/browser/user_script_loader.h | 170 ++++
+ .../browser/user_script_pref_info.cc          |  34 +
+ .../browser/user_script_pref_info.h           |  72 ++
+ .../user_scripts/browser/user_script_prefs.cc | 276 ++++++
+ .../user_scripts/browser/user_script_prefs.h  |  62 ++
+ .../browser/userscripts_browser_client.cc     |  78 ++
+ .../browser/userscripts_browser_client.h      |  62 ++
+ components/user_scripts/common/BUILD.gn       |  49 ++
+ components/user_scripts/common/constants.h    |  21 +
+ components/user_scripts/common/error_utils.cc |  54 ++
+ components/user_scripts/common/error_utils.h  |  24 +
+ .../common/extension_message_generator.cc     |  29 +
+ .../common/extension_message_generator.h      |  11 +
+ .../user_scripts/common/extension_messages.cc |  40 +
+ .../user_scripts/common/extension_messages.h  |  71 ++
+ components/user_scripts/common/host_id.cc     |  31 +
+ components/user_scripts/common/host_id.h      |  35 +
+ .../user_scripts/common/script_constants.h    |  33 +
+ components/user_scripts/common/url_pattern.cc | 803 ++++++++++++++++++
+ components/user_scripts/common/url_pattern.h  | 302 +++++++
+ .../user_scripts/common/url_pattern_set.cc    | 334 ++++++++
+ .../user_scripts/common/url_pattern_set.h     | 161 ++++
+ components/user_scripts/common/user_script.cc | 317 +++++++
+ components/user_scripts/common/user_script.h  | 403 +++++++++
+ .../common/user_scripts_features.cc           |  32 +
+ .../common/user_scripts_features.h            |  34 +
+ components/user_scripts/common/view_type.cc   |  39 +
+ components/user_scripts/common/view_type.h    |  48 ++
+ components/user_scripts/renderer/BUILD.gn     |  67 ++
+ .../renderer/extension_frame_helper.cc        |  96 +++
+ .../renderer/extension_frame_helper.h         |  92 ++
+ .../user_scripts/renderer/injection_host.cc   |  12 +
+ .../user_scripts/renderer/injection_host.h    |  42 +
+ .../renderer/resources/greasemonkey_api.js    |  82 ++
+ .../user_scripts_renderer_resources.grd       |  14 +
+ .../user_scripts/renderer/script_context.cc   | 215 +++++
+ .../user_scripts/renderer/script_context.h    |  70 ++
+ .../user_scripts/renderer/script_injection.cc | 343 ++++++++
+ .../user_scripts/renderer/script_injection.h  | 155 ++++
+ .../renderer/script_injection_callback.cc     |  25 +
+ .../renderer/script_injection_callback.h      |  39 +
+ .../renderer/script_injection_manager.cc      | 417 +++++++++
+ .../renderer/script_injection_manager.h       | 102 +++
+ .../user_scripts/renderer/script_injector.h   |  96 +++
+ .../user_scripts/renderer/scripts_run_info.cc |  31 +
+ .../user_scripts/renderer/scripts_run_info.h  |  70 ++
+ .../renderer/user_script_injector.cc          | 228 +++++
+ .../renderer/user_script_injector.h           |  87 ++
+ .../user_scripts/renderer/user_script_set.cc  | 259 ++++++
+ .../user_scripts/renderer/user_script_set.h   | 102 +++
+ .../renderer/user_script_set_manager.cc       |  77 ++
+ .../renderer/user_script_set_manager.h        |  62 ++
+ .../renderer/user_scripts_dispatcher.cc       |  36 +
+ .../renderer/user_scripts_dispatcher.h        |  48 ++
+ .../renderer/user_scripts_renderer_client.cc  | 105 +++
+ .../renderer/user_scripts_renderer_client.h   |  36 +
+ .../renderer/web_ui_injection_host.cc         |  40 +
+ .../renderer/web_ui_injection_host.h          |  28 +
+ .../strings/userscripts_strings.grdp          |  55 ++
+ tools/gritsettings/resource_ids.spec          |   6 +
+ 111 files changed, 9579 insertions(+), 2 deletions(-)
+ create mode 100644 components/user_scripts/README.md
+ create mode 100755 components/user_scripts/android/BUILD.gn
+ create mode 100644 components/user_scripts/android/java/res/layout/accept_script_item.xml
+ create mode 100644 components/user_scripts/android/java/res/layout/accept_script_list.xml
+ create mode 100644 components/user_scripts/android/java/res/layout/scripts_preference.xml
+ create mode 100755 components/user_scripts/android/java/res/values/dimens.xml
+ create mode 100644 components/user_scripts/android/java/res/xml/userscripts_preferences.xml
+ create mode 100755 components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/FragmentWindowAndroid.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java
+ create mode 100644 components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsBridge.java
+ create mode 100755 components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java
+ create mode 100644 components/user_scripts/android/java_sources.gni
+ create mode 100644 components/user_scripts/android/user_scripts_bridge.cc
+ create mode 100644 components/user_scripts/android/user_scripts_bridge.h
+ create mode 100755 components/user_scripts/browser/BUILD.gn
+ create mode 100755 components/user_scripts/browser/file_task_runner.cc
+ create mode 100755 components/user_scripts/browser/file_task_runner.h
+ create mode 100644 components/user_scripts/browser/resources/browser_resources.grd
+ create mode 100644 components/user_scripts/browser/resources/user-script-ui/BUILD.gn
+ create mode 100644 components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html
+ create mode 100644 components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.js
+ create mode 100644 components/user_scripts/browser/ui/user_scripts_ui.cc
+ create mode 100644 components/user_scripts/browser/ui/user_scripts_ui.h
+ create mode 100755 components/user_scripts/browser/user_script_loader.cc
+ create mode 100755 components/user_scripts/browser/user_script_loader.h
+ create mode 100644 components/user_scripts/browser/user_script_pref_info.cc
+ create mode 100644 components/user_scripts/browser/user_script_pref_info.h
+ create mode 100644 components/user_scripts/browser/user_script_prefs.cc
+ create mode 100644 components/user_scripts/browser/user_script_prefs.h
+ create mode 100755 components/user_scripts/browser/userscripts_browser_client.cc
+ create mode 100755 components/user_scripts/browser/userscripts_browser_client.h
+ create mode 100755 components/user_scripts/common/BUILD.gn
+ create mode 100755 components/user_scripts/common/constants.h
+ create mode 100755 components/user_scripts/common/error_utils.cc
+ create mode 100755 components/user_scripts/common/error_utils.h
+ create mode 100755 components/user_scripts/common/extension_message_generator.cc
+ create mode 100755 components/user_scripts/common/extension_message_generator.h
+ create mode 100755 components/user_scripts/common/extension_messages.cc
+ create mode 100755 components/user_scripts/common/extension_messages.h
+ create mode 100755 components/user_scripts/common/host_id.cc
+ create mode 100755 components/user_scripts/common/host_id.h
+ create mode 100755 components/user_scripts/common/script_constants.h
+ create mode 100755 components/user_scripts/common/url_pattern.cc
+ create mode 100755 components/user_scripts/common/url_pattern.h
+ create mode 100755 components/user_scripts/common/url_pattern_set.cc
+ create mode 100755 components/user_scripts/common/url_pattern_set.h
+ create mode 100755 components/user_scripts/common/user_script.cc
+ create mode 100755 components/user_scripts/common/user_script.h
+ create mode 100644 components/user_scripts/common/user_scripts_features.cc
+ create mode 100644 components/user_scripts/common/user_scripts_features.h
+ create mode 100755 components/user_scripts/common/view_type.cc
+ create mode 100755 components/user_scripts/common/view_type.h
+ create mode 100755 components/user_scripts/renderer/BUILD.gn
+ create mode 100755 components/user_scripts/renderer/extension_frame_helper.cc
+ create mode 100755 components/user_scripts/renderer/extension_frame_helper.h
+ create mode 100755 components/user_scripts/renderer/injection_host.cc
+ create mode 100755 components/user_scripts/renderer/injection_host.h
+ create mode 100755 components/user_scripts/renderer/resources/greasemonkey_api.js
+ create mode 100755 components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd
+ create mode 100755 components/user_scripts/renderer/script_context.cc
+ create mode 100755 components/user_scripts/renderer/script_context.h
+ create mode 100755 components/user_scripts/renderer/script_injection.cc
+ create mode 100755 components/user_scripts/renderer/script_injection.h
+ create mode 100755 components/user_scripts/renderer/script_injection_callback.cc
+ create mode 100755 components/user_scripts/renderer/script_injection_callback.h
+ create mode 100755 components/user_scripts/renderer/script_injection_manager.cc
+ create mode 100755 components/user_scripts/renderer/script_injection_manager.h
+ create mode 100755 components/user_scripts/renderer/script_injector.h
+ create mode 100755 components/user_scripts/renderer/scripts_run_info.cc
+ create mode 100755 components/user_scripts/renderer/scripts_run_info.h
+ create mode 100755 components/user_scripts/renderer/user_script_injector.cc
+ create mode 100755 components/user_scripts/renderer/user_script_injector.h
+ create mode 100755 components/user_scripts/renderer/user_script_set.cc
+ create mode 100755 components/user_scripts/renderer/user_script_set.h
+ create mode 100755 components/user_scripts/renderer/user_script_set_manager.cc
+ create mode 100755 components/user_scripts/renderer/user_script_set_manager.h
+ create mode 100755 components/user_scripts/renderer/user_scripts_dispatcher.cc
+ create mode 100755 components/user_scripts/renderer/user_scripts_dispatcher.h
+ create mode 100755 components/user_scripts/renderer/user_scripts_renderer_client.cc
+ create mode 100755 components/user_scripts/renderer/user_scripts_renderer_client.h
+ create mode 100755 components/user_scripts/renderer/web_ui_injection_host.cc
+ create mode 100755 components/user_scripts/renderer/web_ui_injection_host.h
+ create mode 100755 components/user_scripts/strings/userscripts_strings.grdp
+
+diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
+--- a/chrome/android/BUILD.gn
++++ b/chrome/android/BUILD.gn
+@@ -252,6 +252,10 @@ android_resources("chrome_app_java_resources") {
+     "//third_party/androidx:androidx_preference_preference_java",
+     "//third_party/androidx:androidx_recyclerview_recyclerview_java",
+   ]
++
++  # this need to be into android_resources("chrome_app_java_resources") section because
++  # android:java_resources are packed *_percent.pak and placed in the executable folder
++  deps += [ "//components/user_scripts/android:java_resources" ]
+ }
+ 
+ if (enable_vr) {
+@@ -524,6 +528,7 @@ android_library("chrome_java") {
+     "//components/ukm/android:java",
+     "//components/url_formatter/android:url_formatter_java",
+     "//components/user_prefs/android:java",
++    "//components/user_scripts/android:java",
+     "//components/variations/android:variations_java",
+     "//components/version_info/android:version_constants_java",
+     "//components/viz/common:common_java",
+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
+@@ -86,6 +86,11 @@
+         android:key="useragent_settings"
+         android:order="20"
+         android:title="@string/prefs_useragent_settings"/>
++    <Preference
++        android:fragment="org.chromium.components.user_scripts.UserScriptsPreferences"
++        android:key="userscripts_settings"
++        android:order="20"
++        android:title="@string/prefs_userscripts_settings"/>
+     <Preference
+         android:fragment="org.chromium.chrome.browser.language.settings.LanguageSettings"
+         android:key="languages"
+diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
++++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+@@ -68,6 +68,7 @@ import org.chromium.content_public.browser.BrowserStartupController;
+ import org.chromium.content_public.browser.LoadUrlParams;
+ import org.chromium.ui.base.DeviceFormFactor;
+ import org.chromium.ui.widget.Toast;
++import org.chromium.chrome.browser.user_scripts.UserScriptsUtils;
+ 
+ import java.io.File;
+ 
+@@ -419,6 +420,11 @@ public class DownloadUtils {
+     public static boolean openFile(String filePath, String mimeType, String downloadGuid,
+             OTRProfileID otrProfileID, String originalUrl, String referrer,
+             @DownloadOpenSource int source, Context context) {
++        if (UserScriptsUtils.getInstance().openFile(filePath, mimeType, downloadGuid,
++                                                    originalUrl, referrer,
++                                                    getUriForItem(filePath))) {
++            return true;
++        }
+         DownloadMetrics.recordDownloadOpen(source, mimeType);
+         DownloadManagerService service = DownloadManagerService.getDownloadManagerService();
+ 
+diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
++++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+@@ -112,6 +112,8 @@ import java.util.Date;
+ import java.util.List;
+ import java.util.Locale;
+ 
++import org.chromium.chrome.browser.user_scripts.UserScriptsUtils;
++
+ /**
+  * 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
+@@ -297,6 +299,7 @@ public class ProcessInitializationHandler {
+ 
+                 DefaultBrowserInfo.initBrowserFetcher();
+ 
++                UserScriptsUtils.Initialize();
+                 AfterStartupTaskUtils.setStartupComplete();
+ 
+                 PartnerBrowserCustomizations.getInstance().setOnInitializeAsyncFinished(
+diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
+--- a/chrome/android/java_sources.gni
++++ b/chrome/android/java_sources.gni
+@@ -22,6 +22,7 @@ import("//components/feed/features.gni")
+ import("//components/offline_pages/buildflags/features.gni")
+ import("//components/omnibox/browser/test_java_sources.gni")
+ import("//device/vr/buildflags/buildflags.gni")
++import("//components/user_scripts/android/java_sources.gni")
+ 
+ chrome_java_sources += public_autofill_assistant_java_sources
+ 
+@@ -59,3 +60,5 @@ if (enable_arcore) {
+     "java/src/org/chromium/chrome/browser/vr/ArDelegateImpl.java",
+   ]
+ }
++
++chrome_java_sources += userscripts_java_sources
+diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
+--- a/chrome/browser/BUILD.gn
++++ b/chrome/browser/BUILD.gn
+@@ -3489,6 +3489,11 @@ static_library("browser") {
+       ]
+       deps += [ "//chrome/android/modules/dev_ui/provider:native" ]
+     }
++    deps += [
++      "//components/user_scripts/common",
++      "//components/user_scripts/browser",
++      "//components/user_scripts/android",
++    ]
+   } else {
+     #!is_android
+     sources += [
+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
+@@ -151,6 +151,7 @@
+ #include "components/translate/core/browser/translate_ranker_impl.h"
+ #include "components/translate/core/common/translate_util.h"
+ #include "components/ui_devtools/switches.h"
++#include "components/user_scripts/common/user_scripts_features.h"
+ #include "components/version_info/version_info.h"
+ #include "components/viz/common/features.h"
+ #include "components/viz/common/switches.h"
+@@ -6531,6 +6532,10 @@ const FeatureEntry kFeatureEntries[] = {
+      FEATURE_VALUE_TYPE(features::kUserDataSnapshot)},
+ #endif
+ 
++    {"enable-userscripts-log", flag_descriptions::kEnableLoggingUserScriptsName,
++     flag_descriptions::kEnableLoggingUserScriptsDescription, kOsDesktop | kOsAndroid,
++     FEATURE_VALUE_TYPE(user_scripts::features::kEnableLoggingUserScripts)},
++
+ #if defined(OS_WIN)
+     {"run-video-capture-service-in-browser",
+      flag_descriptions::kRunVideoCaptureServiceInBrowserProcessName,
+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
+@@ -4659,7 +4659,8 @@ ChromeContentBrowserClient::CreateURLLoaderThrottles(
+   chrome::mojom::DynamicParams dynamic_params = {
+       profile->GetPrefs()->GetBoolean(prefs::kForceGoogleSafeSearch),
+       profile->GetPrefs()->GetInteger(prefs::kForceYouTubeRestrict),
+-      profile->GetPrefs()->GetString(prefs::kAllowedDomainsForApps)};
++      profile->GetPrefs()->GetString(prefs::kAllowedDomainsForApps),
++      false /*-> allow_userscript, don't care */};
+   result.push_back(std::make_unique<GoogleURLLoaderThrottle>(
+ #if defined(OS_ANDROID)
+       client_data_header, is_tab_large_enough,
+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
+@@ -5393,6 +5393,11 @@ const char kUserDataSnapshotDescription[] =
+     "update and restoring them after a version rollback.";
+ #endif  // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+ 
++const char kEnableLoggingUserScriptsName[] = "Enable logging user scripts component";
++const char kEnableLoggingUserScriptsDescription[] =
++    "Enables logging for troubleshooting feature. "
++    "Enabling logs may make browsing slower.";
++
+ #if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_MAC)
+ const char kWebShareName[] = "Web Share";
+ const char kWebShareDescription[] =
+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
+@@ -3132,6 +3132,9 @@ extern const char kUserDataSnapshotName[];
+ extern const char kUserDataSnapshotDescription[];
+ #endif  // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+ 
++extern const char kEnableLoggingUserScriptsName[];
++extern const char kEnableLoggingUserScriptsDescription[];
++
+ #if defined(OS_WIN) || defined(OS_CHROMEOS) || defined(OS_MAC)
+ extern const char kWebShareName[];
+ extern const char kWebShareDescription[];
+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
+@@ -237,6 +237,7 @@
+ #include "components/ntp_tiles/popular_sites_impl.h"
+ #include "components/permissions/contexts/geolocation_permission_context_android.h"
+ #include "components/query_tiles/tile_service_prefs.h"
++#include "components/user_scripts/browser/user_script_prefs.h"
+ #else  // defined(OS_ANDROID)
+ #include "chrome/browser/cart/cart_service.h"
+ #include "chrome/browser/device_api/device_service_impl.h"
+@@ -1197,6 +1198,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
+   translate::TranslatePrefs::RegisterProfilePrefs(registry);
+   omnibox::RegisterProfilePrefs(registry);
+   ZeroSuggestProvider::RegisterProfilePrefs(registry);
++  user_scripts::UserScriptsPrefs::RegisterProfilePrefs(registry);
+ 
+ #if BUILDFLAG(ENABLE_SESSION_SERVICE)
+   RegisterSessionServiceLogProfilePrefs(registry);
+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
+@@ -45,6 +45,7 @@ source_set("profile") {
+     "//components/profile_metrics",
+     "//components/sync/driver",
+     "//components/variations",
++    "//components/user_scripts/browser",
+     "//content/public/browser",
+     "//extensions/buildflags",
+   ]
+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
+@@ -216,6 +216,8 @@
+ #include "chrome/browser/lacros/cert_db_initializer_factory.h"
+ #endif
+ 
++#include "components/user_scripts/browser/userscripts_browser_client.h"
++
+ namespace chrome {
+ 
+ void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
+@@ -516,6 +518,7 @@ void ChromeBrowserMainExtraPartsProfiles::
+ #endif
+   WebDataServiceFactory::GetInstance();
+   webrtc_event_logging::WebRtcEventLogManagerKeyedServiceFactory::GetInstance();
++  user_scripts::UserScriptsBrowserClient::GetInstance();
+ }
+ 
+ void ChromeBrowserMainExtraPartsProfiles::PreProfileInit() {
+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
+@@ -117,6 +117,8 @@
+ #include "extensions/common/manifest.h"
+ #endif
+ 
++#include "components/user_scripts/browser/userscripts_browser_client.h"
++
+ #if BUILDFLAG(ENABLE_SESSION_SERVICE)
+ #include "chrome/browser/sessions/session_service_factory.h"
+ #endif
+@@ -1539,6 +1541,13 @@ void ProfileManager::DoFinalInitForServices(Profile* profile,
+   }
+ 
+ #endif
++
++  user_scripts::UserScriptsBrowserClient* userscript_client =
++    user_scripts::UserScriptsBrowserClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->SetProfile(profile);
++  }
++
+ #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+   // Initialization needs to happen after extension system initialization (for
+   // extension::ManagementPolicy) and InitProfileUserPrefs (for setting the
+diff --git a/chrome/browser/profiles/renderer_updater.cc b/chrome/browser/profiles/renderer_updater.cc
+--- a/chrome/browser/profiles/renderer_updater.cc
++++ b/chrome/browser/profiles/renderer_updater.cc
+@@ -29,6 +29,8 @@
+ #include "chrome/browser/ash/login/signin/oauth2_login_manager_factory.h"
+ #endif
+ 
++#include "components/user_scripts/browser/user_script_prefs.h"
++
+ namespace {
+ 
+ #if BUILDFLAG(ENABLE_EXTENSIONS)
+@@ -79,6 +81,7 @@ RendererUpdater::RendererUpdater(Profile* profile) : profile_(profile) {
+   force_google_safesearch_.Init(prefs::kForceGoogleSafeSearch, pref_service);
+   force_youtube_restrict_.Init(prefs::kForceYouTubeRestrict, pref_service);
+   allowed_domains_for_apps_.Init(prefs::kAllowedDomainsForApps, pref_service);
++  activate_userscripts_.Init(user_scripts::prefs::kUserScriptsEnabled, pref_service);
+ 
+   pref_change_registrar_.Init(pref_service);
+   pref_change_registrar_.Add(
+@@ -93,6 +96,10 @@ RendererUpdater::RendererUpdater(Profile* profile) : profile_(profile) {
+       prefs::kAllowedDomainsForApps,
+       base::BindRepeating(&RendererUpdater::UpdateAllRenderers,
+                           base::Unretained(this)));
++  pref_change_registrar_.Add(
++      user_scripts::prefs::kUserScriptsEnabled,
++      base::BindRepeating(&RendererUpdater::UpdateAllRenderers,
++                          base::Unretained(this)));
+ }
+ 
+ RendererUpdater::~RendererUpdater() {
+@@ -241,5 +248,6 @@ void RendererUpdater::UpdateRenderer(
+       ->SetConfiguration(chrome::mojom::DynamicParams::New(
+           force_google_safesearch_.GetValue(),
+           force_youtube_restrict_.GetValue(),
+-          allowed_domains_for_apps_.GetValue()));
++          allowed_domains_for_apps_.GetValue(),
++          activate_userscripts_.GetValue()));
+ }
+diff --git a/chrome/browser/profiles/renderer_updater.h b/chrome/browser/profiles/renderer_updater.h
+--- a/chrome/browser/profiles/renderer_updater.h
++++ b/chrome/browser/profiles/renderer_updater.h
+@@ -82,6 +82,7 @@ class RendererUpdater : public KeyedService,
+ 
+   // Prefs that we sync to the renderers.
+   BooleanPrefMember force_google_safesearch_;
++  BooleanPrefMember activate_userscripts_;
+   IntegerPrefMember force_youtube_restrict_;
+   StringPrefMember allowed_domains_for_apps_;
+ 
+diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
++++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+@@ -85,6 +85,7 @@
+ #include "components/security_interstitials/content/urls.h"
+ #include "components/signin/public/base/signin_buildflags.h"
+ #include "components/site_engagement/content/site_engagement_service.h"
++#include "components/user_scripts/browser/ui/user_scripts_ui.h"
+ #include "content/public/browser/web_contents.h"
+ #include "content/public/browser/web_ui.h"
+ #include "content/public/common/content_client.h"
+@@ -678,6 +679,8 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui,
+     return &NewWebUI<UserActionsUI>;
+   if (url.host_piece() == chrome::kChromeUIVersionHost)
+     return &NewWebUI<VersionUI>;
++  if (url.host_piece() == user_scripts::kChromeUIUserScriptsHost)
++    return &NewWebUI<user_scripts::UserScriptsUI>;
+ 
+ #if !defined(OS_ANDROID)
+ #if !defined(OS_CHROMEOS)
+diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
+--- a/chrome/chrome_paks.gni
++++ b/chrome/chrome_paks.gni
+@@ -105,6 +105,7 @@ template("chrome_extra_paks") {
+       "$root_gen_dir/third_party/blink/public/resources/inspector_overlay_resources.pak",
+       "$root_gen_dir/ui/resources/webui_generated_resources.pak",
+       "$root_gen_dir/ui/resources/webui_resources.pak",
++      "$root_gen_dir/chrome/userscripts_browser_resources.pak",
+     ]
+     deps = [
+       "//base/tracing/protos:chrome_track_event_resources",
+@@ -120,6 +121,7 @@ template("chrome_extra_paks") {
+       "//third_party/blink/public:devtools_inspector_resources",
+       "//third_party/blink/public:resources",
+       "//ui/resources",
++      "//components/user_scripts/browser:userscripts_browser_resources_grit",
+     ]
+     if (defined(invoker.deps)) {
+       deps += invoker.deps
+diff --git a/chrome/common/renderer_configuration.mojom b/chrome/common/renderer_configuration.mojom
+--- a/chrome/common/renderer_configuration.mojom
++++ b/chrome/common/renderer_configuration.mojom
+@@ -12,6 +12,7 @@ struct DynamicParams {
+   bool force_safe_search = true;
+   int32 youtube_restrict = 0;
+   string allowed_domains_for_apps;
++  bool allow_userscript = false;
+ };
+ 
+ interface ChromeOSListener {
+diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
+--- a/chrome/renderer/BUILD.gn
++++ b/chrome/renderer/BUILD.gn
+@@ -171,6 +171,7 @@ static_library("renderer") {
+     "//components/feed:feature_list",
+     "//components/feed/content/renderer:feed_renderer",
+     "//components/history_clusters/core",
++    "//components/user_scripts/renderer",
+     "//components/network_hints/renderer",
+     "//components/no_state_prefetch/common",
+     "//components/no_state_prefetch/renderer",
+diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
+--- a/chrome/renderer/chrome_content_renderer_client.cc
++++ b/chrome/renderer/chrome_content_renderer_client.cc
+@@ -254,6 +254,9 @@
+ #include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate_impl.h"
+ #endif
+ 
++#include "components/user_scripts/common/user_scripts_features.h"
++#include "components/user_scripts/renderer/user_scripts_renderer_client.h"
++
+ using autofill::AutofillAgent;
+ using autofill::AutofillAssistantAgent;
+ using autofill::PasswordAutofillAgent;
+@@ -419,6 +422,12 @@ void ChromeContentRendererClient::RenderThreadStarted() {
+       WebString::FromASCII(extensions::kExtensionScheme));
+ #endif
+ 
++  user_scripts::UserScriptsRendererClient* userscript_client =
++    user_scripts::UserScriptsRendererClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->RenderThreadStarted();
++  }
++
+ #if BUILDFLAG(ENABLE_SPELLCHECK)
+   if (!spellcheck_)
+     InitSpellCheck();
+@@ -555,6 +564,13 @@ void ChromeContentRendererClient::RenderFrameCreated(
+       render_frame, registry);
+ #endif
+ 
++  user_scripts::UserScriptsRendererClient* userscript_client =
++    user_scripts::UserScriptsRendererClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->RenderFrameCreated(
++      render_frame, registry);
++  }
++
+ #if BUILDFLAG(ENABLE_PLUGINS)
+   new PepperHelper(render_frame);
+ #endif
+@@ -1546,7 +1562,14 @@ void ChromeContentRendererClient::RunScriptsAtDocumentStart(
+   ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentStart(
+       render_frame);
+   // |render_frame| might be dead by now.
++  static_assert(false, "Compiler error: extensions cannot be enabled with user scripts");
+ #endif
++  user_scripts::UserScriptsRendererClient* userscript_client =
++    user_scripts::UserScriptsRendererClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->RunScriptsAtDocumentStart(
++      render_frame);
++  }
+ }
+ 
+ void ChromeContentRendererClient::RunScriptsAtDocumentEnd(
+@@ -1555,7 +1578,14 @@ void ChromeContentRendererClient::RunScriptsAtDocumentEnd(
+   ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentEnd(
+       render_frame);
+   // |render_frame| might be dead by now.
++  static_assert(false, "Compiler error: extensions cannot be enabled with user scripts");
+ #endif
++  user_scripts::UserScriptsRendererClient* userscript_client =
++    user_scripts::UserScriptsRendererClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->RunScriptsAtDocumentEnd(
++      render_frame);
++  }
+ }
+ 
+ void ChromeContentRendererClient::RunScriptsAtDocumentIdle(
+@@ -1564,7 +1594,14 @@ void ChromeContentRendererClient::RunScriptsAtDocumentIdle(
+   ChromeExtensionsRendererClient::GetInstance()->RunScriptsAtDocumentIdle(
+       render_frame);
+   // |render_frame| might be dead by now.
++  static_assert(false, "Compiler error: extensions cannot be enabled with user scripts");
+ #endif
++  user_scripts::UserScriptsRendererClient* userscript_client =
++    user_scripts::UserScriptsRendererClient::GetInstance();
++  if (userscript_client) {
++    userscript_client->RunScriptsAtDocumentIdle(
++      render_frame);
++  }
+ }
+ 
+ void ChromeContentRendererClient::
+diff --git a/chrome/renderer/chrome_render_thread_observer.cc b/chrome/renderer/chrome_render_thread_observer.cc
+--- a/chrome/renderer/chrome_render_thread_observer.cc
++++ b/chrome/renderer/chrome_render_thread_observer.cc
+@@ -59,6 +59,8 @@
+ #include "third_party/blink/public/web/web_security_policy.h"
+ #include "third_party/blink/public/web/web_view.h"
+ 
++#include "components/user_scripts/renderer/user_scripts_renderer_client.h"
++
+ #if BUILDFLAG(ENABLE_EXTENSIONS)
+ #include "chrome/renderer/extensions/extension_localization_peer.h"
+ #endif
+@@ -256,6 +258,7 @@ void ChromeRenderThreadObserver::SetInitialConfiguration(
+ void ChromeRenderThreadObserver::SetConfiguration(
+     chrome::mojom::DynamicParamsPtr params) {
+   *GetDynamicConfigParams() = std::move(*params);
++  user_scripts::UserScriptsRendererClient::GetInstance()->ConfigurationUpdated();
+ }
+ 
+ void ChromeRenderThreadObserver::SetContentSettingRules(
+diff --git a/components/components_strings.grd b/components/components_strings.grd
+--- a/components/components_strings.grd
++++ b/components/components_strings.grd
+@@ -335,6 +335,7 @@
+       <part file="undo_strings.grdp" />
+       <part file="version_ui_strings.grdp" />
+       <part file="webapps_strings.grdp" />
++      <part file="user_scripts/strings/userscripts_strings.grdp" />
+ 
+       <if expr="not is_android and not is_ios">
+         <part file="management_strings.grdp" />
+diff --git a/components/user_scripts/README.md b/components/user_scripts/README.md
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/README.md
+@@ -0,0 +1,150 @@
++# Userscripts support for Bromite
++
++UserScript support is under user setting currently disabled by default: when disabled, no code that can impact navigation safety is active.
++
++Activation allows the use of userscripts in Bromite. It is possible to add them in two ways:
++- by selecting files from the file picker in the settings
++- downloading the scripts and opening it from downloads (only if ends with .user.js)
++The new imported scripts are disabled by default: they can be activated via the menu visible on the ui.
++
++Userscript support is currently the one provided by the desktop version. The enabled headers are:
++
++- `@name`
++- `@version`
++- `@description`
++- `@url` or `@homepage`
++- `@include`, `@exclude`, `@match`, `@exclude_match` for the url pattern (only http e https)
++- `@run-at`
++    - `document-start`
++        Start the script after the documentElement is created, but before anything else happens
++	- `document-end`
++        Start the script after the entire document is parsed. Same as DOMContentLoaded
++	- `document-idle`
++        Start the script sometime after DOMContentLoaded, as soon as the document is "idle". Currently this uses the simple heuristic of: min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no particular injection point is guaranteed
++
++The url-patterns are so defined:
++```
++// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>'
++// <scheme> := '*' | 'http' | 'https'
++// <host> := '*' | <IPv4 address> | [<IPv6 address>] |
++//           '*.' <anychar except '/' and '*'>+
++// <port> := [':' ('*' | <port number between 0 and 65535>)]
++// <path> := '/' <any chars>
++//
++// * Host is not used when the scheme is 'file'.
++// * The path can have embedded '*' characters which act as glob wildcards.
++// * '<all_urls>' is a special pattern that matches any valid URL that contains
++//   a valid scheme (as specified by valid_schemes_).
++// * The '*' scheme pattern excludes file URLs.
++//
++// Examples of valid patterns:
++// - http://*/*
++// - http://*/foo*
++// - https://*.google.com/foo*bar
++// - file://monkey*
++// - http://127.0.0.1/*
++// - http://[2607:f8b0:4005:805::200e]/*
++//
++// Examples of invalid patterns:
++// - http://* -- path not specified
++// - http://*foo/bar -- * not allowed as substring of host component
++// - http://foo.*.bar/baz -- * must be first component
++// - http:/bar -- scheme separator not found
++// - foo://* -- invalid scheme
++// - chrome:// -- we don't support chrome internal URLs
++```
++
++---
++## **Beware of the scripts you enter: they can be a source of security problems, you are injecting code into your navigation**.
++---
++## Technical aspects
++
++`user_scripts/common` and `user_scripts/renderer` is the closest to the current chromium code: few changes there, mostly eliminated the superfluous extension related code.
++
++In `user_scripts/browser` you find the actual management (in the browser process) and in `android` basically the settings ui.
++
++At startup it tries to read all files in the `userscripts folder` in `/data/user/0/org.bromite.bromite/app_chrome/userscripts`: this could be a critical process because a crash would prevent the browser from opening, and that's why there is a crash counter that automatically disables the feature after three attempts if it encounters a problem during startup.
++
++The java ui allows userscript management: addition, deletion and activation/deactivation. Any errors while reading the scripts are presented to the user: scripts with errors cannot be activated. There is also the visualization of the script source and the open of its homepage (if foreseen) in incognito browsing.
++
++There is also support for an on-line help at https://github.com/bromite/bromite/wiki/UserScripts.
++
++
++Entry points are `components/user_scripts/browser/userscripts_browser_client.cc` and `components/user_scripts/renderer/user_scripts_renderer_client.cc`: the two attach to the browser and the renderer process.
++
++for userscripts_browser_client.cc
++- `chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc`
++builds the browser side. also gpu process passes here, but the call is avoided.
++
++- `chrome/browser/profiles/profile_manager.cc`
++set the profile
++
++for renderer/user_scripts_renderer_client.cc
++- `chrome/renderer/chrome_content_renderer_client.cc`
++at the renderer side
++
++the two sides have a life of their own and can communicate only via ipc, the renderer does not have access to the disk while the browser does only via its own task runner file (`components/user_scripts/browser/file_task_runner.cc`).
++
++## BROWSER PROCESS
++Once the profile is set, it istance the `components/user_scripts/browser/user_script_loader.cc` and starts it.
++This loads all the files in the folder into the runner file and interprets them. Control then passes to
++`components/user_scripts/browser/user_script_prefs.cc` which verifies through the default profile what the user wants active.
++At that point it passes through IPC to the renderer only the list of active scripts.
++
++## RENDERER PROCESS
++Each time a frame is created, the script pattern is checked and it is injected into the three stages (START, IDLE, END).
++The logic is all in `components/user_scripts/renderer/user_script_set.cc`.
++
++## Simple example
++Here you find a working example that eliminates the google popup, useful in always incognito:
++```
++// ==UserScript==
++// @name         Remove Google Consent
++// @namespace    google
++// @version      0.0.1
++// @description  Autohide Accepts Cookies
++// @author       uazo
++// @match        https://*.google.com/search?*
++// @grant        none
++// @run-at       document-start
++// ==/UserScript==
++
++(function() {
++    'use strict';
++
++    var prepareStyleSheet = function() {
++        var style = document.createElement('style');
++        //style.setAttribute('media', 'screen');
++        style.appendChild(document.createTextNode(''));
++        document.head.appendChild(style);
++        style.sheet.insertRule('body { overflow:scroll !important;position:unset !important }');
++    };
++
++	var hideConsent = function() {
++		document.getElementById("lb").style.display = "none";
++	};
++
++    var checkElementThenRun = function(selector, func) {
++        var el = document.querySelector(selector);
++        if ( el == null ) {
++            if (window.requestAnimationFrame != undefined) {
++                window.requestAnimationFrame(function(){ checkElementThenRun(selector, func)});
++            } else {
++                document.addEventListener('readystatechange', function(e) {
++                    if (document.readyState == 'complete') {
++                        func();
++                    }
++                });
++            }
++        } else {
++            func();
++        }
++    }
++
++    document.cookie = 'CONSENT=YES+IT.it+V13+BX;domain=.google.com';
++    checkElementThenRun('head', prepareStyleSheet);
++    checkElementThenRun('#lb', hideConsent);
++})();
++```
++
++See also: https://github.com/bromite/bromite/pull/857
+diff --git a/components/user_scripts/android/BUILD.gn b/components/user_scripts/android/BUILD.gn
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/android/BUILD.gn
+@@ -0,0 +1,80 @@
++# This file is part of Bromite.
++
++# Bromite 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.
++
++# Bromite is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++
++import("//build/config/android/rules.gni")
++
++generate_jni("user_scripts_jni_headers") {
++  sources = [ "java/src/org/chromium/components/user_scripts/UserScriptsBridge.java" ]
++}
++
++android_resources("java_resources") {
++  sources = [
++    "java/res/xml/userscripts_preferences.xml",
++    "java/res/layout/accept_script_item.xml",
++    "java/res/layout/accept_script_list.xml",
++    "java/res/layout/scripts_preference.xml",
++    "java/res/values/dimens.xml"
++   ]
++
++  deps = [
++    "//components/browser_ui/strings/android:browser_ui_strings_grd",
++    "//components/browser_ui/styles/android:java_resources",
++    "//components/strings:components_strings_grd",
++    "//ui/android:ui_java_resources",
++  ]
++}
++
++android_library("java") {
++  sources = [
++    "java/src/org/chromium/components/user_scripts/FragmentWindowAndroid.java",
++    "java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java",
++    "java/src/org/chromium/components/user_scripts/UserScriptsBridge.java",
++    "java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java",
++    "java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java",
++    "java/src/org/chromium/components/user_scripts/ScriptListPreference.java",
++    "java/src/org/chromium/components/user_scripts/ScriptInfo.java",
++  ]
++  deps = [
++    ":java_resources",
++    "//base:base_java",
++    "//base:jni_java",
++    "//components/embedder_support/android:browser_context_java",
++    "//components/browser_ui/settings/android:java",
++    "//components/browser_ui/widget/android:java",
++    "//content/public/android:content_java",
++    "//components/prefs/android:java",
++    "//third_party/android_deps:android_support_v7_appcompat_java",
++    "//third_party/androidx:androidx_annotation_annotation_java",
++    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
++    "//third_party/androidx:androidx_preference_preference_java",
++    "//ui/android:ui_java",
++  ]
++  resources_package = "org.chromium.components.user_scripts"
++  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
++}
++
++source_set("android") {
++  sources = [
++    "user_scripts_bridge.cc",
++    "user_scripts_bridge.h",
++  ]
++  deps = [
++    ":user_scripts_jni_headers",
++    "//base",
++    "//components/user_scripts/browser",
++    "//components/permissions",
++    "//content/public/browser",
++  ]
++}
+diff --git a/components/user_scripts/android/java/res/layout/accept_script_item.xml b/components/user_scripts/android/java/res/layout/accept_script_item.xml
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/res/layout/accept_script_item.xml
+@@ -0,0 +1,160 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!-- Copyright 2017 The Chromium Authors. All rights reserved.
++     Use of this source code is governed by a BSD-style license that can be
++     found in the LICENSE file. -->
++
++<LinearLayout
++    xmlns:android="http://schemas.android.com/apk/res/android"
++    xmlns:app="http://schemas.android.com/apk/res-auto"
++    xmlns:tools="http://schemas.android.com/tools"
++    android:layout_width="match_parent"
++    android:layout_height="wrap_content"
++    android:orientation="vertical"
++    android:layout_gravity="center_vertical" >
++
++    <LinearLayout
++        android:layout_marginStart="0dp"
++        android:paddingStart="@dimen/draggable_list_item_padding"
++        android:paddingEnd="@dimen/draggable_list_item_padding"
++        style="@style/ListItemContainer">
++
++        <Switch
++            android:id="@+id/switch_widget"
++            android:layout_width="wrap_content"
++            android:layout_height="wrap_content"
++            android:paddingEnd="15dp"
++            android:focusable="false"
++            android:background="@null" />
++
++        <LinearLayout
++            android:layout_width="0dp"
++            android:layout_height="wrap_content"
++            android:layout_weight="1"
++            android:orientation="vertical"
++            android:layout_gravity="center_vertical" >
++
++            <TextView
++                android:id="@+id/title"
++                android:layout_width="match_parent"
++                android:layout_height="wrap_content"
++                style="@style/PreferenceTitle" />
++
++            <TextView
++                android:id="@+id/description"
++                android:layout_width="match_parent"
++                android:layout_height="wrap_content"
++                style="@style/PreferenceSummary" />
++
++            <LinearLayout
++                android:layout_width="match_parent"
++                android:layout_height="wrap_content"
++                android:layout_weight="1"
++                android:paddingTop="5dp"
++                android:layout_gravity="center_vertical" >
++
++               <TextView
++                    android:layout_width="wrap_content"
++                    android:layout_height="wrap_content"
++                    android:paddingEnd="5dp"
++                    style="@style/PreferenceSummary"
++                    android:text="@string/scripts_item_version"
++                    android:textStyle="bold" />
++
++                <TextView
++                    android:id="@+id/version"
++                    android:layout_width="match_parent"
++                    android:layout_height="wrap_content"
++                    style="@style/PreferenceSummary" />
++
++            </LinearLayout>
++
++            <LinearLayout
++                android:layout_width="match_parent"
++                android:layout_height="wrap_content"
++                android:layout_weight="1"
++                android:layout_gravity="center_vertical" >
++
++               <TextView
++                    android:layout_width="wrap_content"
++                    android:layout_height="wrap_content"
++                    android:paddingEnd="5dp"
++                    style="@style/PreferenceSummary"
++                    android:text="@string/scripts_item_filename"
++                    android:textStyle="bold" />
++
++                <TextView
++                    android:id="@+id/file"
++                    android:layout_width="match_parent"
++                    android:layout_height="wrap_content"
++                    style="@style/PreferenceSummary" />
++
++            </LinearLayout>
++
++            <LinearLayout
++                android:id="@+id/url_container"
++                android:layout_width="match_parent"
++                android:layout_height="wrap_content"
++                android:layout_weight="1"
++                android:layout_gravity="center_vertical" >
++
++               <TextView
++                    android:layout_width="wrap_content"
++                    android:layout_height="wrap_content"
++                    android:paddingEnd="5dp"
++                    style="@style/PreferenceSummary"
++                    android:text="@string/scripts_item_url"
++                    android:textStyle="bold" />
++
++                <TextView
++                    android:id="@+id/url"
++                    android:layout_width="match_parent"
++                    android:layout_height="wrap_content"
++                    android:autoLink="web"
++                    android:focusable="true"
++                    android:linksClickable="true" />
++
++            </LinearLayout>
++
++        </LinearLayout>
++
++        <org.chromium.components.browser_ui.widget.listmenu.ListMenuButton
++            android:id="@+id/more"
++            android:layout_width="wrap_content"
++            android:layout_height="match_parent"
++            android:paddingStart="@dimen/default_list_row_padding"
++            android:paddingEnd="@dimen/default_list_row_padding"
++            android:background="@null"
++            android:src="@drawable/ic_more_vert_24dp"
++            app:menuMaxWidth="@dimen/pref_scripts_item_popup_width"
++            app:tint="@color/default_icon_color_tint_list"
++            tools:ignore="ContentDescription" />
++
++    </LinearLayout>
++
++    <LinearLayout
++        android:id="@+id/error_layout"
++        android:layout_marginStart="0dp"
++        android:paddingStart="@dimen/draggable_list_item_padding"
++        android:paddingEnd="2dp"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        style="@style/ListItemContainer">
++
++        <org.chromium.ui.widget.ChromeImageView
++                android:id="@+id/icon"
++                android:layout_width="40dp"
++                android:layout_height="wrap_content"
++                android:paddingEnd="15dp"
++                android:adjustViewBounds="true"
++                android:importantForAccessibility="no"
++                app:srcCompat="@drawable/ic_error_outline_red_24dp"/>
++
++        <TextView
++            android:id="@+id/error"
++            android:layout_width="match_parent"
++            android:layout_height="wrap_content"
++            android:textColor="#F00"
++            style="@style/PreferenceSummary" />
++
++    </LinearLayout>
++</LinearLayout>
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/res/layout/accept_script_list.xml b/components/user_scripts/android/java/res/layout/accept_script_list.xml
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/res/layout/accept_script_list.xml
+@@ -0,0 +1,10 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!-- Copyright 2017 The Chromium Authors. All rights reserved.
++     Use of this source code is governed by a BSD-style license that can be
++     found in the LICENSE file. -->
++
++<androidx.recyclerview.widget.RecyclerView
++    xmlns:android="http://schemas.android.com/apk/res/android"
++    android:id="@+id/script_list"
++    android:layout_width="match_parent"
++    android:layout_height="wrap_content" />
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/res/layout/scripts_preference.xml b/components/user_scripts/android/java/res/layout/scripts_preference.xml
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/res/layout/scripts_preference.xml
+@@ -0,0 +1,40 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!-- Copyright 2017 The Chromium Authors. All rights reserved.
++     Use of this source code is governed by a BSD-style license that can be
++     found in the LICENSE file. -->
++
++<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
++    android:id="@+id/accept_scripts_list_container"
++    style="@style/PreferenceLayout"
++    android:layout_width="match_parent"
++    android:layout_height="wrap_content"
++    android:paddingStart="0dp"
++    android:paddingEnd="0dp"
++    android:padding="0dp"
++    android:orientation="vertical" >
++
++    <TextView
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        android:padding="@dimen/draggable_list_item_padding"
++        android:text="@string/scripts_list_description" />
++
++    <FrameLayout
++        android:id="@android:id/widget_frame"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content" />
++
++    <TextView
++        android:id="@+id/add_script"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        android:background="?attr/selectableItemBackground"
++        android:clickable="true"
++        android:gravity="center_vertical"
++        android:padding="@dimen/draggable_list_item_padding"
++        android:paddingStart="@dimen/pref_scripts_add_button_padding"
++        android:drawablePadding="@dimen/pref_scripts_add_button_padding"
++        android:text="@string/add_script"
++        style="@style/PreferenceTitle" />
++
++</LinearLayout>
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/res/values/dimens.xml b/components/user_scripts/android/java/res/values/dimens.xml
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/android/java/res/values/dimens.xml
+@@ -0,0 +1,11 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!-- Copyright 2014 The Chromium Authors. All rights reserved.
++     Use of this source code is governed by a BSD-style license that can be
++     found in the LICENSE file. -->
++
++<resources xmlns:tools="http://schemas.android.com/tools">
++
++    <dimen name="pref_scripts_add_button_padding">24dp</dimen>
++    <dimen name="pref_scripts_item_popup_width">260dp</dimen>
++
++</resources>
+diff --git a/components/user_scripts/android/java/res/xml/userscripts_preferences.xml b/components/user_scripts/android/java/res/xml/userscripts_preferences.xml
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/res/xml/userscripts_preferences.xml
+@@ -0,0 +1,34 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!--
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++-->
++
++<PreferenceScreen
++    xmlns:android="http://schemas.android.com/apk/res/android"
++    xmlns:app="http://schemas.android.com/apk/res-auto">
++
++    <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
++        android:key="enabled_switch"
++        android:title="@string/option_userscript_flag"
++        android:summaryOn="@string/option_userscript_flag_on"
++        android:summaryOff="@string/option_userscript_flag_off" />
++
++    <org.chromium.components.user_scripts.ScriptListPreference
++        android:key="script_list"
++        android:layout="@layout/scripts_preference"
++        android:widgetLayout="@layout/accept_script_list" />
++
++</PreferenceScreen>
+diff --git a/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java b/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java
+@@ -0,0 +1,84 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.chrome.browser.user_scripts;
++
++import android.content.Context;
++import android.content.Intent;
++import android.provider.Browser;
++import android.provider.MediaStore;
++import android.net.Uri;
++
++import org.chromium.base.ContextUtils;
++import org.chromium.base.ContentUriUtils;
++import org.chromium.base.IntentUtils;
++
++import org.chromium.chrome.browser.IntentHandler;
++import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
++import org.chromium.components.browser_ui.settings.SettingsLauncher;
++
++import org.chromium.components.user_scripts.UserScriptsPreferences;
++import org.chromium.components.user_scripts.UserScriptsBridge;
++import org.chromium.components.user_scripts.IUserScriptsUtils;
++
++public class UserScriptsUtils implements IUserScriptsUtils
++{
++    private static UserScriptsUtils instance;
++
++    private UserScriptsUtils() {}
++
++    public static void Initialize() {
++        instance = new UserScriptsUtils();
++        UserScriptsBridge.registerUtils(instance);
++    }
++
++    public static UserScriptsUtils getInstance() {
++        return instance;
++    }
++
++    public boolean openFile(String filePath, String mimeType, String downloadGuid,
++                            String originalUrl, String referrer, Uri contentUri) {
++        if (UserScriptsBridge.isEnabled() == false) return false;
++
++        Context context = ContextUtils.getApplicationContext();
++
++        if (ContentUriUtils.isContentUri(filePath))
++            filePath = ContentUriUtils.getDisplayName(contentUri, context,
++                MediaStore.MediaColumns.DISPLAY_NAME);
++
++        if (filePath.toUpperCase().endsWith(".USER.JS") == false) return false;
++
++        SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
++        Intent intent = settingsLauncher.createSettingsActivityIntent(
++                context, UserScriptsPreferences.class.getName(),
++                UserScriptsPreferences.createFragmentArgsForInstall(filePath));
++        IntentUtils.safeStartActivity(context, intent);
++
++        return true;
++    }
++
++    public void openSourceFile(String scriptKey) {
++        Context context = ContextUtils.getApplicationContext();
++
++        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("chrome://user-scripts/?key=" + scriptKey));
++        intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
++        intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
++        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
++        intent.setPackage(context.getPackageName());
++        IntentHandler.startChromeLauncherActivityForTrustedIntent(intent);
++    }
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/FragmentWindowAndroid.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/FragmentWindowAndroid.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/FragmentWindowAndroid.java
+@@ -0,0 +1,89 @@
++// Copyright 2019 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++package org.chromium.components.user_scripts;
++
++import android.annotation.TargetApi;
++import android.app.Activity;
++import android.content.Context;
++import android.content.Intent;
++import android.content.IntentSender;
++import android.os.Build;
++import android.view.View;
++
++import androidx.fragment.app.Fragment;
++
++import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate;
++import org.chromium.ui.base.ActivityAndroidPermissionDelegate;
++import org.chromium.ui.base.ImmutableWeakReference;
++import org.chromium.ui.base.IntentRequestTracker;
++import org.chromium.ui.base.IntentRequestTracker.Delegate;
++import org.chromium.ui.base.WindowAndroid;
++
++import java.lang.ref.WeakReference;
++
++/**
++ * Implements intent sending for a fragment based window. This should be created when
++ * onAttach() is called on the fragment, and destroyed when onDetach() is called.
++ */
++public class FragmentWindowAndroid extends WindowAndroid {
++    private Fragment mFragment;
++
++    private static class TrackerDelegateImpl implements Delegate {
++        private final Fragment mFragment;
++        // This WeakReference is purely to avoid gc churn of creating a new WeakReference in
++        // every getActivity call. It is not needed for correctness.
++        private ImmutableWeakReference<Activity> mActivityWeakRefHolder;
++
++        /**
++         * Create an instance of delegate for the given fragment that will own the
++         * IntentRequestTracker.
++         * @param fragment The fragment that owns the IntentRequestTracker.
++         */
++        private TrackerDelegateImpl(Fragment fragment) {
++            mFragment = fragment;
++        }
++
++        @Override
++        public boolean startActivityForResult(Intent intent, int requestCode) {
++            mFragment.startActivityForResult(intent, requestCode, null);
++            return true;
++        }
++
++        @Override
++        public boolean startIntentSenderForResult(IntentSender intentSender, int requestCode) {
++            try {
++                mFragment.startIntentSenderForResult(
++                        intentSender, requestCode, new Intent(), 0, 0, 0, null);
++            } catch (IntentSender.SendIntentException e) {
++                return false;
++            }
++            return true;
++        }
++
++        @Override
++        public void finishActivity(int requestCode) {
++            Activity activity = getActivity().get();
++            if (activity == null) return;
++            activity.finishActivity(requestCode);
++        }
++
++        @Override
++        public final WeakReference<Activity> getActivity() {
++            if (mActivityWeakRefHolder == null
++                    || mActivityWeakRefHolder.get() != mFragment.getActivity()) {
++                mActivityWeakRefHolder = new ImmutableWeakReference<>(mFragment.getActivity());
++            }
++            return mActivityWeakRefHolder;
++        }
++    }
++
++    FragmentWindowAndroid(Context context, Fragment fragment) {
++        super(context, IntentRequestTracker.createFromDelegate(new TrackerDelegateImpl(fragment)));
++        mFragment = fragment;
++
++        setKeyboardDelegate(new ActivityKeyboardVisibilityDelegate(getActivity()));
++        setAndroidPermissionDelegate(new ActivityAndroidPermissionDelegate(getActivity()));
++    }
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java
+@@ -0,0 +1,22 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++public interface IUserScriptsUtils {
++    public void openSourceFile(String scriptKey);
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java
+@@ -0,0 +1,37 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++import java.time.LocalDateTime;
++
++public class ScriptInfo {
++    public String Key;
++    public String Name;
++    public String Description;
++    public String Version;
++    public String FilePath;
++    public String UrlSource;
++
++    public boolean Enabled;
++    public LocalDateTime InstallTime;
++
++    public String ParserError;
++    public boolean ForceDisabled;
++
++    public ScriptInfo() {}
++}
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java
+@@ -0,0 +1,163 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++import android.content.Context;
++import android.view.LayoutInflater;
++import android.view.MotionEvent;
++import android.view.View;
++import android.view.ViewGroup;
++import android.view.accessibility.AccessibilityManager;
++import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
++import android.widget.ImageView;
++import android.widget.TextView;
++import android.widget.Switch;
++import android.widget.CompoundButton;
++import android.widget.LinearLayout;
++
++import androidx.annotation.DrawableRes;
++import androidx.annotation.NonNull;
++import androidx.core.view.ViewCompat;
++import androidx.recyclerview.widget.RecyclerView.ViewHolder;
++
++import org.chromium.components.browser_ui.widget.dragreorder.DragReorderableListAdapter;
++import org.chromium.components.browser_ui.widget.dragreorder.DragStateDelegate;
++import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
++import org.chromium.components.browser_ui.widget.listmenu.ListMenuButtonDelegate;
++import org.chromium.ui.widget.ChromeImageView;
++
++import java.util.ArrayList;
++import java.util.List;
++
++public class ScriptListBaseAdapter extends DragReorderableListAdapter<ScriptInfo> {
++
++    class ItemClickListener {
++        void onScriptOnOff(boolean Enabled) {}
++        void onScriptClicked() {}
++    }
++
++    static class ScriptInfoRowViewHolder extends ViewHolder {
++        private TextView mTitle;
++        private TextView mDescription;
++        private TextView mVersion;
++        private TextView mFile;
++        private TextView mUrl;
++        private TextView mError;
++        private Switch mSwitch;
++        private ChromeImageView mIcon;
++        private LinearLayout mErrorLayout;
++        private LinearLayout mUrlContainer;
++
++        private ListMenuButton mMoreButton;
++
++        private CompoundButton.OnCheckedChangeListener mOnOffListener;
++
++        ScriptInfoRowViewHolder(View view) {
++            super(view);
++
++            mSwitch = view.findViewById(R.id.switch_widget);
++            mTitle = view.findViewById(R.id.title);
++            mDescription = view.findViewById(R.id.description);
++            mVersion = view.findViewById(R.id.version);
++            mFile = view.findViewById(R.id.file);
++            mUrl = view.findViewById(R.id.url);
++            mUrlContainer = view.findViewById(R.id.url_container);
++            mError = view.findViewById(R.id.error);
++            mIcon = view.findViewById(R.id.icon);
++            mErrorLayout = view.findViewById(R.id.error_layout);
++
++            mMoreButton = view.findViewById(R.id.more);
++        }
++
++        protected void updateScriptInfo(ScriptInfo item) {
++            mSwitch.setOnCheckedChangeListener(null);
++            mSwitch.setChecked(item.Enabled);
++            mSwitch.setOnCheckedChangeListener(mOnOffListener);
++
++            mSwitch.setEnabled(true);
++            if (item.ForceDisabled) {
++                mSwitch.setEnabled(false);
++            }
++
++            mTitle.setText(item.Name);
++            mDescription.setText(item.Description);
++            mVersion.setText(item.Version);
++            mFile.setText(item.Key);
++            mUrl.setText(item.UrlSource);
++            mError.setText(item.ParserError);
++
++            mUrl.setVisibility(View.VISIBLE);
++            if (item.UrlSource == null || item.UrlSource.isEmpty()) {
++                mUrlContainer.setVisibility(View.GONE);
++            }
++            mErrorLayout.setVisibility(View.VISIBLE);
++            if (item.ParserError == null || item.ParserError.isEmpty()) {
++                mErrorLayout.setVisibility(View.GONE);
++            }
++        }
++
++        void setMenuButtonDelegate(@NonNull ListMenuButtonDelegate delegate) {
++            mMoreButton.setVisibility(View.VISIBLE);
++            mMoreButton.setDelegate(delegate);
++            // Set item row end padding 0 when MenuButton is visible.
++            ViewCompat.setPaddingRelative(itemView, ViewCompat.getPaddingStart(itemView),
++                    itemView.getPaddingTop(), 0, itemView.getPaddingBottom());
++        }
++
++        void setItemListener(@NonNull ItemClickListener listener) {
++            mOnOffListener = (buttonView, isChecked) -> listener.onScriptOnOff(isChecked);
++            mSwitch.setOnCheckedChangeListener(mOnOffListener);
++            itemView.setOnClickListener(view -> listener.onScriptClicked());
++        }
++    }
++
++    ScriptListBaseAdapter(Context context) {
++        super(context);
++    }
++
++    @Override
++    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
++        View row = LayoutInflater.from(viewGroup.getContext())
++                           .inflate(R.layout.accept_script_item, viewGroup, false);
++        return new ScriptInfoRowViewHolder(row);
++    }
++
++    @Override
++    public void onBindViewHolder(ViewHolder viewHolder, int i) {
++        ((ScriptInfoRowViewHolder) viewHolder).updateScriptInfo(mElements.get(i));
++    }
++
++    void setDisplayedScriptInfo(List<ScriptInfo> values) {
++        mElements = new ArrayList<>(values);
++        notifyDataSetChanged();
++    }
++
++    @Override
++    protected void setOrder(List<ScriptInfo> order) {
++    }
++
++    @Override
++    protected boolean isActivelyDraggable(ViewHolder viewHolder) {
++        return isPassivelyDraggable(viewHolder);
++    }
++
++    @Override
++    protected boolean isPassivelyDraggable(ViewHolder viewHolder) {
++        return viewHolder instanceof ScriptInfoRowViewHolder;
++    }
++}
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java
+@@ -0,0 +1,171 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++import static org.chromium.components.browser_ui.widget.listmenu.BasicListMenu.buildMenuListItem;
++import static org.chromium.components.browser_ui.widget.listmenu.BasicListMenu.buildMenuListItemWithEndIcon;
++
++import android.content.Context;
++import android.content.Intent;
++import android.provider.Browser;
++import android.net.Uri;
++import android.util.AttributeSet;
++import android.widget.TextView;
++import android.widget.Toast;
++
++import androidx.preference.Preference;
++import androidx.preference.PreferenceViewHolder;
++import androidx.recyclerview.widget.DividerItemDecoration;
++import androidx.recyclerview.widget.LinearLayoutManager;
++import androidx.recyclerview.widget.RecyclerView;
++import androidx.recyclerview.widget.RecyclerView.ViewHolder;
++
++import org.chromium.ui.base.WindowAndroid;
++import org.chromium.ui.base.ActivityWindowAndroid;
++
++import org.chromium.base.ApplicationStatus;
++import org.chromium.base.ContextUtils;
++import org.chromium.components.browser_ui.widget.TintedDrawable;
++import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
++import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
++import org.chromium.components.browser_ui.widget.listmenu.ListMenuItemProperties;
++import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
++
++import org.chromium.components.user_scripts.ScriptListBaseAdapter;
++import org.chromium.components.user_scripts.FragmentWindowAndroid;
++
++import java.util.List;
++
++public class ScriptListPreference extends Preference {
++    private static class ScriptListAdapter
++            extends ScriptListBaseAdapter {
++        private final Context mContext;
++
++        ScriptListAdapter(Context context) {
++            super(context);
++            mContext = context;
++        }
++
++        @Override
++        public void onBindViewHolder(ViewHolder holder, int position) {
++            super.onBindViewHolder(holder, position);
++
++            final ScriptInfo info = getItemByPosition(position);
++
++            ModelList menuItems = new ModelList();
++
++            menuItems.add(buildMenuListItem(R.string.remove, 0, 0, true));
++            menuItems.add(buildMenuListItem(R.string.scripts_view_source, 0, 0,
++                                                    info.ParserError == null || info.ParserError.isEmpty()));
++
++            ListMenu.Delegate delegate = (model) -> {
++                int textId = model.get(ListMenuItemProperties.TITLE_ID);
++                if (textId == R.string.remove) {
++                    UserScriptsBridge.RemoveScript(info.Key);
++                } else if (textId == R.string.scripts_view_source) {
++                    UserScriptsBridge.getUtils().openSourceFile(info.Key);
++                }
++            };
++            ((ScriptInfoRowViewHolder) holder)
++                    .setMenuButtonDelegate(() -> new BasicListMenu(mContext, menuItems, delegate));
++            ((ScriptInfoRowViewHolder) holder)
++                    .setItemListener(new ScriptListBaseAdapter.ItemClickListener() {
++                        @Override
++                        public void onScriptOnOff(boolean Enabled) {
++                            UserScriptsBridge.SetScriptEnabled(info.Key, Enabled);
++                        }
++
++                        @Override
++                        public void onScriptClicked() {}
++                    });
++        }
++
++        // @Override
++        public void onDataUpdated(boolean enabled) {
++            List<ScriptInfo> list = UserScriptsBridge.getUserScriptItems();
++            if (enabled == false) {
++                for (ScriptInfo script : list) {
++                    script.ForceDisabled = true;
++                }
++            }
++            setDisplayedScriptInfo(list);
++        }
++    }
++
++    private TextView mAddButton;
++    private RecyclerView mRecyclerView;
++    private ScriptListAdapter mAdapter;
++    private FragmentWindowAndroid mWindowAndroid;
++
++    public ScriptListPreference(Context context, AttributeSet attrs) {
++        super(context, attrs);
++        mAdapter = new ScriptListAdapter(context);
++    }
++
++    public void setWindowAndroid(FragmentWindowAndroid windowAndroid) {
++        mWindowAndroid = windowAndroid;
++    }
++
++    @Override
++    public void onBindViewHolder(PreferenceViewHolder holder) {
++        super.onBindViewHolder(holder);
++
++        mAddButton = (TextView) holder.findViewById(R.id.add_script);
++        mAddButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
++                TintedDrawable.constructTintedDrawable(
++                        getContext(), R.drawable.plus, R.color.default_control_color_active),
++                null, null, null);
++        mAddButton.setOnClickListener(view -> {
++            UserScriptsBridge.SelectAndAddScriptFromFile(mWindowAndroid);
++        });
++
++        mRecyclerView = (RecyclerView) holder.findViewById(R.id.script_list);
++        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
++        mRecyclerView.setLayoutManager(layoutManager);
++        mRecyclerView.addItemDecoration(
++                new DividerItemDecoration(getContext(), layoutManager.getOrientation()));
++        mRecyclerView.setEnabled(this.isEnabled());
++        UserScriptsBridge.RegisterLoadCallback(this);
++
++        // We do not want the RecyclerView to be announced by screen readers every time
++        // the view is bound.
++        if (mRecyclerView.getAdapter() != mAdapter) {
++            mRecyclerView.setAdapter(mAdapter);
++            // Initialize script list.
++            mAdapter.onDataUpdated(this.isEnabled());
++        }
++    }
++
++    @Override
++    public void setEnabled (boolean enabled) {
++        super.setEnabled(enabled);
++        if (mRecyclerView != null) mRecyclerView.setEnabled(enabled);
++        NotifyScriptsChanged();
++    }
++
++    public void NotifyScriptsChanged() {
++        mAdapter.onDataUpdated(this.isEnabled());
++    }
++
++    public void OnUserScriptLoaded(boolean result, String error) {
++        if (result == false) {
++            Toast toast = Toast.makeText(getContext(), error, Toast.LENGTH_LONG);
++            toast.show();
++        }
++    }
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsBridge.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsBridge.java
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsBridge.java
+@@ -0,0 +1,200 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++import java.util.ArrayList;
++import java.util.List;
++import java.lang.ref.WeakReference;
++
++import org.json.JSONArray;
++import org.json.JSONException;
++import org.json.JSONObject;
++
++import android.content.Context;
++import android.content.Intent;
++import android.net.Uri;
++import androidx.annotation.Nullable;
++import android.app.AlertDialog;
++import android.content.DialogInterface;
++
++import org.chromium.base.annotations.CalledByNative;
++import org.chromium.base.annotations.JNINamespace;
++import org.chromium.base.annotations.NativeMethods;
++import org.chromium.base.Log;
++import org.chromium.ui.base.WindowAndroid;
++
++import org.chromium.components.user_scripts.ScriptListPreference;
++import org.chromium.components.user_scripts.IUserScriptsUtils;
++
++@JNINamespace("user_scripts")
++public class UserScriptsBridge {
++    private static final String TAG = "UserScript";
++
++    static WeakReference<ScriptListPreference> observer;
++
++    private static IUserScriptsUtils utilInstance;
++
++    public static void registerUtils(IUserScriptsUtils instance) {
++        utilInstance = instance;
++    }
++
++    public static IUserScriptsUtils getUtils() {
++        return utilInstance;
++    }
++
++    public static boolean isEnabled() {
++        return UserScriptsBridgeJni.get().isEnabled();
++    }
++
++    public static void setEnabled(boolean enabled) {
++        UserScriptsBridgeJni.get().setEnabled(enabled);
++    }
++
++    public static void RemoveScript(String key) {
++        UserScriptsBridgeJni.get().removeScript(key);
++    }
++
++    public static void SetScriptEnabled(String key,
++                                        boolean enabled) {
++        UserScriptsBridgeJni.get().setScriptEnabled(key, enabled);
++    }
++
++    public static void Reload() {
++        UserScriptsBridgeJni.get().reload();
++    }
++
++    public static void SelectAndAddScriptFromFile(WindowAndroid window) {
++        Context context = window.getContext().get();
++
++        Intent fileSelector = new Intent(Intent.ACTION_OPEN_DOCUMENT);
++        fileSelector.addCategory(Intent.CATEGORY_OPENABLE);
++        fileSelector.setType("*/*");
++        fileSelector.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
++
++        window.showIntent(fileSelector,
++            new WindowAndroid.IntentCallback() {
++                @Override
++                public void onIntentCompleted(int resultCode, Intent data) {
++                    if (data == null) return;
++                    Uri filePath = data.getData();
++                    TryToInstall(context, filePath.toString());
++                }
++            },
++            null);
++    }
++
++    public static void TryToInstall(Context context, String ScriptFullPath) {
++        DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
++            @Override
++            public void onClick(DialogInterface dialog, int which) {
++                switch (which){
++                    case DialogInterface.BUTTON_POSITIVE:
++                        UserScriptsBridgeJni.get().tryToInstall(ScriptFullPath);
++                        break;
++
++                    case DialogInterface.BUTTON_NEGATIVE:
++                        break;
++                }
++            }
++        };
++
++        String message = context.getString(R.string.ask_to_install, ScriptFullPath);
++
++        AlertDialog.Builder builder = new AlertDialog.Builder(context);
++        builder.setMessage(message)
++            .setPositiveButton(context.getString(R.string.yes), dialogClickListener)
++            .setNegativeButton(context.getString(R.string.no), dialogClickListener)
++            .show();
++    }
++
++    public static List<ScriptInfo> getUserScriptItems() {
++        List<ScriptInfo> list = new ArrayList<>();
++        try {
++            String json = UserScriptsBridgeJni.get().getScriptsInfo();
++
++            JSONObject jsonObject = new JSONObject(json);
++
++            JSONArray scripts = jsonObject.names();
++            if (scripts != null) {
++                Log.i(TAG, "User Scripts Loaded: %s", json);
++                Log.i(TAG, "Totals scripts: %s", Integer.toString(scripts.length()));
++                for (int i = 0; i < scripts.length(); i++) {
++                    String key = (String) scripts.get(i);
++                    JSONObject script = jsonObject.getJSONObject(key);
++
++                    ScriptInfo si = new ScriptInfo();
++                    si.Key = key;
++                    list.add(si);
++
++                    if(script.has("name")) si.Name = script.getString("name");
++                    if(script.has("description")) si.Description = script.getString("description");
++                    if(script.has("version")) si.Version = script.getString("version");
++                    if(script.has("file_path")) si.FilePath = script.getString("file_path");
++                    if(script.has("url_source")) si.UrlSource = script.getString("url_source");
++                    if(script.has("parser_error")) si.ParserError = script.getString("parser_error");
++                    if(script.has("force_disabled")) si.ForceDisabled = script.getBoolean("force_disabled");;
++                    si.Enabled = script.getBoolean("enabled");
++                }
++            } else {
++                Log.i(TAG, "User Scripts list empty");
++            }
++        } catch (Exception e) {
++            Log.e(TAG, "User Scripts Load Error", e.toString());
++        }
++        return list;
++    }
++
++    public static void RegisterLoadCallback(ScriptListPreference caller) {
++        UserScriptsBridgeJni.get().registerLoadCallback();
++        observer = new WeakReference<ScriptListPreference>(caller);
++    }
++
++    @CalledByNative
++    private static void shouldRefreshUserScriptList() {
++        ScriptListPreference reference = observer.get();
++        if (reference != null) {
++            reference.NotifyScriptsChanged();
++        }
++    }
++
++    @CalledByNative
++    private static void onUserScriptLoaded(boolean result, String error) {
++        ScriptListPreference reference = observer.get();
++        if (reference != null) {
++            reference.OnUserScriptLoaded(result, error);
++        }
++    }
++
++    @NativeMethods
++    interface Natives {
++        boolean isEnabled();
++        void setEnabled(boolean enabled);
++
++        String getScriptsInfo();
++
++        void removeScript(String scriptKey);
++        void setScriptEnabled(String scriptKey, boolean enabled);
++
++        void reload();
++        void selectAndAddScriptFromFile(WindowAndroid window);
++        void tryToInstall(String scriptFullPath);
++
++        void registerLoadCallback();
++    }
++
++}
+diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java
+@@ -0,0 +1,116 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.user_scripts;
++
++import android.app.Activity;
++import android.content.Context;
++import android.content.Intent;
++import android.os.Bundle;
++import android.provider.Browser;
++import android.net.Uri;
++import android.view.MenuItem;
++import android.view.View;
++
++import androidx.preference.Preference;
++import androidx.preference.PreferenceFragmentCompat;
++
++import org.chromium.base.Log;
++import org.chromium.ui.base.WindowAndroid;
++import org.chromium.ui.base.ActivityWindowAndroid;
++
++import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
++import org.chromium.components.browser_ui.settings.SettingsUtils;
++import org.chromium.components.browser_ui.settings.TextMessagePreference;
++
++import org.chromium.components.user_scripts.UserScriptsBridge;
++import org.chromium.components.user_scripts.FragmentWindowAndroid;
++
++public class UserScriptsPreferences
++        extends PreferenceFragmentCompat
++        implements SettingsUtils.ISupportHelpAndFeedback {
++
++    private static final String PREF_ENABLED_SWITCH = "enabled_switch";
++    private static final String PREF_SCRIPTLISTPREFERENCE = "script_list";
++
++    public static final String EXTRA_SCRIPT_FILE = "org.chromium.chrome.preferences.script_file";
++
++    private FragmentWindowAndroid mWindowAndroid;
++
++    @Override
++    public void onDestroy() {
++        if (mWindowAndroid != null) mWindowAndroid.destroy();
++        super.onDestroy();
++    }
++
++    @Override
++    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
++        getActivity().setTitle(R.string.prefs_userscripts_settings);
++        SettingsUtils.addPreferencesFromResource(this, R.xml.userscripts_preferences);
++
++        ChromeSwitchPreference enabledSwitch =
++                (ChromeSwitchPreference) findPreference(PREF_ENABLED_SWITCH);
++        ScriptListPreference listPreference =
++                (ScriptListPreference) findPreference(PREF_SCRIPTLISTPREFERENCE);
++
++        boolean enabled = UserScriptsBridge.isEnabled();
++        enabledSwitch.setChecked(enabled);
++        listPreference.setEnabled(enabled);
++        enabledSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
++            UserScriptsBridge.setEnabled((boolean) newValue);
++            listPreference.setEnabled((boolean) newValue);
++            return true;
++        });
++
++        mWindowAndroid = new FragmentWindowAndroid(getContext(), this);
++        listPreference.setWindowAndroid(mWindowAndroid);
++    }
++
++    @Override
++    public void onActivityResult(int requestCode, int resultCode, Intent data) {
++        // handle picker callback from SelectFileDialog
++        mWindowAndroid.getIntentRequestTracker().onActivityResult(requestCode, resultCode, data);
++    }
++
++    public void onHelpAndFeebackPressed() {
++        Context context = getContext();
++
++        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/bromite/bromite/wiki/UserScripts"));
++        // Let Chromium know that this intent is from Chromium, so that it does not close the app when
++        // the user presses 'back' button.
++        intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
++        intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
++        intent.setPackage(context.getPackageName());
++        context.startActivity(intent);
++    }
++
++    public static Bundle createFragmentArgsForInstall(String filePath) {
++        Bundle fragmentArgs = new Bundle();
++        fragmentArgs.putSerializable(EXTRA_SCRIPT_FILE, filePath);
++        return fragmentArgs;
++    }
++
++    @Override
++    public void onActivityCreated(Bundle savedInstanceState) {
++        super.onActivityCreated(savedInstanceState);
++
++        String scriptToInstall = (String)getArguments().getSerializable(EXTRA_SCRIPT_FILE);
++        if (scriptToInstall != null) {
++            UserScriptsBridge.TryToInstall(getContext(), scriptToInstall);
++        }
++    }
++}
+diff --git a/components/user_scripts/android/java_sources.gni b/components/user_scripts/android/java_sources.gni
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/java_sources.gni
+@@ -0,0 +1,18 @@
++# This file is part of Bromite.
++
++# Bromite 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.
++
++# Bromite is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++
++userscripts_java_sources = [
++  "//components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java",
++]
+diff --git a/components/user_scripts/android/user_scripts_bridge.cc b/components/user_scripts/android/user_scripts_bridge.cc
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/user_scripts_bridge.cc
+@@ -0,0 +1,173 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include <jni.h>
++#include <algorithm>
++#include <string>
++#include <vector>
++#include <sstream>
++#include <iterator>
++
++#include "base/android/callback_android.h"
++#include "base/android/jni_android.h"
++#include "base/android/jni_array.h"
++#include "base/android/jni_string.h"
++#include "base/android/scoped_java_ref.h"
++#include "ui/android/window_android.h"
++
++#include "components/user_scripts/android/user_scripts_jni_headers/UserScriptsBridge_jni.h"
++#include "../browser/userscripts_browser_client.h"
++#include "user_scripts_bridge.h"
++
++using base::android::AttachCurrentThread;
++using base::android::ConvertJavaStringToUTF8;
++using base::android::ConvertUTF16ToJavaString;
++using base::android::ConvertUTF8ToJavaString;
++using base::android::JavaParamRef;
++using base::android::JavaRef;
++using base::android::ScopedJavaGlobalRef;
++using base::android::ScopedJavaLocalRef;
++using content::BrowserContext;
++
++namespace {
++
++user_scripts::UserScriptsBrowserClient* GetUserScriptsBrowserClient() {
++  return user_scripts::UserScriptsBrowserClient::GetInstance();
++}
++
++class CallbackObserver : public user_scripts::UserScriptLoader::Observer {
++  private:
++    void OnScriptsLoaded(user_scripts::UserScriptLoader* loader,
++                         content::BrowserContext* browser_context) override {
++        user_scripts::ShouldRefreshUserScriptList(base::android::AttachCurrentThread());
++    }
++
++    void OnUserScriptLoaded(user_scripts::UserScriptLoader* loader,
++                            bool result, const std::string& error) override {
++        user_scripts::OnUserScriptLoaded(base::android::AttachCurrentThread(),
++          result, error);
++    }
++
++    void OnUserScriptLoaderDestroyed(user_scripts::UserScriptLoader* loader) override {}
++};
++
++CallbackObserver* g_userscripts_loader_observer = NULL;
++
++}
++
++namespace user_scripts {
++
++static jboolean JNI_UserScriptsBridge_IsEnabled(
++                    JNIEnv* env) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return false;
++  return client->GetPrefs()->IsEnabled();
++}
++
++static void JNI_UserScriptsBridge_SetEnabled(
++                    JNIEnv* env,
++                    jboolean is_enabled) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++  client->GetPrefs()->SetEnabled(is_enabled);
++  client->GetLoader()->StartLoad();
++}
++
++static base::android::ScopedJavaLocalRef<jstring> JNI_UserScriptsBridge_GetScriptsInfo(
++                    JNIEnv* env) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return ConvertUTF8ToJavaString(env, {});
++
++  std::string json = client->GetPrefs()->GetScriptsInfo();
++  return ConvertUTF8ToJavaString(env, json);
++}
++
++static void JNI_UserScriptsBridge_RemoveScript(
++                    JNIEnv* env,
++                    const JavaParamRef<jstring>& jscript_key) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  std::string script_key = base::android::ConvertJavaStringToUTF8(jscript_key);
++  client->GetLoader()->RemoveScript(script_key);
++}
++
++static void JNI_UserScriptsBridge_SetScriptEnabled(
++                    JNIEnv* env,
++                    const JavaParamRef<jstring>& jscript_key,
++                    jboolean is_enabled) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  std::string script_key = base::android::ConvertJavaStringToUTF8(jscript_key);
++  client->GetLoader()->SetScriptEnabled(script_key, is_enabled);
++}
++
++static void JNI_UserScriptsBridge_Reload(
++                    JNIEnv* env) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  client->GetLoader()->StartLoad();
++  user_scripts::ShouldRefreshUserScriptList(env);
++}
++
++static void JNI_UserScriptsBridge_SelectAndAddScriptFromFile(
++                    JNIEnv* env,
++                    const JavaParamRef<jobject>& jwindow_android) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  client->GetLoader()->SelectAndAddScriptFromFile(
++                    ui::WindowAndroid::FromJavaWindowAndroid(jwindow_android));
++}
++
++static void JNI_UserScriptsBridge_TryToInstall(JNIEnv* env,
++                         const JavaParamRef<jstring>& jscript_path) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  std::string script_path = base::android::ConvertJavaStringToUTF8(jscript_path);
++  base::FilePath path(script_path);
++
++  client->GetLoader()->TryToInstall(path);
++}
++
++static void JNI_UserScriptsBridge_RegisterLoadCallback(
++                    JNIEnv* env) {
++  user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
++  if (client == NULL) return;
++
++  if (g_userscripts_loader_observer == NULL) {
++    g_userscripts_loader_observer = new CallbackObserver();
++    client->GetLoader()->AddObserver(g_userscripts_loader_observer);
++  }
++}
++
++static void ShouldRefreshUserScriptList(JNIEnv* env) {
++  Java_UserScriptsBridge_shouldRefreshUserScriptList(env);
++}
++
++static void OnUserScriptLoaded(JNIEnv* env,
++              bool result, const std::string& error) {
++  base::android::ScopedJavaLocalRef<jstring> j_error =
++    base::android::ConvertUTF8ToJavaString(env, error);
++
++  Java_UserScriptsBridge_onUserScriptLoaded(env, result, j_error);
++}
++
++}
+diff --git a/components/user_scripts/android/user_scripts_bridge.h b/components/user_scripts/android/user_scripts_bridge.h
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/android/user_scripts_bridge.h
+@@ -0,0 +1,31 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_
++#define COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_
++
++#include <jni.h>
++
++namespace user_scripts {
++
++static void ShouldRefreshUserScriptList(JNIEnv* env);
++static void OnUserScriptLoaded(JNIEnv* env,
++              bool result, const std::string& error);
++
++}  // namespace user_scripts
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/BUILD.gn b/components/user_scripts/browser/BUILD.gn
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/BUILD.gn
+@@ -0,0 +1,82 @@
++# This file is part of Bromite.
++
++# Bromite 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.
++
++# Bromite is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++
++import("//build/config/features.gni")
++import("//tools/grit/grit_rule.gni")
++
++group("browser") {
++  public_deps = [
++    "//components/user_scripts/browser:browser_sources",
++  ]
++}
++
++source_set("browser_sources") {
++  visibility = [ "./*" ]
++
++  sources = [
++    "file_task_runner.cc",
++    "file_task_runner.h",
++    "userscripts_browser_client.cc",
++    "userscripts_browser_client.h",
++    "user_script_loader.cc",
++    "user_script_loader.h",
++    "user_script_prefs.cc",
++    "user_script_prefs.h",
++    "user_script_pref_info.cc",
++    "user_script_pref_info.h",
++    "ui/user_scripts_ui.h",
++    "ui/user_scripts_ui.cc",
++  ]
++
++  deps = [
++    ":userscripts_browser_resources",
++    "//base:i18n",
++    "//components/keyed_service/content",
++    "//components/keyed_service/core",
++    "//components/pref_registry",
++    "//components/prefs",
++    "//content/public/browser",
++    "//crypto:platform",
++    "//components/user_scripts/common",
++    "//services/device/public/mojom",
++    "//services/preferences/public/cpp",
++    "//services/service_manager/public/cpp",
++    "//third_party/blink/public/common",
++    "//ui/display",
++  ]
++
++  public_deps = [
++    "//content/public/common",
++  ]
++
++  configs += [
++    "//build/config:precompiled_headers",
++    "//build/config/compiler:wexit_time_destructors",
++  ]
++}
++
++group("closure_compile") {
++  deps = [ "resources/user-script-ui:closure_compile" ]
++}
++
++grit("userscripts_browser_resources") {
++  source = "resources/browser_resources.grd"
++
++  output_dir = "$root_gen_dir/chrome"
++  outputs = [
++    "grit/userscripts_browser_resources.h",
++    "userscripts_browser_resources.pak",
++  ]
++}
+diff --git a/components/user_scripts/browser/file_task_runner.cc b/components/user_scripts/browser/file_task_runner.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/file_task_runner.cc
+@@ -0,0 +1,40 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include "file_task_runner.h"
++
++#include "base/sequenced_task_runner.h"
++#include "base/task/lazy_thread_pool_task_runner.h"
++#include "base/task/task_traits.h"
++
++namespace user_scripts {
++
++namespace {
++
++base::LazyThreadPoolSequencedTaskRunner g_us_task_runner =
++    LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(
++        base::TaskTraits(base::MayBlock(),
++                         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
++                         base::TaskPriority::USER_VISIBLE));
++
++}  // namespace
++
++scoped_refptr<base::SequencedTaskRunner> GetUserScriptsFileTaskRunner() {
++  return g_us_task_runner.Get();
++}
++
++}  // namespace user_scripts
+diff --git a/components/user_scripts/browser/file_task_runner.h b/components/user_scripts/browser/file_task_runner.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/file_task_runner.h
+@@ -0,0 +1,34 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
++#define USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
++
++#include "base/memory/ref_counted.h"
++#include "base/task/task_traits.h"
++
++namespace base {
++class SequencedTaskRunner;
++}
++
++namespace user_scripts {
++
++scoped_refptr<base::SequencedTaskRunner> GetUserScriptsFileTaskRunner();
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
+diff --git a/components/user_scripts/browser/resources/browser_resources.grd b/components/user_scripts/browser/resources/browser_resources.grd
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/resources/browser_resources.grd
+@@ -0,0 +1,14 @@
++<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
++  <outputs>
++    <output filename="grit/userscripts_browser_resources.h" type="rc_header">
++      <emit emit_type='prepend'></emit>
++    </output>
++    <output filename="userscripts_browser_resources.pak" type="data_package" />
++  </outputs>
++  <release seq="1">
++    <includes>
++      <include name="IDR_USER_SCRIPTS_HTML" file="user-script-ui\user-scripts-ui.html" type="BINDATA" />
++      <include name="IDR_USER_SCRIPTS_JS" file="user-script-ui\user-scripts-ui.js" type="BINDATA" />
++    </includes>
++  </release>
++</grit>
+\ 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
++++ b/components/user_scripts/browser/resources/user-script-ui/BUILD.gn
+@@ -0,0 +1,12 @@
++import("//third_party/closure_compiler/compile_js.gni")
++
++js_type_check("closure_compile") {
++  deps = [ ":view_script_source" ]
++}
++
++js_library("view_script_source") {
++  deps = [
++    "//ui/webui/resources/js:cr.m",
++    "//ui/webui/resources/js:util.m",
++  ]
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html
+@@ -0,0 +1,14 @@
++<!doctype html>
++<html lang="en">
++<head>
++  <meta charset="utf-8">
++  <title>Local State Debug Page</title>
++  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
++  <script type="module" src="user-scripts-ui.js"></script>
++</head>
++<body>
++  <pre id="content">
++    Loading Script Source file...
++  </pre>
++</body>
++</html>
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.js b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.js
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.js
+@@ -0,0 +1,9 @@
++import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
++import {$} from 'chrome://resources/js/util.m.js';
++
++document.addEventListener('DOMContentLoaded', function() {
++  const urlParams = new URLSearchParams(window.location.search);
++  sendWithPromise('requestSource', urlParams.get('key')).then(textContent => {
++    $('content').textContent = textContent[0].content;
++  });
++});
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/ui/user_scripts_ui.cc b/components/user_scripts/browser/ui/user_scripts_ui.cc
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/ui/user_scripts_ui.cc
+@@ -0,0 +1,148 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++
++#include <memory>
++
++#include "base/bind.h"
++#include "base/json/json_string_value_serializer.h"
++#include "base/macros.h"
++#include "base/memory/writable_shared_memory_region.h"
++#include "base/strings/string_util.h"
++#include "base/values.h"
++
++#include "chrome/browser/browser_process.h"
++#include "chrome/browser/profiles/profile.h"
++#include "chrome/common/url_constants.h"
++#include "chrome/grit/userscripts_browser_resources.h"
++#include "components/prefs/pref_service.h"
++#include "content/public/browser/web_ui.h"
++#include "content/public/browser/web_ui_controller.h"
++#include "content/public/browser/web_ui_data_source.h"
++#include "content/public/browser/web_ui_message_handler.h"
++
++#include "user_scripts_ui.h"
++#include "../userscripts_browser_client.h"
++#include "../../common/user_script.h"
++
++namespace {
++
++class UserScriptsUIHandler : public content::WebUIMessageHandler {
++ public:
++  UserScriptsUIHandler();
++  ~UserScriptsUIHandler() override;
++
++  // content::WebUIMessageHandler:
++  void RegisterMessages() override;
++
++ private:
++  void HandleRequestSource(base::Value::ConstListView args);
++  void OnScriptsLoaded(
++      const std::string callback_id,
++      const std::string script_key,
++      std::unique_ptr<user_scripts::UserScriptList> user_scripts);
++
++  std::unique_ptr<user_scripts::UserScriptList> loaded_scripts_;
++
++  base::WeakPtrFactory<UserScriptsUIHandler> weak_factory_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(UserScriptsUIHandler);
++};
++
++UserScriptsUIHandler::UserScriptsUIHandler()
++  : loaded_scripts_(new user_scripts::UserScriptList()) {
++}
++
++UserScriptsUIHandler::~UserScriptsUIHandler() {
++}
++
++void UserScriptsUIHandler::RegisterMessages() {
++  web_ui()->RegisterMessageCallback(
++      "requestSource",
++      base::BindRepeating(&UserScriptsUIHandler::HandleRequestSource,
++                          base::Unretained(this)));
++}
++
++void UserScriptsUIHandler::HandleRequestSource(base::Value::ConstListView args) {
++  AllowJavascript();
++  if (args.size() < 2) return;
++
++  std::string callback_id = args[0].GetString();
++  std::string script_key = args[1].GetString();
++  if (script_key.empty()) {
++    std::string json = "Missing key value.";
++    ResolveJavascriptCallback(base::Value(callback_id), base::Value(json));
++    return;
++  }
++
++  user_scripts::UserScriptsBrowserClient* client = user_scripts::UserScriptsBrowserClient::GetInstance();
++  if (client == NULL) {
++    std::string json = "User scripts disabled.";
++    ResolveJavascriptCallback(base::Value(callback_id), base::Value(json));
++  } else {
++    std::unique_ptr<user_scripts::UserScriptList> scripts_to_load =
++                                    std::move(loaded_scripts_);
++    scripts_to_load->clear();
++
++    client->GetLoader()->LoadScripts(std::move(scripts_to_load),
++      base::BindOnce(
++          &UserScriptsUIHandler::OnScriptsLoaded,
++          weak_factory_.GetWeakPtr(),
++          callback_id, script_key)
++      );
++  }
++}
++
++void UserScriptsUIHandler::OnScriptsLoaded(
++    const std::string callback_id,
++    const std::string script_key,
++    std::unique_ptr<user_scripts::UserScriptList> user_scripts) {
++  loaded_scripts_ = std::move(user_scripts);
++
++  base::ListValue response;
++  for (const std::unique_ptr<user_scripts::UserScript>& script : *loaded_scripts_) {
++    if (script->key() == script_key) {
++      auto scriptData = std::make_unique<base::DictionaryValue>();
++      for (const std::unique_ptr<user_scripts::UserScript::File>& js_file :
++          script->js_scripts()) {
++        base::StringPiece contents = js_file->GetContent();
++        scriptData->SetString("content", contents.data());
++      }
++      response.Append(std::move(scriptData));
++    }
++  }
++
++  ResolveJavascriptCallback(base::Value(callback_id), response);
++}
++
++}  // namespace
++
++namespace user_scripts {
++
++UserScriptsUI::UserScriptsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
++  content::WebUIDataSource* html_source =
++      content::WebUIDataSource::Create(kChromeUIUserScriptsHost);
++  html_source->SetDefaultResource(IDR_USER_SCRIPTS_HTML);
++  html_source->AddResourcePath("user-scripts-ui.js", IDR_USER_SCRIPTS_JS);
++  content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), html_source);
++  web_ui->AddMessageHandler(std::make_unique<UserScriptsUIHandler>());
++}
++
++UserScriptsUI::~UserScriptsUI() {
++}
++
++}
+diff --git a/components/user_scripts/browser/ui/user_scripts_ui.h b/components/user_scripts/browser/ui/user_scripts_ui.h
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/ui/user_scripts_ui.h
+@@ -0,0 +1,39 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_UI_USER_SCRIPTS_UI_H_
++#define USERSCRIPTS_BROWSER_UI_USER_SCRIPTS_UI_H_
++
++#include "base/macros.h"
++#include "content/public/browser/web_ui_controller.h"
++
++namespace user_scripts {
++
++const char kChromeUIUserScriptsHost[] = "user-scripts";
++
++class UserScriptsUI : public content::WebUIController {
++ public:
++  explicit UserScriptsUI(content::WebUI* web_ui);
++  ~UserScriptsUI() override;
++
++ private:
++  DISALLOW_COPY_AND_ASSIGN(UserScriptsUI);
++};
++
++}
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/user_script_loader.cc b/components/user_scripts/browser/user_script_loader.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_loader.cc
+@@ -0,0 +1,721 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include "user_script_loader.h"
++
++#include <stddef.h>
++
++#include <set>
++#include <string>
++#include <utility>
++
++#include "base/bind.h"
++#include "base/memory/writable_shared_memory_region.h"
++#include "base/strings/string_util.h"
++#include "base/strings/strcat.h"
++#include "base/files/file.h"
++#include "base/files/file_util.h"
++#include "base/files/file_enumerator.h"
++#include "base/i18n/file_util_icu.h"
++#include "base/path_service.h"
++#include "base/base_paths_android.h"
++#include "base/strings/utf_string_conversions.h"
++#include "base/android/content_uri_utils.h"
++#include "base/android/jni_android.h"
++#include "base/task/task_traits.h"
++#include "base/version.h"
++
++#include "crypto/sha2.h"
++#include "base/base64.h"
++
++#include "build/build_config.h"
++#include "content/public/browser/browser_context.h"
++#include "content/public/browser/browser_task_traits.h"
++#include "content/public/browser/browser_thread.h"
++#include "content/public/browser/notification_service.h"
++#include "content/public/browser/notification_types.h"
++#include "content/public/browser/render_process_host.h"
++#include "ui/shell_dialogs/select_file_dialog.h"
++#include "content/browser/file_system_access/file_system_chooser.h"
++#include "chrome/browser/ui/chrome_select_file_policy.h"
++#include "ui/android/window_android.h"
++
++#include "../common/user_scripts_features.h"
++#include "../common/extension_messages.h"
++#include "file_task_runner.h"
++#include "user_script_prefs.h"
++#include "user_script_pref_info.h"
++
++using content::BrowserThread;
++using content::BrowserContext;
++
++namespace user_scripts {
++
++namespace {
++
++static const base::StringPiece kUserScriptBegin("// ==UserScript==");
++static const base::StringPiece kUserScriptEnd("// ==/UserScript==");
++static const base::StringPiece kNamespaceDeclaration("// @namespace");
++static const base::StringPiece kNameDeclaration("// @name");
++static const base::StringPiece kVersionDeclaration("// @version");
++static const base::StringPiece kDescriptionDeclaration("// @description");
++static const base::StringPiece kIncludeDeclaration("// @include");
++static const base::StringPiece kExcludeDeclaration("// @exclude");
++static const base::StringPiece kMatchDeclaration("// @match");
++static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match");
++static const base::StringPiece kRunAtDeclaration("// @run-at");
++static const base::StringPiece kRunAtDocumentStartValue("document-start");
++static const base::StringPiece kRunAtDocumentEndValue("document-end");
++static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
++static const base::StringPiece kUrlSourceDeclaration("// @url");
++static const base::StringPiece kUrlHomePageDeclaration("// @homepage");
++
++// internal use
++static const base::StringPiece kParserError("// @error");
++static const base::StringPiece kForceDisabled("// @disabled");
++
++bool invalidChar(unsigned char c)
++{
++  return !(c>=0 && c <128);
++}
++
++void stripUnicode(std::string& str)
++{
++  str.erase(remove_if(str.begin(),str.end(), invalidChar), str.end());
++}
++
++// Helper function to parse greasesmonkey headers
++bool GetDeclarationValue(const base::StringPiece& line,
++                         const base::StringPiece& prefix,
++                         std::string* value) {
++  base::StringPiece::size_type index = line.find(prefix);
++  if (index == base::StringPiece::npos)
++    return false;
++
++  std::string temp(line.data() + index + prefix.length(),
++                   line.length() - index - prefix.length());
++
++  if (temp.empty() || !base::IsUnicodeWhitespace(temp[0]))
++    return false;
++
++  base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
++  return true;
++}
++
++}  // namespace
++
++// static
++bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text,
++                                           std::unique_ptr<UserScript>& script,
++                                           bool *found_metadata,
++                                           std::string& error_message) {
++  // http://wiki.greasespot.net/Metadata_block
++  base::StringPiece line;
++  size_t line_start = 0;
++  size_t line_end = line_start;
++  *found_metadata = false;
++
++  while (line_start < script_text.length()) {
++    line_end = script_text.find('\n', line_start);
++
++    // Handle the case where there is no trailing newline in the file.
++    if (line_end == std::string::npos)
++      line_end = script_text.length() - 1;
++
++    line = base::StringPiece(script_text.data() + line_start,
++                             line_end - line_start);
++
++    if (!*found_metadata) {
++      if (base::StartsWith(line, kUserScriptBegin))
++        *found_metadata = true;
++    } else {
++      if (base::StartsWith(line, kUserScriptEnd))
++        break;
++
++      std::string value;
++      if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
++        // We escape some characters that MatchPattern() considers special.
++        base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
++        base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
++        script->add_glob(value);
++      } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
++        base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
++        base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
++        script->add_exclude_glob(value);
++      } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
++        script->set_name_space(value);
++      } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
++        script->set_name(value);
++      } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
++        base::Version version(value);
++        if (version.IsValid())
++          script->set_version(version.GetString());
++      } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
++        script->set_description(value);
++      } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
++        URLPattern pattern(UserScript::ValidUserScriptSchemes());
++        if (URLPattern::ParseResult::kSuccess != pattern.Parse(value)) {
++          error_message = "Invalid UserScript Schema " + value;
++          return false;
++        }
++        script->add_url_pattern(pattern);
++      } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
++        URLPattern exclude(UserScript::ValidUserScriptSchemes());
++        if (URLPattern::ParseResult::kSuccess != exclude.Parse(value)) {
++          error_message = "Invalid UserScript Schema " + value;
++          return false;
++        }
++        script->add_exclude_url_pattern(exclude);
++      } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
++        if (value == kRunAtDocumentStartValue)
++          script->set_run_location(UserScript::DOCUMENT_START);
++        else if (value == kRunAtDocumentEndValue)
++          script->set_run_location(UserScript::DOCUMENT_END);
++        else if (value == kRunAtDocumentIdleValue)
++          script->set_run_location(UserScript::DOCUMENT_IDLE);
++        else {
++          error_message = "Invalid RunAtDeclaration " + value;
++          return false;
++        }
++      } else if (GetDeclarationValue(line, kUrlSourceDeclaration, &value) ||
++                 GetDeclarationValue(line, kUrlHomePageDeclaration, &value)) {
++        script->set_url_source(value);
++      } else if (GetDeclarationValue(line, kParserError, &value)) {
++        script->set_parser_error(value);
++      } else if (GetDeclarationValue(line, kForceDisabled, &value)) {
++        script->set_force_disabled();
++      }
++
++      // TODO(aa): Handle more types of metadata.
++    }
++
++    line_start = line_end + 1;
++  }
++
++  // If no patterns were specified, default to @include *. This is what
++  // Greasemonkey does.
++  if (script->globs().empty() && script->url_patterns().is_empty())
++    script->add_glob("*");
++
++  return true;
++}
++
++// static
++bool LoadUserScriptFromFile(
++    const base::FilePath& user_script_path, const GURL& original_url,
++    std::unique_ptr<UserScript>& script,
++    bool* found_metadata,
++    std::u16string* error) {
++
++    base::File infile;
++    if (user_script_path.IsContentUri()) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScriptLoader: path " << user_script_path << " is a content uri";
++
++      infile = OpenContentUriForRead(user_script_path);
++    } else {
++      infile = base::File(user_script_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
++    }
++
++    if (!infile.IsValid()) {
++      *error = u"Cannot open script source.";
++      return false;
++    }
++
++    auto length = infile.GetLength();
++    if (length<=0) {
++      *error = u"File is empty.";
++      return false;
++    }
++
++    auto buffer = std::vector<char>(length);
++    int bytes_read = infile.Read(0, buffer.data(), length);
++    if (bytes_read == -1) {
++      *error = u"Could not read source file.";
++      return false;
++    }
++
++    std::string content(buffer.begin(), buffer.end());
++    if (!base::IsStringUTF8(content)) {
++      *error = u"User script must be UTF8 encoded.";
++      return false;
++    }
++
++    std::string detailed_error;
++    bool parseResult = UserScriptLoader::ParseMetadataHeader(content, script,
++                                found_metadata, detailed_error);
++    if (parseResult == false || *found_metadata == false) {
++      std::u16string detailed_error16;
++      base::UTF8ToUTF16(detailed_error.c_str(), detailed_error.length(), &detailed_error16);
++      *error = base::StrCat({u"Invalid script header. ", detailed_error16});
++      return false;
++    }
++
++    script->set_match_origin_as_fallback(MatchOriginAsFallbackBehavior::kNever);
++
++    // remove unicode chars and set content into File
++    stripUnicode(content);
++    std::unique_ptr<UserScript::File> file(new UserScript::File());
++    file->set_content(content);
++    file->set_url(GURL(/*script_key*/ "script.js")); // name doesn't matter
++
++    // create SHA256 of file
++    char raw[crypto::kSHA256Length] = {0};
++    std::string key;
++    crypto::SHA256HashString(content, raw, crypto::kSHA256Length);
++    base::Base64Encode(base::StringPiece(raw, crypto::kSHA256Length), &key);
++    file->set_key(key);
++
++    script->js_scripts().push_back(std::move(file));
++
++    // add into key the filename
++    // this value is used in ui to discriminate scripts
++    script->set_key(user_script_path.BaseName().value());
++    return true;
++}
++
++// static
++bool GetOrCreatePath(base::FilePath& path) {
++  base::PathService::Get(base::DIR_ANDROID_APP_DATA, &path);
++  path = path.AppendASCII("userscripts");
++
++  // create snippets directory if not exists
++  if (!base::PathExists(path)) {
++    LOG(INFO) << "Path " << path << " doesn't exists. Creating";
++    base::File::Error error = base::File::FILE_OK;
++    if (!base::CreateDirectoryAndGetError(path, &error)) {
++      LOG(ERROR) <<
++               "UserScriptLoader: failed to create directory: " << path
++               << " with error code " << error;
++      return false;
++    }
++  }
++  return true;
++}
++
++// static
++void LoadUserScripts(UserScriptList* user_scripts_list) {
++  base::FilePath path;
++  if (GetOrCreatePath(path) == false) return;
++
++  // enumerate all files from script path
++  // we accept all files, but we check if it's a real
++  // userscript
++  base::FileEnumerator dir_enum(
++    path,
++    /*recursive=*/false, base::FileEnumerator::FILES);
++  base::FilePath full_name;
++  while (full_name = dir_enum.Next(), !full_name.empty()) {
++    std::unique_ptr<UserScript> userscript(new UserScript());
++
++    std::u16string error;
++    bool found_metadata;
++    if (LoadUserScriptFromFile(full_name, GURL(), userscript, &found_metadata, &error)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScriptLoader: Found user script " << userscript->name() <<
++              "-" << userscript->version() <<
++              "-" << userscript->description();
++
++      userscript->set_file_path(full_name.AsUTF8Unsafe());
++      user_scripts_list->push_back(std::move(userscript));
++    } else {
++      LOG(ERROR) << "UserScriptLoader: load error " << error;
++    }
++  }
++}
++
++UserScriptLoader::UserScriptLoader(BrowserContext* browser_context,
++                                   UserScriptsPrefs* prefs)
++    : loaded_scripts_(new UserScriptList()),
++      ready_(false),
++      browser_context_(browser_context),
++      prefs_(prefs) {}
++
++UserScriptLoader::~UserScriptLoader() {
++  for (auto& observer : observers_)
++     observer.OnUserScriptLoaderDestroyed(this);
++}
++
++void UserScriptLoader::OnRenderProcessHostCreated(
++    content::RenderProcessHost* process_host) {
++  if (initial_load_complete()) {
++    SendUpdate(process_host, shared_memory_);
++  }
++}
++
++void UserScriptLoader::SetReady(bool ready) {
++  bool was_ready = ready_;
++  ready_ = ready;
++  if (ready_ && !was_ready)
++    AttemptLoad();
++}
++
++void UserScriptLoader::AttemptLoad() {
++  int tryOut = prefs_->GetCurrentStartupTryout();
++  if (tryOut >= 3) {
++    LOG(INFO) << "UserScriptLoader: Possible crash detected. UserScript disabled";
++    prefs_->SetEnabled(false);
++  } else {
++    prefs_->StartupTryout(tryOut+1);
++    StartLoad();
++  }
++}
++
++void UserScriptLoader::StartLoad() {
++  DCHECK_CURRENTLY_ON(BrowserThread::UI);
++
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScriptLoader: StartLoad";
++
++  // Reload any loaded scripts, and clear out |loaded_scripts_| to indicate that
++  // the scripts aren't currently ready.
++  std::unique_ptr<UserScriptList> scripts_to_load = std::move(loaded_scripts_);
++  scripts_to_load->clear();
++
++  if (prefs_->IsEnabled()) {
++    LoadScripts(std::move(scripts_to_load),
++                base::BindOnce(&UserScriptLoader::OnScriptsLoaded,
++                              weak_factory_.GetWeakPtr()));
++  } else {
++    OnScriptsLoaded(std::move(scripts_to_load));
++  }
++}
++
++// static
++base::ReadOnlySharedMemoryRegion UserScriptLoader::Serialize(
++    const UserScriptList& scripts) {
++  base::Pickle pickle;
++  pickle.WriteUInt32(scripts.size());
++  for (const std::unique_ptr<UserScript>& script : scripts) {
++    // TODO(aa): This can be replaced by sending content script metadata to
++    // renderers along with other extension data in ExtensionMsg_Loaded.
++    // See crbug.com/70516.
++    script->Pickle(&pickle);
++    // Write scripts as 'data' so that we can read it out in the slave without
++    // allocating a new string.
++    for (const std::unique_ptr<UserScript::File>& js_file :
++         script->js_scripts()) {
++      base::StringPiece contents = js_file->GetContent();
++      pickle.WriteData(contents.data(), contents.length());
++    }
++    for (const std::unique_ptr<UserScript::File>& css_file :
++         script->css_scripts()) {
++      base::StringPiece contents = css_file->GetContent();
++      pickle.WriteData(contents.data(), contents.length());
++    }
++  }
++
++  // Create the shared memory object.
++  base::MappedReadOnlyRegion shared_memory =
++      base::ReadOnlySharedMemoryRegion::Create(pickle.size());
++  if (!shared_memory.IsValid())
++    return {};
++
++  // Copy the pickle to shared memory.
++  memcpy(shared_memory.mapping.memory(), pickle.data(), pickle.size());
++  return std::move(shared_memory.region);
++}
++
++void UserScriptLoader::AddObserver(Observer* observer) {
++   observers_.AddObserver(observer);
++}
++
++void UserScriptLoader::RemoveObserver(Observer* observer) {
++   observers_.RemoveObserver(observer);
++}
++
++void UserScriptLoader::OnScriptsLoaded(
++    std::unique_ptr<UserScriptList> user_scripts) {
++
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScriptLoader: OnScriptsLoaded";
++
++  // Check user preferences for loaded user scripts
++  prefs_->CompareWithPrefs(*user_scripts);
++  loaded_scripts_ = std::move(user_scripts);
++
++  base::ReadOnlySharedMemoryRegion shared_memory;
++  shared_memory =
++      UserScriptLoader::Serialize(*loaded_scripts_);
++
++  if (!shared_memory.IsValid()) {
++    // This can happen if we run out of file descriptors.  In that case, we
++    // have a choice between silently omitting all user scripts for new tabs,
++    // by nulling out shared_memory_, or only silently omitting new ones by
++    // leaving the existing object in place. The second seems less bad, even
++    // though it removes the possibility that freeing the shared memory block
++    // would open up enough FDs for long enough for a retry to succeed.
++
++    // Pretend the extension change didn't happen.
++    return;
++  }
++
++  // We've got scripts ready to go.
++  shared_memory_ = std::move(shared_memory);
++
++  for (content::RenderProcessHost::iterator i(
++           content::RenderProcessHost::AllHostsIterator());
++       !i.IsAtEnd(); i.Advance()) {
++    SendUpdate(i.GetCurrentValue(), shared_memory_);
++  }
++
++  // DCHECK(false); trying crash
++  prefs_->StartupTryout(0);
++
++  for (auto& observer : observers_)
++    observer.OnScriptsLoaded(this, browser_context_);
++}
++
++void UserScriptLoader::SendUpdate(
++    content::RenderProcessHost* process,
++    const base::ReadOnlySharedMemoryRegion& shared_memory) {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScriptLoader: SendUpdate";
++
++  // If the process is being started asynchronously, early return.  We'll end up
++  // calling InitUserScripts when it's created which will call this again.
++  base::ProcessHandle handle = process->GetProcess().Handle();
++  if (!handle)
++    return;
++
++  base::ReadOnlySharedMemoryRegion region_for_process =
++      shared_memory.Duplicate();
++  if (!region_for_process.IsValid())
++    return;
++
++  process->Send(new ExtensionMsg_UpdateUserScripts(
++     std::move(region_for_process)));
++}
++
++void LoadScriptsOnFileTaskRunner(
++    std::unique_ptr<UserScriptList> user_scripts,
++    UserScriptLoader::LoadScriptsCallback callback) {
++  DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
++  DCHECK(user_scripts.get());
++
++  // load user scripts from path
++  LoadUserScripts(user_scripts.get());
++
++  // Explicit priority to prevent unwanted task priority inheritance.
++  content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
++      ->PostTask(FROM_HERE,
++                 base::BindOnce(std::move(callback), std::move(user_scripts)));
++}
++
++void UserScriptLoader::LoadScripts(
++    std::unique_ptr<UserScriptList> user_scripts,
++    LoadScriptsCallback callback) {
++  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
++
++  GetUserScriptsFileTaskRunner()->PostTask(
++      FROM_HERE,
++      base::BindOnce(&LoadScriptsOnFileTaskRunner, std::move(user_scripts),
++                     std::move(callback)));
++}
++
++void RemoveScriptsOnFileTaskRunner(
++    const std::string& script_id,
++    UserScriptLoader::RemoveScriptCallback callback) {
++  DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
++
++  base::FilePath path;
++  if (GetOrCreatePath(path)) {
++    base::FilePath file = path.Append(script_id);
++    if (base::DeleteFile(file) == false) {
++      LOG(ERROR) <<
++               "ERROR: failed to delete file : " << path;
++    }
++  }
++
++  content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
++      ->PostTask(FROM_HERE,
++                 base::BindOnce(std::move(callback)));
++}
++
++void UserScriptLoader::OnScriptRemoved() {
++  StartLoad();
++}
++
++void UserScriptLoader::RemoveScript(const std::string& script_id) {
++  if (!prefs_->IsEnabled()) return;
++  prefs_->RemoveScriptFromPrefs(script_id);
++
++  GetUserScriptsFileTaskRunner()->PostTask(
++      FROM_HERE,
++      base::BindOnce(&RemoveScriptsOnFileTaskRunner,
++                     std::move(script_id),
++                     base::BindOnce(&UserScriptLoader::OnScriptRemoved,
++                             weak_factory_.GetWeakPtr())));
++}
++
++void UserScriptLoader::SetScriptEnabled(const std::string& script_id, bool is_enabled) {
++  if (!prefs_->IsEnabled()) return;
++  prefs_->SetScriptEnabled(script_id, is_enabled);
++  StartLoad();
++}
++
++void UserScriptLoader::SelectAndAddScriptFromFile(ui::WindowAndroid* nativeWindow) {
++  DCHECK_CURRENTLY_ON(BrowserThread::UI);
++
++  if (!prefs_->IsEnabled()) return;
++
++  dialog_ = ui::SelectFileDialog::Create(
++      this, std::make_unique<ChromeSelectFilePolicy>(nullptr /*web_contents*/));
++
++  ui::SelectFileDialog::FileTypeInfo allowed_file_info;
++  allowed_file_info.extensions = {{FILE_PATH_LITERAL("js")}};
++  allowed_file_info.allowed_paths =
++      ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
++  base::FilePath suggested_name;
++
++  std::vector<std::u16string> types;
++  types.push_back(u"*/*"); /*= java SelectFileDialog.ALL_TYPES*/
++  std::pair<std::vector<std::u16string>, bool> accept_types = std::make_pair(
++      types, false /*use_media_capture*/);
++
++  dialog_->SelectFile(
++      ui::SelectFileDialog::SELECT_OPEN_FILE,
++      std::u16string() /* dialog title*/, suggested_name, &allowed_file_info,
++      0 /* file type index */, std::string() /* default file extension */,
++      nativeWindow,
++      &accept_types /* params */);
++}
++
++
++void LoadScriptFromPathOnFileTaskRunner(
++  const base::FilePath& path,
++  const std::string& display_name,
++  UserScriptLoader::LoadSingleScriptCallback callback ) {
++  DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
++
++  std::unique_ptr<UserScript> userscript(new UserScript());
++  std::u16string error;
++  bool found_metadata = false;
++  bool loaded = LoadUserScriptFromFile(path, GURL(), userscript, &found_metadata, &error);
++
++  bool result = loaded;
++  if (result || found_metadata) {
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScriptLoader: found " << userscript->name() <<
++                                    "-" << userscript->version() <<
++                                    "-" << userscript->description();
++    base::FilePath destination;
++    if (GetOrCreatePath(destination) == false) {
++      error = u"Cannot create destination.";
++    } else {
++      // we need an unique file name
++      if (display_name.empty() == false) {
++        userscript->set_key(display_name);
++      }
++
++      // filename is original filename or display_name
++      std::string file_name(userscript->key());
++      base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_');
++      destination = destination.Append(file_name);
++
++      if (destination.ReferencesParent()) {
++        error = u"Invalid file name.";
++        result = false;
++      } else if (base::PathExists(destination)) {
++        error = u"User script already loaded.";
++        result = false;
++      } else {
++        if (loaded) {
++          // if is a correct userscript, copy it
++          result = base::CopyFile(path, destination);
++          if (result == false) {
++            error = u"Copy error.";
++          }
++        } else {
++          // else, there is a parser error
++          // write minimal values and the error string, so UI can show it
++          std::string combined_string = base::StrCat({
++              kUserScriptBegin, "\n",
++              kNamespaceDeclaration, " ", userscript->name_space(), "\n",
++              kNameDeclaration, " ", userscript->name(), "\n",
++              kVersionDeclaration, " ", userscript->version(), "\n",
++              kDescriptionDeclaration, " ", userscript->description(), "\n",
++              kUrlSourceDeclaration, " ", userscript->url_source(), "\n",
++              kParserError, " ", base::UTF16ToASCII(error), "\n",
++              kForceDisabled, " true\n",
++              kUserScriptEnd, "\n"
++          });
++
++          if (!base::WriteFile(destination, combined_string)) {
++            error = u"Cannot write.";
++            result = false;
++          }
++        }
++      }
++    }
++  }
++
++  if (!error.empty()) {
++    LOG(ERROR) << "UserScriptLoader: load error " << error;
++  }
++
++  // return to callback with eventually the error
++  const std::string string_error = base::UTF16ToASCII(error);
++  content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
++      ->PostTask(FROM_HERE,
++                 base::BindOnce(std::move(callback), result,
++                                std::move(string_error)));
++}
++
++void UserScriptLoader::TryToInstall(const base::FilePath& script_path) {
++  if (!prefs_->IsEnabled()) return;
++
++  std::u16string file_display_name;
++  base::MaybeGetFileDisplayName(script_path, &file_display_name);
++
++  std::string display_name = script_path.BaseName().value();
++  if (base::IsStringASCII(file_display_name))
++    display_name = base::UTF16ToASCII(file_display_name);
++
++  GetUserScriptsFileTaskRunner()->PostTask(
++      FROM_HERE,
++      base::BindOnce(
++        &LoadScriptFromPathOnFileTaskRunner,
++        script_path, display_name,
++        base::BindOnce(
++            &UserScriptLoader::LoadScriptFromPathOnFileTaskRunnerCallback,
++            weak_factory_.GetWeakPtr()
++        )
++      ));
++}
++
++void UserScriptLoader::FileSelected(
++    const base::FilePath& path, int index, void* params) {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScriptLoader: FileSelected " << path;
++
++  UserScriptLoader::TryToInstall(path);
++}
++
++void UserScriptLoader::LoadScriptFromPathOnFileTaskRunnerCallback(
++              bool result, const std::string& error) {
++  for (auto& observer : observers_)
++     observer.OnUserScriptLoaded(this, result, error);
++
++  StartLoad();
++}
++
++void UserScriptLoader::FileSelectionCanceled(
++    void* params) {
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/browser/user_script_loader.h b/components/user_scripts/browser/user_script_loader.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_loader.h
+@@ -0,0 +1,170 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
++#define USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
++
++#include <map>
++#include <memory>
++#include <set>
++
++#include "base/callback_forward.h"
++#include "base/compiler_specific.h"
++#include "base/macros.h"
++#include "base/memory/read_only_shared_memory_region.h"
++#include "base/memory/weak_ptr.h"
++#include "base/observer_list.h"
++#include "content/public/browser/render_process_host_creation_observer.h"
++#include "ui/shell_dialogs/select_file_dialog.h"
++#include "content/browser/file_system_access/file_system_chooser.h"
++#include "ui/android/window_android.h"
++
++#include "../common/host_id.h"
++#include "../common/user_script.h"
++#include "user_script_prefs.h"
++
++namespace base {
++class ReadOnlySharedMemoryRegion;
++}
++
++namespace content {
++class BrowserContext;
++class RenderProcessHost;
++}
++
++namespace user_scripts {
++
++// Manages one "logical unit" of user scripts in shared memory by constructing a
++// new shared memory region when the set of scripts changes. Also notifies
++// renderers of new shared memory region when new renderers appear, or when
++// script reloading completes. Script loading lives on the file thread.
++class UserScriptLoader : public content::RenderProcessHostCreationObserver,
++                         public ui::SelectFileDialog::Listener {
++ public:
++  using LoadScriptsCallback =
++      base::OnceCallback<void(std::unique_ptr<UserScriptList>)>;
++  using LoadSingleScriptCallback =
++      base::OnceCallback<void(bool result, const std::string& error)>;
++
++  using RemoveScriptCallback =
++      base::OnceCallback<void()>;
++  class Observer {
++   public:
++    virtual void OnScriptsLoaded(UserScriptLoader* loader,
++                                 content::BrowserContext* browser_context) = 0;
++    virtual void OnUserScriptLoaderDestroyed(UserScriptLoader* loader) = 0;
++    virtual void OnUserScriptLoaded(UserScriptLoader* loader,
++                                bool result, const std::string& error) = 0;
++  };
++
++  // Parses the includes out of |script| and returns them in |includes|.
++  static bool ParseMetadataHeader(const base::StringPiece& script_text,
++                                  std::unique_ptr<UserScript>& script,
++                                  bool *found_metadata,
++                                  std::string& error_message);
++
++  UserScriptLoader(content::BrowserContext* browser_context,
++                   UserScriptsPrefs* prefs);
++                   //const HostID& host_id);
++  ~UserScriptLoader() override;
++
++  // Initiates procedure to start loading scripts on the file thread.
++  void StartLoad();
++
++  // Returns true if we have any scripts ready.
++  bool initial_load_complete() const { return shared_memory_.IsValid(); }
++
++  // Pickle user scripts and return pointer to the shared memory.
++  static base::ReadOnlySharedMemoryRegion Serialize(
++      const user_scripts::UserScriptList& scripts);
++
++  // Adds or removes observers.
++  void AddObserver(Observer* observer);
++  void RemoveObserver(Observer* observer);
++
++  // Sets the flag if the initial set of hosts has finished loading; if it's
++  // set to be true, calls AttempLoad() to bootstrap.
++  void SetReady(bool ready);
++
++  void RemoveScript(const std::string& script_id);
++  void SetScriptEnabled(const std::string& script_id, bool is_enabled);
++
++  void SelectAndAddScriptFromFile(ui::WindowAndroid* wa);
++  void TryToInstall(const base::FilePath& script_path);
++
++  void LoadScripts(std::unique_ptr<UserScriptList> user_scripts,
++                      LoadScriptsCallback callback);
++
++ protected:
++  content::BrowserContext* browser_context() const { return browser_context_; }
++
++  UserScriptsPrefs* prefs() const { return prefs_; }
++
++ private:
++  void OnRenderProcessHostCreated(
++      content::RenderProcessHost* process_host) override;
++
++  // Attempts to initiate a load.
++  void AttemptLoad();
++
++  // Called once we have finished loading the scripts on the file thread.
++  void OnScriptsLoaded(std::unique_ptr<UserScriptList> user_scripts);
++
++  // Sends the renderer process a new set of user scripts. If
++  // |changed_hosts| is not empty, this signals that only the scripts from
++  // those hosts should be updated. Otherwise, all hosts will be
++  // updated.
++  void SendUpdate(content::RenderProcessHost* process,
++                  const base::ReadOnlySharedMemoryRegion& shared_memory);
++
++  // Contains the scripts that were found the last time scripts were updated.
++  base::ReadOnlySharedMemoryRegion shared_memory_;
++
++  // List of scripts that are currently loaded. This is null when a load is in
++  // progress.
++  std::unique_ptr<UserScriptList> loaded_scripts_;
++
++  // If the initial set of hosts has finished loading.
++  bool ready_;
++
++  // The browser_context for which the scripts managed here are installed.
++  content::BrowserContext* browser_context_;
++
++  // Manage load and store from preferences
++  UserScriptsPrefs* prefs_;
++
++  // The associated observers.
++  base::ObserverList<Observer>::Unchecked observers_;
++
++  void OnScriptRemoved();
++
++  // Manage file dialog requests
++  scoped_refptr<ui::SelectFileDialog> dialog_;
++  void FileSelected(const base::FilePath& path,
++                    int index, void* params) override;
++  void FileSelectionCanceled(void* params) override;
++  void LoadScriptFromPathOnFileTaskRunnerCallback(
++                    bool result, const std::string& error );
++
++  base::WeakPtrFactory<UserScriptLoader> weak_factory_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(UserScriptLoader);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
+diff --git a/components/user_scripts/browser/user_script_pref_info.cc b/components/user_scripts/browser/user_script_pref_info.cc
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_pref_info.cc
+@@ -0,0 +1,34 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include "user_script_pref_info.h"
++
++namespace user_scripts {
++
++UserScriptsListPrefs::ScriptInfo::ScriptInfo(const std::string& name,
++                                             const std::string& description,
++                                             const base::Time& install_time,
++                                             bool enabled)
++    : install_time(install_time),
++      enabled(enabled),
++      name_(name),
++      description_(description) {}
++
++UserScriptsListPrefs::ScriptInfo::ScriptInfo(const ScriptInfo& other) = default;
++UserScriptsListPrefs::ScriptInfo::~ScriptInfo() = default;
++
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/user_script_pref_info.h b/components/user_scripts/browser/user_script_pref_info.h
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_pref_info.h
+@@ -0,0 +1,72 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
++#define USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
++
++#include "base/values.h"
++#include "base/time/time.h"
++#include "components/keyed_service/core/keyed_service.h"
++
++namespace user_scripts {
++
++class UserScriptsListPrefs : public KeyedService {
++ public:
++  struct ScriptInfo {
++    ScriptInfo(const std::string& name,
++               const std::string& description,
++               const base::Time& install_time,
++               bool enabled);
++    ScriptInfo(const ScriptInfo& other);
++    ~ScriptInfo();
++
++    const std::string& name() const { return name_; }
++    void set_name(const std::string& name) { name_ = name; }
++
++    const std::string& description() const { return description_; }
++    void set_description(const std::string& description) { description_ = description; }
++
++    const std::string& version() const { return version_; }
++    void set_version(const std::string& version) { version_ = version; }
++
++    const std::string& file_path() const { return file_path_; }
++    void set_file_path(const std::string& file_path) { file_path_ = file_path; }
++
++    const std::string& url_source() const { return url_source_; }
++    void set_url_source(const std::string& url_source) { url_source_ = url_source; }
++
++    const std::string& parser_error() const { return parser_error_; }
++    void set_parser_error(const std::string& parser_error) { parser_error_ = parser_error; }
++
++    base::Time install_time;
++    bool enabled;
++
++    bool force_disabled;
++
++  private:
++    std::string name_;
++    std::string description_;
++    std::string version_;
++    std::string file_path_;
++    std::string url_source_;
++    std::string parser_error_;
++  };
++};
++
++}
++
++#endif // USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/user_script_prefs.cc b/components/user_scripts/browser/user_script_prefs.cc
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_prefs.cc
+@@ -0,0 +1,276 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include <map>
++
++#include "base/values.h"
++#include "base/strings/string_number_conversions.h"
++#include "base/json/json_writer.h"
++
++#include "chrome/browser/browser_process.h"
++#include "chrome/browser/profiles/profile.h"
++#include "chrome/browser/profiles/profile_manager.h"
++
++#include "components/prefs/pref_service.h"
++#include "components/prefs/scoped_user_pref_update.h"
++#include "components/pref_registry/pref_registry_syncable.h"
++#include "user_script_prefs.h"
++#include "user_script_pref_info.h"
++#include "../common/user_script.h"
++#include "../common/user_scripts_features.h"
++
++namespace user_scripts {
++
++namespace prefs {
++  const char kUserScriptsEnabled[] = "userscripts.enabled";
++}
++
++namespace {
++
++const char kUserScriptsStartup[] = "userscripts.startup";
++
++const char kUserScriptsList[] = "userscripts.scripts";
++const char kScriptIsEnabled[] = "enabled";
++const char kScriptName[] = "name";
++const char kScriptDescription[] = "description";
++const char kScriptVersion[] = "version";
++const char kScriptInstallTime[] = "install_time";
++const char kScriptFilePath[] = "file_path";
++const char kScriptUrlSource[] = "url_source";
++const char kScriptParserError[] = "parser_error";
++const char kScriptForceDisabled[] = "force_disabled";
++
++class PrefUpdate : public DictionaryPrefUpdate {
++ public:
++  PrefUpdate(PrefService* service,
++             const std::string& id,
++             const std::string& path)
++      : DictionaryPrefUpdate(service, path), id_(id) {}
++
++  ~PrefUpdate() override = default;
++
++  base::DictionaryValue* Get() override {
++    base::DictionaryValue* dict = DictionaryPrefUpdate::Get();
++    base::Value* dict_item =
++        dict->FindKeyOfType(id_, base::Value::Type::DICTIONARY);
++    if (!dict_item)
++      dict_item = dict->SetKey(id_, base::Value(base::Value::Type::DICTIONARY));
++    return static_cast<base::DictionaryValue*>(dict_item);
++  }
++
++ private:
++  const std::string id_;
++
++  DISALLOW_COPY_AND_ASSIGN(PrefUpdate);
++};
++
++bool GetInt64FromPref(const base::DictionaryValue* dict,
++                      const std::string& key,
++                      int64_t* value) {
++  DCHECK(dict);
++  const std::string* value_str = dict->FindStringKey(key);
++  if (!value_str) {
++    VLOG(2) << "Can't find key in local pref dictionary. Invalid key: " << key
++            << ".";
++    return false;
++  }
++
++  if (!base::StringToInt64(*value_str, value)) {
++    VLOG(2) << "Can't change string to int64_t. Invalid string value: "
++            << *value_str << ".";
++    return false;
++  }
++
++  return true;
++}
++
++}
++
++UserScriptsPrefs::UserScriptsPrefs(
++    PrefService* prefs)
++    : prefs_(prefs) {
++}
++
++// static
++void UserScriptsPrefs::RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
++  registry->RegisterBooleanPref(prefs::kUserScriptsEnabled, false);
++  registry->RegisterIntegerPref(kUserScriptsStartup, 0);
++  registry->RegisterDictionaryPref(kUserScriptsList);
++}
++
++bool UserScriptsPrefs::IsEnabled() {
++  return prefs_->GetBoolean(prefs::kUserScriptsEnabled);
++}
++
++void UserScriptsPrefs::SetEnabled(bool enabled) {
++  prefs_->SetBoolean(prefs::kUserScriptsEnabled, enabled);
++  prefs_->CommitPendingWrite();
++}
++
++void UserScriptsPrefs::StartupTryout(int number) {
++  prefs_->SetInteger(kUserScriptsStartup, number);
++  prefs_->CommitPendingWrite();
++}
++
++int UserScriptsPrefs::GetCurrentStartupTryout() {
++  return prefs_->GetInteger(kUserScriptsStartup);
++}
++
++void UserScriptsPrefs::CompareWithPrefs(UserScriptList& user_scripts) {
++  if (IsEnabled() == false) {
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScriptsPrefs: disabled by user";
++
++    user_scripts.clear();
++    return;
++  }
++
++  std::vector<std::string> all_scripts;
++
++  auto it = user_scripts.begin();
++  while (it != user_scripts.end())
++  {
++    std::string key = it->get()->key();
++    all_scripts.push_back(key);
++
++    std::unique_ptr<UserScriptsListPrefs::ScriptInfo> scriptInfo =
++        UserScriptsPrefs::CreateScriptInfoFromPrefs(key);
++
++    // add or update prefs
++    scriptInfo->set_name(it->get()->name());
++    scriptInfo->set_description(it->get()->description());
++    scriptInfo->set_version(it->get()->version());
++    scriptInfo->set_file_path(it->get()->file_path());
++    scriptInfo->set_url_source(it->get()->url_source());
++    scriptInfo->set_parser_error(it->get()->parser_error());
++    scriptInfo->force_disabled = (it->get()->force_disabled());
++
++    PrefUpdate update(prefs_, key, kUserScriptsList);
++    base::DictionaryValue* script_dict = update.Get();
++
++    script_dict->SetString(kScriptName, scriptInfo->name());
++    script_dict->SetString(kScriptDescription, scriptInfo->description());
++    script_dict->SetBoolean(kScriptIsEnabled, scriptInfo->enabled);
++    script_dict->SetString(kScriptVersion, scriptInfo->version());
++    script_dict->SetString(kScriptFilePath, scriptInfo->file_path());
++    script_dict->SetString(kScriptUrlSource, scriptInfo->url_source());
++    script_dict->SetString(kScriptParserError, scriptInfo->parser_error());
++    script_dict->SetBoolean(kScriptForceDisabled, scriptInfo->force_disabled);
++
++    std::string install_time_str =
++        base::NumberToString(scriptInfo->install_time.ToInternalValue());
++    script_dict->SetString(kScriptInstallTime, install_time_str);
++
++   if (!scriptInfo->enabled) {
++      it = user_scripts.erase(it);
++    } else {
++      ++it;
++    }
++  }
++
++  // remove script from prefs if no more present
++  std::vector<std::string> all_scripts_to_remove;
++  const base::DictionaryValue* dict =
++    prefs_->GetDictionary(kUserScriptsList);
++  for (base::DictionaryValue::Iterator script_it(*dict); !script_it.IsAtEnd();
++       script_it.Advance()) {
++    const std::string& key = script_it.key();
++
++    if (std::find(all_scripts.begin(), all_scripts.end(), key) == all_scripts.end()) {
++      all_scripts_to_remove.push_back(key);
++    }
++  }
++
++  DictionaryPrefUpdate update(prefs_, kUserScriptsList);
++  base::DictionaryValue* const update_dict = update.Get();
++  for (auto key : all_scripts_to_remove) {
++      update_dict->RemoveKey(key);
++  }
++
++  return;
++}
++
++std::string UserScriptsPrefs::GetScriptsInfo() {
++  std::string json_string;
++
++  const base::DictionaryValue* dict =
++    prefs_->GetDictionary(kUserScriptsList);
++
++  if (dict) {
++    base::JSONWriter::WriteWithOptions(
++        *dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
++    base::TrimWhitespaceASCII(json_string, base::TRIM_ALL, &json_string);
++  }
++
++  return json_string;
++}
++
++std::unique_ptr<UserScriptsListPrefs::ScriptInfo> UserScriptsPrefs::CreateScriptInfoFromPrefs(
++    const std::string& script_id) const {
++
++  auto scriptInfo = std::make_unique<UserScriptsListPrefs::ScriptInfo>(
++      script_id, "", base::Time::Now(), false);
++
++  const base::DictionaryValue* scripts =
++      prefs_->GetDictionary(kUserScriptsList);
++  if (!scripts)
++    return scriptInfo;
++
++  const base::DictionaryValue* script = static_cast<const base::DictionaryValue*>(
++    scripts->FindDictKey(script_id));
++  if (!script)
++    return scriptInfo;
++
++  const std::string* name = script->FindStringKey(kScriptName);
++  const std::string* description = script->FindStringKey(kScriptDescription);
++  const std::string* version = script->FindStringKey(kScriptVersion);
++  const std::string* file_path = script->FindStringKey(kScriptFilePath);
++  const std::string* url_source = script->FindStringKey(kScriptUrlSource);
++  const std::string* parser_error = script->FindStringKey(kScriptParserError);
++
++  scriptInfo->set_name( name ? *name : "no name" );
++  scriptInfo->set_description( description ? *description : "no description" );
++  scriptInfo->set_version( version ? *version : "no version" );
++  scriptInfo->enabled = script->FindBoolKey(kScriptIsEnabled).value_or(false);
++  scriptInfo->set_file_path( file_path ? *file_path : "no file path" );
++  scriptInfo->set_url_source( url_source ? *url_source : "" );
++  scriptInfo->set_parser_error( parser_error ? *parser_error : "" );
++  scriptInfo->force_disabled = script->FindBoolKey(kScriptForceDisabled).value_or(false);
++
++  int64_t time_interval = 0;
++  if (GetInt64FromPref(script, kScriptInstallTime, &time_interval)) {
++    scriptInfo->install_time = base::Time::FromInternalValue(time_interval);
++  }
++
++  return scriptInfo;
++}
++
++void UserScriptsPrefs::RemoveScriptFromPrefs(const std::string& script_id) {
++  DictionaryPrefUpdate update(prefs_, kUserScriptsList);
++  base::DictionaryValue* const update_dict = update.Get();
++  update_dict->RemoveKey(script_id);
++}
++
++void UserScriptsPrefs::SetScriptEnabled(const std::string& script_id, bool is_enabled) {
++  PrefUpdate update(prefs_, script_id, kUserScriptsList);
++  base::DictionaryValue* script_dict = update.Get();
++  if (script_dict->FindBoolKey(kScriptForceDisabled).value_or(false))
++    is_enabled = true;
++  script_dict->SetBoolean(kScriptIsEnabled, is_enabled);
++}
++
++}
+diff --git a/components/user_scripts/browser/user_script_prefs.h b/components/user_scripts/browser/user_script_prefs.h
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/browser/user_script_prefs.h
+@@ -0,0 +1,62 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
++#define USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
++
++#include "content/public/browser/browser_context.h"
++#include "components/prefs/pref_service.h"
++#include "components/pref_registry/pref_registry_syncable.h"
++
++#include "user_script_pref_info.h"
++#include "../common/user_script.h"
++
++namespace user_scripts {
++
++namespace prefs {
++    extern const char kUserScriptsEnabled[];
++}
++
++class UserScriptsPrefs {
++ public:
++    UserScriptsPrefs(
++        PrefService* prefs);
++
++    static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
++
++    bool IsEnabled();
++    void SetEnabled(bool enabled);
++
++    void StartupTryout(int number);
++    int GetCurrentStartupTryout();
++
++    void CompareWithPrefs(UserScriptList& user_scripts);
++
++    std::string GetScriptsInfo();
++    void RemoveScriptFromPrefs(const std::string& script_id);
++    void SetScriptEnabled(const std::string& script_id, bool is_enabled);
++
++    std::unique_ptr<UserScriptsListPrefs::ScriptInfo> CreateScriptInfoFromPrefs(
++        const std::string& script_id) const;
++
++ private:
++    PrefService* prefs_;
++};
++
++}
++
++#endif // USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
+\ No newline at end of file
+diff --git a/components/user_scripts/browser/userscripts_browser_client.cc b/components/user_scripts/browser/userscripts_browser_client.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/userscripts_browser_client.cc
+@@ -0,0 +1,78 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include "userscripts_browser_client.h"
++
++#include "base/logging.h"
++
++#include "chrome/browser/browser_process.h"
++
++#include "chrome/browser/profiles/profile.h"
++#include "chrome/browser/profiles/profile_manager.h"
++
++#include "../common/user_scripts_features.h"
++#include "user_script_loader.h"
++#include "file_task_runner.h"
++#include "user_script_prefs.h"
++
++namespace user_scripts {
++
++namespace {
++
++// remember: was ExtensionsBrowserClient
++UserScriptsBrowserClient* g_userscripts_browser_client = NULL;
++
++}  // namespace
++
++UserScriptsBrowserClient::UserScriptsBrowserClient() {}
++
++UserScriptsBrowserClient::~UserScriptsBrowserClient() = default;
++
++// static
++UserScriptsBrowserClient* UserScriptsBrowserClient::GetInstance() {
++  // only for browser process
++  if (!g_browser_process)
++    return NULL;
++
++  // singleton
++  if (g_userscripts_browser_client)
++    return g_userscripts_browser_client;
++
++  // make file task runner
++  GetUserScriptsFileTaskRunner().get();
++
++  // new instance singleton
++  g_userscripts_browser_client = new UserScriptsBrowserClient();
++
++  return g_userscripts_browser_client;
++}
++
++void UserScriptsBrowserClient::SetProfile(content::BrowserContext* context) {
++  browser_context_ = context;
++
++  prefs_ =
++    std::make_unique<user_scripts::UserScriptsPrefs>(
++      static_cast<Profile*>(context)->GetPrefs());
++
++  userscript_loader_ =
++    std::make_unique<user_scripts::UserScriptLoader>(browser_context_, prefs_.get());
++  if (prefs_->IsEnabled()) {
++    userscript_loader_->SetReady(true);
++  }
++}
++
++}  // namespace user_scripts
+diff --git a/components/user_scripts/browser/userscripts_browser_client.h b/components/user_scripts/browser/userscripts_browser_client.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/browser/userscripts_browser_client.h
+@@ -0,0 +1,62 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
++#define USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
++
++#include <memory>
++#include <string>
++#include <vector>
++
++#include "content/public/browser/browser_context.h"
++
++#include "../common/user_script.h"
++#include "user_script_loader.h"
++#include "user_script_prefs.h"
++
++namespace user_scripts {
++
++class UserScriptsBrowserClient {
++ public:
++  UserScriptsBrowserClient();
++  UserScriptsBrowserClient(const UserScriptsBrowserClient&) = delete;
++  UserScriptsBrowserClient& operator=(const UserScriptsBrowserClient&) = delete;
++  virtual ~UserScriptsBrowserClient();
++
++  // Returns the single instance of |this|.
++  static UserScriptsBrowserClient* GetInstance();
++
++  void SetProfile(content::BrowserContext* context);
++
++  user_scripts::UserScriptsPrefs* GetPrefs() {
++    return prefs_.get();
++  }
++
++  user_scripts::UserScriptLoader* GetLoader() {
++    return userscript_loader_.get();
++  }
++
++  private:
++    std::unique_ptr<UserScriptList> scripts_;
++    content::BrowserContext* browser_context_;
++    std::unique_ptr<user_scripts::UserScriptsPrefs> prefs_;
++    std::unique_ptr<user_scripts::UserScriptLoader> userscript_loader_;
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
+diff --git a/components/user_scripts/common/BUILD.gn b/components/user_scripts/common/BUILD.gn
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/BUILD.gn
+@@ -0,0 +1,49 @@
++# Copyright 2014 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++import("//build/config/features.gni")
++import("//mojo/public/tools/bindings/mojom.gni")
++
++static_library("common") {
++  sources = [
++    "user_scripts_features.cc",
++    "user_scripts_features.h",
++    "constants.h",
++    "host_id.cc",
++    "host_id.h",
++    "script_constants.h",
++    "url_pattern_set.cc",
++    "url_pattern_set.h",
++    "url_pattern.cc",
++    "url_pattern.h",
++    "user_script.cc",
++    "user_script.h",
++    "view_type.cc",
++    "view_type.h",
++    "extension_messages.cc",
++    "extension_messages.h",
++    "extension_message_generator.cc",
++    "extension_message_generator.h",
++  ]
++
++  configs += [
++    "//build/config:precompiled_headers",
++    "//build/config/compiler:wexit_time_destructors",
++  ]
++
++  public_deps = [
++    "//components/services/app_service/public/cpp:app_file_handling",
++    "//content/public/common",
++    "//ipc",
++    "//skia",
++  ]
++
++  deps = [
++    "//base",
++    "//components/url_formatter",
++    "//components/url_matcher",
++    "//components/version_info",
++    "//crypto",
++  ]
++}
+diff --git a/components/user_scripts/common/constants.h b/components/user_scripts/common/constants.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/constants.h
+@@ -0,0 +1,21 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_CONSTANTS_H_
++#define USERSCRIPTS_COMMON_CONSTANTS_H_
++
++#include "base/files/file_path.h"
++#include "base/strings/string_piece_forward.h"
++#include "components/services/app_service/public/mojom/types.mojom.h"
++#include "components/version_info/channel.h"
++#include "ui/base/layout.h"
++
++namespace user_scripts {
++
++// The origin of injected CSS.
++enum CSSOrigin { /*CSS_ORIGIN_AUTHOR,*/ CSS_ORIGIN_USER };
++
++}  // namespace user_scripts
++
++#endif  // USERSCRIPTS_COMMON_CONSTANTS_H_
+diff --git a/components/user_scripts/common/error_utils.cc b/components/user_scripts/common/error_utils.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/error_utils.cc
+@@ -0,0 +1,54 @@
++// Copyright (c) 2011 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "error_utils.h"
++
++#include <initializer_list>
++
++#include "base/check_op.h"
++#include "base/strings/string_tokenizer.h"
++#include "base/strings/string_util.h"
++#include "base/strings/utf_string_conversions.h"
++
++namespace user_scripts {
++
++namespace {
++
++std::string FormatErrorMessageInternal(
++    base::StringPiece format,
++    std::initializer_list<base::StringPiece> args) {
++  std::string format_str = format.as_string();
++  base::StringTokenizer tokenizer(format_str, "*");
++  tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS);
++
++  std::vector<base::StringPiece> result_pieces;
++  auto* args_it = args.begin();
++  while (tokenizer.GetNext()) {
++    if (!tokenizer.token_is_delim()) {
++      result_pieces.push_back(tokenizer.token_piece());
++      continue;
++    }
++
++    CHECK_NE(args_it, args.end())
++        << "More placeholders (*) than substitutions.";
++
++    // Substitute the argument.
++    result_pieces.push_back(*args_it);
++    args_it++;
++  }
++
++  // Not all substitutions were consumed.
++  CHECK_EQ(args_it, args.end()) << "Fewer placeholders (*) than substitutions.";
++
++  return base::JoinString(result_pieces, "" /* separator */);
++}
++
++}  // namespace
++
++std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
++                                           base::StringPiece s1) {
++  return FormatErrorMessageInternal(format, {s1});
++}
++
++}  // namespace user_scripts
+diff --git a/components/user_scripts/common/error_utils.h b/components/user_scripts/common/error_utils.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/error_utils.h
+@@ -0,0 +1,24 @@
++// Copyright (c) 2011 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_ERROR_UTILS_H_
++#define USERSCRIPTS_COMMON_ERROR_UTILS_H_
++
++#include <string>
++
++#include "base/strings/string_piece.h"
++
++namespace user_scripts {
++
++class ErrorUtils {
++ public:
++   // Creates an error messages from a pattern.
++   static std::string FormatErrorMessage(base::StringPiece format,
++                                         base::StringPiece s1);
++
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_COMMON_ERROR_UTILS_H_
+diff --git a/components/user_scripts/common/extension_message_generator.cc b/components/user_scripts/common/extension_message_generator.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/extension_message_generator.cc
+@@ -0,0 +1,29 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++// Get basic type definitions.
++#define IPC_MESSAGE_IMPL
++#include "components/user_scripts/common/extension_message_generator.h"
++
++// Generate constructors.
++#include "ipc/struct_constructor_macros.h"
++#include "components/user_scripts/common/extension_message_generator.h"
++
++// Generate param traits write methods.
++#include "ipc/param_traits_write_macros.h"
++namespace IPC {
++#include "components/user_scripts/common/extension_message_generator.h"
++}  // namespace IPC
++
++// Generate param traits read methods.
++#include "ipc/param_traits_read_macros.h"
++namespace IPC {
++#include "components/user_scripts/common/extension_message_generator.h"
++}  // namespace IPC
++
++// Generate param traits log methods.
++#include "ipc/param_traits_log_macros.h"
++namespace IPC {
++#include "components/user_scripts/common/extension_message_generator.h"
++}  // namespace IPC
+diff --git a/components/user_scripts/common/extension_message_generator.h b/components/user_scripts/common/extension_message_generator.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/extension_message_generator.h
+@@ -0,0 +1,11 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++// Multiply-included file, hence no include guard.
++
++#undef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++#include "extension_messages.h"
++#ifndef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++#error "Failed to include header extension_messages.h"
++#endif
+diff --git a/components/user_scripts/common/extension_messages.cc b/components/user_scripts/common/extension_messages.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/extension_messages.cc
+@@ -0,0 +1,40 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "extension_messages.h"
++
++#include <stddef.h>
++
++#include <memory>
++#include <utility>
++
++#include "content/public/common/common_param_traits.h"
++
++namespace IPC {
++
++void ParamTraits<HostID>::Write(base::Pickle* m, const param_type& p) {
++  WriteParam(m, p.type());
++  WriteParam(m, p.id());
++}
++
++bool ParamTraits<HostID>::Read(const base::Pickle* m,
++                               base::PickleIterator* iter,
++                               param_type* r) {
++  HostID::HostType type;
++  std::string id;
++  if (!ReadParam(m, iter, &type))
++    return false;
++  if (!ReadParam(m, iter, &id))
++    return false;
++  *r = HostID(type, id);
++  return true;
++}
++
++void ParamTraits<HostID>::Log(
++    const param_type& p, std::string* l) {
++  LogParam(p.type(), l);
++  LogParam(p.id(), l);
++}
++
++}  // namespace IPC
+diff --git a/components/user_scripts/common/extension_messages.h b/components/user_scripts/common/extension_messages.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/extension_messages.h
+@@ -0,0 +1,71 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++// IPC messages for extensions.
++
++#ifndef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++#define USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++
++#include <stdint.h>
++
++#include <map>
++#include <memory>
++#include <set>
++#include <string>
++#include <vector>
++
++#include "base/macros.h"
++#include "base/memory/read_only_shared_memory_region.h"
++#include "base/values.h"
++#include "content/public/common/common_param_traits.h"
++#include "constants.h"
++#include "host_id.h"
++#include "ipc/ipc_message_start.h"
++#include "ipc/ipc_message_macros.h"
++#include "url/gurl.h"
++#include "url/origin.h"
++
++#define IPC_MESSAGE_START ExtensionMsgStart
++
++IPC_ENUM_TRAITS_MAX_VALUE(HostID::HostType, HostID::HOST_TYPE_LAST)
++
++// Singly-included section for custom IPC traits.
++#ifndef INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++#define INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++
++namespace IPC {
++
++template <>
++struct ParamTraits<HostID> {
++  typedef HostID param_type;
++  static void Write(base::Pickle* m, const param_type& p);
++  static bool Read(const base::Pickle* m,
++                   base::PickleIterator* iter,
++                   param_type* r);
++  static void Log(const param_type& p, std::string* l);
++};
++
++
++}  // namespace IPC
++
++#endif  // INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
++
++// Notification that the user scripts have been updated. It has one
++// ReadOnlySharedMemoryRegion argument consisting of the pickled script data.
++// This memory region is valid in the context of the renderer.
++// If |owner| is not empty, then the shared memory handle refers to |owner|'s
++// programmatically-defined scripts. Otherwise, the handle refers to all
++// hosts' statically defined scripts. So far, only extension-hosts support
++// statically defined scripts; WebUI-hosts don't.
++// If |changed_hosts| is not empty, only the host in that set will
++// be updated. Otherwise, all hosts that have scripts in the shared memory
++// region will be updated. Note that the empty set => all hosts case is not
++// supported for per-extension programmatically-defined script regions; in such
++// regions, the owner is expected to list itself as the only changed host.
++// If |whitelisted_only| is true, this process should only run whitelisted
++// scripts and not all user scripts.
++IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateUserScripts,
++                     base::ReadOnlySharedMemoryRegion)
++
++#endif  // USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+diff --git a/components/user_scripts/common/host_id.cc b/components/user_scripts/common/host_id.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/host_id.cc
+@@ -0,0 +1,31 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "host_id.h"
++
++#include <tuple>
++
++HostID::HostID()
++    : type_(HostType::EXTENSIONS) {
++}
++
++HostID::HostID(HostType type, const std::string& id)
++    : type_(type), id_(id) {
++}
++
++HostID::HostID(const HostID& host_id)
++    : type_(host_id.type()),
++      id_(host_id.id()) {
++}
++
++HostID::~HostID() {
++}
++
++bool HostID::operator<(const HostID& host_id) const {
++  return std::tie(type_, id_) < std::tie(host_id.type_, host_id.id_);
++}
++
++bool HostID::operator==(const HostID& host_id) const {
++  return type_ == host_id.type_ && id_ == host_id.id_;
++}
+diff --git a/components/user_scripts/common/host_id.h b/components/user_scripts/common/host_id.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/host_id.h
+@@ -0,0 +1,35 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_HOST_ID_H_
++#define USERSCRIPTS_COMMON_HOST_ID_H_
++
++#include <string>
++
++// IDs of hosts who own user scripts.
++// A HostID is immutable after creation.
++struct HostID {
++  enum HostType { EXTENSIONS, WEBUI, HOST_TYPE_LAST = WEBUI };
++
++  HostID();
++  HostID(HostType type, const std::string& id);
++  HostID(const HostID& host_id);
++  ~HostID();
++
++  bool operator<(const HostID& host_id) const;
++  bool operator==(const HostID& host_id) const;
++
++  HostType type() const { return type_; }
++  const std::string& id() const { return id_; }
++
++ private:
++  // The type of the host.
++  HostType type_;
++
++  // Similar to extension_id, host_id is a unique indentifier for a host,
++  // e.g., an Extension or WebUI.
++  std::string id_;
++};
++
++#endif  // USERSCRIPTS_COMMON_HOST_ID_H_
+diff --git a/components/user_scripts/common/script_constants.h b/components/user_scripts/common/script_constants.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/script_constants.h
+@@ -0,0 +1,33 @@
++// Copyright 2020 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
++#define USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
++
++namespace user_scripts {
++
++// Whether to fall back to matching the origin for frames where the URL
++// cannot be matched directly, such as those with about: or data: schemes.
++enum class MatchOriginAsFallbackBehavior {
++  // Never fall back on the origin; this means scripts will never match on
++  // these frames.
++  kNever,
++  // Match the origin only for about:-scheme frames, and then climb the frame
++  // tree to find an appropriate ancestor to get a full URL (including path).
++  // This is for supporting the "match_about_blank" key.
++  // TODO(devlin): I wonder if we could simplify this to be "MatchForAbout",
++  // and not worry about climbing the frame tree. It would be a behavior
++  // change, but I wonder how many extensions it would impact in practice.
++  kMatchForAboutSchemeAndClimbTree,
++  // Match the origin as a fallback whenever applicable. This won't have a
++  // corresponding path.
++  kAlways,
++};
++
++// TODO(devlin): Move the other non-UserScript-specific constants like
++// RunLocation and InjectionType from UserScript into here.
++
++}
++
++#endif  // USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
+diff --git a/components/user_scripts/common/url_pattern.cc b/components/user_scripts/common/url_pattern.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/url_pattern.cc
+@@ -0,0 +1,803 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "url_pattern.h"
++
++#include <stddef.h>
++
++#include <ostream>
++
++#include "base/logging.h"
++#include "base/strings/pattern.h"
++#include "base/strings/strcat.h"
++#include "base/strings/string_number_conversions.h"
++#include "base/strings/string_piece.h"
++#include "base/strings/string_split.h"
++#include "base/strings/string_util.h"
++#include "base/strings/stringprintf.h"
++#include "content/public/common/url_constants.h"
++#include "constants.h"
++#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
++#include "net/base/url_util.h"
++#include "url/gurl.h"
++#include "url/url_util.h"
++
++const char URLPattern::kAllUrlsPattern[] = "<all_urls>";
++
++namespace {
++
++// TODO(aa): What about more obscure schemes like javascript: ?
++// Note: keep this array in sync with kValidSchemeMasks.
++const char* const kValidSchemes[] = {
++    url::kHttpScheme,         url::kHttpsScheme,
++    url::kFileScheme,         url::kFtpScheme,
++    /*content::kChromeUIScheme,*/ /*extensions::kExtensionScheme,*/
++    url::kFileSystemScheme,   url::kWsScheme,
++    url::kWssScheme,          url::kDataScheme,
++    url::kUrnScheme,
++};
++
++const int kValidSchemeMasks[] = {
++    URLPattern::SCHEME_HTTP,       URLPattern::SCHEME_HTTPS,
++    URLPattern::SCHEME_FILE,       URLPattern::SCHEME_FTP,
++    /*URLPattern::SCHEME_CHROMEUI,*/   /*URLPattern::SCHEME_EXTENSION,*/
++    URLPattern::SCHEME_FILESYSTEM, URLPattern::SCHEME_WS,
++    URLPattern::SCHEME_WSS,        URLPattern::SCHEME_DATA,
++    URLPattern::SCHEME_URN,
++};
++
++static_assert(base::size(kValidSchemes) == base::size(kValidSchemeMasks),
++              "must keep these arrays in sync");
++
++const char kParseSuccess[] = "Success.";
++const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator.";
++const char kParseErrorInvalidScheme[] = "Invalid scheme.";
++const char kParseErrorWrongSchemeType[] = "Wrong scheme type.";
++const char kParseErrorEmptyHost[] = "Host can not be empty.";
++const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard.";
++const char kParseErrorEmptyPath[] = "Empty path.";
++const char kParseErrorInvalidPort[] = "Invalid port.";
++const char kParseErrorInvalidHost[] = "Invalid host.";
++
++// Message explaining each URLPattern::ParseResult.
++const char* const kParseResultMessages[] = {
++  kParseSuccess,
++  kParseErrorMissingSchemeSeparator,
++  kParseErrorInvalidScheme,
++  kParseErrorWrongSchemeType,
++  kParseErrorEmptyHost,
++  kParseErrorInvalidHostWildcard,
++  kParseErrorEmptyPath,
++  kParseErrorInvalidPort,
++  kParseErrorInvalidHost,
++};
++
++static_assert(static_cast<int>(URLPattern::ParseResult::kNumParseResults) ==
++                  base::size(kParseResultMessages),
++              "must add message for each parse result");
++
++const char kPathSeparator[] = "/";
++
++bool IsStandardScheme(base::StringPiece scheme) {
++  // "*" gets the same treatment as a standard scheme.
++  if (scheme == "*")
++    return true;
++
++  return url::IsStandard(scheme.data(),
++                         url::Component(0, static_cast<int>(scheme.length())));
++}
++
++bool IsValidPortForScheme(base::StringPiece scheme, base::StringPiece port) {
++  if (port == "*")
++    return true;
++
++  // Only accept non-wildcard ports if the scheme uses ports.
++  if (url::DefaultPortForScheme(scheme.data(), scheme.length()) ==
++      url::PORT_UNSPECIFIED) {
++    return false;
++  }
++
++  int parsed_port = url::PORT_UNSPECIFIED;
++  if (!base::StringToInt(port, &parsed_port))
++    return false;
++  return (parsed_port >= 0) && (parsed_port < 65536);
++}
++
++// Returns |path| with the trailing wildcard stripped if one existed.
++//
++// The functions that rely on this (OverlapsWith and Contains) are only
++// called for the patterns inside URLPatternSet. In those cases, we know that
++// the path will have only a single wildcard at the end. This makes figuring
++// out overlap much easier. It seems like there is probably a computer-sciency
++// way to solve the general case, but we don't need that yet.
++base::StringPiece StripTrailingWildcard(base::StringPiece path) {
++  if (base::EndsWith(path, "*"))
++    path.remove_suffix(1);
++  return path;
++}
++
++// Removes trailing dot from |host_piece| if any.
++base::StringPiece CanonicalizeHostForMatching(base::StringPiece host_piece) {
++  if (base::EndsWith(host_piece, "."))
++    host_piece.remove_suffix(1);
++  return host_piece;
++}
++
++}  // namespace
++
++// static
++bool URLPattern::IsValidSchemeForExtensions(base::StringPiece scheme) {
++  for (size_t i = 0; i < base::size(kValidSchemes); ++i) {
++    if (scheme == kValidSchemes[i])
++      return true;
++  }
++  return false;
++}
++
++// static
++int URLPattern::GetValidSchemeMaskForExtensions() {
++  int result = 0;
++  for (size_t i = 0; i < base::size(kValidSchemeMasks); ++i)
++    result |= kValidSchemeMasks[i];
++  return result;
++}
++
++URLPattern::URLPattern()
++    : valid_schemes_(SCHEME_NONE),
++      match_all_urls_(false),
++      match_subdomains_(false),
++      port_("*") {}
++
++URLPattern::URLPattern(int valid_schemes)
++    : valid_schemes_(valid_schemes),
++      match_all_urls_(false),
++      match_subdomains_(false),
++      port_("*") {}
++
++URLPattern::URLPattern(int valid_schemes, base::StringPiece pattern)
++    // Strict error checking is used, because this constructor is only
++    // appropriate when we know |pattern| is valid.
++    : valid_schemes_(valid_schemes),
++      match_all_urls_(false),
++      match_subdomains_(false),
++      port_("*") {
++  ParseResult result = Parse(pattern);
++  DCHECK_EQ(ParseResult::kSuccess, result)
++      << "Parsing unexpectedly failed for pattern: " << pattern << ": "
++      << GetParseResultString(result);
++}
++
++URLPattern::URLPattern(const URLPattern& other) = default;
++
++URLPattern::URLPattern(URLPattern&& other) = default;
++
++URLPattern::~URLPattern() {
++}
++
++URLPattern& URLPattern::operator=(const URLPattern& other) = default;
++
++URLPattern& URLPattern::operator=(URLPattern&& other) = default;
++
++bool URLPattern::operator<(const URLPattern& other) const {
++  return GetAsString() < other.GetAsString();
++}
++
++bool URLPattern::operator>(const URLPattern& other) const {
++  return GetAsString() > other.GetAsString();
++}
++
++bool URLPattern::operator==(const URLPattern& other) const {
++  return GetAsString() == other.GetAsString();
++}
++
++std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern) {
++  return out << '"' << url_pattern.GetAsString() << '"';
++}
++
++URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern) {
++  spec_.clear();
++  SetMatchAllURLs(false);
++  SetMatchSubdomains(false);
++  SetPort("*");
++
++  // Special case pattern to match every valid URL.
++  if (pattern == kAllUrlsPattern) {
++    SetMatchAllURLs(true);
++    return ParseResult::kSuccess;
++  }
++
++  // Parse out the scheme.
++  size_t scheme_end_pos = pattern.find(url::kStandardSchemeSeparator);
++  bool has_standard_scheme_separator = true;
++
++  // Some urls also use ':' alone as the scheme separator.
++  if (scheme_end_pos == base::StringPiece::npos) {
++    scheme_end_pos = pattern.find(':');
++    has_standard_scheme_separator = false;
++  }
++
++  if (scheme_end_pos == base::StringPiece::npos)
++    return ParseResult::kMissingSchemeSeparator;
++
++  if (!SetScheme(pattern.substr(0, scheme_end_pos)))
++    return ParseResult::kInvalidScheme;
++
++  bool standard_scheme = IsStandardScheme(scheme_);
++  if (standard_scheme != has_standard_scheme_separator)
++    return ParseResult::kWrongSchemeSeparator;
++
++  // Advance past the scheme separator.
++  scheme_end_pos +=
++      (standard_scheme ? strlen(url::kStandardSchemeSeparator) : 1);
++  if (scheme_end_pos >= pattern.size())
++    return ParseResult::kEmptyHost;
++
++  // Parse out the host and path.
++  size_t host_start_pos = scheme_end_pos;
++  size_t path_start_pos = 0;
++
++  if (!standard_scheme) {
++    path_start_pos = host_start_pos;
++  } else if (scheme_ == url::kFileScheme) {
++    size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
++    if (host_end_pos == base::StringPiece::npos) {
++      // Allow hostname omission.
++      // e.g. file://* is interpreted as file:///*,
++      // file://foo* is interpreted as file:///foo*.
++      path_start_pos = host_start_pos - 1;
++    } else {
++      // Ignore hostname if scheme is file://.
++      // e.g. file://localhost/foo is equal to file:///foo.
++      path_start_pos = host_end_pos;
++    }
++  } else {
++    size_t host_end_pos = pattern.find(kPathSeparator, host_start_pos);
++
++    // Host is required.
++    if (host_start_pos == host_end_pos)
++      return ParseResult::kEmptyHost;
++
++    if (host_end_pos == base::StringPiece::npos)
++      return ParseResult::kEmptyPath;
++
++    base::StringPiece host_and_port =
++        pattern.substr(host_start_pos, host_end_pos - host_start_pos);
++
++    size_t port_separator_pos = base::StringPiece::npos;
++    if (host_and_port[0] != '[') {
++      // Not IPv6 (either IPv4 or just a normal address).
++      port_separator_pos = host_and_port.find(':');
++    } else {  // IPv6.
++      size_t host_end_pos = host_and_port.find(']');
++      if (host_end_pos == base::StringPiece::npos)
++        return ParseResult::kInvalidHost;
++      if (host_end_pos == 1)
++        return ParseResult::kEmptyHost;
++
++      if (host_end_pos < host_and_port.length() - 1) {
++        // The host isn't the only component. Check for a port. This would
++        // require a ':' to follow the closing ']' from the host.
++        if (host_and_port[host_end_pos + 1] != ':')
++          return ParseResult::kInvalidHost;
++
++        port_separator_pos = host_end_pos + 1;
++      }
++    }
++
++    if (port_separator_pos != base::StringPiece::npos &&
++        !SetPort(host_and_port.substr(port_separator_pos + 1))) {
++      return ParseResult::kInvalidPort;
++    }
++
++    // Note: this substr() will be the entire string if the port position
++    // wasn't found.
++    base::StringPiece host_piece = host_and_port.substr(0, port_separator_pos);
++
++    if (host_piece.empty())
++      return ParseResult::kEmptyHost;
++
++    if (host_piece == "*") {
++      match_subdomains_ = true;
++      host_piece = base::StringPiece();
++    } else if (base::StartsWith(host_piece, "*.")) {
++      if (host_piece.length() == 2) {
++        // We don't allow just '*.' as a host.
++        return ParseResult::kEmptyHost;
++      }
++      match_subdomains_ = true;
++      host_piece = host_piece.substr(2);
++    }
++
++    host_ = std::string(host_piece);
++
++    path_start_pos = host_end_pos;
++  }
++
++  SetPath(pattern.substr(path_start_pos));
++
++  // No other '*' can occur in the host, though. This isn't necessary, but is
++  // done as a convenience to developers who might otherwise be confused and
++  // think '*' works as a glob in the host.
++  if (host_.find('*') != std::string::npos)
++    return ParseResult::kInvalidHostWildcard;
++
++  if (!host_.empty()) {
++    // If |host_| is present (i.e., isn't a wildcard), we need to canonicalize
++    // it.
++    url::CanonHostInfo host_info;
++    host_ = net::CanonicalizeHost(host_, &host_info);
++    // net::CanonicalizeHost() returns an empty string on failure.
++    if (host_.empty())
++      return ParseResult::kInvalidHost;
++  }
++
++  // Null characters are not allowed in hosts.
++  if (host_.find('\0') != std::string::npos)
++    return ParseResult::kInvalidHost;
++
++  return ParseResult::kSuccess;
++}
++
++void URLPattern::SetValidSchemes(int valid_schemes) {
++  // TODO(devlin): Should we check that valid_schemes agrees with |scheme_|
++  // here? Otherwise, valid_schemes_ and schemes_ may stop agreeing with each
++  // other (e.g., in the case of `*://*/*`, where the scheme should only be
++  // http or https).
++  spec_.clear();
++  valid_schemes_ = valid_schemes;
++}
++
++void URLPattern::SetHost(base::StringPiece host) {
++  spec_.clear();
++  host_.assign(host.data(), host.size());
++}
++
++void URLPattern::SetMatchAllURLs(bool val) {
++  spec_.clear();
++  match_all_urls_ = val;
++
++  if (val) {
++    match_subdomains_ = true;
++    scheme_ = "*";
++    host_.clear();
++    SetPath("/*");
++  }
++}
++
++void URLPattern::SetMatchSubdomains(bool val) {
++  spec_.clear();
++  match_subdomains_ = val;
++}
++
++bool URLPattern::SetScheme(base::StringPiece scheme) {
++  spec_.clear();
++  scheme_.assign(scheme.data(), scheme.size());
++  if (scheme_ == "*") {
++    valid_schemes_ &= (SCHEME_HTTP | SCHEME_HTTPS);
++  } else if (!IsValidScheme(scheme_)) {
++    return false;
++  }
++  return true;
++}
++
++bool URLPattern::IsValidScheme(base::StringPiece scheme) const {
++  if (valid_schemes_ == SCHEME_ALL)
++    return true;
++
++  for (size_t i = 0; i < base::size(kValidSchemes); ++i) {
++    if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i]))
++      return true;
++  }
++
++  return false;
++}
++
++void URLPattern::SetPath(base::StringPiece path) {
++  spec_.clear();
++  path_.assign(path.data(), path.size());
++  path_escaped_ = path_;
++  base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\");
++  base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?");
++}
++
++bool URLPattern::SetPort(base::StringPiece port) {
++  spec_.clear();
++  if (IsValidPortForScheme(scheme_, port)) {
++    port_.assign(port.data(), port.size());
++    return true;
++  }
++  return false;
++}
++
++bool URLPattern::MatchesURL(const GURL& test) const {
++  // Invalid URLs can never match.
++  if (!test.is_valid())
++    return false;
++
++  const GURL* test_url = &test;
++  bool has_inner_url = test.inner_url() != nullptr;
++
++  if (has_inner_url) {
++    if (!test.SchemeIsFileSystem())
++      return false;  // The only nested URLs we handle are filesystem URLs.
++    test_url = test.inner_url();
++  }
++
++  // Ensure the scheme matches first, since <all_urls> may not match this URL if
++  // the scheme is excluded.
++  if (!MatchesScheme(test_url->scheme_piece()))
++    return false;
++
++  if (match_all_urls_)
++    return true;
++
++  // Unless |match_all_urls_| is true, the grammar only permits matching
++  // URLs with nonempty paths.
++  if (!test.has_path())
++    return false;
++
++  std::string path_for_request = test.PathForRequest();
++  if (has_inner_url) {
++    path_for_request = base::StringPrintf("%s%s", test_url->path_piece().data(),
++                                          path_for_request.c_str());
++  }
++
++  return MatchesSecurityOriginHelper(*test_url) &&
++         MatchesPath(path_for_request);
++}
++
++bool URLPattern::MatchesSecurityOrigin(const GURL& test) const {
++  const GURL* test_url = &test;
++  bool has_inner_url = test.inner_url() != NULL;
++
++  if (has_inner_url) {
++    if (!test.SchemeIsFileSystem())
++      return false;  // The only nested URLs we handle are filesystem URLs.
++    test_url = test.inner_url();
++  }
++
++  if (!MatchesScheme(test_url->scheme()))
++    return false;
++
++  if (match_all_urls_)
++    return true;
++
++  return MatchesSecurityOriginHelper(*test_url);
++}
++
++bool URLPattern::MatchesScheme(base::StringPiece test) const {
++  if (!IsValidScheme(test))
++    return false;
++
++  return scheme_ == "*" || test == scheme_;
++}
++
++bool URLPattern::MatchesHost(base::StringPiece host) const {
++  // TODO(devlin): This is a bit sad. Parsing urls is expensive. However, it's
++  // important that we do this conversion to a GURL in order to canonicalize the
++  // host (the pattern's host_ already is canonicalized from Parse()). We can't
++  // just do string comparison.
++  return MatchesHost(
++      GURL(base::StringPrintf("%s%s%s/", url::kHttpScheme,
++                              url::kStandardSchemeSeparator, host.data())));
++}
++
++bool URLPattern::MatchesHost(const GURL& test) const {
++  base::StringPiece test_host(CanonicalizeHostForMatching(test.host_piece()));
++  const base::StringPiece pattern_host(CanonicalizeHostForMatching(host_));
++
++  // If the hosts are exactly equal, we have a match.
++  if (test_host == pattern_host)
++    return true;
++
++  // If we're matching subdomains, and we have no host in the match pattern,
++  // that means that we're matching all hosts, which means we have a match no
++  // matter what the test host is.
++  if (match_subdomains_ && pattern_host.empty())
++    return true;
++
++  // Otherwise, we can only match if our match pattern matches subdomains.
++  if (!match_subdomains_)
++    return false;
++
++  // We don't do subdomain matching against IP addresses, so we can give up now
++  // if the test host is an IP address.
++  if (test.HostIsIPAddress())
++    return false;
++
++  // Check if the test host is a subdomain of our host.
++  if (test_host.length() <= (pattern_host.length() + 1))
++    return false;
++
++  if (!base::EndsWith(test_host, pattern_host))
++    return false;
++
++  return test_host[test_host.length() - pattern_host.length() - 1] == '.';
++}
++
++bool URLPattern::MatchesEffectiveTld(
++    net::registry_controlled_domains::PrivateRegistryFilter private_filter,
++    net::registry_controlled_domains::UnknownRegistryFilter unknown_filter)
++    const {
++  // Check if it matches all urls or is a pattern like http://*/*.
++  if (match_all_urls_ || (match_subdomains_ && host_.empty()))
++    return true;
++
++  // If this doesn't even match subdomains, it can't possibly be a TLD wildcard.
++  if (!match_subdomains_)
++    return false;
++
++  // If there was more than just a TLD in the host (e.g., *.foobar.com), it
++  // doesn't match all hosts in an effective TLD.
++  if (net::registry_controlled_domains::HostHasRegistryControlledDomain(
++          host_, unknown_filter, private_filter)) {
++    return false;
++  }
++
++  // At this point the host could either be just a TLD ("com") or some unknown
++  // TLD-like string ("notatld"). To disambiguate between them construct a
++  // fake URL, and check the registry.
++  //
++  // If we recognized this TLD, then this is a pattern like *.com, and it
++  // matches an effective TLD.
++  return net::registry_controlled_domains::HostHasRegistryControlledDomain(
++      "notatld." + host_, unknown_filter, private_filter);
++}
++
++bool URLPattern::MatchesSingleOrigin() const {
++  // Strictly speaking, the port is part of the origin, but in URLPattern it
++  // defaults to *. It's not very interesting anyway, so leave it out.
++  return !MatchesEffectiveTld() && scheme_ != "*" && !match_subdomains_;
++}
++
++bool URLPattern::MatchesPath(base::StringPiece test) const {
++  // Make the behaviour of OverlapsWith consistent with MatchesURL, which is
++  // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'.
++  // The below if is a no-copy way of doing (test + "/*" == path_escaped_).
++  if (path_escaped_.length() == test.length() + 2 &&
++      base::StartsWith(path_escaped_.c_str(), test) &&
++      base::EndsWith(path_escaped_, "/*")) {
++    return true;
++  }
++
++  return base::MatchPattern(test, path_escaped_);
++}
++
++const std::string& URLPattern::GetAsString() const {
++  if (!spec_.empty())
++    return spec_;
++
++  if (match_all_urls_) {
++    spec_ = kAllUrlsPattern;
++    return spec_;
++  }
++
++  bool standard_scheme = IsStandardScheme(scheme_);
++
++  std::string spec = scheme_ +
++      (standard_scheme ? url::kStandardSchemeSeparator : ":");
++
++  if (scheme_ != url::kFileScheme && standard_scheme) {
++    if (match_subdomains_) {
++      spec += "*";
++      if (!host_.empty())
++        spec += ".";
++    }
++
++    if (!host_.empty())
++      spec += host_;
++
++    if (port_ != "*") {
++      spec += ":";
++      spec += port_;
++    }
++  }
++
++  if (!path_.empty())
++    spec += path_;
++
++  spec_ = std::move(spec);
++  return spec_;
++}
++
++bool URLPattern::OverlapsWith(const URLPattern& other) const {
++  if (match_all_urls() || other.match_all_urls())
++    return true;
++  return (MatchesAnyScheme(other.GetExplicitSchemes()) ||
++          other.MatchesAnyScheme(GetExplicitSchemes()))
++      && (MatchesHost(other.host()) || other.MatchesHost(host()))
++      && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port()))
++      && (MatchesPath(StripTrailingWildcard(other.path())) ||
++          other.MatchesPath(StripTrailingWildcard(path())));
++}
++
++bool URLPattern::Contains(const URLPattern& other) const {
++  // Important: it's not enough to just check match_all_urls(); we also need to
++  // make sure that the schemes in this pattern are a superset of those in
++  // |other|.
++  if (match_all_urls() &&
++      (valid_schemes_ & other.valid_schemes_) == other.valid_schemes_) {
++    return true;
++  }
++
++  return MatchesAllSchemes(other.GetExplicitSchemes()) &&
++         MatchesHost(other.host()) &&
++         (!other.match_subdomains_ || match_subdomains_) &&
++         MatchesPortPattern(other.port()) &&
++         MatchesPath(StripTrailingWildcard(other.path()));
++}
++
++absl::optional<URLPattern> URLPattern::CreateIntersection(
++    const URLPattern& other) const {
++  // Easy case: Schemes don't overlap. Return nullopt.
++  int intersection_schemes = URLPattern::SCHEME_NONE;
++  if (valid_schemes_ == URLPattern::SCHEME_ALL)
++    intersection_schemes = other.valid_schemes_;
++  else if (other.valid_schemes_ == URLPattern::SCHEME_ALL)
++    intersection_schemes = valid_schemes_;
++  else
++    intersection_schemes = valid_schemes_ & other.valid_schemes_;
++
++  if (intersection_schemes == URLPattern::SCHEME_NONE)
++    return absl::nullopt;
++
++  {
++    // In a few cases, we can (mostly) return a copy of one of the patterns.
++    // This can happen when either:
++    // - The URLPattern's are identical (possibly excluding valid_schemes_)
++    // - One of the patterns has match_all_urls() equal to true.
++    // NOTE(devlin): Theoretically, we could use Contains() instead of
++    // match_all_urls() here. However, Contains() strips the trailing wildcard
++    // from the path, which could yield the incorrect result.
++    const URLPattern* copy_source = nullptr;
++    if (*this == other || other.match_all_urls())
++      copy_source = this;
++    else if (match_all_urls())
++      copy_source = &other;
++
++    if (copy_source) {
++      // NOTE: equality checks don't take into account valid_schemes_, and
++      // schemes can be different in the case of match_all_urls() as well, so
++      // we can't always just return *copy_source.
++      if (intersection_schemes == copy_source->valid_schemes_)
++        return *copy_source;
++      URLPattern result(intersection_schemes);
++      ParseResult parse_result = result.Parse(copy_source->GetAsString());
++      CHECK_EQ(ParseResult::kSuccess, parse_result);
++      return result;
++    }
++  }
++
++  // No more easy cases. Go through component by component to find the patterns
++  // that intersect.
++
++  // Note: Alias the function type (rather than using auto) because
++  // MatchesHost() is overloaded.
++  using match_function_type = bool (URLPattern::*)(base::StringPiece) const;
++
++  auto get_intersection = [this, &other](base::StringPiece own_str,
++                                         base::StringPiece other_str,
++                                         match_function_type match_function,
++                                         base::StringPiece* out) {
++    if ((this->*match_function)(other_str)) {
++      *out = other_str;
++      return true;
++    }
++    if ((other.*match_function)(own_str)) {
++      *out = own_str;
++      return true;
++    }
++    return false;
++  };
++
++  base::StringPiece scheme;
++  base::StringPiece host;
++  base::StringPiece port;
++  base::StringPiece path;
++  // If any pieces fail to overlap, then there is no intersection.
++  if (!get_intersection(scheme_, other.scheme_, &URLPattern::MatchesScheme,
++                        &scheme) ||
++      !get_intersection(host_, other.host_, &URLPattern::MatchesHost, &host) ||
++      !get_intersection(port_, other.port_, &URLPattern::MatchesPortPattern,
++                        &port) ||
++      !get_intersection(path_, other.path_, &URLPattern::MatchesPath, &path)) {
++    return absl::nullopt;
++  }
++
++  // Only match subdomains if both patterns match subdomains.
++  base::StringPiece subdomains;
++  if (match_subdomains_ && other.match_subdomains_) {
++    // The host may be empty (e.g., in the case of *://*/* - in that case, only
++    // append '*' instead of '*.'.
++    subdomains = host.empty() ? "*" : "*.";
++  }
++
++  base::StringPiece scheme_separator =
++      IsStandardScheme(scheme) ? url::kStandardSchemeSeparator : ":";
++
++  std::string pattern_str = base::StrCat(
++      {scheme, scheme_separator, subdomains, host, ":", port, path});
++
++  URLPattern pattern(intersection_schemes);
++  ParseResult result = pattern.Parse(pattern_str);
++  // TODO(devlin): I don't think there's any way this should ever fail, but
++  // use a CHECK() to flush any cases out. If nothing crops up, downgrade this
++  // to a DCHECK in M72.
++  CHECK_EQ(ParseResult::kSuccess, result);
++
++  return pattern;
++}
++
++bool URLPattern::MatchesAnyScheme(
++    const std::vector<std::string>& schemes) const {
++  for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
++    if (MatchesScheme(*i))
++      return true;
++  }
++
++  return false;
++}
++
++bool URLPattern::MatchesAllSchemes(
++    const std::vector<std::string>& schemes) const {
++  for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
++    if (!MatchesScheme(*i))
++      return false;
++  }
++
++  return true;
++}
++
++bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const {
++  // Ignore hostname if scheme is file://.
++  if (scheme_ != url::kFileScheme && !MatchesHost(test))
++    return false;
++
++  if (!MatchesPortPattern(base::NumberToString(test.EffectiveIntPort())))
++    return false;
++
++  return true;
++}
++
++bool URLPattern::MatchesPortPattern(base::StringPiece port) const {
++  return port_ == "*" || port_ == port;
++}
++
++std::vector<std::string> URLPattern::GetExplicitSchemes() const {
++  std::vector<std::string> result;
++
++  if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) {
++    result.push_back(scheme_);
++    return result;
++  }
++
++  for (size_t i = 0; i < base::size(kValidSchemes); ++i) {
++    if (MatchesScheme(kValidSchemes[i])) {
++      result.push_back(kValidSchemes[i]);
++    }
++  }
++
++  return result;
++}
++
++std::vector<URLPattern> URLPattern::ConvertToExplicitSchemes() const {
++  std::vector<std::string> explicit_schemes = GetExplicitSchemes();
++  std::vector<URLPattern> result;
++
++  for (std::vector<std::string>::const_iterator i = explicit_schemes.begin();
++       i != explicit_schemes.end(); ++i) {
++    URLPattern temp = *this;
++    temp.SetScheme(*i);
++    temp.SetMatchAllURLs(false);
++    result.push_back(temp);
++  }
++
++  return result;
++}
++
++// static
++const char* URLPattern::GetParseResultString(
++    URLPattern::ParseResult parse_result) {
++  return kParseResultMessages[static_cast<int>(parse_result)];
++}
+diff --git a/components/user_scripts/common/url_pattern.h b/components/user_scripts/common/url_pattern.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/url_pattern.h
+@@ -0,0 +1,302 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++#ifndef USERSCRIPTS_COMMON_URL_PATTERN_H_
++#define USERSCRIPTS_COMMON_URL_PATTERN_H_
++
++#include <functional>
++#include <iosfwd>
++#include <string>
++#include <vector>
++
++#include "base/strings/string_piece.h"
++#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
++
++class GURL;
++
++// A pattern that can be used to match URLs. A URLPattern is a very restricted
++// subset of URL syntax:
++//
++// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>'
++// <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' |
++//             'chrome-extension' | 'filesystem'
++// <host> := '*' | <IPv4 address> | [<IPv6 address>] |
++//           '*.' <anychar except '/' and '*'>+
++// <port> := [':' ('*' | <port number between 0 and 65535>)]
++// <path> := '/' <any chars>
++//
++// * Host is not used when the scheme is 'file'.
++// * The path can have embedded '*' characters which act as glob wildcards.
++// * '<all_urls>' is a special pattern that matches any valid URL that contains
++//   a valid scheme (as specified by valid_schemes_).
++// * The '*' scheme pattern excludes file URLs.
++//
++// Examples of valid patterns:
++// - http://*/*
++// - http://*/foo*
++// - https://*.google.com/foo*bar
++// - file://monkey*
++// - http://127.0.0.1/*
++// - http://[2607:f8b0:4005:805::200e]/*
++//
++// Examples of invalid patterns:
++// - http://* -- path not specified
++// - http://*foo/bar -- * not allowed as substring of host component
++// - http://foo.*.bar/baz -- * must be first component
++// - http:/bar -- scheme separator not found
++// - foo://* -- invalid scheme
++// - chrome:// -- we don't support chrome internal URLs
++class URLPattern {
++ public:
++  // A collection of scheme bitmasks for use with valid_schemes.
++  enum SchemeMasks {
++    SCHEME_NONE = 0,
++    SCHEME_HTTP = 1 << 0,
++    SCHEME_HTTPS = 1 << 1,
++    SCHEME_FILE = 1 << 2,
++    SCHEME_FTP = 1 << 3,
++    SCHEME_CHROMEUI = 1 << 4,
++    SCHEME_EXTENSION = 1 << 5,
++    SCHEME_FILESYSTEM = 1 << 6,
++    SCHEME_WS = 1 << 7,
++    SCHEME_WSS = 1 << 8,
++    SCHEME_DATA = 1 << 9,
++    SCHEME_URN = 1 << 10,
++
++    // IMPORTANT!
++    // SCHEME_ALL will match every scheme, including chrome://, chrome-
++    // extension://, about:, etc. Because this has lots of security
++    // implications, third-party extensions should usually not be able to get
++    // access to URL patterns initialized this way. If there is a reason
++    // for violating this general rule, document why this it safe.
++    SCHEME_ALL = -1,
++  };
++
++  // Error codes returned from Parse().
++  enum class ParseResult {
++    kSuccess = 0,
++    kMissingSchemeSeparator,
++    kInvalidScheme,
++    kWrongSchemeSeparator,
++    kEmptyHost,
++    kInvalidHostWildcard,
++    kEmptyPath,
++    kInvalidPort,
++    kInvalidHost,
++    kNumParseResults,
++  };
++
++  // The <all_urls> string pattern.
++  static const char kAllUrlsPattern[];
++
++  // Returns true if the given |scheme| is considered valid for extensions.
++  static bool IsValidSchemeForExtensions(base::StringPiece scheme);
++
++  // Returns the mask for all schemes considered valid for extensions.
++  static int GetValidSchemeMaskForExtensions();
++
++  explicit URLPattern(int valid_schemes);
++
++  // Convenience to construct a URLPattern from a string. If the string is not
++  // known ahead of time, use Parse() instead, which returns success or failure.
++  URLPattern(int valid_schemes, base::StringPiece pattern);
++
++  URLPattern();
++  URLPattern(const URLPattern& other);
++  URLPattern(URLPattern&& other);
++  ~URLPattern();
++
++  URLPattern& operator=(const URLPattern& other);
++  URLPattern& operator=(URLPattern&& other);
++
++  bool operator<(const URLPattern& other) const;
++  bool operator>(const URLPattern& other) const;
++  bool operator==(const URLPattern& other) const;
++
++  // Initializes this instance by parsing the provided string. Returns
++  // URLPattern::ParseResult::kSuccess on success, or an error code otherwise.
++  // On failure, this instance will have some intermediate values and is in an
++  // invalid state.
++  ParseResult Parse(base::StringPiece pattern_str);
++
++  // Gets the bitmask of valid schemes.
++  int valid_schemes() const { return valid_schemes_; }
++  void SetValidSchemes(int valid_schemes);
++
++  // Gets the host the pattern matches. This can be an empty string if the
++  // pattern matches all hosts (the input was <scheme>://*/<whatever>).
++  const std::string& host() const { return host_; }
++  void SetHost(base::StringPiece host);
++
++  // Gets whether to match subdomains of host().
++  bool match_subdomains() const { return match_subdomains_; }
++  void SetMatchSubdomains(bool val);
++
++  // Gets the path the pattern matches with the leading slash. This can have
++  // embedded asterisks which are interpreted using glob rules.
++  const std::string& path() const { return path_; }
++  void SetPath(base::StringPiece path);
++
++  // Returns true if this pattern matches all (valid) urls.
++  bool match_all_urls() const { return match_all_urls_; }
++  void SetMatchAllURLs(bool val);
++
++  // Sets the scheme for pattern matches. This can be a single '*' if the
++  // pattern matches all valid schemes (as defined by the valid_schemes_
++  // property). Returns false on failure (if the scheme is not valid).
++  bool SetScheme(base::StringPiece scheme);
++  // Note: You should use MatchesScheme() instead of this getter unless you
++  // absolutely need the exact scheme. This is exposed for testing.
++  const std::string& scheme() const { return scheme_; }
++
++  // Returns true if the specified scheme can be used in this URL pattern, and
++  // false otherwise. Uses valid_schemes_ to determine validity.
++  bool IsValidScheme(base::StringPiece scheme) const;
++
++  // Returns true if this instance matches the specified URL. Always returns
++  // false for invalid URLs.
++  bool MatchesURL(const GURL& test) const;
++
++  // Returns true if this instance matches the specified security origin.
++  bool MatchesSecurityOrigin(const GURL& test) const;
++
++  // Returns true if |test| matches our scheme.
++  // Note that if test is "filesystem", this may fail whereas MatchesURL
++  // may succeed.  MatchesURL is smart enough to look at the inner_url instead
++  // of the outer "filesystem:" part.
++  bool MatchesScheme(base::StringPiece test) const;
++
++  // Returns true if |test| matches our host.
++  bool MatchesHost(base::StringPiece test) const;
++  bool MatchesHost(const GURL& test) const;
++
++  // Returns true if |test| matches our path.
++  bool MatchesPath(base::StringPiece test) const;
++
++  // Returns true if the pattern matches all patterns in an (e)TLD. This
++  // includes patterns like *://*.com/*, *://*.co.uk/*, etc. A pattern that
++  // matches all domains (e.g., *://*/*) will return true.
++  // |private_filter| specifies whether private registries (like appspot.com)
++  // should be considered; if included, patterns like *://*.appspot.com/* will
++  // return true. By default, we exclude private registries (so *.appspot.com
++  // returns false).
++  // Note: This is an expensive method, and should be used sparingly!
++  // You should probably use URLPatternSet::ShouldWarnAllHosts(), which is
++  // cached.
++  bool MatchesEffectiveTld(
++      net::registry_controlled_domains::PrivateRegistryFilter private_filter =
++          net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES,
++      net::registry_controlled_domains::UnknownRegistryFilter unknown_filter =
++          net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES) const;
++
++  // Returns true if the pattern only matches a single origin. The pattern may
++  // include a path.
++  bool MatchesSingleOrigin() const;
++
++  // Sets the port. Returns false if the port is invalid.
++  bool SetPort(base::StringPiece port);
++  const std::string& port() const { return port_; }
++
++  // Returns a string representing this instance.
++  const std::string& GetAsString() const;
++
++  // Determines whether there is a URL that would match this instance and
++  // another instance. This method is symmetrical: Calling
++  // other.OverlapsWith(this) would result in the same answer.
++  bool OverlapsWith(const URLPattern& other) const;
++
++  // Returns true if this pattern matches all possible URLs that |other| can
++  // match. For example, http://*.google.com encompasses http://www.google.com.
++  bool Contains(const URLPattern& other) const;
++
++  // Creates a new URLPattern that represents the intersection of this
++  // URLPattern with the |other|, or base::nullopt if no intersection exists.
++  // For instance, given the patterns http://*.google.com/* and
++  // *://maps.google.com/*, the intersection is http://maps.google.com/*.
++  // NOTES:
++  // - Though scheme intersections are supported, the serialization of
++  //   URLPatternSet does not record them. Be sure that this is safe for your
++  //   use cases.
++  // - Path intersection is done on a best-effort basis. If one path clearly
++  //   contains another, it will be handled correctly, but this method does not
++  //   deal with cases like /*a* and /*b* (where technically the intersection
++  //   is /*a*b*|/*b*a*); the intersection returned for that case will be empty.
++  absl::optional<URLPattern> CreateIntersection(const URLPattern& other) const;
++
++  // Converts this URLPattern into an equivalent set of URLPatterns that don't
++  // use a wildcard in the scheme component. If this URLPattern doesn't use a
++  // wildcard scheme, then the returned set will contain one element that is
++  // equivalent to this instance.
++  std::vector<URLPattern> ConvertToExplicitSchemes() const;
++
++  static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) {
++    if (a.match_all_urls_ && b.match_all_urls_)
++      return false;
++    return a.host_.compare(b.host_) < 0;
++  }
++
++  // Used for origin comparisons in a std::set.
++  class EffectiveHostCompareFunctor {
++   public:
++    bool operator()(const URLPattern& a, const URLPattern& b) const {
++      return EffectiveHostCompare(a, b);
++    }
++  };
++
++  // Get an error string for a ParseResult.
++  static const char* GetParseResultString(URLPattern::ParseResult parse_result);
++
++ private:
++  // Returns true if any of the |schemes| items matches our scheme.
++  bool MatchesAnyScheme(const std::vector<std::string>& schemes) const;
++
++  // Returns true if all of the |schemes| items matches our scheme.
++  bool MatchesAllSchemes(const std::vector<std::string>& schemes) const;
++
++  bool MatchesSecurityOriginHelper(const GURL& test) const;
++
++  // Returns true if our port matches the |port| pattern (it may be "*").
++  bool MatchesPortPattern(base::StringPiece port) const;
++
++  // If the URLPattern contains a wildcard scheme, returns a list of
++  // equivalent literal schemes, otherwise returns the current scheme.
++  std::vector<std::string> GetExplicitSchemes() const;
++
++  // A bitmask containing the schemes which are considered valid for this
++  // pattern. Parse() uses this to decide whether a pattern contains a valid
++  // scheme.
++  int valid_schemes_;
++
++  // True if this is a special-case "<all_urls>" pattern.
++  bool match_all_urls_;
++
++  // The scheme for the pattern.
++  std::string scheme_;
++
++  // The host without any leading "*" components.
++  std::string host_;
++
++  // Whether we should match subdomains of the host. This is true if the first
++  // component of the pattern's host was "*".
++  bool match_subdomains_;
++
++  // The port.
++  std::string port_;
++
++  // The path to match. This is everything after the host of the URL, or
++  // everything after the scheme in the case of file:// URLs.
++  std::string path_;
++
++  // The path with "?" and "\" characters escaped for use with the
++  // MatchPattern() function.
++  std::string path_escaped_;
++
++  // A string representing this URLPattern.
++  mutable std::string spec_;
++};
++
++std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern);
++
++typedef std::vector<URLPattern> URLPatternList;
++
++#endif  // USERSCRIPTS_COMMON_URL_PATTERN_H_
+diff --git a/components/user_scripts/common/url_pattern_set.cc b/components/user_scripts/common/url_pattern_set.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/url_pattern_set.cc
+@@ -0,0 +1,334 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "url_pattern_set.h"
++
++#include <iterator>
++#include <ostream>
++
++#include "base/containers/contains.h"
++#include "base/logging.h"
++#include "base/stl_util.h"
++#include "base/values.h"
++#include "error_utils.h"
++#include "url_pattern.h"
++#include "url/gurl.h"
++#include "url/origin.h"
++#include "url/url_constants.h"
++#include "user_scripts_features.h"
++
++namespace user_scripts {
++
++namespace {
++
++const char kInvalidURLPatternError[] = "Invalid url pattern '*'";
++
++}  // namespace
++
++// static
++URLPatternSet URLPatternSet::CreateDifference(const URLPatternSet& set1,
++                                              const URLPatternSet& set2) {
++  return URLPatternSet(base::STLSetDifference<std::set<URLPattern>>(
++      set1.patterns_, set2.patterns_));
++}
++
++// static
++URLPatternSet URLPatternSet::CreateIntersection(
++    const URLPatternSet& set1,
++    const URLPatternSet& set2,
++    IntersectionBehavior intersection_behavior) {
++  // Note: leverage return value optimization; always return the same object.
++  URLPatternSet result;
++
++  if (intersection_behavior == IntersectionBehavior::kStringComparison) {
++    // String comparison just relies on STL set behavior, which looks at the
++    // string representation.
++    result = URLPatternSet(base::STLSetIntersection<std::set<URLPattern>>(
++        set1.patterns_, set2.patterns_));
++    return result;
++  }
++
++  // Look for a semantic intersection.
++
++  // Step 1: Iterate over each set. Find any patterns that are completely
++  // contained by the other (thus being necessarily present in any intersection)
++  // and add them, collecting the others in a set of unique items.
++  // Note: Use a collection of pointers for the uniques to avoid excessive
++  // copies. Since these are owned by the URLPatternSet passed in, which is
++  // const, this should be safe.
++  std::vector<const URLPattern*> unique_set1;
++  for (const URLPattern& pattern : set1) {
++    if (set2.ContainsPattern(pattern))
++      result.patterns_.insert(pattern);
++    else
++      unique_set1.push_back(&pattern);
++  }
++  std::vector<const URLPattern*> unique_set2;
++  for (const URLPattern& pattern : set2) {
++    if (set1.ContainsPattern(pattern))
++      result.patterns_.insert(pattern);
++    else
++      unique_set2.push_back(&pattern);
++  }
++
++  // If we're just looking for patterns contained by both, we're done.
++  if (intersection_behavior == IntersectionBehavior::kPatternsContainedByBoth)
++    return result;
++
++  DCHECK_EQ(IntersectionBehavior::kDetailed, intersection_behavior);
++
++  // Step 2: Iterate over all the unique patterns and find the intersections
++  // they have with the other patterns.
++  for (const auto* pattern : unique_set1) {
++    for (const auto* pattern2 : unique_set2) {
++      absl::optional<URLPattern> intersection =
++          pattern->CreateIntersection(*pattern2);
++      if (intersection)
++        result.patterns_.insert(std::move(*intersection));
++    }
++  }
++
++  return result;
++}
++
++// static
++URLPatternSet URLPatternSet::CreateUnion(const URLPatternSet& set1,
++                                         const URLPatternSet& set2) {
++  return URLPatternSet(
++      base::STLSetUnion<std::set<URLPattern>>(set1.patterns_, set2.patterns_));
++}
++
++// static
++URLPatternSet URLPatternSet::CreateUnion(
++    const std::vector<URLPatternSet>& sets) {
++  URLPatternSet result;
++  if (sets.empty())
++    return result;
++
++  // N-way union algorithm is basic O(nlog(n)) merge algorithm.
++  //
++  // Do the first merge step into a working set so that we don't mutate any of
++  // the input.
++  // TODO(devlin): Looks like this creates a bunch of copies; we can probably
++  // clean that up.
++  std::vector<URLPatternSet> working;
++  for (size_t i = 0; i < sets.size(); i += 2) {
++    if (i + 1 < sets.size())
++      working.push_back(CreateUnion(sets[i], sets[i + 1]));
++    else
++      working.push_back(sets[i].Clone());
++  }
++
++  for (size_t skip = 1; skip < working.size(); skip *= 2) {
++    for (size_t i = 0; i < (working.size() - skip); i += skip) {
++      URLPatternSet u = CreateUnion(working[i], working[i + skip]);
++      working[i].patterns_.swap(u.patterns_);
++    }
++  }
++
++  result.patterns_.swap(working[0].patterns_);
++  return result;
++}
++
++URLPatternSet::URLPatternSet() = default;
++
++URLPatternSet::URLPatternSet(URLPatternSet&& rhs) = default;
++
++URLPatternSet::URLPatternSet(const std::set<URLPattern>& patterns)
++    : patterns_(patterns) {}
++
++URLPatternSet::~URLPatternSet() = default;
++
++URLPatternSet& URLPatternSet::operator=(URLPatternSet&& rhs) = default;
++
++bool URLPatternSet::operator==(const URLPatternSet& other) const {
++  return patterns_ == other.patterns_;
++}
++
++std::ostream& operator<<(std::ostream& out,
++                         const URLPatternSet& url_pattern_set) {
++  out << "{ ";
++
++  auto iter = url_pattern_set.patterns().cbegin();
++  if (!url_pattern_set.patterns().empty()) {
++    out << *iter;
++    ++iter;
++  }
++
++  for (;iter != url_pattern_set.patterns().end(); ++iter)
++    out << ", " << *iter;
++
++  if (!url_pattern_set.patterns().empty())
++    out << " ";
++
++  out << "}";
++  return out;
++}
++
++URLPatternSet URLPatternSet::Clone() const {
++  return URLPatternSet(patterns_);
++}
++
++bool URLPatternSet::is_empty() const {
++  return patterns_.empty();
++}
++
++size_t URLPatternSet::size() const {
++  return patterns_.size();
++}
++
++bool URLPatternSet::AddPattern(const URLPattern& pattern) {
++  return patterns_.insert(pattern).second;
++}
++
++void URLPatternSet::AddPatterns(const URLPatternSet& set) {
++  patterns_.insert(set.patterns().begin(),
++                   set.patterns().end());
++}
++
++void URLPatternSet::ClearPatterns() {
++  patterns_.clear();
++}
++
++bool URLPatternSet::AddOrigin(int valid_schemes, const GURL& origin) {
++  if (origin.is_empty())
++    return false;
++  const url::Origin real_origin = url::Origin::Create(origin);
++  DCHECK(real_origin.IsSameOriginWith(url::Origin::Create(origin.GetOrigin())));
++  URLPattern origin_pattern(valid_schemes);
++  // Origin adding could fail if |origin| does not match |valid_schemes|.
++  if (origin_pattern.Parse(origin.spec()) !=
++      URLPattern::ParseResult::kSuccess) {
++    return false;
++  }
++  origin_pattern.SetPath("/*");
++  return AddPattern(origin_pattern);
++}
++
++bool URLPatternSet::Contains(const URLPatternSet& other) const {
++  for (auto it = other.begin(); it != other.end(); ++it) {
++    if (!ContainsPattern(*it))
++      return false;
++  }
++
++  return true;
++}
++
++bool URLPatternSet::ContainsPattern(const URLPattern& pattern) const {
++  for (auto it = begin(); it != end(); ++it) {
++    if (it->Contains(pattern))
++      return true;
++  }
++  return false;
++}
++
++bool URLPatternSet::MatchesURL(const GURL& url) const {
++  for (auto pattern = patterns_.cbegin(); pattern != patterns_.cend();
++       ++pattern) {
++    if (pattern->MatchesURL(url)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScripts: URLPatternSet::MatchesURL true " << url.spec();
++
++      return true;
++    }
++  }
++
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: URLPatternSet::MatchesURL false " << url.spec();
++
++  return false;
++}
++
++bool URLPatternSet::MatchesAllURLs() const {
++  for (auto host = begin(); host != end(); ++host) {
++    if (host->match_all_urls() ||
++        (host->match_subdomains() && host->host().empty()))
++      return true;
++  }
++  return false;
++}
++
++bool URLPatternSet::MatchesSecurityOrigin(const GURL& origin) const {
++  for (auto pattern = patterns_.begin(); pattern != patterns_.end();
++       ++pattern) {
++    if (pattern->MatchesSecurityOrigin(origin))
++      return true;
++  }
++
++  return false;
++}
++
++bool URLPatternSet::OverlapsWith(const URLPatternSet& other) const {
++  // Two extension extents overlap if there is any one URL that would match at
++  // least one pattern in each of the extents.
++  for (auto i = patterns_.cbegin(); i != patterns_.cend(); ++i) {
++    for (auto j = other.patterns().cbegin(); j != other.patterns().cend();
++         ++j) {
++      if (i->OverlapsWith(*j))
++        return true;
++    }
++  }
++
++  return false;
++}
++
++std::unique_ptr<base::ListValue> URLPatternSet::ToValue() const {
++  std::unique_ptr<base::ListValue> value(new base::ListValue);
++  for (auto i = patterns_.cbegin(); i != patterns_.cend(); ++i) {
++    base::Value pattern_str_value(i->GetAsString());
++    if (!base::Contains(value->GetList(), pattern_str_value))
++      value->Append(std::move(pattern_str_value));
++  }
++  return value;
++}
++
++bool URLPatternSet::Populate(const std::vector<std::string>& patterns,
++                             int valid_schemes,
++                             bool allow_file_access,
++                             std::string* error) {
++  ClearPatterns();
++  for (size_t i = 0; i < patterns.size(); ++i) {
++    URLPattern pattern(valid_schemes);
++    if (pattern.Parse(patterns[i]) != URLPattern::ParseResult::kSuccess) {
++      if (error) {
++        *error = ErrorUtils::FormatErrorMessage(kInvalidURLPatternError,
++                                                patterns[i]);
++      } else {
++        LOG(ERROR) << "Invalid url pattern: " << patterns[i];
++      }
++      return false;
++    }
++    if (!allow_file_access && pattern.MatchesScheme(url::kFileScheme)) {
++      pattern.SetValidSchemes(
++          pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
++    }
++    AddPattern(pattern);
++  }
++  return true;
++}
++
++std::unique_ptr<std::vector<std::string>> URLPatternSet::ToStringVector()
++    const {
++  std::unique_ptr<std::vector<std::string>> value(new std::vector<std::string>);
++  for (auto i = patterns_.cbegin(); i != patterns_.cend(); ++i) {
++    value->push_back(i->GetAsString());
++  }
++  return value;
++}
++
++bool URLPatternSet::Populate(const base::ListValue& value,
++                             int valid_schemes,
++                             bool allow_file_access,
++                             std::string* error) {
++  std::vector<std::string> patterns;
++  for (size_t i = 0; i < value.GetList().size(); ++i) {
++    std::string item;
++    if (!value.GetString(i, &item))
++      return false;
++    patterns.push_back(item);
++  }
++  return Populate(patterns, valid_schemes, allow_file_access, error);
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/common/url_pattern_set.h b/components/user_scripts/common/url_pattern_set.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/url_pattern_set.h
+@@ -0,0 +1,161 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_URL_PATTERN_SET_H_
++#define USERSCRIPTS_COMMON_URL_PATTERN_SET_H_
++
++#include <stddef.h>
++
++#include <iosfwd>
++#include <memory>
++#include <set>
++
++#include "base/macros.h"
++#include "url_pattern.h"
++
++class GURL;
++
++namespace base {
++class ListValue;
++class Value;
++}
++
++namespace user_scripts {
++
++// Represents the set of URLs an extension uses for web content.
++class URLPatternSet {
++ public:
++  typedef std::set<URLPattern>::const_iterator const_iterator;
++  typedef std::set<URLPattern>::iterator iterator;
++
++  // Returns |set1| - |set2|.
++  static URLPatternSet CreateDifference(const URLPatternSet& set1,
++                                        const URLPatternSet& set2);
++
++  enum class IntersectionBehavior {
++    // For the following descriptions, consider the two URLPatternSets:
++    // Set 1: {"https://example.com/*", "https://*.google.com/*", "http://*/*"}
++    // Set 2: {"https://example.com/*", "https://google.com/maps",
++    //         "*://chromium.org/*"}
++
++    // Only includes patterns that are exactly in both sets. The intersection of
++    // the two sets above is {"https://example.com/*"}, since that is the only
++    // pattern that appears exactly in each.
++    kStringComparison,
++
++    // Includes patterns that are effectively contained by both sets. The
++    // intersection of the two sets above is
++    // {
++    //   "https://example.com/*" (contained exactly by each set)
++    //   "https://google.com/maps" (contained exactly by set 2 and a strict
++    //                              subset of https://*.google.com/* in set 1)
++    // }
++    kPatternsContainedByBoth,
++
++    // Includes patterns that are contained by both sets and creates new
++    // patterns to represent the intersection of any others. The intersection of
++    // the two sets above is
++    // {
++    //   "https://example.com/*" (contained exactly by each set)
++    //   "https://google.com/maps" (contained exactly by set 2 and a strict
++    //                              subset of https://*.google.com/* in set 1)
++    //   "http://chromium.org/*" (the overlap between "http://*/*" in set 1 and
++    //                            *://chromium.org/*" in set 2).
++    // }
++    // Note that this is the most computationally expensive - potentially
++    // O(n^2) - since it can require comparing each pattern in one set to every
++    // pattern in the other set.
++    kDetailed,
++  };
++
++  // Returns the intersection of |set1| and |set2| according to
++  // |intersection_behavior|.
++  static URLPatternSet CreateIntersection(
++      const URLPatternSet& set1,
++      const URLPatternSet& set2,
++      IntersectionBehavior intersection_behavior);
++
++  // Returns the union of |set1| and |set2|.
++  static URLPatternSet CreateUnion(const URLPatternSet& set1,
++                                   const URLPatternSet& set2);
++
++  // Returns the union of all sets in |sets|.
++  static URLPatternSet CreateUnion(const std::vector<URLPatternSet>& sets);
++
++  URLPatternSet();
++  URLPatternSet(URLPatternSet&& rhs);
++  explicit URLPatternSet(const std::set<URLPattern>& patterns);
++  ~URLPatternSet();
++
++  URLPatternSet& operator=(URLPatternSet&& rhs);
++  bool operator==(const URLPatternSet& rhs) const;
++
++  bool is_empty() const;
++  size_t size() const;
++  const std::set<URLPattern>& patterns() const { return patterns_; }
++  const_iterator begin() const { return patterns_.begin(); }
++  const_iterator end() const { return patterns_.end(); }
++  iterator erase(iterator iter) { return patterns_.erase(iter); }
++
++  // Returns a copy of this URLPatternSet; not instrumented as a copy
++  // constructor to avoid accidental/unnecessary copies.
++  URLPatternSet Clone() const;
++
++  // Adds a pattern to the set. Returns true if a new pattern was inserted,
++  // false if the pattern was already in the set.
++  bool AddPattern(const URLPattern& pattern);
++
++  // Adds all patterns from |set| into this.
++  void AddPatterns(const URLPatternSet& set);
++
++  void ClearPatterns();
++
++  // Adds a pattern based on |origin| to the set.
++  bool AddOrigin(int valid_schemes, const GURL& origin);
++
++  // Returns true if every URL that matches |set| is matched by this. In other
++  // words, if every pattern in |set| is encompassed by a pattern in this.
++  bool Contains(const URLPatternSet& set) const;
++
++  // Returns true if any pattern in this set encompasses |pattern|.
++  bool ContainsPattern(const URLPattern& pattern) const;
++
++  // Test if the extent contains a URL.
++  bool MatchesURL(const GURL& url) const;
++
++  // Test if the extent matches all URLs (for example, <all_urls>).
++  bool MatchesAllURLs() const;
++
++  bool MatchesSecurityOrigin(const GURL& origin) const;
++
++  // Returns true if there is a single URL that would be in two extents.
++  bool OverlapsWith(const URLPatternSet& other) const;
++
++  // Converts to and from Value for serialization to preferences.
++  std::unique_ptr<base::ListValue> ToValue() const;
++  bool Populate(const base::ListValue& value,
++                int valid_schemes,
++                bool allow_file_access,
++                std::string* error);
++
++  // Converts to and from a vector of strings.
++  std::unique_ptr<std::vector<std::string>> ToStringVector() const;
++  bool Populate(const std::vector<std::string>& patterns,
++                int valid_schemes,
++                bool allow_file_access,
++                std::string* error);
++
++ private:
++  // The list of URL patterns that comprise the extent.
++  std::set<URLPattern> patterns_;
++
++  DISALLOW_COPY_AND_ASSIGN(URLPatternSet);
++};
++
++std::ostream& operator<<(std::ostream& out,
++                         const URLPatternSet& url_pattern_set);
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_COMMON_URL_PATTERN_SET_H_
+diff --git a/components/user_scripts/common/user_script.cc b/components/user_scripts/common/user_script.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/user_script.cc
+@@ -0,0 +1,317 @@
++// Copyright 2013 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "user_script.h"
++
++#include <stddef.h>
++#include <stdint.h>
++
++#include <memory>
++#include <utility>
++
++#include "base/atomic_sequence_num.h"
++#include "base/command_line.h"
++#include "base/pickle.h"
++#include "base/strings/pattern.h"
++#include "base/strings/string_util.h"
++#include "user_scripts_features.h"
++
++namespace {
++
++// This cannot be a plain int or int64_t because we need to generate unique IDs
++// from multiple threads.
++base::AtomicSequenceNumber g_user_script_id_generator;
++
++bool UrlMatchesGlobs(const std::vector<std::string>* globs,
++                     const GURL& url) {
++  for (auto glob = globs->cbegin(); glob != globs->cend(); ++glob) {
++    if (base::MatchPattern(url.spec(), *glob))
++      return true;
++  }
++
++  return false;
++}
++
++}  // namespace
++
++namespace user_scripts {
++
++// The bitmask for valid user script injectable schemes used by URLPattern.
++enum {
++  kValidUserScriptSchemes = //URLPattern::SCHEME_CHROMEUI |
++                            URLPattern::SCHEME_HTTP |
++                            URLPattern::SCHEME_HTTPS
++                            //| URLPattern::SCHEME_FILE |
++                            //URLPattern::SCHEME_FTP
++};
++
++// static
++const char UserScript::kFileExtension[] = ".user.js";
++
++// static
++int UserScript::GenerateUserScriptID() {
++  return g_user_script_id_generator.GetNext();
++}
++
++bool UserScript::IsURLUserScript(const GURL& url,
++                                 const std::string& mime_type) {
++  return base::EndsWith(url.ExtractFileName(), kFileExtension,
++                        base::CompareCase::INSENSITIVE_ASCII) &&
++         mime_type != "text/html";
++}
++
++// static
++int UserScript::ValidUserScriptSchemes(bool canExecuteScriptEverywhere) {
++  if (canExecuteScriptEverywhere)
++    return URLPattern::SCHEME_ALL;
++  int valid_schemes = kValidUserScriptSchemes;
++  // if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
++  //         switches::kExtensionsOnChromeURLs)) {
++  //   valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
++  // }
++  return valid_schemes;
++}
++
++UserScript::File::File(const base::FilePath& extension_root,
++                       const base::FilePath& relative_path,
++                       const GURL& url)
++    : extension_root_(extension_root),
++      relative_path_(relative_path),
++      url_(url) {
++}
++
++UserScript::File::File() {}
++
++UserScript::File::File(const File& other)
++    : extension_root_(other.extension_root_),
++      relative_path_(other.relative_path_),
++      url_(other.url_),
++      external_content_(other.external_content_),
++      content_(other.content_),
++      key_(other.key_) {}
++
++UserScript::File::~File() {}
++
++UserScript::UserScript() = default;
++UserScript::~UserScript() = default;
++
++void UserScript::add_url_pattern(const URLPattern& pattern) {
++  url_set_.AddPattern(pattern);
++}
++
++void UserScript::add_exclude_url_pattern(const URLPattern& pattern) {
++  exclude_url_set_.AddPattern(pattern);
++}
++
++bool UserScript::MatchesURL(const GURL& url) const {
++  if (!url_set_.is_empty()) {
++    if (!url_set_.MatchesURL(url)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScripts: No Match for url_set";
++      return false;
++    }
++  }
++
++  if (!exclude_url_set_.is_empty()) {
++    if (exclude_url_set_.MatchesURL(url)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScripts: No Match for exclude_url_set";
++      return false;
++    }
++  }
++
++  if (!globs_.empty()) {
++    if (!UrlMatchesGlobs(&globs_, url)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScripts: No Match for globs";
++      return false;
++    }
++  }
++
++  if (!exclude_globs_.empty()) {
++    if (UrlMatchesGlobs(&exclude_globs_, url)) {
++      if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++        LOG(INFO) << "UserScripts: No Match for exclude_globs";
++      return false;
++    }
++  }
++
++  return true;
++}
++
++bool UserScript::MatchesDocument(const GURL& effective_document_url,
++                                 bool is_subframe) const {
++  if (is_subframe && !match_all_frames())
++    return false;
++
++  return MatchesURL(effective_document_url);
++}
++
++void UserScript::File::Pickle(base::Pickle* pickle) const {
++  pickle->WriteString(url_.spec());
++  // Do not write path. It's not needed in the renderer.
++  // Do not write content. It will be serialized by other means.
++}
++
++void UserScript::File::Unpickle(const base::Pickle& pickle,
++                                base::PickleIterator* iter) {
++  // Read the url from the pickle.
++  std::string url;
++  CHECK(iter->ReadString(&url));
++  set_url(GURL(url));
++}
++
++void UserScript::Pickle(base::Pickle* pickle) const {
++  // Write the simple types to the pickle.
++  pickle->WriteInt(run_location());
++  pickle->WriteInt(user_script_id_);
++  pickle->WriteBool(emulate_greasemonkey());
++  pickle->WriteBool(match_all_frames());
++  pickle->WriteInt(static_cast<int>(match_origin_as_fallback()));
++  pickle->WriteBool(is_incognito_enabled());
++
++  PickleHostID(pickle, host_id_);
++  pickle->WriteInt(consumer_instance_type());
++  PickleGlobs(pickle, globs_);
++  PickleGlobs(pickle, exclude_globs_);
++  PickleURLPatternSet(pickle, url_set_);
++  PickleURLPatternSet(pickle, exclude_url_set_);
++  PickleScripts(pickle, js_scripts_);
++  PickleScripts(pickle, css_scripts_);
++}
++
++void UserScript::PickleGlobs(base::Pickle* pickle,
++                             const std::vector<std::string>& globs) const {
++  pickle->WriteUInt32(globs.size());
++  for (auto glob = globs.cbegin(); glob != globs.cend(); ++glob) {
++    pickle->WriteString(*glob);
++  }
++}
++
++void UserScript::PickleHostID(base::Pickle* pickle,
++                              const HostID& host_id) const {
++  pickle->WriteInt(host_id.type());
++  pickle->WriteString(host_id.id());
++}
++
++void UserScript::PickleURLPatternSet(base::Pickle* pickle,
++                                     const URLPatternSet& pattern_list) const {
++  pickle->WriteUInt32(pattern_list.patterns().size());
++  for (auto pattern = pattern_list.begin(); pattern != pattern_list.end();
++       ++pattern) {
++    pickle->WriteInt(pattern->valid_schemes());
++    pickle->WriteString(pattern->GetAsString());
++  }
++}
++
++void UserScript::PickleScripts(base::Pickle* pickle,
++                               const FileList& scripts) const {
++  pickle->WriteUInt32(scripts.size());
++  for (const std::unique_ptr<File>& file : scripts)
++    file->Pickle(pickle);
++}
++
++void UserScript::Unpickle(const base::Pickle& pickle,
++                          base::PickleIterator* iter) {
++  // Read the run location.
++  int run_location = 0;
++  CHECK(iter->ReadInt(&run_location));
++  CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST);
++  run_location_ = static_cast<RunLocation>(run_location);
++
++  CHECK(iter->ReadInt(&user_script_id_));
++  CHECK(iter->ReadBool(&emulate_greasemonkey_));
++  CHECK(iter->ReadBool(&match_all_frames_));
++  int match_origin_as_fallback_int = 0;
++  CHECK(iter->ReadInt(&match_origin_as_fallback_int));
++  match_origin_as_fallback_ =
++      static_cast<MatchOriginAsFallbackBehavior>(match_origin_as_fallback_int);
++  CHECK(iter->ReadBool(&incognito_enabled_));
++
++  UnpickleHostID(pickle, iter, &host_id_);
++
++  int consumer_instance_type = 0;
++  CHECK(iter->ReadInt(&consumer_instance_type));
++  consumer_instance_type_ =
++      static_cast<ConsumerInstanceType>(consumer_instance_type);
++
++  UnpickleGlobs(pickle, iter, &globs_);
++  UnpickleGlobs(pickle, iter, &exclude_globs_);
++  UnpickleURLPatternSet(pickle, iter, &url_set_);
++  UnpickleURLPatternSet(pickle, iter, &exclude_url_set_);
++  UnpickleScripts(pickle, iter, &js_scripts_);
++  UnpickleScripts(pickle, iter, &css_scripts_);
++}
++
++void UserScript::UnpickleGlobs(const base::Pickle& pickle,
++                               base::PickleIterator* iter,
++                               std::vector<std::string>* globs) {
++  uint32_t num_globs = 0;
++  CHECK(iter->ReadUInt32(&num_globs));
++  globs->clear();
++  for (uint32_t i = 0; i < num_globs; ++i) {
++    std::string glob;
++    CHECK(iter->ReadString(&glob));
++    globs->push_back(glob);
++  }
++}
++
++void UserScript::UnpickleHostID(const base::Pickle& pickle,
++                                base::PickleIterator* iter,
++                                HostID* host_id) {
++  int type = 0;
++  std::string id;
++  CHECK(iter->ReadInt(&type));
++  CHECK(iter->ReadString(&id));
++  *host_id = HostID(static_cast<HostID::HostType>(type), id);
++}
++
++void UserScript::UnpickleURLPatternSet(const base::Pickle& pickle,
++                                       base::PickleIterator* iter,
++                                       URLPatternSet* pattern_list) {
++  uint32_t num_patterns = 0;
++  CHECK(iter->ReadUInt32(&num_patterns));
++
++  pattern_list->ClearPatterns();
++  for (uint32_t i = 0; i < num_patterns; ++i) {
++    int valid_schemes;
++    CHECK(iter->ReadInt(&valid_schemes));
++
++    std::string pattern_str;
++    CHECK(iter->ReadString(&pattern_str));
++
++    URLPattern pattern(kValidUserScriptSchemes);
++    URLPattern::ParseResult result = pattern.Parse(pattern_str);
++    CHECK(URLPattern::ParseResult::kSuccess == result)
++        << URLPattern::GetParseResultString(result) << " "
++        << pattern_str.c_str();
++
++    pattern.SetValidSchemes(valid_schemes);
++    pattern_list->AddPattern(pattern);
++  }
++}
++
++void UserScript::UnpickleScripts(const base::Pickle& pickle,
++                                 base::PickleIterator* iter,
++                                 FileList* scripts) {
++  uint32_t num_files = 0;
++  CHECK(iter->ReadUInt32(&num_files));
++  scripts->clear();
++  for (uint32_t i = 0; i < num_files; ++i) {
++    std::unique_ptr<File> file(new File());
++    file->Unpickle(pickle, iter);
++    scripts->push_back(std::move(file));
++  }
++}
++
++UserScriptIDPair::UserScriptIDPair(int id, const HostID& host_id)
++    : id(id), host_id(host_id) {}
++
++UserScriptIDPair::UserScriptIDPair(int id) : id(id), host_id(HostID()) {}
++
++bool operator<(const UserScriptIDPair& a, const UserScriptIDPair& b) {
++  return a.id < b.id;
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/common/user_script.h b/components/user_scripts/common/user_script.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/user_script.h
+@@ -0,0 +1,403 @@
++// Copyright 2013 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_USER_SCRIPT_H_
++#define USERSCRIPTS_COMMON_USER_SCRIPT_H_
++
++#include <memory>
++#include <string>
++#include <vector>
++
++#include "base/files/file_path.h"
++#include "base/strings/string_piece.h"
++#include "script_constants.h"
++#include "host_id.h"
++#include "url_pattern.h"
++#include "url_pattern_set.h"
++#include "url/gurl.h"
++
++namespace base {
++class Pickle;
++class PickleIterator;
++}
++
++namespace user_scripts {
++
++// Represents a user script, either a standalone one, or one that is part of an
++// extension.
++class UserScript {
++ public:
++  // The file extension for standalone user scripts.
++  static const char kFileExtension[];
++
++  static int GenerateUserScriptID();
++
++  // Check if a URL should be treated as a user script and converted to an
++  // extension.
++  static bool IsURLUserScript(const GURL& url, const std::string& mime_type);
++
++  // Get the valid user script schemes for the current process. If
++  // canExecuteScriptEverywhere is true, this will return ALL_SCHEMES.
++  static int ValidUserScriptSchemes(bool canExecuteScriptEverywhere = false);
++
++  // TODO(rdevlin.cronin) This and RunLocation don't really belong here, since
++  // they are used for more than UserScripts (e.g., tabs.executeScript()).
++  // The type of injected script.
++  enum InjectionType {
++    // A content script specified in the extension's manifest.
++    CONTENT_SCRIPT,
++    // A script injected via, e.g. tabs.executeScript().
++    //PROGRAMMATIC_SCRIPT
++  };
++  // The last type of injected script; used for enum verification in IPC.
++  // Update this if you add more injected script types!
++  static const InjectionType INJECTION_TYPE_LAST = CONTENT_SCRIPT/*PROGRAMMATIC_SCRIPT*/;
++
++  // Locations that user scripts can be run inside the document.
++  // The three run locations must strictly follow each other in both load order
++  // (i.e., start *always* comes before end) and numerically, as we use
++  // arithmetic checking (e.g., curr == last + 1). So, no bitmasks here!!
++  enum RunLocation {
++    UNDEFINED,
++    DOCUMENT_START,  // After the documentElement is created, but before
++                     // anything else happens.
++    DOCUMENT_END,  // After the entire document is parsed. Same as
++                   // DOMContentLoaded.
++    DOCUMENT_IDLE,  // Sometime after DOMContentLoaded, as soon as the document
++                    // is "idle". Currently this uses the simple heuristic of:
++                    // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no
++                    // particular injection point is guaranteed.
++    RUN_DEFERRED,  // The user script's injection was deferred for permissions
++                   // reasons, and was executed at a later time.
++    BROWSER_DRIVEN,  // The user script will be injected when triggered by an
++                     // IPC in the browser process.
++    RUN_LOCATION_LAST  // Leave this as the last item.
++  };
++
++  // Holds script file info.
++  class File {
++   public:
++    File(const base::FilePath& extension_root,
++         const base::FilePath& relative_path,
++         const GURL& url);
++    File();
++    File(const File& other);
++    ~File();
++
++    const base::FilePath& extension_root() const { return extension_root_; }
++    const base::FilePath& relative_path() const { return relative_path_; }
++
++    const GURL& url() const { return url_; }
++    void set_url(const GURL& url) { url_ = url; }
++
++    // If external_content_ is set returns it as content otherwise it returns
++    // content_
++    const base::StringPiece GetContent() const {
++      if (external_content_.data())
++        return external_content_;
++      else
++        return content_;
++    }
++    void set_external_content(const base::StringPiece& content) {
++      external_content_ = content;
++    }
++    void set_content(const base::StringPiece& content) {
++      content_.assign(content.begin(), content.end());
++    }
++
++    const std::string& key() const { return key_; }
++    void set_key(const std::string& key) {
++      key_ = key;
++    }
++
++    // Serialization support. The content and FilePath members will not be
++    // serialized!
++    void Pickle(base::Pickle* pickle) const;
++    void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
++
++   private:
++    // Where the script file lives on the disk. We keep the path split so that
++    // it can be localized at will.
++    base::FilePath extension_root_;
++    base::FilePath relative_path_;
++
++    // The url to this script file.
++    GURL url_;
++
++    // The script content. It can be set to either loaded_content_ or
++    // externally allocated string.
++    base::StringPiece external_content_;
++
++    // Set when the content is loaded by LoadContent
++    std::string content_;
++
++    std::string key_;
++  };
++
++  using FileList = std::vector<std::unique_ptr<File>>;
++
++  // Type of a API consumer instance that user scripts will be injected on.
++  enum ConsumerInstanceType { TAB, WEBVIEW };
++
++  // Constructor. Default the run location to document end, which is like
++  // Greasemonkey and probably more useful for typical scripts.
++  UserScript();
++  ~UserScript();
++
++  // Performs a copy of all fields except file contents.
++  // static std::unique_ptr<UserScript> CopyMetadataFrom(const UserScript& other);
++
++  const std::string& name_space() const { return name_space_; }
++  void set_name_space(const std::string& name_space) {
++    name_space_ = name_space;
++  }
++
++  const std::string& name() const { return name_; }
++  void set_name(const std::string& name) { name_ = name; }
++
++  const std::string& version() const { return version_; }
++  void set_version(const std::string& version) {
++    version_ = version;
++  }
++
++  const std::string& key() const { return key_; }
++  void set_key(const std::string& key) {
++    key_ = key;
++  }
++
++  const std::string& file_path() const { return file_path_; }
++  void set_file_path(const std::string& file_path) {
++    file_path_ = file_path;
++  }
++
++  const std::string& url_source() const { return url_source_; }
++  void set_url_source(const std::string& url_source) {
++    url_source_ = url_source;
++  }
++
++  const std::string& description() const { return description_; }
++  void set_description(const std::string& description) {
++    description_ = description;
++  }
++
++  const std::string& parser_error() const { return parser_error_; }
++  void set_parser_error(const std::string& parser_error) {
++    parser_error_ = parser_error;
++  }
++
++  bool force_disabled() const { return force_disabled_; }
++  void set_force_disabled() {
++    force_disabled_ = true;
++  }
++
++  // The place in the document to run the script.
++  RunLocation run_location() const { return run_location_; }
++  void set_run_location(RunLocation location) { run_location_ = location; }
++
++  // Whether to emulate greasemonkey when running this script.
++  bool emulate_greasemonkey() const { return emulate_greasemonkey_; }
++  void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; }
++
++  // Whether to match all frames, or only the top one.
++  bool match_all_frames() const { return match_all_frames_; }
++  void set_match_all_frames(bool val) { match_all_frames_ = val; }
++
++  // Whether to match the origin as a fallback if the URL cannot be used
++  // directly.
++  MatchOriginAsFallbackBehavior match_origin_as_fallback() const {
++    return match_origin_as_fallback_;
++  }
++  void set_match_origin_as_fallback(MatchOriginAsFallbackBehavior val) {
++    match_origin_as_fallback_ = val;
++  }
++
++  // The globs, if any, that determine which pages this script runs against.
++  // These are only used with "standalone" Greasemonkey-like user scripts.
++  const std::vector<std::string>& globs() const { return globs_; }
++  void add_glob(const std::string& glob) { globs_.push_back(glob); }
++  void clear_globs() { globs_.clear(); }
++  const std::vector<std::string>& exclude_globs() const {
++    return exclude_globs_;
++  }
++  void add_exclude_glob(const std::string& glob) {
++    exclude_globs_.push_back(glob);
++  }
++  void clear_exclude_globs() { exclude_globs_.clear(); }
++
++  // The URLPatterns, if any, that determine which pages this script runs
++  // against.
++  const URLPatternSet& url_patterns() const { return url_set_; }
++  void add_url_pattern(const URLPattern& pattern);
++  const URLPatternSet& exclude_url_patterns() const {
++    return exclude_url_set_;
++  }
++  void add_exclude_url_pattern(const URLPattern& pattern);
++
++  // List of js scripts for this user script
++  FileList& js_scripts() { return js_scripts_; }
++  const FileList& js_scripts() const { return js_scripts_; }
++
++  // List of css scripts for this user script
++  FileList& css_scripts() { return css_scripts_; }
++  const FileList& css_scripts() const { return css_scripts_; }
++
++  const std::string& extension_id() const { return host_id_.id(); }
++
++  const HostID& host_id() const { return host_id_; }
++  void set_host_id(const HostID& host_id) { host_id_ = host_id; }
++
++  const ConsumerInstanceType& consumer_instance_type() const {
++    return consumer_instance_type_;
++  }
++  void set_consumer_instance_type(
++      const ConsumerInstanceType& consumer_instance_type) {
++    consumer_instance_type_ = consumer_instance_type;
++  }
++
++  int id() const { return user_script_id_; }
++  void set_id(int id) { user_script_id_ = id; }
++
++  // TODO(lazyboy): Incognito information is extension specific, it doesn't
++  // belong here. We should be able to determine this in the renderer/ where it
++  // is used.
++  bool is_incognito_enabled() const { return incognito_enabled_; }
++  void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; }
++
++  // Returns true if the script should be applied to the specified URL, false
++  // otherwise.
++  bool MatchesURL(const GURL& url) const;
++
++  // Returns true if the script should be applied to the given
++  // |effective_document_url|. It is the caller's responsibility to calculate
++  // |effective_document_url| based on match_origin_as_fallback().
++  bool MatchesDocument(const GURL& effective_document_url,
++                       bool is_subframe) const;
++
++  // Serializes the UserScript into a pickle. The content of the scripts and
++  // paths to UserScript::Files will not be serialized!
++  void Pickle(base::Pickle* pickle) const;
++
++  // Deserializes the script from a pickle. Note that this always succeeds
++  // because presumably we were the one that pickled it, and we did it
++  // correctly.
++  void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
++
++ private:
++  // base::Pickle helper functions used to pickle the individual types of
++  // components.
++  void PickleGlobs(base::Pickle* pickle,
++                   const std::vector<std::string>& globs) const;
++  void PickleHostID(base::Pickle* pickle, const HostID& host_id) const;
++  void PickleURLPatternSet(base::Pickle* pickle,
++                           const URLPatternSet& pattern_list) const;
++  void PickleScripts(base::Pickle* pickle, const FileList& scripts) const;
++
++  // Unpickle helper functions used to unpickle individual types of components.
++  void UnpickleGlobs(const base::Pickle& pickle,
++                     base::PickleIterator* iter,
++                     std::vector<std::string>* globs);
++  void UnpickleHostID(const base::Pickle& pickle,
++                      base::PickleIterator* iter,
++                      HostID* host_id);
++  void UnpickleURLPatternSet(const base::Pickle& pickle,
++                             base::PickleIterator* iter,
++                             URLPatternSet* pattern_list);
++  void UnpickleScripts(const base::Pickle& pickle,
++                       base::PickleIterator* iter,
++                       FileList* scripts);
++
++  // The location to run the script inside the document.
++  RunLocation run_location_ = DOCUMENT_IDLE;
++
++  // The namespace of the script. This is used by Greasemonkey in the same way
++  // as XML namespaces. Only used when parsing Greasemonkey-style scripts.
++  std::string name_space_;
++
++  // The script's name. Only used when parsing Greasemonkey-style scripts.
++  std::string name_;
++
++  // A longer description. Only used when parsing Greasemonkey-style scripts.
++  std::string description_;
++
++  // Parser error to show to user
++  std::string parser_error_;
++
++  // A version number of the script. Only used when parsing Greasemonkey-style
++  // scripts.
++  std::string version_;
++
++  // Greasemonkey-style globs that determine pages to inject the script into.
++  // These are only used with standalone scripts.
++  std::vector<std::string> globs_;
++  std::vector<std::string> exclude_globs_;
++
++  // URLPatterns that determine pages to inject the script into. These are
++  // only used with scripts that are part of extensions.
++  URLPatternSet url_set_;
++  URLPatternSet exclude_url_set_;
++
++  // List of js scripts defined in content_scripts
++  FileList js_scripts_;
++
++  // List of css scripts defined in content_scripts
++  FileList css_scripts_;
++
++  // internal key of scripts
++  std::string key_;
++
++  std::string file_path_;
++
++  // url source of script
++  std::string url_source_;
++
++  // The ID of the host this script is a part of. The |ID| of the
++  // |host_id| can be empty if the script is a "standlone" user script.
++  HostID host_id_;
++
++  // The type of the consumer instance that the script will be injected.
++  ConsumerInstanceType consumer_instance_type_ = TAB;
++
++  // The globally-unique id associated with this user script. -1 indicates
++  // "invalid".
++  int user_script_id_ = -1;
++
++  // Whether we should try to emulate Greasemonkey's APIs when running this
++  // script.
++  bool emulate_greasemonkey_ = false;
++
++  // Whether the user script should run in all frames, or only just the top one.
++  bool match_all_frames_ = false;
++
++  // Whether the user script should run in frames whose initiator / precursor
++  // origin matches a match pattern, if an appropriate URL cannot be found for
++  // the frame for matching purposes, such as in the case of about:, data:, and
++  // other schemes.
++  MatchOriginAsFallbackBehavior match_origin_as_fallback_ =
++      MatchOriginAsFallbackBehavior::kNever;
++
++  // True if the script should be injected into an incognito tab.
++  bool incognito_enabled_ = false;
++
++  // Script cannot be enabled
++  bool force_disabled_ = false;
++
++  DISALLOW_COPY_AND_ASSIGN(UserScript);
++};
++
++// Information we need while removing scripts from a UserScriptLoader.
++struct UserScriptIDPair {
++  UserScriptIDPair(int id, const HostID& host_id);
++  explicit UserScriptIDPair(int id);
++
++  int id;
++  HostID host_id;
++};
++
++bool operator<(const UserScriptIDPair& a, const UserScriptIDPair& b);
++
++using UserScriptList = std::vector<std::unique_ptr<UserScript>>;
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_COMMON_USER_SCRIPT_H_
+diff --git a/components/user_scripts/common/user_scripts_features.cc b/components/user_scripts/common/user_scripts_features.cc
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/common/user_scripts_features.cc
+@@ -0,0 +1,32 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#include "user_scripts_features.h"
++
++#include "build/build_config.h"
++
++namespace user_scripts {
++
++namespace features {
++
++const base::Feature kEnableLoggingUserScripts =
++                                        {"EnableLoggingUserScripts",
++                                          base::FEATURE_DISABLED_BY_DEFAULT};
++
++}
++
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/common/user_scripts_features.h b/components/user_scripts/common/user_scripts_features.h
+new file mode 100644
+--- /dev/null
++++ b/components/user_scripts/common/user_scripts_features.h
+@@ -0,0 +1,34 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++#ifndef USERSCRIPTS_COMMON_USERSCRIPTS_FEATURES_H_
++#define USERSCRIPTS_COMMON_USERSCRIPTS_FEATURES_H_
++
++// This file defines all the base::FeatureList features for the Password Manager
++// module.
++
++#include "base/feature_list.h"
++
++namespace user_scripts {
++
++namespace features {
++    extern const base::Feature kEnableLoggingUserScripts;
++}
++
++}
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/common/view_type.cc b/components/user_scripts/common/view_type.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/view_type.cc
+@@ -0,0 +1,39 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "view_type.h"
++
++#include "base/strings/string_piece.h"
++
++namespace user_scripts {
++
++bool GetViewTypeFromString(const std::string& view_type,
++                           ViewType* view_type_out) {
++  // TODO(devlin): This map doesn't contain the following values:
++  // - VIEW_TYPE_BACKGROUND_CONTENTS
++  // - VIEW_TYPE_COMPONENT
++  // - VIEW_TYPE_EXTENSION_GUEST
++  // Why? Is it just because we don't expose those types to JS?
++  static const struct {
++    ViewType type;
++    base::StringPiece name;
++  } constexpr kTypeMap[] = {
++      // {VIEW_TYPE_APP_WINDOW, "APP_WINDOW"},
++      // {VIEW_TYPE_EXTENSION_BACKGROUND_PAGE, "BACKGROUND"},
++      // {VIEW_TYPE_EXTENSION_DIALOG, "EXTENSION_DIALOG"},
++      // {VIEW_TYPE_EXTENSION_POPUP, "POPUP"},
++      {VIEW_TYPE_TAB_CONTENTS, "TAB"},
++  };
++
++  for (const auto& entry : kTypeMap) {
++    if (entry.name == view_type) {
++      *view_type_out = entry.type;
++      return true;
++    }
++  }
++
++  return false;
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/common/view_type.h b/components/user_scripts/common/view_type.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/common/view_type.h
+@@ -0,0 +1,48 @@
++// Copyright (c) 2012 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_COMMON_VIEW_TYPE_H_
++#define USERSCRIPTS_COMMON_VIEW_TYPE_H_
++
++#include <string>
++
++namespace user_scripts {
++
++// Icky RTTI used by a few systems to distinguish the host type of a given
++// WebContents.
++//
++// Do not change or reuse the the entry values in this list as this is used in
++// ExtensionViewType enum in tools/metrics/histograms/enums.xml.
++//
++// TODO(aa): Remove this and teach those systems to keep track of their own
++// data.
++enum ViewType {
++  VIEW_TYPE_INVALID = 0,
++  // VIEW_TYPE_APP_WINDOW = 1,
++  // VIEW_TYPE_BACKGROUND_CONTENTS = 2,
++
++  // // For custom parts of Chrome if no other type applies.
++  // VIEW_TYPE_COMPONENT = 3,
++
++  // VIEW_TYPE_EXTENSION_BACKGROUND_PAGE = 4,
++  // VIEW_TYPE_EXTENSION_DIALOG = 5,
++  // VIEW_TYPE_EXTENSION_GUEST = 6,
++  // VIEW_TYPE_EXTENSION_POPUP = 7,
++
++  // Panels were removed in https://crbug.com/571511.
++  // DEPRECATED_VIEW_TYPE_PANEL = 8,
++
++  VIEW_TYPE_TAB_CONTENTS = 9,
++
++  VIEW_TYPE_LAST = VIEW_TYPE_TAB_CONTENTS
++};
++
++// Matches the |view_type| to the corresponding ViewType, and populates
++// |view_type_out|. Returns true if a match is found.
++bool GetViewTypeFromString(const std::string& view_type,
++                           ViewType* view_type_out);
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_COMMON_VIEW_TYPE_H_
+diff --git a/components/user_scripts/renderer/BUILD.gn b/components/user_scripts/renderer/BUILD.gn
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/BUILD.gn
+@@ -0,0 +1,67 @@
++# Copyright 2015 The Chromium Authors. All rights reserved.
++# Use of this source code is governed by a BSD-style license that can be
++# found in the LICENSE file.
++
++import("//tools/grit/grit_rule.gni")
++import("//tools/grit/repack.gni")
++
++group("user_scripts_resources") {
++  public_deps = [
++    ":user_scripts_renderer_resources",
++  ]
++}
++
++grit("user_scripts_renderer_resources") {
++  source = "resources/user_scripts_renderer_resources.grd"
++  outputs = [
++    "grit/user_scripts_renderer_resources.h",
++    "user_scripts_renderer_resources.pak",
++  ]
++  grit_flags = [
++    "-E",
++    "mojom_root=" + rebase_path(root_gen_dir, root_build_dir),
++  ]
++}
++
++static_library("renderer") {
++  sources = [
++    "extension_frame_helper.cc",
++    "extension_frame_helper.h",
++    "injection_host.cc",
++    "injection_host.h",
++    "script_injection_manager.cc",
++    "script_injection_manager.h",
++    "script_injection_callback.cc",
++    "script_injection_callback.h",
++    "script_injection.cc",
++    "script_injection.h",
++    "script_injector.h",
++    "script_context.cc",
++    "script_context.h",
++    "scripts_run_info.cc",
++    "scripts_run_info.h",
++    "user_script_injector.cc",
++    "user_script_injector.h",
++    "user_script_set_manager.cc",
++    "user_script_set_manager.h",
++    "user_script_set.cc",
++    "user_script_set.h",
++    "user_scripts_dispatcher.cc",
++    "user_scripts_dispatcher.h",
++    "user_scripts_renderer_client.cc",
++    "user_scripts_renderer_client.h",
++    "web_ui_injection_host.cc",
++    "web_ui_injection_host.h",
++  ]
++
++  deps = [
++    ":user_scripts_resources",
++    "//base",
++    "//content/public/common",
++    "//content/public/renderer",
++    "//components/user_scripts/common",
++    "//mojo/public/cpp/bindings",
++    "//third_party/blink/public:blink_headers",
++    "//v8",
++  ]
++}
+diff --git a/components/user_scripts/renderer/extension_frame_helper.cc b/components/user_scripts/renderer/extension_frame_helper.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/extension_frame_helper.cc
+@@ -0,0 +1,96 @@
++// Copyright 2013 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "extension_frame_helper.h"
++
++#include <set>
++
++#include "base/metrics/histogram_macros.h"
++#include "base/strings/string_util.h"
++#include "base/timer/elapsed_timer.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_view.h"
++#include "../common/constants.h"
++#include "third_party/blink/public/platform/web_security_origin.h"
++#include "third_party/blink/public/web/web_console_message.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_document_loader.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "third_party/blink/public/web/web_settings.h"
++#include "third_party/blink/public/web/web_view.h"
++
++namespace user_scripts {
++
++namespace {
++
++base::LazyInstance<std::set<const ExtensionFrameHelper*>>::DestructorAtExit
++    g_frame_helpers = LAZY_INSTANCE_INITIALIZER;
++
++// Runs every callback in |callbacks_to_be_run_and_cleared| while |frame_helper|
++// is valid, and clears |callbacks_to_be_run_and_cleared|.
++void RunCallbacksWhileFrameIsValid(
++    base::WeakPtr<ExtensionFrameHelper> frame_helper,
++    std::vector<base::OnceClosure>* callbacks_to_be_run_and_cleared) {
++  // The JavaScript code can cause re-entrancy. To avoid a deadlock, don't run
++  // callbacks that are added during the iteration.
++  std::vector<base::OnceClosure> callbacks;
++  callbacks_to_be_run_and_cleared->swap(callbacks);
++  for (auto& callback : callbacks) {
++    std::move(callback).Run();
++    if (!frame_helper.get())
++      return;  // Frame and ExtensionFrameHelper invalidated by callback.
++  }
++}
++
++}  // namespace
++
++ExtensionFrameHelper::ExtensionFrameHelper(content::RenderFrame* render_frame)
++    : content::RenderFrameObserver(render_frame),
++      content::RenderFrameObserverTracker<ExtensionFrameHelper>(render_frame),
++      tab_id_(-1) {
++  g_frame_helpers.Get().insert(this);
++}
++
++ExtensionFrameHelper::~ExtensionFrameHelper() {
++  g_frame_helpers.Get().erase(this);
++}
++
++void ExtensionFrameHelper::ScheduleAtDocumentStart(
++    base::OnceClosure callback) {
++  document_element_created_callbacks_.push_back(std::move(callback));
++}
++
++void ExtensionFrameHelper::ScheduleAtDocumentEnd(
++    base::OnceClosure callback) {
++  document_load_finished_callbacks_.push_back(std::move(callback));
++}
++
++void ExtensionFrameHelper::ScheduleAtDocumentIdle(
++    base::OnceClosure callback) {
++  document_idle_callbacks_.push_back(std::move(callback));
++}
++
++void ExtensionFrameHelper::RunScriptsAtDocumentStart() {
++  RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
++                                &document_element_created_callbacks_);
++  // |this| might be dead by now.
++}
++
++void ExtensionFrameHelper::RunScriptsAtDocumentEnd() {
++  RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
++                                &document_load_finished_callbacks_);
++  // |this| might be dead by now.
++}
++
++void ExtensionFrameHelper::RunScriptsAtDocumentIdle() {
++  RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
++                                &document_idle_callbacks_);
++  // |this| might be dead by now.
++}
++
++void ExtensionFrameHelper::OnDestruct() {
++  delete this;
++}
++
++}  // namespace user_scripts
+diff --git a/components/user_scripts/renderer/extension_frame_helper.h b/components/user_scripts/renderer/extension_frame_helper.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/extension_frame_helper.h
+@@ -0,0 +1,92 @@
++// Copyright 2013 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_EXTENSION_FRAME_HELPER_H_
++#define USERSCRIPTS_RENDERER_EXTENSION_FRAME_HELPER_H_
++
++#include <string>
++#include <vector>
++
++#include "base/callback_forward.h"
++#include "base/macros.h"
++#include "base/memory/weak_ptr.h"
++#include "content/public/renderer/render_frame_observer.h"
++#include "content/public/renderer/render_frame_observer_tracker.h"
++#include "../common/view_type.h"
++#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
++#include "v8/include/v8.h"
++
++struct ExtensionMsg_ExternalConnectionInfo;
++struct ExtensionMsg_TabConnectionInfo;
++
++namespace base {
++class ListValue;
++}
++
++namespace user_scripts {
++
++class Dispatcher;
++struct Message;
++struct PortId;
++class ScriptContext;
++
++// RenderFrame-level plumbing for extension features.
++class ExtensionFrameHelper
++    : public content::RenderFrameObserver,
++      public content::RenderFrameObserverTracker<ExtensionFrameHelper> {
++ public:
++  ExtensionFrameHelper(content::RenderFrame* render_frame /*,
++                       Dispatcher* extension_dispatcher*/);
++  ~ExtensionFrameHelper() override;
++
++  int tab_id() const { return tab_id_; }
++
++  // Called when the document element has been inserted in this frame. This
++  // method may invoke untrusted JavaScript code that invalidate the frame and
++  // this ExtensionFrameHelper.
++  void RunScriptsAtDocumentStart();
++
++  // Called after the DOMContentLoaded event has fired.
++  void RunScriptsAtDocumentEnd();
++
++  // Called before the window.onload event is fired.
++  void RunScriptsAtDocumentIdle();
++
++  // Schedule a callback, to be run at the next RunScriptsAtDocumentStart
++  // notification. Only call this when you are certain that there will be such a
++  // notification, e.g. from RenderFrameObserver::DidCreateDocumentElement.
++  // Otherwise the callback is never invoked, or invoked for a document that you
++  // were not expecting.
++  void ScheduleAtDocumentStart(base::OnceClosure callback);
++
++  // Schedule a callback, to be run at the next RunScriptsAtDocumentEnd call.
++  void ScheduleAtDocumentEnd(base::OnceClosure callback);
++
++  // Schedule a callback, to be run at the next RunScriptsAtDocumentIdle call.
++  void ScheduleAtDocumentIdle(base::OnceClosure callback);
++
++ private:
++
++  void OnDestruct() override;
++
++  // The id of the tab the render frame is attached to.
++  int tab_id_;
++
++  // Callbacks to be run at the next RunScriptsAtDocumentStart notification.
++  std::vector<base::OnceClosure> document_element_created_callbacks_;
++
++  // Callbacks to be run at the next RunScriptsAtDocumentEnd notification.
++  std::vector<base::OnceClosure> document_load_finished_callbacks_;
++
++  // Callbacks to be run at the next RunScriptsAtDocumentIdle notification.
++  std::vector<base::OnceClosure> document_idle_callbacks_;
++
++  base::WeakPtrFactory<ExtensionFrameHelper> weak_ptr_factory_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(ExtensionFrameHelper);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_EXTENSION_FRAME_HELPER_H_
+diff --git a/components/user_scripts/renderer/injection_host.cc b/components/user_scripts/renderer/injection_host.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/injection_host.cc
+@@ -0,0 +1,12 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "injection_host.h"
++
++InjectionHost::InjectionHost(const HostID& host_id) :
++    id_(host_id) {
++}
++
++InjectionHost::~InjectionHost() {
++}
+diff --git a/components/user_scripts/renderer/injection_host.h b/components/user_scripts/renderer/injection_host.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/injection_host.h
+@@ -0,0 +1,42 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_INJECTION_HOST_H_
++#define USERSCRIPTS_RENDERER_INJECTION_HOST_H_
++
++#include "base/macros.h"
++#include "../common/host_id.h"
++#include "url/gurl.h"
++
++namespace content {
++class RenderFrame;
++}
++
++// An interface for all kinds of hosts who own user scripts.
++class InjectionHost {
++ public:
++  InjectionHost(const HostID& host_id);
++  virtual ~InjectionHost();
++
++  // Returns the CSP to be used for the isolated world. Currently this only
++  // bypasses the main world CSP. If null is returned, the main world CSP is not
++  // bypassed.
++  virtual const std::string* GetContentSecurityPolicy() const = 0;
++
++  // The base url for the host.
++  virtual const GURL& url() const = 0;
++
++  // The human-readable name of the host.
++  virtual const std::string& name() const = 0;
++
++  const HostID& id() const { return id_; }
++
++ private:
++  // The ID of the host.
++  HostID id_;
++
++  DISALLOW_COPY_AND_ASSIGN(InjectionHost);
++};
++
++#endif  // USERSCRIPTS_RENDERER_INJECTION_HOST_H_
+diff --git a/components/user_scripts/renderer/resources/greasemonkey_api.js b/components/user_scripts/renderer/resources/greasemonkey_api.js
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/resources/greasemonkey_api.js
+@@ -0,0 +1,82 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++// -----------------------------------------------------------------------------
++// NOTE: If you change this file you need to touch renderer_resources.grd to
++// have your change take effect.
++// -----------------------------------------------------------------------------
++
++// Partial implementation of the Greasemonkey API, see:
++// http://wiki.greasespot.net/Greasemonkey_Manual:APIs
++
++function GM_addStyle(css) {
++  var parent = document.getElementsByTagName("head")[0];
++  if (!parent) {
++    parent = document.documentElement;
++  }
++  var style = document.createElement("style");
++  style.type = "text/css";
++  var textNode = document.createTextNode(css);
++  style.appendChild(textNode);
++  parent.appendChild(style);
++}
++
++function GM_xmlhttpRequest(details) {
++  function setupEvent(xhr, url, eventName, callback) {
++    xhr[eventName] = function () {
++      var isComplete = xhr.readyState == 4;
++      var responseState = {
++        responseText: xhr.responseText,
++        readyState: xhr.readyState,
++        responseHeaders: isComplete ? xhr.getAllResponseHeaders() : "",
++        status: isComplete ? xhr.status : 0,
++        statusText: isComplete ? xhr.statusText : "",
++        finalUrl: isComplete ? url : ""
++      };
++      callback(responseState);
++    };
++  }
++
++  var xhr = new XMLHttpRequest();
++  var eventNames = ["onload", "onerror", "onreadystatechange"];
++  for (var i = 0; i < eventNames.length; i++ ) {
++    var eventName = eventNames[i];
++    if (eventName in details) {
++      setupEvent(xhr, details.url, eventName, details[eventName]);
++    }
++  }
++
++  xhr.open(details.method, details.url);
++
++  if (details.overrideMimeType) {
++    xhr.overrideMimeType(details.overrideMimeType);
++  }
++  if (details.headers) {
++    for (var header in details.headers) {
++      xhr.setRequestHeader(header, details.headers[header]);
++    }
++  }
++  xhr.send(details.data ? details.data : null);
++}
++
++function GM_openInTab(url) {
++  window.open(url, "");
++}
++
++function GM_log(message) {
++  window.console.log(message);
++}
++
++(function() {
++  function generateGreasemonkeyStub(name) {
++    return function() {
++      console.log("%s is not supported.", name);
++    };
++  }
++
++  var apis = ["GM_getValue", "GM_setValue", "GM_registerMenuCommand"];
++  for (var i = 0, api; api = apis[i]; i++) {
++    window[api] = generateGreasemonkeyStub(api);
++  }
++})();
+diff --git a/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd b/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd
+@@ -0,0 +1,14 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
++  <outputs>
++    <output filename="grit/user_scripts_renderer_resources.h" type="rc_header">
++      <emit emit_type='prepend'></emit>
++    </output>
++    <output filename="user_scripts_renderer_resources.pak" type="data_package" />
++  </outputs>
++  <release seq="1">
++    <includes>
++      <include name="IDR_GREASEMONKEY_API_JS" file="greasemonkey_api.js" type="BINDATA" />
++    </includes>
++  </release>
++</grit>
+diff --git a/components/user_scripts/renderer/script_context.cc b/components/user_scripts/renderer/script_context.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_context.cc
+@@ -0,0 +1,215 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "script_context.h"
++
++#include "base/command_line.h"
++#include "base/containers/flat_set.h"
++#include "base/containers/contains.h"
++#include "base/logging.h"
++#include "base/no_destructor.h"
++#include "base/stl_util.h"
++#include "base/strings/string_util.h"
++#include "base/strings/stringprintf.h"
++#include "base/values.h"
++#include "content/public/common/content_switches.h"
++#include "content/public/common/url_constants.h"
++#include "content/public/renderer/render_frame.h"
++#include "../common/constants.h"
++#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
++#include "third_party/blink/public/platform/web_security_origin.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_document_loader.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "third_party/blink/public/web/web_navigation_params.h"
++#include "v8/include/v8.h"
++
++namespace user_scripts {
++
++namespace {
++
++GURL GetEffectiveDocumentURL(
++    blink::WebLocalFrame* frame,
++    const GURL& document_url,
++    MatchOriginAsFallbackBehavior match_origin_as_fallback,
++    bool allow_inaccessible_parents) {
++  auto should_consider_origin = [document_url, match_origin_as_fallback]() {
++    switch (match_origin_as_fallback) {
++      case MatchOriginAsFallbackBehavior::kNever:
++        return false;
++      case MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree:
++        return document_url.SchemeIs(url::kAboutScheme);
++      case MatchOriginAsFallbackBehavior::kAlways:
++        // TODO(devlin): Add more schemes here - blob, filesystem, etc.
++        return document_url.SchemeIs(url::kAboutScheme) ||
++               document_url.SchemeIs(url::kDataScheme);
++    }
++
++    NOTREACHED();
++  };
++
++  // If we don't need to consider the origin, we're done.
++  if (!should_consider_origin())
++    return document_url;
++
++  // Get the "security origin" for the frame. For about: frames, this is the
++  // origin of that of the controlling frame - e.g., an about:blank frame on
++  // https://example.com will have the security origin of https://example.com.
++  // Other frames, like data: frames, will have an opaque origin. For these,
++  // we can get the precursor origin.
++  const blink::WebSecurityOrigin web_frame_origin = frame->GetSecurityOrigin();
++  const url::Origin frame_origin = web_frame_origin;
++  const url::SchemeHostPort& tuple_or_precursor_tuple =
++      frame_origin.GetTupleOrPrecursorTupleIfOpaque();
++
++  // When there's no valid tuple (which can happen in the case of e.g. a
++  // browser-initiated navigation to an opaque URL), there's no origin to
++  // fallback to. Bail.
++  if (!tuple_or_precursor_tuple.IsValid())
++    return document_url;
++
++  const url::Origin origin_or_precursor_origin =
++      url::Origin::Create(tuple_or_precursor_tuple.GetURL());
++
++  if (!allow_inaccessible_parents &&
++      !web_frame_origin.CanAccess(
++          blink::WebSecurityOrigin(origin_or_precursor_origin))) {
++    // The frame can't access its precursor. Bail.
++    return document_url;
++  }
++
++  // Looks like the initiator origin is an appropriate fallback!
++
++  if (match_origin_as_fallback == MatchOriginAsFallbackBehavior::kAlways) {
++    // The easy case! We use the origin directly. We're done.
++    return origin_or_precursor_origin.GetURL();
++  }
++
++  DCHECK_EQ(MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree,
++            match_origin_as_fallback);
++
++  // Unfortunately, in this case, we have to climb the frame tree. This is for
++  // match patterns that are associated with paths as well, not just origins.
++  // For instance, if an extension wants to run on google.com/maps/* with
++  // match_about_blank true, then it should run on about:-scheme frames created
++  // by google.com/maps, but not about:-scheme frames created by google.com
++  // (which is what the precursor tuple origin would be).
++
++  // Traverse the frame/window hierarchy to find the closest non-about:-page
++  // with the same origin as the precursor and return its URL.
++  // Note: This can return the incorrect result, e.g. if a parent frame
++  // navigates a grandchild frame.
++  blink::WebFrame* parent = frame;
++  GURL parent_url;
++  blink::WebDocument parent_document;
++  base::flat_set<blink::WebFrame*> already_visited_frames;
++  do {
++    already_visited_frames.insert(parent);
++    if (parent->Parent())
++      parent = parent->Parent();
++    else
++      parent = parent->Opener();
++
++    // Avoid an infinite loop - see https://crbug.com/568432 and
++    // https://crbug.com/883526.
++    if (base::Contains(already_visited_frames, parent))
++      return document_url;
++
++    parent_document = parent && parent->IsWebLocalFrame()
++                          ? parent->ToWebLocalFrame()->GetDocument()
++                          : blink::WebDocument();
++
++    // We reached the end of the ancestral chain without finding a valid parent,
++    // or found a remote web frame (in which case, it's a different origin).
++    // Bail and use the original URL.
++    if (parent_document.IsNull())
++      return document_url;
++
++    url::SchemeHostPort parent_tuple_or_precursor_tuple =
++        url::Origin(parent->GetSecurityOrigin())
++            .GetTupleOrPrecursorTupleIfOpaque();
++    if (!parent_tuple_or_precursor_tuple.IsValid() ||
++        parent_tuple_or_precursor_tuple != tuple_or_precursor_tuple) {
++      // The parent has a different tuple origin than frame; this could happen
++      // in edge cases where a parent navigates an iframe or popup of a child
++      // frame at a different origin. [1] In this case, bail, since we can't
++      // find a full URL (i.e., one including the path) with the same security
++      // origin to use for the frame in question.
++      // [1] Consider a frame tree like:
++      // <html> <!--example.com-->
++      //   <iframe id="a" src="a.com">
++      //     <iframe id="b" src="b.com"></iframe>
++      //   </iframe>
++      // </html>
++      // Frame "a" is cross-origin from the top-level frame, and so the
++      // example.com top-level frame can't directly access frame "b". However,
++      // it can navigate it through
++      // window.frames[0].frames[0].location.href = 'about:blank';
++      // In that case, the precursor origin tuple origin of frame "b" would be
++      // example.com, but the parent tuple origin is a.com.
++      // Note that usually, this would have bailed earlier with a remote frame,
++      // but it may not if we're at the process limit.
++      return document_url;
++    }
++
++    parent_url = GURL(parent_document.Url());
++  } while (parent_url.SchemeIs(url::kAboutScheme));
++
++  DCHECK(!parent_url.is_empty());
++  DCHECK(!parent_document.IsNull());
++
++  // We should know that the frame can access the parent document (unless we
++  // explicitly allow it not to), since it has the same tuple origin as the
++  // frame, and we checked the frame access above.
++  DCHECK(allow_inaccessible_parents ||
++         web_frame_origin.CanAccess(parent_document.GetSecurityOrigin()));
++  return parent_url;
++}
++
++using FrameToDocumentLoader =
++    base::flat_map<blink::WebLocalFrame*, blink::WebDocumentLoader*>;
++
++FrameToDocumentLoader& FrameDocumentLoaderMap() {
++  static base::NoDestructor<FrameToDocumentLoader> map;
++  return *map;
++}
++
++blink::WebDocumentLoader* CurrentDocumentLoader(
++    const blink::WebLocalFrame* frame) {
++  auto& map = FrameDocumentLoaderMap();
++  auto it = map.find(frame);
++  return it == map.end() ? frame->GetDocumentLoader() : it->second;
++}
++
++}  // namespace
++
++// static
++GURL ScriptContext::GetDocumentLoaderURLForFrame(
++    const blink::WebLocalFrame* frame) {
++  // Normally we would use frame->document().url() to determine the document's
++  // URL, but to decide whether to inject a content script, we use the URL from
++  // the data source. This "quirk" helps prevents content scripts from
++  // inadvertently adding DOM elements to the compose iframe in Gmail because
++  // the compose iframe's dataSource URL is about:blank, but the document URL
++  // changes to match the parent document after Gmail document.writes into
++  // it to create the editor.
++  // http://code.google.com/p/chromium/issues/detail?id=86742
++  blink::WebDocumentLoader* document_loader = CurrentDocumentLoader(frame);
++  return document_loader ? GURL(document_loader->GetUrl()) : GURL();
++}
++
++// static
++GURL ScriptContext::GetEffectiveDocumentURLForInjection(
++    blink::WebLocalFrame* frame,
++    const GURL& document_url,
++    MatchOriginAsFallbackBehavior match_origin_as_fallback) {
++  // We explicitly allow inaccessible parents here. Extensions should still be
++  // able to inject into a sandboxed iframe if it has access to the embedding
++  // origin.
++  constexpr bool allow_inaccessible_parents = true;
++  return GetEffectiveDocumentURL(frame, document_url, match_origin_as_fallback,
++                                 allow_inaccessible_parents);
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/script_context.h b/components/user_scripts/renderer/script_context.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_context.h
+@@ -0,0 +1,70 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_SCRIPT_CONTEXT_H_
++#define USERSCRIPTS_RENDERER_SCRIPT_CONTEXT_H_
++
++#include <memory>
++#include <string>
++#include <utility>
++#include <vector>
++
++#include "base/callback.h"
++#include "base/compiler_specific.h"
++#include "base/macros.h"
++#include "base/threading/thread_checker.h"
++#include "base/unguessable_token.h"
++#include "../common/script_constants.h"
++#include "script_injection_callback.h"
++#include "url/gurl.h"
++#include "v8/include/v8.h"
++
++namespace blink {
++class WebDocumentLoader;
++class WebLocalFrame;
++}
++
++namespace content {
++class RenderFrame;
++}
++
++namespace user_scripts {
++
++// Extensions wrapper for a v8::Context.
++//
++// v8::Contexts can be constructed on any thread, and must only be accessed or
++// destroyed that thread.
++//
++// Note that ScriptContexts bound to worker threads will not have the full
++// functionality as those bound to the main RenderThread.
++class ScriptContext {
++ public:
++  // TODO(devlin): Move all these Get*URL*() methods out of here? While they are
++  // vaguely ScriptContext related, there's enough here that they probably
++  // warrant another class or utility file.
++
++  // Utility to get the URL we will match against for a frame. If the frame has
++  // committed, this is the commited URL. Otherwise it is the provisional URL.
++  // The returned URL may be invalid.
++  static GURL GetDocumentLoaderURLForFrame(const blink::WebLocalFrame* frame);
++
++  // Used to determine the "effective" URL for extension script injection.
++  // If |document_url| is an about: or data: URL, returns the URL of the first
++  // frame without an about: or data: URL that matches the initiator origin.
++  // This may not be the immediate parent. Returns |document_url| if it is not
++  // an about: or data: URL, if |match_origin_as_fallback| is set to not match,
++  // or if a suitable parent cannot be found.
++  // Considers parent contexts that cannot be accessed (as is the case for
++  // sandboxed frames).
++  static GURL GetEffectiveDocumentURLForInjection(
++      blink::WebLocalFrame* frame,
++      const GURL& document_url,
++      MatchOriginAsFallbackBehavior match_origin_as_fallback);
++
++//   DISALLOW_COPY_AND_ASSIGN(ScriptContext);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_SCRIPT_CONTEXT_H_
+diff --git a/components/user_scripts/renderer/script_injection.cc b/components/user_scripts/renderer/script_injection.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection.cc
+@@ -0,0 +1,343 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "script_injection.h"
++
++#include <map>
++#include <utility>
++
++#include "base/bind.h"
++#include "base/feature_list.h"
++#include "base/lazy_instance.h"
++#include "base/macros.h"
++#include "base/metrics/histogram_macros.h"
++#include "base/timer/elapsed_timer.h"
++#include "base/values.h"
++#include "base/logging.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_frame_observer.h"
++#include "content/public/renderer/v8_value_converter.h"
++#include "../common/host_id.h"
++#include "script_injection_callback.h"
++#include "scripts_run_info.h"
++#include "third_party/blink/public/platform/web_isolated_world_info.h"
++#include "third_party/blink/public/platform/web_security_origin.h"
++#include "third_party/blink/public/platform/web_string.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "third_party/blink/public/web/web_script_source.h"
++#include "url/gurl.h"
++
++namespace user_scripts {
++
++namespace {
++
++using IsolatedWorldMap = std::map<std::string, int>;
++base::LazyInstance<IsolatedWorldMap>::DestructorAtExit g_isolated_worlds =
++    LAZY_INSTANCE_INITIALIZER;
++
++const int64_t kInvalidRequestId = -1;
++
++// Gets the isolated world ID to use for the given |injection_host|. If no
++// isolated world has been created for that |injection_host| one will be created
++// and initialized.
++int GetIsolatedWorldIdForInstance(const InjectionHost* injection_host) {
++  static int g_next_isolated_world_id = 1; // Embedder isolated worlds can use IDs in [1, 1<<29).
++
++  IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
++
++  int id = 0;
++  const std::string& key = injection_host->id().id();
++  auto iter = isolated_worlds.find(key);
++  if (iter != isolated_worlds.end()) {
++    id = iter->second;
++  } else {
++    id = g_next_isolated_world_id++;
++    // This map will tend to pile up over time, but realistically, you're never
++    // going to have enough injection hosts for it to matter.
++    isolated_worlds[key] = id;
++  }
++
++  blink::WebIsolatedWorldInfo info;
++  info.security_origin =
++      blink::WebSecurityOrigin::Create(injection_host->url());
++  info.human_readable_name = blink::WebString::FromUTF8(injection_host->name());
++  info.stable_id = blink::WebString::FromUTF8(key);
++
++  const std::string* csp = injection_host->GetContentSecurityPolicy();
++  if (csp)
++    info.content_security_policy = blink::WebString::FromUTF8(*csp);
++
++  // Even though there may be an existing world for this |injection_host|'s key,
++  // the properties may have changed (e.g. due to an extension update).
++  // Overwrite any existing entries.
++  blink::SetIsolatedWorldInfo(id, info);
++
++  return id;
++}
++
++// This class manages its own lifetime.
++class TimedScriptInjectionCallback : public ScriptInjectionCallback {
++ public:
++  TimedScriptInjectionCallback(base::WeakPtr<ScriptInjection> injection)
++      : ScriptInjectionCallback(
++            base::BindOnce(&TimedScriptInjectionCallback::OnCompleted,
++                       base::Unretained(this))),
++        injection_(injection) {}
++  ~TimedScriptInjectionCallback() override {}
++
++  void OnCompleted(const std::vector<v8::Local<v8::Value>>& result) {
++    if (injection_) {
++      base::TimeTicks timestamp(base::TimeTicks::Now());
++      absl::optional<base::TimeDelta> elapsed;
++      // If the script will never execute (such as if the context is destroyed),
++      // willExecute() will not be called, but OnCompleted() will. Only log a
++      // time for execution if the script, in fact, executed.
++      if (!start_time_.is_null())
++        elapsed = timestamp - start_time_;
++      injection_->OnJsInjectionCompleted(result, elapsed);
++    }
++  }
++
++  void WillExecute() override {
++    start_time_ = base::TimeTicks::Now();
++  }
++
++ private:
++  base::WeakPtr<ScriptInjection> injection_;
++  base::TimeTicks start_time_;
++};
++
++}  // namespace
++
++// Watches for the deletion of a RenderFrame, after which is_valid will return
++// false.
++class ScriptInjection::FrameWatcher : public content::RenderFrameObserver {
++ public:
++  FrameWatcher(content::RenderFrame* render_frame,
++               ScriptInjection* injection)
++      : content::RenderFrameObserver(render_frame),
++        injection_(injection) {}
++  ~FrameWatcher() override {}
++
++ private:
++  void WillDetach() override { injection_->invalidate_render_frame(); }
++  void OnDestruct() override { injection_->invalidate_render_frame(); }
++
++  ScriptInjection* injection_;
++
++  DISALLOW_COPY_AND_ASSIGN(FrameWatcher);
++};
++
++// static
++std::string ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id) {
++  const IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
++
++  for (const auto& iter : isolated_worlds) {
++    if (iter.second == isolated_world_id)
++      return iter.first;
++  }
++  return std::string();
++}
++
++// static
++void ScriptInjection::RemoveIsolatedWorld(const std::string& host_id) {
++  g_isolated_worlds.Get().erase(host_id);
++}
++
++ScriptInjection::ScriptInjection(
++    std::unique_ptr<ScriptInjector> injector,
++    content::RenderFrame* render_frame,
++    std::unique_ptr<const InjectionHost> injection_host,
++    UserScript::RunLocation run_location,
++    bool log_activity)
++    : injector_(std::move(injector)),
++      render_frame_(render_frame),
++      injection_host_(std::move(injection_host)),
++      run_location_(run_location),
++      request_id_(kInvalidRequestId),
++      complete_(false),
++      did_inject_js_(false),
++      log_activity_(log_activity),
++      frame_watcher_(new FrameWatcher(render_frame, this)) {
++  CHECK(injection_host_.get());
++}
++
++ScriptInjection::~ScriptInjection() {
++  if (!complete_)
++    NotifyWillNotInject(ScriptInjector::WONT_INJECT);
++}
++
++ScriptInjection::InjectionResult ScriptInjection::TryToInject(
++    UserScript::RunLocation current_location,
++    ScriptsRunInfo* scripts_run_info,
++    CompletionCallback async_completion_callback) {
++  if (current_location < run_location_)
++    return INJECTION_WAITING;  // Wait for the right location.
++
++  if (request_id_ != kInvalidRequestId) {
++    // We're waiting for permission right now, try again later.
++    return INJECTION_WAITING;
++  }
++
++  if (!injection_host_) {
++    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
++    return INJECTION_FINISHED;  // We're done.
++  }
++
++  InjectionResult result = Inject(scripts_run_info);
++  // If the injection is blocked, we need to set the manager so we can
++  // notify it upon completion.
++  if (result == INJECTION_BLOCKED)
++    async_completion_callback_ = std::move(async_completion_callback);
++  return result;
++}
++
++ScriptInjection::InjectionResult ScriptInjection::OnPermissionGranted(
++    ScriptsRunInfo* scripts_run_info) {
++  if (!injection_host_) {
++    NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
++    return INJECTION_FINISHED;
++  }
++
++  return Inject(scripts_run_info);
++}
++
++void ScriptInjection::OnHostRemoved() {
++  injection_host_.reset(nullptr);
++}
++
++void ScriptInjection::NotifyWillNotInject(
++    ScriptInjector::InjectFailureReason reason) {
++  complete_ = true;
++  injector_->OnWillNotInject(reason, render_frame_);
++}
++
++ScriptInjection::InjectionResult ScriptInjection::Inject(
++    ScriptsRunInfo* scripts_run_info) {
++  DCHECK(injection_host_);
++  //DCHECK(scripts_run_info);
++  DCHECK(!complete_);
++  bool should_inject_js = injector_->ShouldInjectJs(
++      run_location_, scripts_run_info->executing_scripts[host_id().id()]);
++  bool should_inject_css = injector_->ShouldInjectCss(
++      run_location_, scripts_run_info->injected_stylesheets[host_id().id()]);
++
++  // This can happen if the extension specified a script to
++  // be run in multiple rules, and the script has already run.
++  // See crbug.com/631247.
++  if (!should_inject_js && !should_inject_css) {
++    return INJECTION_FINISHED;
++  }
++
++  if (should_inject_js)
++    InjectJs(&(scripts_run_info->executing_scripts[host_id().id()]),
++             &(scripts_run_info->num_js));
++  if (should_inject_css)
++    InjectCss(&(scripts_run_info->injected_stylesheets[host_id().id()]),
++              &(scripts_run_info->num_css));
++
++  complete_ = did_inject_js_ || !should_inject_js;
++
++  if (complete_) {
++    injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
++                                   render_frame_);
++  } else {
++    ++scripts_run_info->num_blocking_js;
++  }
++
++  return complete_ ? INJECTION_FINISHED : INJECTION_BLOCKED;
++}
++
++void ScriptInjection::InjectJs(std::set<std::string>* executing_scripts,
++                               size_t* num_injected_js_scripts) {
++  DCHECK(!did_inject_js_);
++  std::vector<blink::WebScriptSource> sources = injector_->GetJsSources(
++      run_location_, executing_scripts, num_injected_js_scripts);
++  DCHECK(!sources.empty());
++  int world_id = GetIsolatedWorldIdForInstance(injection_host_.get());
++  bool is_user_gesture = injector_->IsUserGesture();
++
++  std::unique_ptr<blink::WebScriptExecutionCallback> callback(
++      new TimedScriptInjectionCallback(weak_ptr_factory_.GetWeakPtr()));
++
++  base::ElapsedTimer exec_timer;
++
++  // For content scripts executing during page load, we run them asynchronously
++  // in order to reduce UI jank experienced by the user. (We don't do this for
++  // DOCUMENT_START scripts, because there's no UI to jank until after those
++  // run, so we run them as soon as we can.)
++  // Note: We could potentially also run deferred and browser-driven scripts
++  // asynchronously; however, these are rare enough that there probably isn't
++  // UI jank. If this changes, we can update this.
++  bool should_execute_asynchronously =
++      injector_->script_type() == UserScript::CONTENT_SCRIPT &&
++      (run_location_ == UserScript::DOCUMENT_END ||
++       run_location_ == UserScript::DOCUMENT_IDLE);
++  blink::WebLocalFrame::ScriptExecutionType execution_option =
++      should_execute_asynchronously
++          ? blink::WebLocalFrame::kAsynchronousBlockingOnload
++          : blink::WebLocalFrame::kSynchronous;
++
++  render_frame_->GetWebFrame()->RequestExecuteScript(
++      world_id, sources, is_user_gesture,
++      execution_option, callback.release(),
++      blink::BackForwardCacheAware::kPossiblyDisallow);
++}
++
++void ScriptInjection::OnJsInjectionCompleted(
++    const std::vector<v8::Local<v8::Value>>& results,
++    absl::optional<base::TimeDelta> elapsed) {
++  DCHECK(!did_inject_js_);
++
++  bool expects_results = injector_->ExpectsResults();
++  if (expects_results) {
++    if (!results.empty() && !results[0].IsEmpty()) {
++      // Right now, we only support returning single results (per frame).
++      // It's safe to always use the main world context when converting
++      // here. V8ValueConverterImpl shouldn't actually care about the
++      // context scope, and it switches to v8::Object's creation context
++      // when encountered.
++      v8::Local<v8::Context> context =
++          render_frame_->GetWebFrame()->MainWorldScriptContext();
++      execution_result_ =
++          content::V8ValueConverter::Create()->FromV8Value(results[0], context);
++    }
++    if (!execution_result_.get())
++      execution_result_ = std::make_unique<base::Value>();
++  }
++  did_inject_js_ = true;
++
++  // If |async_completion_callback_| is set, it means the script finished
++  // asynchronously, and we should run it.
++  if (!async_completion_callback_.is_null()) {
++    complete_ = true;
++    injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
++                                   render_frame_);
++    // Warning: this object can be destroyed after this line!
++    std::move(async_completion_callback_).Run(this);
++  }
++}
++
++void ScriptInjection::InjectCss(std::set<std::string>* injected_stylesheets,
++                                size_t* num_injected_stylesheets) {
++  std::vector<blink::WebString> css_sources = injector_->GetCssSources(
++      run_location_, injected_stylesheets, num_injected_stylesheets);
++  blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
++  // Default CSS origin is "author", but can be overridden to "user" by scripts.
++  absl::optional<CSSOrigin> css_origin = injector_->GetCssOrigin();
++  blink::WebDocument::CSSOrigin blink_css_origin =
++      css_origin && *css_origin == CSS_ORIGIN_USER
++          ? blink::WebDocument::kUserOrigin
++          : blink::WebDocument::kAuthorOrigin;
++  blink::WebStyleSheetKey style_sheet_key;
++  if (const absl::optional<std::string>& injection_key =
++          injector_->GetInjectionKey())
++    style_sheet_key = blink::WebString::FromASCII(*injection_key);
++  for (const blink::WebString& css : css_sources)
++    web_frame->GetDocument().InsertStyleSheet(css, &style_sheet_key,
++                                              blink_css_origin);
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/script_injection.h b/components/user_scripts/renderer/script_injection.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection.h
+@@ -0,0 +1,155 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_SCRIPT_INJECTION_H_
++#define USERSCRIPTS_RENDERER_SCRIPT_INJECTION_H_
++
++#include <stdint.h>
++
++#include <memory>
++#include <vector>
++
++#include "base/callback.h"
++#include "base/macros.h"
++#include "base/memory/weak_ptr.h"
++#include "../common/user_script.h"
++#include "injection_host.h"
++#include "script_injector.h"
++
++struct HostID;
++
++namespace content {
++class RenderFrame;
++}
++
++namespace v8 {
++class Value;
++template <class T> class Local;
++}
++
++namespace user_scripts {
++struct ScriptsRunInfo;
++
++// A script wrapper which is aware of whether or not it is allowed to execute,
++// and contains the implementation to do so.
++class ScriptInjection {
++ public:
++  enum InjectionResult {
++    INJECTION_FINISHED,
++    INJECTION_BLOCKED,
++    INJECTION_WAITING
++  };
++
++  using CompletionCallback = base::OnceCallback<void(ScriptInjection*)>;
++
++  // Return the id of the injection host associated with the given world.
++  static std::string GetHostIdForIsolatedWorld(int world_id);
++
++  // Remove the isolated world associated with the given injection host.
++  static void RemoveIsolatedWorld(const std::string& host_id);
++
++  ScriptInjection(std::unique_ptr<ScriptInjector> injector,
++                  content::RenderFrame* render_frame,
++                  std::unique_ptr<const InjectionHost> injection_host,
++                  UserScript::RunLocation run_location,
++                  bool log_activity);
++  ~ScriptInjection();
++
++  // Try to inject the script at the |current_location|. This returns
++  // INJECTION_FINISHED if injection has injected or will never inject, returns
++  // INJECTION_BLOCKED if injection is running asynchronously and has not
++  // finished yet, returns INJECTION_WAITING if injections is delayed (either
++  // for permission purposes or because |current_location| is not the designated
++  // |run_location_|).
++  // If INJECTION_BLOCKED is returned, |async_completion_callback| will be
++  // called upon completion.
++  InjectionResult TryToInject(
++      UserScript::RunLocation current_location,
++      ScriptsRunInfo* scripts_run_info,
++      CompletionCallback async_completion_callback);
++
++  // Called when permission for the given injection has been granted.
++  // Returns INJECTION_FINISHED if injection has injected or will never inject,
++  // returns INJECTION_BLOCKED if injection is ran asynchronously.
++  InjectionResult OnPermissionGranted(ScriptsRunInfo* scripts_run_info);
++
++  // Resets the pointer of the injection host when the host is gone.
++  void OnHostRemoved();
++
++  void invalidate_render_frame() { render_frame_ = nullptr; }
++
++  // Accessors.
++  content::RenderFrame* render_frame() const { return render_frame_; }
++  const HostID& host_id() const { return injection_host_->id(); }
++  int64_t request_id() const { return request_id_; }
++
++  // Called when JS injection for the given frame has been completed or
++  // cancelled.
++  void OnJsInjectionCompleted(const std::vector<v8::Local<v8::Value>>& results,
++                              absl::optional<base::TimeDelta> elapsed);
++
++ private:
++  class FrameWatcher;
++
++  // Sends a message to the browser to request permission to inject.
++  void RequestPermissionFromBrowser();
++
++  // Injects the script. Returns INJECTION_FINISHED if injection has finished,
++  // otherwise INJECTION_BLOCKED.
++  InjectionResult Inject(ScriptsRunInfo* scripts_run_info);
++
++  // Inject any JS scripts into the frame for the injection.
++  void InjectJs(std::set<std::string>* executing_scripts,
++                size_t* num_injected_js_scripts);
++
++  // Inject any CSS source into the frame for the injection.
++  void InjectCss(std::set<std::string>* injected_stylesheets,
++                 size_t* num_injected_stylesheets);
++
++  // Notify that we will not inject, and mark it as acknowledged.
++  void NotifyWillNotInject(ScriptInjector::InjectFailureReason reason);
++
++  // The injector for this injection.
++  std::unique_ptr<ScriptInjector> injector_;
++
++  // The RenderFrame into which this should inject the script.
++  content::RenderFrame* render_frame_;
++
++  // The associated injection host.
++  std::unique_ptr<const InjectionHost> injection_host_;
++
++  // The location in the document load at which we inject the script.
++  UserScript::RunLocation run_location_;
++
++  // This injection's request id. This will be -1 unless the injection is
++  // currently waiting on permission.
++  int64_t request_id_;
++
++  // Whether or not the injection is complete, either via injecting the script
++  // or because it will never complete.
++  bool complete_;
++
++  // Whether or not the injection successfully injected JS.
++  bool did_inject_js_;
++
++  // Whether or not we should log dom activity for this injection.
++  bool log_activity_;
++
++  // Results storage.
++  std::unique_ptr<base::Value> execution_result_;
++
++  // The callback to run upon completing asynchronously.
++  CompletionCallback async_completion_callback_;
++
++  // A helper class to hold the render frame and watch for its deletion.
++  std::unique_ptr<FrameWatcher> frame_watcher_;
++
++  base::WeakPtrFactory<ScriptInjection> weak_ptr_factory_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(ScriptInjection);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_SCRIPT_INJECTION_H_
+diff --git a/components/user_scripts/renderer/script_injection_callback.cc b/components/user_scripts/renderer/script_injection_callback.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection_callback.cc
+@@ -0,0 +1,25 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "script_injection_callback.h"
++
++#include "third_party/blink/public/platform/web_vector.h"
++
++namespace user_scripts {
++
++ScriptInjectionCallback::ScriptInjectionCallback(
++    CompleteCallback injection_completed_callback)
++    : injection_completed_callback_(std::move(injection_completed_callback)) {}
++
++ScriptInjectionCallback::~ScriptInjectionCallback() {
++}
++
++void ScriptInjectionCallback::Completed(
++    const blink::WebVector<v8::Local<v8::Value>>& result) {
++  std::vector<v8::Local<v8::Value>> stl_result(result.begin(), result.end());
++  std::move(injection_completed_callback_).Run(stl_result);
++  delete this;
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/script_injection_callback.h b/components/user_scripts/renderer/script_injection_callback.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection_callback.h
+@@ -0,0 +1,39 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
++#define USERSCRIPTS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
++
++#include <vector>
++
++#include "base/callback.h"
++#include "base/macros.h"
++#include "third_party/blink/public/web/web_script_execution_callback.h"
++#include "v8/include/v8.h"
++
++namespace user_scripts {
++
++// A wrapper around a callback to notify a script injection when injection
++// completes.
++// This class manages its own lifetime.
++class ScriptInjectionCallback : public blink::WebScriptExecutionCallback {
++ public:
++  using CompleteCallback =
++      base::OnceCallback<void(const std::vector<v8::Local<v8::Value>>& result)>;
++
++  explicit ScriptInjectionCallback(
++      CompleteCallback injection_completed_callback);
++  ~ScriptInjectionCallback() override;
++
++  void Completed(const blink::WebVector<v8::Local<v8::Value>>& result) override;
++
++ private:
++  CompleteCallback injection_completed_callback_;
++
++  DISALLOW_COPY_AND_ASSIGN(ScriptInjectionCallback);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_SCRIPT_INJECTION_CALLBACK_H_
+diff --git a/components/user_scripts/renderer/script_injection_manager.cc b/components/user_scripts/renderer/script_injection_manager.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection_manager.cc
+@@ -0,0 +1,417 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "script_injection_manager.h"
++
++#include <memory>
++#include <utility>
++
++#include "base/auto_reset.h"
++#include "base/bind.h"
++#include "base/feature_list.h"
++#include "base/memory/weak_ptr.h"
++#include "base/threading/thread_task_runner_handle.h"
++#include "base/values.h"
++#include "base/logging.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_frame_observer.h"
++#include "content/public/renderer/render_thread.h"
++#include "extension_frame_helper.h"
++#include "../common/host_id.h"
++#include "script_injection.h"
++#include "scripts_run_info.h"
++#include "web_ui_injection_host.h"
++#include "ipc/ipc_message_macros.h"
++#include "third_party/blink/public/platform/web_url_error.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_frame.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "third_party/blink/public/web/web_view.h"
++#include "url/gurl.h"
++#include "../common/user_scripts_features.h"
++
++namespace user_scripts {
++
++namespace {
++
++// The length of time to wait after the DOM is complete to try and run user
++// scripts.
++const int kScriptIdleTimeoutInMs = 200;
++
++// Returns the RunLocation that follows |run_location|.
++UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
++  switch (run_location) {
++    case UserScript::DOCUMENT_START:
++      return UserScript::DOCUMENT_END;
++    case UserScript::DOCUMENT_END:
++      return UserScript::DOCUMENT_IDLE;
++    case UserScript::DOCUMENT_IDLE:
++      return UserScript::RUN_LOCATION_LAST;
++    case UserScript::UNDEFINED:
++    case UserScript::RUN_DEFERRED:
++    case UserScript::BROWSER_DRIVEN:
++    case UserScript::RUN_LOCATION_LAST:
++      break;
++  }
++  NOTREACHED();
++  return UserScript::RUN_LOCATION_LAST;
++}
++
++}  // namespace
++
++class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
++ public:
++  RFOHelper(content::RenderFrame* render_frame,
++            ScriptInjectionManager* manager);
++  ~RFOHelper() override;
++
++  // commit @9f2aac4
++  void Initialize();
++
++ private:
++  // RenderFrameObserver implementation.
++  void DidCreateNewDocument() override;
++  void DidCreateDocumentElement() override;
++  void DidFailProvisionalLoad() override;
++  void DidDispatchDOMContentLoadedEvent() override;
++  void WillDetach() override;
++  void OnDestruct() override;
++  void OnStop() override;
++
++  // Tells the ScriptInjectionManager to run tasks associated with
++  // document_idle.
++  void RunIdle();
++
++  void StartInjectScripts(UserScript::RunLocation run_location);
++
++  // Indicate that the frame is no longer valid because it is starting
++  // a new load or closing.
++  void InvalidateAndResetFrame(bool force_reset);
++
++  // The owning ScriptInjectionManager.
++  ScriptInjectionManager* manager_;
++
++  bool should_run_idle_ = true; // commit @9f2aac4
++
++  base::WeakPtrFactory<RFOHelper> weak_factory_{this};
++};
++
++ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
++                                             ScriptInjectionManager* manager)
++    : content::RenderFrameObserver(render_frame),
++      manager_(manager),
++      should_run_idle_(true) {}
++
++ScriptInjectionManager::RFOHelper::~RFOHelper() {
++}
++
++
++void ScriptInjectionManager::RFOHelper::Initialize() {
++  // Set up for the initial empty document, for which the Document created
++  // events do not happen as it's already present.
++  DidCreateNewDocument();
++  // The initial empty document for a main frame may have scripts attached to it
++  // but we do not want to invalidate the frame and lose them when the next
++  // document loads. For example the IncognitoApiTest.IncognitoSplitMode test
++  // does `chrome.tabs.create()` with a script to be run, which is added to the
++  // frame before it navigates, so it needs to be preserved. However scripts in
++  // child frames are expected to be run inside the initial empty document. For
++  // example the ExecuteScriptApiTest.FrameWithHttp204 test creates a child
++  // frame at about:blank and expects to run injected scripts inside it.
++  // This is all quite inconsistent however tests both depend on us queuing and
++  // not queueing the DOCUMENT_START events in the initial empty document.
++  if (!render_frame()->IsMainFrame()) {
++    DidCreateDocumentElement();
++  }
++}
++
++void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
++  // A new document is going to be shown, so invalidate the old document state.
++  // Don't force-reset the frame, because it is possible that a script injection
++  // was scheduled before the page was loaded, e.g. by navigating to a
++  // javascript: URL before the page has loaded.
++  constexpr bool kForceReset = false;
++  InvalidateAndResetFrame(kForceReset);
++}
++
++void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: DidCreateDocumentElement -> DOCUMENT_START";
++
++  ExtensionFrameHelper::Get(render_frame())
++      ->ScheduleAtDocumentStart(
++          base::BindOnce(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
++                     weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_START));
++}
++
++void ScriptInjectionManager::RFOHelper::DidFailProvisionalLoad() {
++  auto it = manager_->frame_statuses_.find(render_frame());
++  if (it != manager_->frame_statuses_.end() &&
++      it->second == UserScript::DOCUMENT_START) {
++    // Since the provisional load failed, the frame stays at its previous loaded
++    // state and origin (or the parent's origin for new/about:blank frames).
++    // Reset the frame to DOCUMENT_IDLE in order to reflect that the frame is
++    // done loading, and avoid any deadlock in the system.
++    //
++    // We skip injection of DOCUMENT_END and DOCUMENT_IDLE scripts, because the
++    // injections closely follow the DOMContentLoaded (and onload) events, which
++    // are not triggered after a failed provisional load.
++    // This assumption is verified in the checkDOMContentLoadedEvent subtest of
++    // ExecuteScriptApiTest.FrameWithHttp204 (browser_tests).
++    constexpr bool kForceReset = true;
++    InvalidateAndResetFrame(kForceReset);
++    should_run_idle_ = false;
++    manager_->frame_statuses_[render_frame()] = UserScript::DOCUMENT_IDLE;
++  }
++}
++
++void ScriptInjectionManager::RFOHelper::DidDispatchDOMContentLoadedEvent() {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: DidDispatchDOMContentLoadedEvent -> DOCUMENT_END";
++
++  DCHECK(content::RenderThread::Get());
++  ExtensionFrameHelper::Get(render_frame())
++      ->ScheduleAtDocumentEnd(
++          base::BindOnce(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
++                     weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_END));
++
++  // We try to run idle in two places: a delayed task here and in response to
++  // ContentRendererClient::RunScriptsAtDocumentIdle(). DidDispatchDOMContentLoadedEvent()
++  // corresponds to completing the document's load, whereas
++  // RunScriptsAtDocumentIdle() corresponds to completing the document and all
++  // subresources' load (but before the window.onload event). We don't want to
++  // hold up script injection for a particularly slow subresource, so we set a
++  // delayed task from here - but if we finish everything before that point
++  // (i.e., RunScriptsAtDocumentIdle() is triggered), then there's no reason to
++  // keep waiting.
++  render_frame()
++      ->GetTaskRunner(blink::TaskType::kInternalDefault)
++      ->PostDelayedTask(
++          FROM_HERE,
++          base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
++                         weak_factory_.GetWeakPtr()),
++          base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
++
++  ExtensionFrameHelper::Get(render_frame())
++      ->ScheduleAtDocumentIdle(
++          base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
++                     weak_factory_.GetWeakPtr()));
++}
++
++void ScriptInjectionManager::RFOHelper::WillDetach() {
++  // The frame is closing - invalidate.
++  constexpr bool kForceReset = true;
++  InvalidateAndResetFrame(kForceReset);
++}
++
++void ScriptInjectionManager::RFOHelper::OnDestruct() {
++  manager_->RemoveObserver(this);
++}
++
++void ScriptInjectionManager::RFOHelper::OnStop() {
++  // If the navigation request fails (e.g. 204/205/downloads), notify the
++  // extension to avoid keeping the frame in a START state indefinitely which
++  // leads to deadlocks.
++  DidFailProvisionalLoad();
++}
++
++void ScriptInjectionManager::RFOHelper::RunIdle() {
++  // Only notify the manager if the frame hasn't already had idle run since the
++  // task to RunIdle() was posted.
++  if (should_run_idle_) {
++    should_run_idle_ = false;
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScripts: RunIdle -> DOCUMENT_IDLE";
++    manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
++  }
++}
++
++void ScriptInjectionManager::RFOHelper::StartInjectScripts(
++    UserScript::RunLocation run_location) {
++  manager_->StartInjectScripts(render_frame(), run_location);
++}
++
++void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame(
++    bool force_reset) {
++  // Invalidate any pending idle injections, and reset the frame inject on idle.
++  weak_factory_.InvalidateWeakPtrs();
++  // We reset to inject on idle, because the frame can be reused (in the case of
++  // navigation).
++  should_run_idle_ = true;
++
++  // Reset the frame if either |force_reset| is true, or if the manager is
++  // keeping track of the state of the frame (in which case we need to clean it
++  // up).
++  if (force_reset || manager_->frame_statuses_.count(render_frame()) != 0)
++    manager_->InvalidateForFrame(render_frame());
++}
++
++ScriptInjectionManager::ScriptInjectionManager(
++    UserScriptSetManager* user_script_set_manager)
++    : user_script_set_manager_(user_script_set_manager),
++      user_script_set_manager_observation_(this) {
++  user_script_set_manager_observation_.Observe(user_script_set_manager_);
++}
++
++ScriptInjectionManager::~ScriptInjectionManager() {
++  for (const auto& injection : pending_injections_)
++    injection->invalidate_render_frame();
++  for (const auto& injection : running_injections_)
++    injection->invalidate_render_frame();
++}
++
++void ScriptInjectionManager::OnRenderFrameCreated(
++    content::RenderFrame* render_frame) {
++  rfo_helpers_.push_back(std::make_unique<RFOHelper>(render_frame, this));
++  rfo_helpers_.back()->Initialize(); // commit @9f2aac4
++}
++
++void ScriptInjectionManager::OnInjectionFinished(
++    ScriptInjection* injection) {
++  auto iter =
++      std::find_if(running_injections_.begin(), running_injections_.end(),
++                   [injection](const std::unique_ptr<ScriptInjection>& mode) {
++                     return injection == mode.get();
++                   });
++  if (iter != running_injections_.end())
++    running_injections_.erase(iter);
++}
++
++void ScriptInjectionManager::OnUserScriptsUpdated(
++    const std::set<HostID>& changed_hosts) {
++  for (auto iter = pending_injections_.begin();
++       iter != pending_injections_.end();) {
++    if (changed_hosts.count((*iter)->host_id()) > 0)
++      iter = pending_injections_.erase(iter);
++    else
++      ++iter;
++  }
++}
++
++void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
++  for (auto iter = rfo_helpers_.begin(); iter != rfo_helpers_.end(); ++iter) {
++    if (iter->get() == helper) {
++      rfo_helpers_.erase(iter);
++      break;
++    }
++  }
++}
++
++void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
++  // If the frame invalidated is the frame being injected into, we need to
++  // note it.
++  active_injection_frames_.erase(frame);
++
++  for (auto iter = pending_injections_.begin();
++       iter != pending_injections_.end();) {
++    if ((*iter)->render_frame() == frame)
++      iter = pending_injections_.erase(iter);
++    else
++      ++iter;
++  }
++
++  frame_statuses_.erase(frame);
++}
++
++void ScriptInjectionManager::StartInjectScripts(
++    content::RenderFrame* frame,
++    UserScript::RunLocation run_location) {
++  auto iter = frame_statuses_.find(frame);
++  // We also don't execute if we detect that the run location is somehow out of
++  // order. This can happen if:
++  // - The first run location reported for the frame isn't DOCUMENT_START, or
++  // - The run location reported doesn't immediately follow the previous
++  //   reported run location.
++  // We don't want to run because extensions may have requirements that scripts
++  // running in an earlier run location have run by the time a later script
++  // runs. Better to just not run.
++  // Note that we check run_location > NextRunLocation() in the second clause
++  // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
++  // can happen multiple times, so we can receive earlier/equal run locations.
++  if ((iter == frame_statuses_.end() &&
++           run_location != UserScript::DOCUMENT_START) ||
++      (iter != frame_statuses_.end() &&
++           run_location > NextRunLocation(iter->second))) {
++    // We also invalidate the frame, because the run order of pending injections
++    // may also be bad.
++    InvalidateForFrame(frame);
++    return;
++  } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
++    // Certain run location signals (like DidCreateDocumentElement) can happen
++    // multiple times. Ignore the subsequent signals.
++    return;
++  }
++
++  // Otherwise, all is right in the world, and we can get on with the
++  // injections!
++  frame_statuses_[frame] = run_location;
++  InjectScripts(frame, run_location);
++}
++
++void ScriptInjectionManager::InjectScripts(
++    content::RenderFrame* frame,
++    UserScript::RunLocation run_location) {
++  // Find any injections that want to run on the given frame.
++  ScriptInjectionVector frame_injections;
++  for (auto iter = pending_injections_.begin();
++       iter != pending_injections_.end();) {
++    if ((*iter)->render_frame() == frame) {
++      frame_injections.push_back(std::move(*iter));
++      iter = pending_injections_.erase(iter);
++    } else {
++      ++iter;
++    }
++  }
++
++  // Add any injections for user scripts.
++  int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
++  user_script_set_manager_->GetAllInjections(&frame_injections, frame, tab_id,
++                                             run_location);
++
++  // Note that we are running in |frame|.
++  active_injection_frames_.insert(frame);
++
++  ScriptsRunInfo scripts_run_info(frame, run_location);
++
++  for (auto iter = frame_injections.begin(); iter != frame_injections.end();) {
++    // It's possible for thScriptsRunInfoe frame to be invalidated in the course of injection
++    // (if a script removes its own frame, for example). If this happens, abort.
++    if (!active_injection_frames_.count(frame))
++      break;
++    std::unique_ptr<ScriptInjection> injection(std::move(*iter));
++    iter = frame_injections.erase(iter);
++    TryToInject(std::move(injection), run_location, &scripts_run_info);
++  }
++
++  // We are done running in the frame.
++  active_injection_frames_.erase(frame);
++
++  scripts_run_info.LogRun(activity_logging_enabled_);
++}
++
++void ScriptInjectionManager::TryToInject(
++    std::unique_ptr<ScriptInjection> injection,
++    UserScript::RunLocation run_location,
++    ScriptsRunInfo* scripts_run_info) {
++  // Try to inject the script. If the injection is waiting (i.e., for
++  // permission), add it to the list of pending injections. If the injection
++  // has blocked, add it to the list of running injections.
++  // The Unretained below is safe because this object owns all the
++  // ScriptInjections, so is guaranteed to outlive them.
++  switch (injection->TryToInject(
++      run_location, scripts_run_info,
++      base::BindOnce(&ScriptInjectionManager::OnInjectionFinished,
++                 base::Unretained(this)))) {
++    case ScriptInjection::INJECTION_WAITING:
++      pending_injections_.push_back(std::move(injection));
++      break;
++    case ScriptInjection::INJECTION_BLOCKED:
++      running_injections_.push_back(std::move(injection));
++      break;
++    case ScriptInjection::INJECTION_FINISHED:
++      break;
++  }
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/script_injection_manager.h b/components/user_scripts/renderer/script_injection_manager.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injection_manager.h
+@@ -0,0 +1,102 @@
++#include <stdint.h>
++
++#include <map>
++#include <set>
++#include <string>
++#include <vector>
++
++#include "base/callback.h"
++#include "base/macros.h"
++#include "base/scoped_observation.h"
++#include "../common/user_script.h"
++#include "script_injection.h"
++#include "user_script_set_manager.h"
++
++namespace user_scripts {
++
++// The ScriptInjectionManager manages extensions injecting scripts into frames
++// via both content/user scripts and tabs.executeScript(). It is responsible for
++// maintaining any pending injections awaiting permission or the appropriate
++// load point, and injecting them when ready.
++class ScriptInjectionManager : public UserScriptSetManager::Observer {
++ public:
++  explicit ScriptInjectionManager(
++      UserScriptSetManager* user_script_set_manager);
++  virtual ~ScriptInjectionManager();
++
++  // Notifies that a new render view has been created.
++  void OnRenderFrameCreated(content::RenderFrame* render_frame);
++
++  // Removes pending injections of the unloaded extension.
++  //void OnExtensionUnloaded(const std::string& extension_id);
++
++  void set_activity_logging_enabled(bool enabled) {
++    activity_logging_enabled_ = enabled;
++  }
++
++ private:
++  // A RenderFrameObserver implementation which watches the various render
++  // frames in order to notify the ScriptInjectionManager of different
++  // document load states and IPCs.
++  class RFOHelper;
++
++  using FrameStatusMap =
++      std::map<content::RenderFrame*, UserScript::RunLocation>;
++
++  using ScriptInjectionVector = std::vector<std::unique_ptr<ScriptInjection>>;
++
++  // Notifies that an injection has been finished.
++  void OnInjectionFinished(ScriptInjection* injection);
++
++  // UserScriptSetManager::Observer implementation.
++  void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) override;
++
++  // Notifies that an RFOHelper should be removed.
++  void RemoveObserver(RFOHelper* helper);
++
++  // Invalidate any pending tasks associated with |frame|.
++  void InvalidateForFrame(content::RenderFrame* frame);
++
++  // Starts the process to inject appropriate scripts into |frame|.
++  void StartInjectScripts(content::RenderFrame* frame,
++                          UserScript::RunLocation run_location);
++
++  // Actually injects the scripts into |frame|.
++  void InjectScripts(content::RenderFrame* frame,
++                     UserScript::RunLocation run_location);
++
++  // Try to inject and store injection if it has not finished.
++  void TryToInject(std::unique_ptr<ScriptInjection> injection,
++                   UserScript::RunLocation run_location,
++                   ScriptsRunInfo* scripts_run_info);
++
++  // The map of active web frames to their corresponding statuses. The
++  // RunLocation of the frame corresponds to the last location that has ran.
++  FrameStatusMap frame_statuses_;
++
++  // The frames currently being injected into, so long as that frame is valid.
++  std::set<content::RenderFrame*> active_injection_frames_;
++
++  // The collection of RFOHelpers.
++  std::vector<std::unique_ptr<RFOHelper>> rfo_helpers_;
++
++  // The set of UserScripts associated with extensions. Owned by the Dispatcher.
++  UserScriptSetManager* user_script_set_manager_;
++
++  // Pending injections which are waiting for either the proper run location or
++  // user consent.
++  ScriptInjectionVector pending_injections_;
++
++  // Running injections which are waiting for async callbacks from blink.
++  ScriptInjectionVector running_injections_;
++
++  // Whether or not dom activity should be logged for scripts injected.
++  bool activity_logging_enabled_ = false;
++
++  base::ScopedObservation<UserScriptSetManager, UserScriptSetManager::Observer>
++      user_script_set_manager_observation_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(ScriptInjectionManager);
++};
++
++}
+diff --git a/components/user_scripts/renderer/script_injector.h b/components/user_scripts/renderer/script_injector.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/script_injector.h
+@@ -0,0 +1,96 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
++#define USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
++
++#include <memory>
++#include <vector>
++
++#include "../common/constants.h"
++#include "../common/user_script.h"
++#include "third_party/blink/public/web/web_script_source.h"
++
++class InjectionHost;
++
++namespace blink {
++class WebLocalFrame;
++}
++
++namespace user_scripts {
++
++// The pseudo-delegate class for a ScriptInjection that provides all necessary
++// information about how to inject the script, including what code to inject and
++// when (run location), but without any injection logic.
++class ScriptInjector {
++ public:
++  // The possible reasons for not injecting the script.
++  enum InjectFailureReason {
++    EXTENSION_REMOVED,  // The extension was removed before injection.
++    NOT_ALLOWED,        // The script is not allowed to inject.
++    WONT_INJECT         // The injection won't inject because the user rejected
++                        // (or just did not accept) the injection.
++  };
++
++  virtual ~ScriptInjector() {}
++
++  // Returns the script type of this particular injection.
++  virtual UserScript::InjectionType script_type() const = 0;
++
++  // Returns true if the script is running inside a user gesture.
++  virtual bool IsUserGesture() const = 0;
++
++  // Returns the CSS origin of this injection.
++  virtual absl::optional<CSSOrigin> GetCssOrigin() const = 0;
++
++  // Returns the key for this injection, if it's a CSS injection.
++  virtual const absl::optional<std::string> GetInjectionKey() const = 0;
++
++  // Returns true if the script expects results.
++  virtual bool ExpectsResults() const = 0;
++
++  // Returns true if the script should inject JS source at the given
++  // |run_location|.
++  virtual bool ShouldInjectJs(
++      UserScript::RunLocation run_location,
++      const std::set<std::string>& executing_scripts) const = 0;
++
++  // Returns true if the script should inject CSS at the given |run_location|.
++  virtual bool ShouldInjectCss(
++      UserScript::RunLocation run_location,
++      const std::set<std::string>& injected_stylesheets) const = 0;
++
++  // Returns the javascript sources to inject at the given |run_location|.
++  // Only called if ShouldInjectJs() is true.
++  virtual std::vector<blink::WebScriptSource> GetJsSources(
++      UserScript::RunLocation run_location,
++      std::set<std::string>* executing_scripts,
++      size_t* num_injected_js_scripts) const = 0;
++
++  // Returns the css to inject at the given |run_location|.
++  // Only called if ShouldInjectCss() is true.
++  virtual std::vector<blink::WebString> GetCssSources(
++      UserScript::RunLocation run_location,
++      std::set<std::string>* injected_stylesheets,
++      size_t* num_injected_stylesheets) const = 0;
++
++  // Notifies the script that injection has completed, with a possibly-populated
++  // list of results (depending on whether or not ExpectsResults() was true).
++  // |render_frame| contains the render frame, or null if the frame was
++  // invalidated.
++  virtual void OnInjectionComplete(
++      std::unique_ptr<base::Value> execution_result,
++      UserScript::RunLocation run_location,
++      content::RenderFrame* render_frame) = 0;
++
++  // Notifies the script that injection will never occur.
++  // |render_frame| contains the render frame, or null if the frame was
++  // invalidated.
++  virtual void OnWillNotInject(InjectFailureReason reason,
++                               content::RenderFrame* render_frame) = 0;
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
+diff --git a/components/user_scripts/renderer/scripts_run_info.cc b/components/user_scripts/renderer/scripts_run_info.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/scripts_run_info.cc
+@@ -0,0 +1,31 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "scripts_run_info.h"
++
++#include "base/metrics/histogram_macros.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_thread.h"
++#include "script_context.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++
++namespace user_scripts {
++
++ScriptsRunInfo::ScriptsRunInfo(content::RenderFrame* render_frame,
++                               UserScript::RunLocation location)
++    : num_css(0u),
++      num_js(0u),
++      num_blocking_js(0u),
++      routing_id_(render_frame->GetRoutingID()),
++      run_location_(location),
++      frame_url_(ScriptContext::GetDocumentLoaderURLForFrame(
++          render_frame->GetWebFrame())) {}
++
++ScriptsRunInfo::~ScriptsRunInfo() {
++}
++
++void ScriptsRunInfo::LogRun(bool send_script_activity) {
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/scripts_run_info.h b/components/user_scripts/renderer/scripts_run_info.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/scripts_run_info.h
+@@ -0,0 +1,70 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_SCRIPTS_RUN_INFO_H_
++#define USERSCRIPTS_RENDERER_SCRIPTS_RUN_INFO_H_
++
++#include <stddef.h>
++
++#include <map>
++#include <set>
++#include <string>
++
++#include "base/macros.h"
++#include "base/timer/elapsed_timer.h"
++#include "../common/user_script.h"
++
++namespace content {
++class RenderFrame;
++}
++
++namespace user_scripts {
++
++// A struct containing information about a script run.
++struct ScriptsRunInfo {
++  // Map of extensions IDs to the executing script paths.
++  typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
++
++  ScriptsRunInfo(content::RenderFrame* render_frame,
++                 UserScript::RunLocation location);
++  ~ScriptsRunInfo();
++
++  // The number of CSS scripts injected.
++  size_t num_css;
++  // The number of JS scripts injected.
++  size_t num_js;
++  // The number of blocked JS scripts injected.
++  size_t num_blocking_js;
++  // A map of extension ids to executing script paths.
++  ExecutingScriptsMap executing_scripts;
++  // A map of extension ids to injected stylesheet paths.
++  ExecutingScriptsMap injected_stylesheets;
++  // The elapsed time since the ScriptsRunInfo was constructed.
++  base::ElapsedTimer timer;
++
++  // Log information about a given script run. If |send_script_activity| is
++  // true, this also informs the browser of the script run.
++  void LogRun(bool send_script_activity);
++
++  static void LogLongInjectionTaskTime(UserScript::RunLocation run_location,
++                                       const base::TimeDelta& elapsed);
++
++ private:
++  // The routinig id to use to notify the browser of any injections. Since the
++  // frame may be deleted in injection, we don't hold on to a reference to it
++  // directly.
++  int routing_id_;
++
++  // The run location at which injection is happening.
++  UserScript::RunLocation run_location_;
++
++  // The url of the frame, preserved for the same reason as the routing id.
++  GURL frame_url_;
++
++  DISALLOW_COPY_AND_ASSIGN(ScriptsRunInfo);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_SCRIPTS_RUN_INFO_H_
+diff --git a/components/user_scripts/renderer/user_script_injector.cc b/components/user_scripts/renderer/user_script_injector.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_injector.cc
+@@ -0,0 +1,228 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "user_script_injector.h"
++
++#include <tuple>
++#include <vector>
++
++#include "base/logging.h"
++#include "base/lazy_instance.h"
++#include "content/public/common/url_constants.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_thread.h"
++#include "content/public/renderer/render_view.h"
++#include "components/user_scripts/renderer/grit/user_scripts_renderer_resources.h"
++#include "injection_host.h"
++#include "script_context.h"
++#include "scripts_run_info.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "third_party/blink/public/web/web_script_source.h"
++#include "ui/base/resource/resource_bundle.h"
++#include "url/gurl.h"
++
++namespace user_scripts {
++
++namespace {
++
++struct RoutingInfoKey {
++  int routing_id;
++  int script_id;
++
++  RoutingInfoKey(int routing_id, int script_id)
++      : routing_id(routing_id), script_id(script_id) {}
++
++  bool operator<(const RoutingInfoKey& other) const {
++    return std::tie(routing_id, script_id) <
++           std::tie(other.routing_id, other.script_id);
++  }
++};
++
++using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
++
++// A map records whether a given |script_id| from a webview-added user script
++// is allowed to inject on the render of given |routing_id|.
++// Once a script is added, the decision of whether or not allowed to inject
++// won't be changed.
++// After removed by the webview, the user scipt will also be removed
++// from the render. Therefore, there won't be any query from the same
++// |script_id| and |routing_id| pair.
++// base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
++//    LAZY_INSTANCE_INITIALIZER;
++
++// Greasemonkey API source that is injected with the scripts.
++struct GreasemonkeyApiJsString {
++  GreasemonkeyApiJsString();
++  blink::WebScriptSource GetSource() const;
++
++ private:
++  blink::WebString source_;
++};
++
++// The below constructor, monstrous as it is, just makes a WebScriptSource from
++// the GreasemonkeyApiJs resource.
++GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
++  std::string greasemonky_api_js(
++      ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
++          IDR_GREASEMONKEY_API_JS));
++  source_ = blink::WebString::FromUTF8(greasemonky_api_js);
++}
++
++blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
++  return blink::WebScriptSource(source_);
++}
++
++base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
++    LAZY_INSTANCE_INITIALIZER;
++
++bool ShouldInjectScripts(const UserScript::FileList& scripts,
++                         const std::set<std::string>& injected_files) {
++  for (const std::unique_ptr<UserScript::File>& file : scripts) {
++    // Check if the script is already injected.
++    if (injected_files.count(file->url().path()) == 0) {
++      return true;
++    }
++  }
++  return false;
++}
++
++}  // namespace
++
++UserScriptInjector::UserScriptInjector(const UserScript* script,
++                                       UserScriptSet* script_list)
++    : script_(script),
++      user_script_set_(script_list),
++      script_id_(script_->id()),
++      user_script_set_observer_(this) {
++  user_script_set_observer_.Observe(script_list);
++}
++
++UserScriptInjector::~UserScriptInjector() {
++}
++
++void UserScriptInjector::OnUserScriptsUpdated(
++    const std::set<HostID>& changed_hosts,
++    const UserScriptList& scripts) {
++  // When user scripts are updated, all the old script pointers are invalidated.
++  script_ = nullptr;
++  // If the host causing this injection changed, then this injection
++  // will be removed, and there's no guarantee the backing script still exists.
++  // if (changed_hosts.count(host_id_) > 0)
++  //   return;
++
++  for (const std::unique_ptr<UserScript>& script : scripts) {
++    if (script->id() == script_id_) {
++      script_ = script.get();
++      break;
++    }
++  }
++  // If |host_id_| wasn't in |changed_hosts|, then the script for this injection
++  // should be guaranteed to exist.
++  DCHECK(script_);
++}
++
++UserScript::InjectionType UserScriptInjector::script_type() const {
++  return UserScript::CONTENT_SCRIPT;
++}
++
++bool UserScriptInjector::IsUserGesture() const {
++  return false;
++}
++
++bool UserScriptInjector::ExpectsResults() const {
++  return false;
++}
++
++absl::optional<CSSOrigin> UserScriptInjector::GetCssOrigin() const {
++  return absl::nullopt;
++}
++
++const absl::optional<std::string> UserScriptInjector::GetInjectionKey() const {
++  return absl::nullopt;
++}
++
++bool UserScriptInjector::ShouldInjectJs(
++    UserScript::RunLocation run_location,
++    const std::set<std::string>& executing_scripts) const {
++  return script_ && script_->run_location() == run_location &&
++         !script_->js_scripts().empty() &&
++         ShouldInjectScripts(script_->js_scripts(), executing_scripts);
++}
++
++bool UserScriptInjector::ShouldInjectCss(
++    UserScript::RunLocation run_location,
++    const std::set<std::string>& injected_stylesheets) const {
++  return script_ && run_location == UserScript::DOCUMENT_START &&
++         !script_->css_scripts().empty() &&
++         ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
++}
++
++std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
++    UserScript::RunLocation run_location,
++    std::set<std::string>* executing_scripts,
++    size_t* num_injected_js_scripts) const {
++  DCHECK(script_);
++  std::vector<blink::WebScriptSource> sources;
++
++  DCHECK_EQ(script_->run_location(), run_location);
++
++  const UserScript::FileList& js_scripts = script_->js_scripts();
++  sources.reserve(js_scripts.size() +
++                  (script_->emulate_greasemonkey() ? 1 : 0));
++  // Emulate Greasemonkey API for scripts that were converted to extension
++  // user scripts.
++  if (script_->emulate_greasemonkey())
++    sources.push_back(g_greasemonkey_api.Get().GetSource());
++  for (const std::unique_ptr<UserScript::File>& file : js_scripts) {
++    const GURL& script_url = file->url();
++    // Check if the script is already injected.
++    if (executing_scripts->count(script_url.path()) != 0)
++      continue;
++
++    sources.push_back(blink::WebScriptSource(
++        user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
++        script_url));
++
++    (*num_injected_js_scripts) += 1;
++    executing_scripts->insert(script_url.path());
++  }
++
++  return sources;
++}
++
++std::vector<blink::WebString> UserScriptInjector::GetCssSources(
++    UserScript::RunLocation run_location,
++    std::set<std::string>* injected_stylesheets,
++    size_t* num_injected_stylesheets) const {
++  DCHECK(script_);
++  DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
++
++  std::vector<blink::WebString> sources;
++
++  const UserScript::FileList& css_scripts = script_->css_scripts();
++  sources.reserve(css_scripts.size());
++  for (const std::unique_ptr<UserScript::File>& file : script_->css_scripts()) {
++    const std::string& stylesheet_path = file->url().path();
++    // Check if the stylesheet is already injected.
++    if (injected_stylesheets->count(stylesheet_path) != 0)
++      continue;
++
++    sources.push_back(user_script_set_->GetCssSource(*file));
++    (*num_injected_stylesheets) += 1;
++    injected_stylesheets->insert(stylesheet_path);
++  }
++  return sources;
++}
++
++void UserScriptInjector::OnInjectionComplete(
++    std::unique_ptr<base::Value> execution_result,
++    UserScript::RunLocation run_location,
++    content::RenderFrame* render_frame) {}
++
++void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
++                                         content::RenderFrame* render_frame) {
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/user_script_injector.h b/components/user_scripts/renderer/user_script_injector.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_injector.h
+@@ -0,0 +1,87 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_USER_SCRIPT_INJECTOR_H_
++#define USERSCRIPTS_RENDERER_USER_SCRIPT_INJECTOR_H_
++
++#include <memory>
++#include <string>
++
++#include "base/macros.h"
++#include "base/values.h"
++#include "base/scoped_observation.h"
++#include "../common/user_script.h"
++#include "script_injection.h"
++#include "user_script_set.h"
++
++class InjectionHost;
++
++namespace blink {
++class WebLocalFrame;
++}
++
++namespace user_scripts {
++
++// A ScriptInjector for UserScripts.
++class UserScriptInjector : public ScriptInjector,
++                           public UserScriptSet::Observer {
++ public:
++  UserScriptInjector(const UserScript* user_script,
++                     UserScriptSet* user_script_set);
++  ~UserScriptInjector() override;
++
++ private:
++  // UserScriptSet::Observer implementation.
++  void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
++                            const UserScriptList& scripts) override;
++
++  // ScriptInjector implementation.
++  UserScript::InjectionType script_type() const override;
++  bool IsUserGesture() const override;
++  absl::optional<CSSOrigin> GetCssOrigin() const override;
++  const absl::optional<std::string> GetInjectionKey() const override;
++  bool ExpectsResults() const override;
++  bool ShouldInjectJs(
++      UserScript::RunLocation run_location,
++      const std::set<std::string>& executing_scripts) const override;
++  bool ShouldInjectCss(
++      UserScript::RunLocation run_location,
++      const std::set<std::string>& injected_stylesheets) const override;
++  std::vector<blink::WebScriptSource> GetJsSources(
++      UserScript::RunLocation run_location,
++      std::set<std::string>* executing_scripts,
++      size_t* num_injected_js_scripts) const override;
++  std::vector<blink::WebString> GetCssSources(
++      UserScript::RunLocation run_location,
++      std::set<std::string>* injected_stylesheets,
++      size_t* num_injected_stylesheets) const override;
++  void OnInjectionComplete(std::unique_ptr<base::Value> execution_result,
++                           UserScript::RunLocation run_location,
++                           content::RenderFrame* render_frame) override;
++  void OnWillNotInject(InjectFailureReason reason,
++                       content::RenderFrame* render_frame) override;
++
++  // The associated user script. Owned by the UserScriptInjector that created
++  // this object.
++  const UserScript* script_;
++
++  // The UserScriptSet that eventually owns the UserScript this
++  // UserScriptInjector points to.
++  // Outlives |this|.
++  UserScriptSet* const user_script_set_;
++
++  // The id of the associated user script. We cache this because when we update
++  // the |script_| associated with this injection, the old referance may be
++  // deleted.
++  int script_id_;
++
++  base::ScopedObservation<UserScriptSet, UserScriptSet::Observer>
++      user_script_set_observer_{this};
++
++  DISALLOW_COPY_AND_ASSIGN(UserScriptInjector);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_USER_SCRIPT_INJECTOR_H_
+diff --git a/components/user_scripts/renderer/user_script_set.cc b/components/user_scripts/renderer/user_script_set.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_set.cc
+@@ -0,0 +1,259 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "user_script_set.h"
++
++#include <stddef.h>
++
++#include <utility>
++
++#include "base/logging.h"
++#include "base/debug/alias.h"
++#include "base/memory/ref_counted.h"
++#include "base/strings/strcat.h"
++#include "content/public/common/url_constants.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_thread.h"
++#include "injection_host.h"
++#include "script_context.h"
++#include "script_injection.h"
++#include "user_script_injector.h"
++#include "web_ui_injection_host.h"
++#include "third_party/blink/public/web/web_document.h"
++#include "third_party/blink/public/web/web_local_frame.h"
++#include "url/gurl.h"
++#include "../common/user_scripts_features.h"
++
++namespace user_scripts {
++
++namespace {
++
++// These two strings are injected before and after the Greasemonkey API and
++// user script to wrap it in an anonymous scope.
++const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
++const char kUserScriptTail[] = "\n})(window);";
++// Maximum number of total content scripts we allow (across all extensions).
++// The limit exists to diagnose https://crbug.com/723381. The number is
++// arbitrarily chosen.
++// TODO(lazyboy): Remove when the bug is fixed.
++const uint32_t kNumScriptsArbitraryMax = 100000u;
++
++GURL GetDocumentUrlForFrame(blink::WebLocalFrame* frame) {
++  GURL data_source_url = ScriptContext::GetDocumentLoaderURLForFrame(frame);
++  if (!data_source_url.is_empty() && frame->IsViewSourceModeEnabled()) {
++    data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
++                           data_source_url.spec());
++  }
++
++  return data_source_url;
++}
++
++}  // namespace
++
++UserScriptSet::UserScriptSet() {}
++
++UserScriptSet::~UserScriptSet() {
++}
++
++void UserScriptSet::AddObserver(Observer* observer) {
++  observers_.AddObserver(observer);
++}
++
++void UserScriptSet::RemoveObserver(Observer* observer) {
++  observers_.RemoveObserver(observer);
++}
++
++void UserScriptSet::GetInjections(
++    std::vector<std::unique_ptr<ScriptInjection>>* injections,
++    content::RenderFrame* render_frame,
++    int tab_id,
++    UserScript::RunLocation run_location,
++    bool log_activity) {
++  GURL document_url = GetDocumentUrlForFrame(render_frame->GetWebFrame());
++  for (const std::unique_ptr<UserScript>& script : scripts_) {
++    std::unique_ptr<ScriptInjection> injection = GetInjectionForScript(
++        script.get(), render_frame, tab_id, run_location, document_url,
++        /* is_declarative, */ log_activity);
++    if (injection.get())
++      injections->push_back(std::move(injection));
++  }
++}
++
++bool UserScriptSet::UpdateUserScripts(
++    base::ReadOnlySharedMemoryRegion shared_memory,
++    const std::set<HostID>& changed_hosts,
++    bool whitelisted_only) {
++  bool only_inject_incognito = false;
++  //ExtensionsRendererClient::Get()->IsIncognitoProcess();
++
++  // Create the shared memory mapping.
++  shared_memory_mapping_ = shared_memory.Map();
++  if (!shared_memory.IsValid())
++    return false;
++
++  // First get the size of the memory block.
++  const base::Pickle::Header* pickle_header =
++      shared_memory_mapping_.GetMemoryAs<base::Pickle::Header>();
++  if (!pickle_header)
++    return false;
++
++  // Now read in the rest of the block.
++  size_t pickle_size =
++      sizeof(base::Pickle::Header) + pickle_header->payload_size;
++
++  // Unpickle scripts.
++  uint32_t num_scripts = 0;
++  auto memory = shared_memory_mapping_.GetMemoryAsSpan<char>(pickle_size);
++  if (!memory.size())
++    return false;
++
++  base::Pickle pickle(memory.data(), pickle_size);
++  base::PickleIterator iter(pickle);
++  base::debug::Alias(&pickle_size);
++  CHECK(iter.ReadUInt32(&num_scripts));
++
++  // Sometimes the shared memory contents seem to be corrupted
++  // (https://crbug.com/723381). Set an arbitrary max limit to the number of
++  // scripts so that we don't add OOM noise to crash reports.
++  CHECK_LT(num_scripts, kNumScriptsArbitraryMax);
++
++  scripts_.clear();
++  script_sources_.clear();
++  scripts_.reserve(num_scripts);
++  for (uint32_t i = 0; i < num_scripts; ++i) {
++    std::unique_ptr<UserScript> script(new UserScript());
++    script->Unpickle(pickle, &iter);
++
++    // Note that this is a pointer into shared memory. We don't own it. It gets
++    // cleared up when the last renderer or browser process drops their
++    // reference to the shared memory.
++    for (size_t j = 0; j < script->js_scripts().size(); ++j) {
++      const char* body = NULL;
++      int body_length = 0;
++      CHECK(iter.ReadData(&body, &body_length));
++      script->js_scripts()[j]->set_external_content(
++          base::StringPiece(body, body_length));
++    }
++    for (size_t j = 0; j < script->css_scripts().size(); ++j) {
++      const char* body = NULL;
++      int body_length = 0;
++      CHECK(iter.ReadData(&body, &body_length));
++      script->css_scripts()[j]->set_external_content(
++          base::StringPiece(body, body_length));
++    }
++
++    if (only_inject_incognito && !script->is_incognito_enabled())
++      continue;  // This script shouldn't run in an incognito tab.
++
++    scripts_.push_back(std::move(script));
++  }
++
++  for (auto& observer : observers_)
++    observer.OnUserScriptsUpdated(changed_hosts, scripts_);
++  return true;
++}
++
++void UserScriptSet::AddScript(std::unique_ptr<UserScript> script) {
++  scripts_.push_back(std::move(script));
++}
++
++std::unique_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
++    const UserScript* script,
++    content::RenderFrame* render_frame,
++    int tab_id,
++    UserScript::RunLocation run_location,
++    const GURL& document_url,
++    //bool is_declarative,
++    bool log_activity) {
++  std::unique_ptr<ScriptInjection> injection;
++  std::unique_ptr<const InjectionHost> injection_host;
++  blink::WebLocalFrame* web_frame = render_frame->GetWebFrame();
++
++  const HostID& host_id = script->host_id();
++  injection_host.reset(new WebUIInjectionHost(host_id));
++
++  GURL effective_document_url =
++      ScriptContext::GetEffectiveDocumentURLForInjection(
++          web_frame, document_url, script->match_origin_as_fallback());
++
++  bool is_subframe = web_frame->Parent();
++  if (!script->MatchesDocument(effective_document_url, is_subframe)) {
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScripts: No Match name=" << script->name() << " " <<
++                                         "url=" << effective_document_url.spec();
++    return injection;
++  }
++
++  std::unique_ptr<ScriptInjector> injector(
++      new UserScriptInjector(script, this));
++
++  bool inject_css = !script->css_scripts().empty() &&
++                    run_location == UserScript::DOCUMENT_START;
++  bool inject_js =
++      !script->js_scripts().empty() && script->run_location() == run_location;
++  if (inject_css || inject_js) {
++    injection.reset(new ScriptInjection(std::move(injector), render_frame,
++                                        std::move(injection_host), run_location,
++                                        log_activity));
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScripts: Match name=" << script->name() << " " <<
++                                         "url=" << effective_document_url.spec();
++  } else {
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts)) {
++      if (script->run_location() != run_location)
++        LOG(INFO) << "UserScripts: run location for name=" << script->name() <<
++                                        "current " << run_location << " " <<
++                                        "need " << script->run_location();
++      else
++        LOG(INFO) << "UserScripts: Match but no run name=" << script->name() << " " <<
++                                        "url=" << effective_document_url.spec();
++    }
++  }
++  return injection;
++}
++
++blink::WebString UserScriptSet::GetJsSource(const UserScript::File& file,
++                                            bool emulate_greasemonkey) {
++  const GURL& url = file.url();
++  auto iter = script_sources_.find(url);
++  if (iter != script_sources_.end()) {
++    return iter->second;
++  }
++
++  base::StringPiece script_content = file.GetContent();
++  blink::WebString source;
++  if (emulate_greasemonkey) {
++    // We add this dumb function wrapper for user scripts to emulate what
++    // Greasemonkey does. |script_content| becomes:
++    // concat(kUserScriptHead, script_content, kUserScriptTail).
++    std::string content =
++        base::StrCat({kUserScriptHead, script_content, kUserScriptTail});
++    source = blink::WebString::FromUTF8(content);
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScripts: Injecting w/greasemonkey " << file.url();
++  } else {
++    source = blink::WebString::FromUTF8(script_content.data(),
++                                        script_content.length());
++    if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++      LOG(INFO) << "UserScripts: Injecting " << file.url();
++  }
++  script_sources_[url] = source;
++  return source;
++}
++
++blink::WebString UserScriptSet::GetCssSource(const UserScript::File& file) {
++  const GURL& url = file.url();
++  auto iter = script_sources_.find(url);
++  if (iter != script_sources_.end())
++    return iter->second;
++
++  base::StringPiece script_content = file.GetContent();
++  return script_sources_
++      .insert(std::make_pair(
++          url, blink::WebString::FromUTF8(script_content.data(),
++                                          script_content.length())))
++      .first->second;
++}
++
++}  // namespace extensions
+diff --git a/components/user_scripts/renderer/user_script_set.h b/components/user_scripts/renderer/user_script_set.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_set.h
+@@ -0,0 +1,102 @@
++// Copyright 2014 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
++#define USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
++
++#include <map>
++#include <memory>
++#include <set>
++#include <string>
++#include <vector>
++
++#include "base/macros.h"
++#include "base/memory/read_only_shared_memory_region.h"
++#include "base/observer_list.h"
++#include "../common/user_script.h"
++#include "third_party/blink/public/platform/web_string.h"
++
++class GURL;
++
++namespace content {
++class RenderFrame;
++}
++
++namespace user_scripts {
++class ScriptInjection;
++
++// The UserScriptSet is a collection of UserScripts which knows how to update
++// itself from SharedMemory and create ScriptInjections for UserScripts to
++// inject on a page.
++class UserScriptSet {
++ public:
++  class Observer {
++   public:
++    // Called when the set of user scripts is updated. |changed_hosts| contains
++    // the hosts whose scripts have been altered. Note that *all* script objects
++    // are invalidated, even if they aren't in |changed_hosts|.
++    virtual void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
++                                      const UserScriptList& scripts) = 0;
++  };
++
++  UserScriptSet();
++  ~UserScriptSet();
++
++  // Adds or removes observers.
++  void AddObserver(Observer* observer);
++  void RemoveObserver(Observer* observer);
++  void AddScript(std::unique_ptr<UserScript> script);
++
++  // Append any ScriptInjections that should run on the given |render_frame| and
++  // |tab_id|, at the given |run_location|, to |injections|.
++  // |extensions| is passed in to verify the corresponding extension is still
++  // valid.
++  void GetInjections(std::vector<std::unique_ptr<ScriptInjection>>* injections,
++                     content::RenderFrame* render_frame,
++                     int tab_id,
++                     UserScript::RunLocation run_location,
++                     bool log_activity);
++
++  // Updates scripts given the shared memory region containing user scripts.
++  // Returns true if the scripts were successfully updated.
++  bool UpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory,
++                         const std::set<HostID>& changed_hosts,
++                         bool whitelisted_only);
++
++  // Returns the contents of a script file.
++  // Note that copying is cheap as this uses WebString.
++  blink::WebString GetJsSource(const UserScript::File& file,
++                               bool emulate_greasemonkey);
++  blink::WebString GetCssSource(const UserScript::File& file);
++
++ private:
++  // Returns a new ScriptInjection for the given |script| to execute in the
++  // |render_frame|, or NULL if the script should not execute.
++  std::unique_ptr<ScriptInjection> GetInjectionForScript(
++      const UserScript* script,
++      content::RenderFrame* render_frame,
++      int tab_id,
++      UserScript::RunLocation run_location,
++      const GURL& document_url,
++      //bool is_declarative,
++      bool log_activity);
++
++  // Shared memory mapping containing raw script data.
++  base::ReadOnlySharedMemoryMapping shared_memory_mapping_;
++
++  // The UserScripts this injector manages.
++  UserScriptList scripts_;
++
++  // Map of user script file url -> source.
++  std::map<GURL, blink::WebString> script_sources_;
++
++  // The associated observers.
++  base::ObserverList<Observer>::Unchecked observers_;
++
++  DISALLOW_COPY_AND_ASSIGN(UserScriptSet);
++};
++
++}  // namespace extensions
++
++#endif  // USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
+diff --git a/components/user_scripts/renderer/user_script_set_manager.cc b/components/user_scripts/renderer/user_script_set_manager.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_set_manager.cc
+@@ -0,0 +1,77 @@
++#include "user_script_set_manager.h"
++
++#include "base/logging.h"
++#include "content/public/renderer/render_thread.h"
++#include "../common/host_id.h"
++#include "../common/extension_messages.h"
++#include "../common/user_scripts_features.h"
++#include "user_script_set.h"
++
++namespace user_scripts {
++
++UserScriptSetManager::UserScriptSetManager() {
++  content::RenderThread::Get()->AddObserver(this);
++}
++
++UserScriptSetManager::~UserScriptSetManager() {
++}
++
++void UserScriptSetManager::AddObserver(Observer* observer) {
++  observers_.AddObserver(observer);
++}
++
++void UserScriptSetManager::RemoveObserver(Observer* observer) {
++  observers_.RemoveObserver(observer);
++}
++
++bool UserScriptSetManager::OnControlMessageReceived(
++    const IPC::Message& message) {
++  bool handled = true;
++  IPC_BEGIN_MESSAGE_MAP(UserScriptSetManager, message)
++    IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
++    IPC_MESSAGE_UNHANDLED(handled = false)
++  IPC_END_MESSAGE_MAP()
++  return handled;
++}
++
++void UserScriptSetManager::GetAllInjections(
++    std::vector<std::unique_ptr<ScriptInjection>>* injections,
++    content::RenderFrame* render_frame,
++    int tab_id,
++    UserScript::RunLocation run_location) {
++
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: GetAllInjections";
++
++  // static_scripts_ is UserScriptSet
++  static_scripts_.GetInjections(injections, render_frame, tab_id, run_location,
++                                activity_logging_enabled_);
++}
++
++void UserScriptSetManager::OnUpdateUserScripts(
++    base::ReadOnlySharedMemoryRegion shared_memory) {
++  if (!shared_memory.IsValid()) {
++    NOTREACHED() << "Bad scripts handle";
++    return;
++  }
++
++  UserScriptSet* scripts = NULL;
++  scripts = &static_scripts_;
++
++  DCHECK(scripts);
++
++  // If no hosts are included in the set, that indicates that all
++  // hosts were updated. Add them all to the set so that observers and
++  // individual UserScriptSets don't need to know this detail.
++  //const std::set<HostID>* effective_hosts = &changed_hosts;
++  std::set<HostID> all_hosts;
++  const std::set<HostID>* effective_hosts = &all_hosts;
++
++  if (scripts->UpdateUserScripts(std::move(shared_memory), *effective_hosts,
++                                 false /*whitelisted_only*/)) {
++    for (auto& observer : observers_)
++      observer.OnUserScriptsUpdated(all_hosts /* *effective_hosts*/);
++  }
++}
++
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/user_script_set_manager.h b/components/user_scripts/renderer/user_script_set_manager.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_script_set_manager.h
+@@ -0,0 +1,62 @@
++#ifndef USERSCRIPTS_RENDER_SET_MANAGER_H_
++#define USERSCRIPTS_RENDER_SET_MANAGER_H_
++
++#include <map>
++#include <set>
++#include <string>
++#include <vector>
++
++#include "base/macros.h"
++#include "base/memory/read_only_shared_memory_region.h"
++#include "base/observer_list.h"
++#include "content/public/renderer/render_thread_observer.h"
++#include "../common/host_id.h"
++#include "user_script_set.h"
++#include "script_injection.h"
++
++namespace user_scripts {
++
++class UserScriptSetManager : public content::RenderThreadObserver {
++ public:
++  class Observer {
++   public:
++    virtual void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) = 0;
++  };
++
++  UserScriptSetManager();
++
++  ~UserScriptSetManager() override;
++
++  void AddObserver(Observer* observer);
++  void RemoveObserver(Observer* observer);
++
++  // Append all injections from |static_scripts| and each of
++  // |programmatic_scripts_| to |injections|.
++  void GetAllInjections(
++      std::vector<std::unique_ptr<ScriptInjection>>* injections,
++      content::RenderFrame* render_frame,
++      int tab_id,
++      UserScript::RunLocation run_location);
++
++private:
++  // content::RenderThreadObserver implementation.
++  bool OnControlMessageReceived(const IPC::Message& message) override;
++
++  base::ObserverList<Observer>::Unchecked observers_;
++
++  // Handle the UpdateUserScripts extension message.
++  void OnUpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory);
++                           //, const HostID& host_id,
++                           //const std::set<HostID>& changed_hosts,
++                           //bool whitelisted_only);
++
++  // Scripts statically defined in extension manifests.
++  UserScriptSet static_scripts_;
++
++  // Whether or not dom activity should be logged for scripts injected.
++  bool activity_logging_enabled_ = false;
++};
++
++}
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/user_scripts_dispatcher.cc b/components/user_scripts/renderer/user_scripts_dispatcher.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_scripts_dispatcher.cc
+@@ -0,0 +1,36 @@
++#include "user_scripts_dispatcher.h"
++
++#include <stddef.h>
++
++#include <algorithm>
++#include <memory>
++#include <utility>
++
++#include "content/public/renderer/render_thread.h"
++#include "extension_frame_helper.h"
++
++namespace user_scripts {
++
++// ex ChromeExtensionsDispatcherDelegate
++UserScriptsDispatcher::UserScriptsDispatcher()
++    : user_script_set_manager_observer_(this) {
++      user_script_set_manager_.reset(new UserScriptSetManager());
++      script_injection_manager_.reset(
++          new ScriptInjectionManager(user_script_set_manager_.get()));
++      user_script_set_manager_observer_.Observe(user_script_set_manager_.get());
++}
++
++UserScriptsDispatcher::~UserScriptsDispatcher() {
++}
++
++void UserScriptsDispatcher::OnRenderThreadStarted(content::RenderThread* thread) {
++}
++
++void UserScriptsDispatcher::OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) {
++}
++
++void UserScriptsDispatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
++  script_injection_manager_->OnRenderFrameCreated(render_frame);
++}
++
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/user_scripts_dispatcher.h b/components/user_scripts/renderer/user_scripts_dispatcher.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_scripts_dispatcher.h
+@@ -0,0 +1,48 @@
++#ifndef USERSCRIPTS_RENDER_DISPATCHER_H_
++#define USERSCRIPTS_RENDER_DISPATCHER_H_
++
++#include "user_script_set_manager.h"
++#include "script_injection_manager.h"
++
++#include <stdint.h>
++
++#include <map>
++#include <memory>
++#include <set>
++#include <string>
++#include <utility>
++#include <vector>
++
++#include "base/macros.h"
++#include "base/scoped_observation.h"
++#include "content/public/renderer/render_thread_observer.h"
++#include "content/public/renderer/render_thread.h"
++#include "../common/host_id.h"
++#include "user_script_set_manager.h"
++#include "script_injection.h"
++
++namespace user_scripts {
++
++class UserScriptsDispatcher : public content::RenderThreadObserver,
++                              public UserScriptSetManager::Observer {
++
++ public:
++  explicit UserScriptsDispatcher();
++  ~UserScriptsDispatcher() override;
++
++  void OnRenderThreadStarted(content::RenderThread* thread);
++  void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) override;
++  void OnRenderFrameCreated(content::RenderFrame* render_frame);
++
++ private:
++  std::unique_ptr<UserScriptSetManager> user_script_set_manager_;
++
++  std::unique_ptr<ScriptInjectionManager> script_injection_manager_;
++
++  base::ScopedObservation<UserScriptSetManager, UserScriptSetManager::Observer>
++      user_script_set_manager_observer_{this};
++};
++
++}
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/user_scripts_renderer_client.cc b/components/user_scripts/renderer/user_scripts_renderer_client.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_scripts_renderer_client.cc
+@@ -0,0 +1,105 @@
++#include "user_scripts_renderer_client.h"
++
++#include <memory>
++#include <utility>
++
++#include "base/logging.h"
++#include "base/lazy_instance.h"
++#include "content/public/renderer/render_frame.h"
++#include "content/public/renderer/render_thread.h"
++#include "content/public/renderer/render_frame_visitor.h"
++#include "chrome/renderer/chrome_render_thread_observer.h"
++
++#include "../common/user_scripts_features.h"
++#include "user_scripts_dispatcher.h"
++#include "extension_frame_helper.h"
++
++namespace user_scripts {
++
++// was ChromeExtensionsRendererClient
++UserScriptsRendererClient::UserScriptsRendererClient() {}
++
++UserScriptsRendererClient::~UserScriptsRendererClient() {}
++
++// static
++UserScriptsRendererClient* UserScriptsRendererClient::GetInstance() {
++  static base::LazyInstance<UserScriptsRendererClient>::Leaky client =
++      LAZY_INSTANCE_INITIALIZER;
++  return client.Pointer();
++}
++
++void UserScriptsRendererClient::RenderThreadStarted() {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: RenderThreadStarted";
++
++  content::RenderThread* thread = content::RenderThread::Get();
++  dispatcher_ = std::make_unique<UserScriptsDispatcher>();
++
++  dispatcher_->OnRenderThreadStarted(thread);
++  thread->AddObserver(dispatcher_.get());
++}
++
++void UserScriptsRendererClient::ConfigurationUpdated() {
++  if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
++    LOG(INFO) << "UserScripts: Configuration Updated";
++
++  struct WatchFrame : public content::RenderFrameVisitor {
++    bool Visit(content::RenderFrame* frame) override {
++      if (frame)
++        UserScriptsRendererClient::GetInstance()->RenderFrameCreated(frame, NULL);
++      return true;  // Continue visiting.
++    }
++  };
++  WatchFrame visitor = {};
++  content::RenderFrame::ForEach(&visitor);
++}
++
++void UserScriptsRendererClient::RenderFrameCreated(
++    content::RenderFrame* render_frame,
++    service_manager::BinderRegistry* registry) {
++
++  auto params = ChromeRenderThreadObserver::GetDynamicParams();
++  enabled_ = params.allow_userscript;
++  if (!enabled_) return;
++
++  if (loaded_ == false) {
++    loaded_ = true;
++    new user_scripts::ExtensionFrameHelper(render_frame);
++    dispatcher_->OnRenderFrameCreated(render_frame);
++  }
++}
++
++void UserScriptsRendererClient::RunScriptsAtDocumentStart(content::RenderFrame* render_frame) {
++  if (!enabled_ || !loaded_) return;
++
++  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
++  if (!frame_helper)
++    return;  // The frame is invisible to user scripts.
++
++  frame_helper->RunScriptsAtDocumentStart();
++  // |frame_helper| and |render_frame| might be dead by now.
++}
++
++void UserScriptsRendererClient::RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) {
++  if (!enabled_ || !loaded_) return;
++
++  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
++  if (!frame_helper)
++    return;  // The frame is invisible to user scripts.
++
++  frame_helper->RunScriptsAtDocumentEnd();
++  // |frame_helper| and |render_frame| might be dead by now.
++}
++
++void UserScriptsRendererClient::RunScriptsAtDocumentIdle(content::RenderFrame* render_frame) {
++  if (!enabled_ || !loaded_) return;
++
++  ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
++  if (!frame_helper)
++    return;  // The frame is invisible to user scripts.
++
++  frame_helper->RunScriptsAtDocumentIdle();
++  // |frame_helper| and |render_frame| might be dead by now.
++}
++
++}
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/user_scripts_renderer_client.h b/components/user_scripts/renderer/user_scripts_renderer_client.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/user_scripts_renderer_client.h
+@@ -0,0 +1,36 @@
++#ifndef USERSCRIPTS_RENDER_CLIENT_H_
++#define USERSCRIPTS_RENDER_CLIENT_H_
++
++#include <memory>
++#include <string>
++
++#include "base/macros.h"
++#include "user_scripts_dispatcher.h"
++#include "services/service_manager/public/cpp/binder_registry.h"
++
++namespace user_scripts {
++
++class UserScriptsRendererClient {
++ public:
++  UserScriptsRendererClient();
++  ~UserScriptsRendererClient();
++
++  static UserScriptsRendererClient* GetInstance();
++
++  void RenderThreadStarted();
++  void ConfigurationUpdated();
++  void RenderFrameCreated(content::RenderFrame* render_frame,
++    service_manager::BinderRegistry* registry);
++  void RunScriptsAtDocumentStart(content::RenderFrame* render_frame);
++  void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame);
++  void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame);
++
++ private:
++  std::unique_ptr<UserScriptsDispatcher> dispatcher_;
++  bool enabled_ = false;
++  bool loaded_ = false;
++};
++
++}
++
++#endif
+\ No newline at end of file
+diff --git a/components/user_scripts/renderer/web_ui_injection_host.cc b/components/user_scripts/renderer/web_ui_injection_host.cc
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/web_ui_injection_host.cc
+@@ -0,0 +1,40 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#include "web_ui_injection_host.h"
++#include "base/no_destructor.h"
++
++namespace {
++
++// The default secure CSP to be used in order to prevent remote scripts.
++const char kDefaultSecureCSP[] = "script-src 'self'; object-src 'self';";
++
++}
++
++WebUIInjectionHost::WebUIInjectionHost(const HostID& host_id)
++  : InjectionHost(host_id),
++    url_(host_id.id()) {
++}
++
++WebUIInjectionHost::~WebUIInjectionHost() {
++}
++
++const std::string* WebUIInjectionHost::GetContentSecurityPolicy() const {
++  // Use the main world CSP.
++  // return nullptr;
++
++  // The isolated world will use its own CSP which blocks remotely hosted
++  // code.
++  static const base::NoDestructor<std::string> default_isolated_world_csp(
++      kDefaultSecureCSP);
++  return default_isolated_world_csp.get();
++}
++
++const GURL& WebUIInjectionHost::url() const {
++  return url_;
++}
++
++const std::string& WebUIInjectionHost::name() const {
++  return id().id();
++}
+diff --git a/components/user_scripts/renderer/web_ui_injection_host.h b/components/user_scripts/renderer/web_ui_injection_host.h
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/renderer/web_ui_injection_host.h
+@@ -0,0 +1,28 @@
++// Copyright 2015 The Chromium Authors. All rights reserved.
++// Use of this source code is governed by a BSD-style license that can be
++// found in the LICENSE file.
++
++#ifndef USERSCRIPTS_RENDERER_WEB_UI_INJECTION_HOST_H_
++#define USERSCRIPTS_RENDERER_WEB_UI_INJECTION_HOST_H_
++
++#include "base/macros.h"
++#include "injection_host.h"
++
++class WebUIInjectionHost : public InjectionHost {
++ public:
++  WebUIInjectionHost(const HostID& host_id);
++  ~WebUIInjectionHost() override;
++
++ private:
++  // InjectionHost:
++  const std::string* GetContentSecurityPolicy() const override;
++  const GURL& url() const override;
++  const std::string& name() const override;
++
++ private:
++  GURL url_;
++
++  DISALLOW_COPY_AND_ASSIGN(WebUIInjectionHost);
++};
++
++#endif  // USERSCRIPTS_RENDERER_WEB_UI_INJECTION_HOST_H_
+diff --git a/components/user_scripts/strings/userscripts_strings.grdp b/components/user_scripts/strings/userscripts_strings.grdp
+new file mode 100755
+--- /dev/null
++++ b/components/user_scripts/strings/userscripts_strings.grdp
+@@ -0,0 +1,55 @@
++<?xml version="1.0" encoding="utf-8"?>
++<grit-part>
++
++  <!-- Preferences -->
++  <message name="IDS_PREFS_USERSCRIPTS_SETTINGS"
++           desc="."
++           formatter_data="android_java">
++    User Scripts
++  </message>
++
++  <message name="IDS_OPTION_USERSCRIPT_FLAG" desc="." formatter_data="android_java">
++    Activate User Scripts
++  </message>
++
++  <message name="IDS_OPTION_USERSCRIPT_FLAG_ON" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    ON
++  </message>
++  <message name="IDS_OPTION_USERSCRIPT_FLAG_OFF" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    OFF
++  </message>
++
++  <message name="IDS_ADD_SCRIPT" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    Add script
++  </message>
++  <message name="IDS_SCRIPTS_LIST_DESCRIPTION" desc="." formatter_data="android_java">
++    Experimental support for Greasemonkey-style user scripts.
++  </message>
++
++  <message name="IDS_SCRIPTS_ITEM_VERSION" desc="." formatter_data="android_java">
++    Version:
++  </message>
++  <message name="IDS_SCRIPTS_ITEM_FILENAME" desc="." formatter_data="android_java">
++    File:
++  </message>
++  <message name="IDS_SCRIPTS_ITEM_URL" desc="." formatter_data="android_java">
++    Url:
++  </message>
++
++  <message name="IDS_SCRIPTS_VIEW_SOURCE" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    View source
++  </message>
++
++  <message name="IDS_ASK_TO_INSTALL" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    Only install user scripts that you have verified are secure, user scripts can steal your credentials and data.
++
++Do you want to install <ph name="FILE">%s</ph>?
++  </message>
++  <message name="IDS_YES" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    Yes
++  </message>
++  <message name="IDS_NO" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
++    No
++  </message>
++
++</grit-part>
+\ No newline at end of file
+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
+@@ -585,6 +585,12 @@
+   "components/autofill/core/browser/autofill_address_rewriter_resources.grd":{
+     "includes": [3720]
+   },
++  "components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd": {
++    "includes": [6000],
++  },
++  "components/user_scripts/browser/resources/browser_resources.grd": {
++    "includes": [6020],
++  },
+   # END components/ section.
+ 
+   # START ios/ section.
+-- 
+2.20.1
+

+ 39 - 39
build/patches/Revert-the-removal-of-an-option-to-block-autoplay.patch

@@ -42,10 +42,10 @@ Subject: Revert the removal of an option to block autoplay
  create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-xxxhdpi/settings_autoplay.png
 
 diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn
-index 624ba958356ba..216eb2c2381ef 100644
+index d6cba78972382..ea78c679219ac 100644
 --- a/components/browser_ui/site_settings/android/BUILD.gn
 +++ b/components/browser_ui/site_settings/android/BUILD.gn
-@@ -145,6 +145,11 @@ android_resources("java_resources") {
+@@ -146,6 +146,11 @@ android_resources("java_resources") {
      "java/res/drawable-xxxhdpi/permission_protected_media.png",
      "java/res/drawable-xxxhdpi/settings_sensors.png",
      "java/res/drawable-xxxhdpi/web_asset.png",
@@ -166,10 +166,10 @@ literal 0
 HcmV?d00001
 
 diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
-index 31260c27852a3..7bae9b4f3afcc 100644
+index beb3ee433ee3b..1cedae5d0dd21 100644
 --- a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
 +++ b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
-@@ -61,6 +61,10 @@
+@@ -65,6 +65,10 @@
      <org.chromium.components.browser_ui.settings.ChromeBasePreference
          android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
          android:key="protected_content" />
@@ -181,7 +181,7 @@ index 31260c27852a3..7bae9b4f3afcc 100644
      <org.chromium.components.browser_ui.settings.ChromeBasePreference
          android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
 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
-index 04379683aa48b..a78f79463e044 100644
+index 52533463e9fc1..79b9378da083d 100644
 --- 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
 @@ -113,7 +113,10 @@ public class ContentSettingsResources {
@@ -197,10 +197,10 @@ index 04379683aa48b..a78f79463e044 100644
                  return new ResourceItem(R.drawable.ic_brightness_medium_24dp,
                          R.drawable.ic_brightness_medium_20dp, R.string.auto_dark_web_content_title,
 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
-index 2f289db26c670..6440e0ff63271 100644
+index bf6fccd181179..935f7a2f3bd1f 100644
 --- 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
-@@ -567,6 +567,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+@@ -586,6 +586,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
          int resource = 0;
          if (mCategory.showSites(SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS)) {
              resource = R.string.website_settings_add_site_description_automatic_downloads;
@@ -209,7 +209,7 @@ index 2f289db26c670..6440e0ff63271 100644
          } else if (mCategory.showSites(SiteSettingsCategory.Type.BACKGROUND_SYNC)) {
              resource = R.string.website_settings_add_site_description_background_sync;
          } else if (mCategory.showSites(SiteSettingsCategory.Type.JAVASCRIPT)) {
-@@ -680,6 +682,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+@@ -708,6 +710,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
          boolean exception = false;
          if (mCategory.showSites(SiteSettingsCategory.Type.SOUND)) {
              exception = true;
@@ -219,7 +219,7 @@ index 2f289db26c670..6440e0ff63271 100644
              exception = true;
          } else if (mCategory.showSites(SiteSettingsCategory.Type.COOKIES)) {
 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
-index 046bf481a685c..78c8579eb9c6c 100644
+index e4a08578f18df..ff492e0e00c77 100644
 --- 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
 @@ -119,6 +119,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
@@ -231,7 +231,7 @@ index 046bf481a685c..78c8579eb9c6c 100644
              case ContentSettingsType.IDLE_DETECTION:
                  return "idle_detection_permission_list";
              case ContentSettingsType.JAVASCRIPT:
-@@ -483,6 +485,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+@@ -485,6 +487,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
  
              if (type == ContentSettingsType.ADS) {
                  setUpAdsPreference(preference);
@@ -240,7 +240,7 @@ index 046bf481a685c..78c8579eb9c6c 100644
              } else if (type == ContentSettingsType.SOUND) {
                  setUpSoundPreference(preference);
              } else if (type == ContentSettingsType.JAVASCRIPT) {
-@@ -971,6 +975,24 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+@@ -975,6 +979,24 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
          setupContentSettingsPreference(preference, currentValue, false /* isEmbargoed */);
      }
  
@@ -266,32 +266,32 @@ index 046bf481a685c..78c8579eb9c6c 100644
          BrowserContextHandle browserContextHandle =
                  getSiteSettingsDelegate().getBrowserContextHandle();
 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
-index 801c2454e2984..65aa0fd8899db 100644
+index 435b5037ceb43..cbb425c4bb946 100644
 --- 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
-@@ -42,7 +42,7 @@ public class SiteSettingsCategory {
-             Type.CLIPBOARD, Type.COOKIES, Type.IDLE_DETECTION, Type.DEVICE_LOCATION,
+@@ -43,7 +43,7 @@ public class SiteSettingsCategory {
              Type.JAVASCRIPT, Type.MICROPHONE, Type.NFC, Type.NOTIFICATIONS, Type.POPUPS,
              Type.PROTECTED_MEDIA, Type.SENSORS, Type.SOUND, Type.USB, Type.VIRTUAL_REALITY,
--            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE})
-+            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE, Type.AUTOPLAY})
+             Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE,
+-            Type.TIMEZONE_OVERRIDE})
++            Type.TIMEZONE_OVERRIDE, Type.AUTOPLAY})
      @Retention(RetentionPolicy.SOURCE)
      public @interface Type {
          // All updates here must also be reflected in {@link #preferenceKey(int)
-@@ -72,10 +72,11 @@ public class SiteSettingsCategory {
-         int USE_STORAGE = 22;
+@@ -74,10 +74,11 @@ public class SiteSettingsCategory {
          int AUTO_DARK_WEB_CONTENT = 23;
          int REQUEST_DESKTOP_SITE = 24;
-+        int AUTOPLAY = 25;
+         int TIMEZONE_OVERRIDE = 25;
++        int AUTOPLAY = 26;
          /**
           * Number of handled categories used for calculating array sizes.
           */
--        int NUM_ENTRIES = 25;
-+        int NUM_ENTRIES = 26;
+-        int NUM_ENTRIES = 26;
++        int NUM_ENTRIES = 27;
      }
  
      private final BrowserContextHandle mBrowserContextHandle;
-@@ -186,6 +187,8 @@ public class SiteSettingsCategory {
+@@ -188,6 +189,8 @@ public class SiteSettingsCategory {
                  return ContentSettingsType.NFC;
              case Type.NOTIFICATIONS:
                  return ContentSettingsType.NOTIFICATIONS;
@@ -300,7 +300,7 @@ index 801c2454e2984..65aa0fd8899db 100644
              case Type.POPUPS:
                  return ContentSettingsType.POPUPS;
              case Type.PROTECTED_MEDIA:
-@@ -262,6 +265,8 @@ public class SiteSettingsCategory {
+@@ -266,6 +269,8 @@ public class SiteSettingsCategory {
                  return "nfc";
              case Type.NOTIFICATIONS:
                  return "notifications";
@@ -310,7 +310,7 @@ index 801c2454e2984..65aa0fd8899db 100644
                  return "popups";
              case Type.PROTECTED_MEDIA:
 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
-index fa69c4b50b536..8d0c56709ab3d 100644
+index b9931fc4cfdf6..cfe33f8df1e4e 100644
 --- 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
 @@ -216,6 +216,14 @@ public final class Website implements Serializable {
@@ -329,7 +329,7 @@ index fa69c4b50b536..8d0c56709ab3d 100644
              // It is possible to set the permission without having an existing exception,
              // because we always show the sound permission in Site Settings.
 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
-index 9523dcca60d51..5a2c743b245f3 100644
+index 6219383231f75..52b2ae69b4493 100644
 --- 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
 @@ -61,6 +61,7 @@ public class WebsitePermissionsFetcher {
@@ -341,7 +341,7 @@ index 9523dcca60d51..5a2c743b245f3 100644
              case ContentSettingsType.REQUEST_DESKTOP_SITE:
              case ContentSettingsType.JAVASCRIPT:
 diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc
-index ee7d9bf53f8d3..f0aaa0ffd23e8 100644
+index 6d6e4c1373b83..855b7740343c0 100644
 --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc
 +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc
 @@ -820,6 +820,7 @@ static void JNI_WebsitePreferenceBridge_SetContentSettingEnabled(
@@ -353,7 +353,7 @@ index ee7d9bf53f8d3..f0aaa0ffd23e8 100644
          break;
        default:
 diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp
-index 9a03fda430baa..35c484f1f74b1 100644
+index 1ba79aa78394d..19f8f9e646163 100644
 --- a/components/browser_ui/strings/android/site_settings.grdp
 +++ b/components/browser_ui/strings/android/site_settings.grdp
 @@ -12,6 +12,15 @@
@@ -373,7 +373,7 @@ index 9a03fda430baa..35c484f1f74b1 100644
      Augmented reality
    </message>
 diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
-index 336b5498e3ff4..6de84a2e75ca0 100644
+index d90f46d6806dc..ca9cb7afb1d8e 100644
 --- a/components/content_settings/core/browser/content_settings_registry.cc
 +++ b/components/content_settings/core/browser/content_settings_registry.cc
 @@ -313,7 +313,7 @@ void ContentSettingsRegistry::Init() {
@@ -386,7 +386,7 @@ index 336b5498e3ff4..6de84a2e75ca0 100644
             ValidSettings(CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK),
             WebsiteSettingsInfo::SINGLE_ORIGIN_ONLY_SCOPE,
 diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
-index 75473709e0ee6..e26e3d099bd1c 100644
+index dc99328a458a9..4c7c45c15d89e 100644
 --- a/components/content_settings/core/browser/content_settings_utils.cc
 +++ b/components/content_settings/core/browser/content_settings_utils.cc
 @@ -152,6 +152,8 @@ void GetRendererContentSettingRules(const HostContentSettingsMap* map,
@@ -397,9 +397,9 @@ index 75473709e0ee6..e26e3d099bd1c 100644
 +                             &(rules->autoplay_rules));
    map->GetSettingsForOneType(ContentSettingsType::POPUPS,
                               &(rules->popup_redirect_rules));
- }
+ 
 diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
-index 7006397be6f0d..49d322793d64e 100644
+index be7fb3ebdcf3b..1d82d80c8a03b 100644
 --- a/components/content_settings/core/common/content_settings.cc
 +++ b/components/content_settings/core/common/content_settings.cc
 @@ -187,6 +187,7 @@ bool RendererContentSettingRules::IsRendererContentSetting(
@@ -411,7 +411,7 @@ index 7006397be6f0d..49d322793d64e 100644
           content_type == ContentSettingsType::POPUPS ||
           content_type == ContentSettingsType::MIXEDSCRIPT ||
 diff --git a/components/content_settings/core/common/content_settings.h b/components/content_settings/core/common/content_settings.h
-index 135e07ab6c5d2..2721a8d0d2e93 100644
+index 905833daa3ba1..5e4245af5e741 100644
 --- a/components/content_settings/core/common/content_settings.h
 +++ b/components/content_settings/core/common/content_settings.h
 @@ -77,6 +77,7 @@ struct RendererContentSettingRules {
@@ -423,7 +423,7 @@ index 135e07ab6c5d2..2721a8d0d2e93 100644
    ContentSettingsForOneType mixed_content_rules;
    ContentSettingsForOneType auto_dark_content_rules;
 diff --git a/components/content_settings/core/common/content_settings.mojom b/components/content_settings/core/common/content_settings.mojom
-index f2f3eba2167a2..1393eb5741925 100644
+index 659646570aae2..93fc1ec5dd151 100644
 --- a/components/content_settings/core/common/content_settings.mojom
 +++ b/components/content_settings/core/common/content_settings.mojom
 @@ -75,6 +75,7 @@ struct ContentSettingPatternSource {
@@ -435,7 +435,7 @@ index f2f3eba2167a2..1393eb5741925 100644
    array<ContentSettingPatternSource> mixed_content_rules;
    array<ContentSettingPatternSource> auto_dark_content_rules;
 diff --git a/components/content_settings/core/common/content_settings_mojom_traits.cc b/components/content_settings/core/common/content_settings_mojom_traits.cc
-index 4ae7a3b123dc2..af71710b33e8d 100644
+index e0268395cc200..b9bd816e67238 100644
 --- a/components/content_settings/core/common/content_settings_mojom_traits.cc
 +++ b/components/content_settings/core/common/content_settings_mojom_traits.cc
 @@ -99,6 +99,7 @@ bool StructTraits<content_settings::mojom::RendererContentSettingRulesDataView,
@@ -445,9 +445,9 @@ index 4ae7a3b123dc2..af71710b33e8d 100644
 +         data.ReadAutoplayRules(&out->autoplay_rules) &&
           data.ReadPopupRedirectRules(&out->popup_redirect_rules) &&
           data.ReadMixedContentRules(&out->mixed_content_rules) &&
-          data.ReadAutoDarkContentRules(&out->auto_dark_content_rules);
+          data.ReadAutoDarkContentRules(&out->auto_dark_content_rules) &&
 diff --git a/components/content_settings/core/common/content_settings_mojom_traits.h b/components/content_settings/core/common/content_settings_mojom_traits.h
-index 7127ca7d852b6..66244e9875e43 100644
+index 34993a32e4f23..1b209fd7d420f 100644
 --- a/components/content_settings/core/common/content_settings_mojom_traits.h
 +++ b/components/content_settings/core/common/content_settings_mojom_traits.h
 @@ -135,6 +135,11 @@ struct StructTraits<
@@ -463,10 +463,10 @@ index 7127ca7d852b6..66244e9875e43 100644
        const RendererContentSettingRules& r) {
      return r.popup_redirect_rules;
 diff --git a/components/content_settings/renderer/content_settings_agent_impl.cc b/components/content_settings/renderer/content_settings_agent_impl.cc
-index c88d87dc4c5f0..defee1d9a7f31 100644
+index 8a4119c3b432b..e93f2be7cb0eb 100644
 --- a/components/content_settings/renderer/content_settings_agent_impl.cc
 +++ b/components/content_settings/renderer/content_settings_agent_impl.cc
-@@ -427,6 +427,17 @@ bool ContentSettingsAgentImpl::AllowRunningInsecureContent(
+@@ -439,6 +439,17 @@ bool ContentSettingsAgentImpl::AllowRunningInsecureContent(
    return false;
  }
  
@@ -485,7 +485,7 @@ index c88d87dc4c5f0..defee1d9a7f31 100644
    if (!content_setting_rules_)
      return default_value;
 diff --git a/components/content_settings/renderer/content_settings_agent_impl.h b/components/content_settings/renderer/content_settings_agent_impl.h
-index e618979c959c3..3d833922010ef 100644
+index b9f7c20a4f320..e5e617f5649dd 100644
 --- a/components/content_settings/renderer/content_settings_agent_impl.h
 +++ b/components/content_settings/renderer/content_settings_agent_impl.h
 @@ -99,6 +99,7 @@ class ContentSettingsAgentImpl

+ 36 - 36
build/patches/Site-setting-for-images.patch

@@ -33,10 +33,10 @@ Subject: Site setting for images
  create mode 100644 components/browser_ui/site_settings/android/java/res/drawable-xxxhdpi/permission_images.png
 
 diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn
-index 216eb2c2381ef..ae17c85496092 100644
+index ea78c679219ac..36bfd0553d944 100644
 --- a/components/browser_ui/site_settings/android/BUILD.gn
 +++ b/components/browser_ui/site_settings/android/BUILD.gn
-@@ -112,6 +112,7 @@ android_resources("java_resources") {
+@@ -113,6 +113,7 @@ android_resources("java_resources") {
    sources = [
      "java/res/drawable-hdpi/ic_volume_up_grey600_24dp.png",
      "java/res/drawable-hdpi/permission_background_sync.png",
@@ -44,7 +44,7 @@ index 216eb2c2381ef..ae17c85496092 100644
      "java/res/drawable-hdpi/permission_javascript.png",
      "java/res/drawable-hdpi/permission_popups.png",
      "java/res/drawable-hdpi/permission_protected_media.png",
-@@ -119,6 +120,7 @@ android_resources("java_resources") {
+@@ -120,6 +121,7 @@ android_resources("java_resources") {
      "java/res/drawable-hdpi/web_asset.png",
      "java/res/drawable-mdpi/ic_volume_up_grey600_24dp.png",
      "java/res/drawable-mdpi/permission_background_sync.png",
@@ -52,7 +52,7 @@ index 216eb2c2381ef..ae17c85496092 100644
      "java/res/drawable-mdpi/permission_javascript.png",
      "java/res/drawable-mdpi/permission_popups.png",
      "java/res/drawable-mdpi/permission_protected_media.png",
-@@ -126,6 +128,7 @@ android_resources("java_resources") {
+@@ -127,6 +129,7 @@ android_resources("java_resources") {
      "java/res/drawable-mdpi/web_asset.png",
      "java/res/drawable-xhdpi/ic_volume_up_grey600_24dp.png",
      "java/res/drawable-xhdpi/permission_background_sync.png",
@@ -60,7 +60,7 @@ index 216eb2c2381ef..ae17c85496092 100644
      "java/res/drawable-xhdpi/permission_javascript.png",
      "java/res/drawable-xhdpi/permission_popups.png",
      "java/res/drawable-xhdpi/permission_protected_media.png",
-@@ -133,6 +136,7 @@ android_resources("java_resources") {
+@@ -134,6 +137,7 @@ android_resources("java_resources") {
      "java/res/drawable-xhdpi/web_asset.png",
      "java/res/drawable-xxhdpi/ic_volume_up_grey600_24dp.png",
      "java/res/drawable-xxhdpi/permission_background_sync.png",
@@ -68,7 +68,7 @@ index 216eb2c2381ef..ae17c85496092 100644
      "java/res/drawable-xxhdpi/permission_javascript.png",
      "java/res/drawable-xxhdpi/permission_popups.png",
      "java/res/drawable-xxhdpi/permission_protected_media.png",
-@@ -140,6 +144,7 @@ android_resources("java_resources") {
+@@ -141,6 +145,7 @@ android_resources("java_resources") {
      "java/res/drawable-xxhdpi/web_asset.png",
      "java/res/drawable-xxxhdpi/ic_volume_up_grey600_24dp.png",
      "java/res/drawable-xxxhdpi/permission_background_sync.png",
@@ -159,7 +159,7 @@ literal 0
 HcmV?d00001
 
 diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
-index 7bae9b4f3afcc..f237d3c39a11a 100644
+index 1cedae5d0dd21..e2bc1755968ce 100644
 --- a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
 +++ b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
 @@ -41,6 +41,10 @@
@@ -170,11 +170,11 @@ index 7bae9b4f3afcc..f237d3c39a11a 100644
 +    <org.chromium.components.browser_ui.settings.ChromeBasePreference
 +        android:key="images"
 +        android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings" />
-     <!-- Popups -->
+     <!-- Timezone Override -->
      <org.chromium.components.browser_ui.settings.ChromeBasePreference
-         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
+         android:key="timezone_override"
 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
-index a78f79463e044..f74cf19b9475e 100644
+index 79b9378da083d..476b04a3795fa 100644
 --- 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
 @@ -134,6 +134,10 @@ public class ContentSettingsResources {
@@ -189,10 +189,10 @@ index a78f79463e044..f74cf19b9475e 100644
                  return new ResourceItem(R.drawable.settings_bluetooth, /*smallIcon=*/0,
                          R.string.website_settings_bluetooth, ContentSettingValues.ASK,
 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
-index 6440e0ff63271..091e13aae5736 100644
+index 935f7a2f3bd1f..b26d79df98978 100644
 --- 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
-@@ -581,6 +581,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+@@ -600,6 +600,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
                                 browserContextHandle, ContentSettingsType.SOUND)
                      ? R.string.website_settings_add_site_description_sound_block
                      : R.string.website_settings_add_site_description_sound_allow;
@@ -201,7 +201,7 @@ index 6440e0ff63271..091e13aae5736 100644
          } else if (mCategory.showSites(SiteSettingsCategory.Type.COOKIES)) {
              if (mRequiresFourStateSetting) {
                  resource = cookieSettingsExceptionShouldBlock()
-@@ -686,6 +688,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+@@ -714,6 +716,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
              exception = true;
          } else if (mCategory.showSites(SiteSettingsCategory.Type.JAVASCRIPT)) {
              exception = true;
@@ -211,7 +211,7 @@ index 6440e0ff63271..091e13aae5736 100644
              exception = true;
          } else if (mCategory.showSites(SiteSettingsCategory.Type.BACKGROUND_SYNC)
 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
-index 78c8579eb9c6c..3386be1defb35 100644
+index ff492e0e00c77..103c9923fc3b5 100644
 --- 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
 @@ -123,6 +123,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
@@ -223,7 +223,7 @@ index 78c8579eb9c6c..3386be1defb35 100644
              case ContentSettingsType.JAVASCRIPT:
                  return "javascript_permission_list";
              case ContentSettingsType.POPUPS:
-@@ -493,6 +495,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+@@ -495,6 +497,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
                  setUpJavascriptPreference(preference);
              } else if (type == ContentSettingsType.COOKIES) {
                  setUpCookiesPreference(preference);
@@ -232,7 +232,7 @@ index 78c8579eb9c6c..3386be1defb35 100644
              } else if (type == ContentSettingsType.GEOLOCATION) {
                  setUpLocationPreference(preference);
              } else if (type == ContentSettingsType.NOTIFICATIONS) {
-@@ -1060,6 +1064,24 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+@@ -1064,6 +1068,24 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
          setupContentSettingsPreference(preference, permission, false /* isEmbargoed */);
      }
  
@@ -258,32 +258,32 @@ index 78c8579eb9c6c..3386be1defb35 100644
          return value == ContentSettingValues.ALLOW
                  ? getString(R.string.website_settings_permissions_allowed_dse)
 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
-index 65aa0fd8899db..fdd60ef3f2820 100644
+index cbb425c4bb946..f6ad8557ca4bc 100644
 --- 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
-@@ -42,7 +42,7 @@ public class SiteSettingsCategory {
-             Type.CLIPBOARD, Type.COOKIES, Type.IDLE_DETECTION, Type.DEVICE_LOCATION,
+@@ -43,7 +43,7 @@ public class SiteSettingsCategory {
              Type.JAVASCRIPT, Type.MICROPHONE, Type.NFC, Type.NOTIFICATIONS, Type.POPUPS,
              Type.PROTECTED_MEDIA, Type.SENSORS, Type.SOUND, Type.USB, Type.VIRTUAL_REALITY,
--            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE, Type.AUTOPLAY})
-+            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE, Type.AUTOPLAY, Type.IMAGES})
+             Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE,
+-            Type.TIMEZONE_OVERRIDE, Type.AUTOPLAY})
++            Type.TIMEZONE_OVERRIDE, Type.AUTOPLAY, Type.IMAGES})
      @Retention(RetentionPolicy.SOURCE)
      public @interface Type {
          // All updates here must also be reflected in {@link #preferenceKey(int)
-@@ -73,10 +73,11 @@ public class SiteSettingsCategory {
-         int AUTO_DARK_WEB_CONTENT = 23;
+@@ -75,10 +75,11 @@ public class SiteSettingsCategory {
          int REQUEST_DESKTOP_SITE = 24;
-         int AUTOPLAY = 25;
-+        int IMAGES = 26;
+         int TIMEZONE_OVERRIDE = 25;
+         int AUTOPLAY = 26;
++        int IMAGES = 27;
          /**
           * Number of handled categories used for calculating array sizes.
           */
--        int NUM_ENTRIES = 26;
-+        int NUM_ENTRIES = 27;
+-        int NUM_ENTRIES = 27;
++        int NUM_ENTRIES = 28;
      }
  
      private final BrowserContextHandle mBrowserContextHandle;
-@@ -179,6 +180,8 @@ public class SiteSettingsCategory {
+@@ -181,6 +182,8 @@ public class SiteSettingsCategory {
                  return ContentSettingsType.GEOLOCATION;
              case Type.IDLE_DETECTION:
                  return ContentSettingsType.IDLE_DETECTION;
@@ -292,7 +292,7 @@ index 65aa0fd8899db..fdd60ef3f2820 100644
              case Type.JAVASCRIPT:
                  return ContentSettingsType.JAVASCRIPT;
              case Type.MICROPHONE:
-@@ -259,6 +262,8 @@ public class SiteSettingsCategory {
+@@ -263,6 +266,8 @@ public class SiteSettingsCategory {
                  return "idle_detection";
              case Type.JAVASCRIPT:
                  return "javascript";
@@ -314,7 +314,7 @@ index f4a9d3db2a09c..0347dd0e38568 100644
              ContentSettingsType.ADS,
              ContentSettingsType.BACKGROUND_SYNC,
 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
-index 8d0c56709ab3d..93c7922da5472 100644
+index cfe33f8df1e4e..80d450d4aa3df 100644
 --- 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
 @@ -200,6 +200,12 @@ public final class Website implements Serializable {
@@ -331,7 +331,7 @@ index 8d0c56709ab3d..93c7922da5472 100644
              // It is possible to set the permission without having an existing exception,
              // because we show the javascript permission in Site Settings if javascript
 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
-index 5a2c743b245f3..7fe5bc120fa68 100644
+index 52b2ae69b4493..78aa09dbf178a 100644
 --- 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
 @@ -64,6 +64,7 @@ public class WebsitePermissionsFetcher {
@@ -343,7 +343,7 @@ index 5a2c743b245f3..7fe5bc120fa68 100644
              case ContentSettingsType.JAVASCRIPT_JIT:
              case ContentSettingsType.POPUPS:
 diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc
-index f0aaa0ffd23e8..3817a770ceee7 100644
+index 855b7740343c0..6cdfe0ca2dcde 100644
 --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc
 +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc
 @@ -816,6 +816,7 @@ static void JNI_WebsitePreferenceBridge_SetContentSettingEnabled(
@@ -355,7 +355,7 @@ index f0aaa0ffd23e8..3817a770ceee7 100644
        case ContentSettingsType::POPUPS:
        case ContentSettingsType::SENSORS:
 diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp
-index 35c484f1f74b1..99fe74e63ba17 100644
+index 19f8f9e646163..4f9b8d42ff18e 100644
 --- a/components/browser_ui/strings/android/site_settings.grdp
 +++ b/components/browser_ui/strings/android/site_settings.grdp
 @@ -42,6 +42,9 @@
@@ -368,7 +368,7 @@ index 35c484f1f74b1..99fe74e63ba17 100644
    <message name="IDS_JAVASCRIPT_PERMISSION_TITLE" desc="Title of the permission to run javascript [CHAR_LIMIT=32]">
      JavaScript
    </message>
-@@ -185,6 +188,9 @@
+@@ -188,6 +191,9 @@
    <message name="IDS_WEBSITE_SETTINGS_ADD_SITE_TOAST" desc="The toast shown when a new site has been added to the exception list.">
      Site <ph name="SITE_NAME">%1$s<ex>google.com</ex></ph> added
    </message>
@@ -379,7 +379,7 @@ index 35c484f1f74b1..99fe74e63ba17 100644
      Site URL
    </message>
 diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
-index 2d5914436ec6e..e20cf92723470 100644
+index a1a312f0eed18..9867f47b21a7e 100644
 --- a/components/content_settings/core/browser/content_settings_registry.cc
 +++ b/components/content_settings/core/browser/content_settings_registry.cc
 @@ -143,7 +143,8 @@ void ContentSettingsRegistry::Init() {
@@ -393,7 +393,7 @@ index 2d5914436ec6e..e20cf92723470 100644
             ContentSettingsInfo::PERSISTENT,
             ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS);
 diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
-index e26e3d099bd1c..cabd39f76db5d 100644
+index 4c7c45c15d89e..ef4b47e7e39f3 100644
 --- a/components/content_settings/core/browser/content_settings_utils.cc
 +++ b/components/content_settings/core/browser/content_settings_utils.cc
 @@ -133,13 +133,8 @@ void GetRendererContentSettingRules(const HostContentSettingsMap* map,

+ 1358 - 0
build/patches/Timezone-customization.patch

@@ -0,0 +1,1358 @@
+From: uazo <uazo@users.noreply.github.com>
+Date: Wed, 30 Sep 2020 07:40:01 +0000
+Subject: Timezone customization
+
+Allow specifying a custom timezone, or using a random one.
+
+See also: https://github.com/bromite/bromite/wiki/TimezoneOverride
+---
+ .../ChromeSiteSettingsDelegate.java           |  16 ++
+ .../browser_ui/site_settings/android/BUILD.gn |   3 +
+ .../res/layout/time_zone_select_dialog.xml    |  36 ++++
+ ...ezoneoverride_site_settings_preference.xml |  68 ++++++
+ .../res/xml/site_settings_preferences.xml     |   4 +
+ .../java/res/xml/website_preferences.xml      |   9 +
+ .../ContentSettingsResources.java             |  35 +++-
+ .../site_settings/SingleCategorySettings.java |  77 ++++++-
+ .../site_settings/SingleWebsiteSettings.java  |   8 +-
+ .../site_settings/SiteSettings.java           |   2 +-
+ .../site_settings/SiteSettingsCategory.java   |  10 +-
+ .../site_settings/SiteSettingsDelegate.java   |   2 +
+ ...imezoneOverrideSiteSettingsPreference.java | 193 ++++++++++++++++++
+ .../browser_ui/site_settings/Website.java     |  10 +
+ .../WebsitePermissionsFetcher.java            |   3 +
+ .../WebsitePreferenceBridge.java              |  12 ++
+ .../android/website_preference_bridge.cc      |  16 ++
+ .../strings/android/site_settings.grdp        |  35 ++++
+ .../browser/content_settings_pref_provider.cc |  16 ++
+ .../browser/content_settings_pref_provider.h  |   4 +
+ .../core/browser/content_settings_registry.cc |  12 ++
+ .../core/browser/content_settings_utils.cc    |   7 +
+ .../core/browser/host_content_settings_map.cc |   8 +
+ .../core/browser/host_content_settings_map.h  |   3 +
+ .../core/common/content_settings.cc           |   3 +-
+ .../core/common/content_settings.h            |   2 +
+ .../core/common/content_settings.mojom        |   2 +
+ .../common/content_settings_mojom_traits.cc   |   4 +-
+ .../common/content_settings_mojom_traits.h    |  10 +
+ .../core/common/content_settings_types.h      |   3 +
+ .../core/common/pref_names.cc                 |   3 +
+ .../content_settings/core/common/pref_names.h |   2 +
+ .../renderer/content_settings_agent_impl.cc   |  89 ++++++++
+ .../renderer/content_settings_agent_impl.h    |   4 +
+ .../WebLayerSiteSettingsDelegate.java         |   3 +
+ 35 files changed, 699 insertions(+), 15 deletions(-)
+ create mode 100755 components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
+ create mode 100755 components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
+ create mode 100755 components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java
+
+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
+@@ -41,6 +41,10 @@ import org.chromium.url.GURL;
+ 
+ import java.util.Set;
+ 
++import android.content.Intent;
++import android.provider.Browser;
++import android.net.Uri;
++
+ /**
+  * A SiteSettingsDelegate instance that contains Chrome-specific Site Settings logic.
+  */
+@@ -248,4 +252,16 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate {
+             mPrivacySandboxController.dismissSnackbar();
+         }
+     }
++
++    // open wiki page for documentation about the timezone override feature
++    @Override
++    public void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity) {
++        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/bromite/bromite/wiki/TimezoneOverride"));
++        // Let Chromium know that this intent is from Chromium, so that it does not close the app when
++        // the user presses 'back' button.
++        intent.putExtra(Browser.EXTRA_APPLICATION_ID, currentActivity.getPackageName());
++        intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
++        intent.setPackage(currentActivity.getPackageName());
++        currentActivity.startActivity(intent);
++    }
+ }
+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
+@@ -65,6 +65,7 @@ android_library("java") {
+     "java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.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/TimezoneOverrideSiteSettingsPreference.java"
+   ]
+   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
+   resources_package = "org.chromium.components.browser_ui.site_settings"
+@@ -160,6 +161,8 @@ android_resources("java_resources") {
+     "java/res/xml/single_website_preferences.xml",
+     "java/res/xml/site_settings_preferences.xml",
+     "java/res/xml/website_preferences.xml",
++    "java/res/layout/timezoneoverride_site_settings_preference.xml",
++    "java/res/layout/time_zone_select_dialog.xml",
+   ]
+ 
+   deps = [
+diff --git a/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml b/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
+new file mode 100755
+--- /dev/null
++++ b/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
+@@ -0,0 +1,36 @@
++<?xml version="1.0" encoding="utf-8"?>
++<!--
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++-->
++
++<!-- Layout used by the TimezoneOverrideSettingsPreference. -->
++
++<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
++    android:orientation="vertical"
++    style="@style/AlertDialogContent">
++
++  <ListView
++      android:id="@+id/listView"
++      android:layout_width="match_parent"
++      android:layout_height="wrap_content"
++      android:layout_marginBottom="8dp"
++      android:layout_marginEnd="8dp"
++      android:layout_marginStart="8dp"
++      android:layout_marginTop="8dp"
++      android:choiceMode="singleChoice"
++      android:listSelector="#666666"/>
++
++</LinearLayout>
+\ No newline at end of file
+diff --git a/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml b/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
+new file mode 100755
+--- /dev/null
++++ b/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
+@@ -0,0 +1,68 @@
++<!--
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++-->
++
++<!-- Layout used by the TimezoneOverrideSettingsPreference. -->
++
++<LinearLayout
++    xmlns:android="http://schemas.android.com/apk/res/android"
++    xmlns:app="http://schemas.android.com/apk/res-auto"
++    android:layout_width="match_parent"
++    android:layout_height="wrap_content"
++    android:focusable="false"
++    android:orientation="vertical">
++
++  <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
++      android:id="@+id/radio_button_layout"
++      android:layout_width="match_parent"
++      android:layout_height="match_parent">
++
++    <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
++        android:id="@+id/allowed"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
++        app:primaryText="@string/website_settings_category_timezone_override_allowed" />
++
++    <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
++        android:id="@+id/blocked"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
++        app:primaryText="@string/website_settings_category_timezone_override_random" />
++
++    <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
++        android:id="@+id/ask"
++        android:layout_width="match_parent"
++        android:layout_height="wrap_content"
++        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
++        android:inputType="text"
++        android:hint="@string/website_settings_category_timezone_override_custom_hint"
++        app:descriptionText="@string/website_settings_category_timezone_override_custom" />
++
++  </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
++
++  <org.chromium.ui.widget.ButtonCompat
++    android:id="@+id/select_button"
++    style="@style/TextButton"
++    android:layout_width="match_parent"
++    android:layout_height="wrap_content"
++    android:paddingBottom="16dp"
++    android:paddingTop="16dp"
++    android:text="@string/website_settings_select_button"
++    app:verticalInset="0dp" />
++
++</LinearLayout>
+diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
+--- a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
++++ b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
+@@ -41,6 +41,10 @@
+     <org.chromium.components.browser_ui.settings.ChromeBasePreference
+         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
+         android:key="javascript" />
++    <!-- Timezone Override -->
++    <org.chromium.components.browser_ui.settings.ChromeBasePreference
++        android:key="timezone_override"
++        android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings" />
+     <!-- Popups -->
+     <org.chromium.components.browser_ui.settings.ChromeBasePreference
+         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
+diff --git a/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
+--- a/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
++++ b/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
+@@ -13,6 +13,12 @@
+         android:key="cookie_info_text"
+         android:summary="@string/website_settings_cookie_info"/>
+ 
++  <!-- A text message describing timeoverride settings. -->
++  <org.chromium.components.browser_ui.settings.TextMessagePreference
++      android:key="timeoverride_info_text"
++      android:summary="@string/website_settings_timeoverride_info"/>
++
++
+     <!-- A common binary toggle, only shown for specific categories that allow
+          turning default values for that category on/off.-->
+     <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+@@ -26,6 +32,9 @@
+     <!-- A four state toggle for cookie preferences, only shown for the Cookies category. -->
+     <org.chromium.components.browser_ui.site_settings.FourStateCookieSettingsPreference
+         android:key="four_state_cookie_toggle" />
++  <!-- A specific state toggle for time override preferences, only shown for the TimeOverride category. -->
++  <org.chromium.components.browser_ui.site_settings.TimezoneOverrideSiteSettingsPreference
++      android:key="timeoverride_state_toggle" />
+ 
+     <!-- A checkbox for enabling a quiet ui for notification prompts. Only shown in the Notifications category. -->
+     <org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference
+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
+@@ -276,6 +276,12 @@ public class ContentSettingsResources {
+                         ContentSettingValues.ASK, ContentSettingValues.BLOCK,
+                         R.string.website_settings_category_vr_ask,
+                         R.string.website_settings_category_vr_blocked);
++
++            case ContentSettingsType.TIMEZONE_OVERRIDE:
++                return new ResourceItem(R.drawable.web_asset, 0, R.string.timezone_override_permission_title,
++                            ContentSettingValues.ALLOW, ContentSettingValues.BLOCK,
++                            R.string.website_settings_category_timezone_override_custom,
++                            R.string.website_settings_category_timezone_override_random);
+         }
+         assert false; // NOTREACHED
+         return null;
+@@ -441,6 +447,23 @@ public class ContentSettingsResources {
+         }
+     }
+ 
++    public static int getCategorySummary(int contentType, @Nullable @ContentSettingValues int value) {
++        if (contentType == ContentSettingsType.TIMEZONE_OVERRIDE) {
++            switch (value) {
++                case ContentSettingValues.ALLOW:
++                    return R.string.website_settings_category_timezone_override_allowed;
++                case ContentSettingValues.ASK:
++                    return R.string.website_settings_category_timezone_override_custom;
++                case ContentSettingValues.BLOCK:
++                    return R.string.website_settings_category_timezone_override_random;
++                default:
++                    return 0;
++            }
++        }
++        else
++            return getCategorySummary(value);
++    }
++
+     /**
+      * Returns the string resource id for a content type to show with a permission category.
+      * @param enabled Whether the content type is enabled.
+@@ -455,7 +478,10 @@ public class ContentSettingsResources {
+      * with a particular website.
+      * @param value The ContentSetting for which we want the resource.
+      */
+-    public static int getSiteSummary(@ContentSettingValues @Nullable Integer value) {
++    public static int getSiteSummary(int contentType, @ContentSettingValues @Nullable Integer value) {
++        if (contentType == ContentSettingsType.TIMEZONE_OVERRIDE)
++            return getCategorySummary(contentType, value);
++
+         switch (value) {
+             case ContentSettingValues.ALLOW:
+                 return R.string.website_settings_permissions_allow;
+@@ -561,6 +587,13 @@ public class ContentSettingsResources {
+                 return descriptionIDs;
+             }
+         }
++        else if (contentType == ContentSettingsType.TIMEZONE_OVERRIDE) {
++          int[] descriptionIDs = {
++                  R.string.website_settings_category_timezone_override_allowed, // ALLOWED
++                  R.string.website_settings_category_timezone_override_custom,  // ASK
++                  R.string.website_settings_category_timezone_override_random}; // BLOCKED
++          return descriptionIDs;
++        }
+ 
+         assert false;
+         return null;
+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
+@@ -58,6 +58,10 @@ import org.chromium.ui.widget.Toast;
+ 
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.RetentionPolicy;
++import android.app.Activity;
++import android.content.Intent;
++import org.chromium.components.browser_ui.site_settings.TimezoneOverrideSiteSettingsPreference;
++
+ import java.util.ArrayList;
+ import java.util.Collection;
+ import java.util.Collections;
+@@ -163,6 +167,7 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+     public static final String BINARY_TOGGLE_KEY = "binary_toggle";
+     public static final String TRI_STATE_TOGGLE_KEY = "tri_state_toggle";
+     public static final String FOUR_STATE_COOKIE_TOGGLE_KEY = "four_state_cookie_toggle";
++    public static final String TIMEOVERRIDE_STATE_TOGGLE_KEY = "timeoverride_state_toggle";
+ 
+     // Keys for category-specific preferences (toggle, link, button etc.), dynamically shown.
+     public static final String NOTIFICATIONS_VIBRATE_TOGGLE_KEY = "notifications_vibrate";
+@@ -170,6 +175,7 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+     public static final String EXPLAIN_PROTECTED_MEDIA_KEY = "protected_content_learn_more";
+     private static final String ADD_EXCEPTION_KEY = "add_exception";
+     public static final String COOKIE_INFO_TEXT_KEY = "cookie_info_text";
++    public static final String TIMEOVERRIDE_INFO_TEXT = "timeoverride_info_text";
+ 
+     // Keys for Allowed/Blocked preference groups/headers.
+     private static final String ALLOWED_GROUP = "allowed_group";
+@@ -247,7 +253,10 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+             Integer contentSetting = website.site().getContentSetting(
+                     browserContextHandle, SiteSettingsCategory.contentSettingsType(i));
+             if (contentSetting != null) {
+-                return ContentSettingValues.BLOCK == contentSetting;
++                if (i == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)
++                    return ContentSettingValues.ALLOW != contentSetting;
++                else
++                    return ContentSettingValues.BLOCK == contentSetting;
+             }
+         }
+         return false;
+@@ -407,7 +416,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+             if (queryHasChanged) getInfoForOrigins();
+         });
+ 
+-        if (getSiteSettingsDelegate().isHelpAndFeedbackEnabled()) {
++        if (getSiteSettingsDelegate().isHelpAndFeedbackEnabled() ||
++            mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) ) {
+             MenuItem help = menu.add(
+                     Menu.NONE, R.id.menu_id_site_settings_help, Menu.NONE, R.string.menu_help);
+             help.setIcon(VectorDrawableCompat.create(
+@@ -418,7 +428,10 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+     @Override
+     public boolean onOptionsItemSelected(MenuItem item) {
+         if (item.getItemId() == R.id.menu_id_site_settings_help) {
+-            if (mCategory.showSites(SiteSettingsCategory.Type.PROTECTED_MEDIA)) {
++            if (mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)) {
++                getSiteSettingsDelegate()
++                        .launchTimeZoneOverrideHelpAndFeedbackActivity(getActivity());
++            } else if (mCategory.showSites(SiteSettingsCategory.Type.PROTECTED_MEDIA)) {
+                 getSiteSettingsDelegate().launchProtectedContentHelpAndFeedbackActivity(
+                         getActivity());
+             } else {
+@@ -497,6 +510,12 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+             WebsitePreferenceBridge.setDefaultContentSetting(
+                     browserContextHandle, mCategory.getContentSettingsType(), setting);
+             getInfoForOrigins();
++        } else if (TIMEOVERRIDE_STATE_TOGGLE_KEY.equals(preference.getKey())) {
++            @ContentSettingValues
++            int setting = (int) newValue;
++            WebsitePreferenceBridge.setDefaultContentSetting(
++                    browserContextHandle, ContentSettingsType.TIMEZONE_OVERRIDE, setting);
++            getInfoForOrigins();
+         } else if (FOUR_STATE_COOKIE_TOGGLE_KEY.equals(preference.getKey())) {
+             setCookieSettingsPreference((CookieSettingsState) newValue);
+             getInfoForOrigins();
+@@ -594,6 +613,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+             assert WebsitePreferenceBridge.isCategoryEnabled(
+                     browserContextHandle, ContentSettingsType.AUTO_DARK_WEB_CONTENT);
+             resource = R.string.website_settings_add_site_description_auto_dark_block;
++        } else if (mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)) {
++            resource = R.string.website_settings_category_timezone_override_allowed;
+         }
+         assert resource > 0;
+         return getString(resource);
+@@ -634,6 +655,13 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+         if (mCategory.showSites(SiteSettingsCategory.Type.COOKIES) && mRequiresFourStateSetting) {
+             setting = cookieSettingsExceptionShouldBlock() ? ContentSettingValues.BLOCK
+                                                            : ContentSettingValues.ALLOW;
++        } else if (mRequiresTriStateSetting) {
++            setting = WebsitePreferenceBridge.getDefaultContentSetting(browserContextHandle, mCategory.getContentSettingsType());
++            if (setting == ContentSettingValues.ALLOW) {
++              setting = ContentSettingValues.BLOCK;
++            } else {
++              setting = ContentSettingValues.ALLOW;
++            }
+         } else {
+             setting = (WebsitePreferenceBridge.isCategoryEnabled(
+                               browserContextHandle, mCategory.getContentSettingsType()))
+@@ -696,6 +724,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+                 && WebsitePreferenceBridge.isCategoryEnabled(
+                         browserContextHandle, ContentSettingsType.AUTO_DARK_WEB_CONTENT)) {
+             exception = true;
++        } else if (mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)) {
++            exception = true;
+         }
+         if (exception) {
+             getPreferenceScreen().addPreference(new AddExceptionPreference(getStyledContext(),
+@@ -867,7 +897,14 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+             TriStateSiteSettingsPreference triStateToggle =
+                     (TriStateSiteSettingsPreference) getPreferenceScreen().findPreference(
+                             TRI_STATE_TOGGLE_KEY);
+-            return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK);
++            if (triStateToggle != null)
++              return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK);
++
++            TimezoneOverrideSiteSettingsPreference timeOverrideStatePreference =
++                    (TimezoneOverrideSiteSettingsPreference) getPreferenceScreen().findPreference(
++                            TIMEOVERRIDE_STATE_TOGGLE_KEY);
++            if (timeOverrideStatePreference != null)
++              return (timeOverrideStatePreference.getCheckedSetting() != ContentSettingValues.ALLOW);
+         } else if (mRequiresFourStateSetting) {
+             FourStateCookieSettingsPreference fourStateCookieToggle =
+                     (FourStateCookieSettingsPreference) getPreferenceScreen().findPreference(
+@@ -896,6 +933,9 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+                 (FourStateCookieSettingsPreference) screen.findPreference(
+                         FOUR_STATE_COOKIE_TOGGLE_KEY);
+         // TODO(crbug.com/1104836): Remove the old third-party cookie blocking UI
++        TimezoneOverrideSiteSettingsPreference timeOverrideStatePreference =
++                (TimezoneOverrideSiteSettingsPreference) screen.findPreference(
++                        TIMEOVERRIDE_STATE_TOGGLE_KEY);
+         Preference notificationsVibrate = screen.findPreference(NOTIFICATIONS_VIBRATE_TOGGLE_KEY);
+         Preference notificationsQuietUi = screen.findPreference(NOTIFICATIONS_QUIET_UI_TOGGLE_KEY);
+         Preference explainProtectedMediaKey = screen.findPreference(EXPLAIN_PROTECTED_MEDIA_KEY);
+@@ -907,20 +947,32 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+         if (mRequiresTriStateSetting) {
+             screen.removePreference(binaryToggle);
+             screen.removePreference(fourStateCookieToggle);
+-            configureTriStateToggle(triStateToggle, contentType);
++            if (mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)) {
++              screen.removePreference(triStateToggle);
++              configureTimeOverrideStateToggle(timeOverrideStatePreference);
++            }
++            else {
++              screen.removePreference(timeOverrideStatePreference);
++              configureTriStateToggle(triStateToggle, contentType);
++            }
+         } else if (mRequiresFourStateSetting) {
+             screen.removePreference(binaryToggle);
+             screen.removePreference(triStateToggle);
++            screen.removePreference(timeOverrideStatePreference);
+             configureFourStateCookieToggle(fourStateCookieToggle);
+         } else {
+             screen.removePreference(triStateToggle);
+             screen.removePreference(fourStateCookieToggle);
++            screen.removePreference(timeOverrideStatePreference);
+             configureBinaryToggle(binaryToggle, contentType);
+         }
+ 
+         if (!mCategory.showSites(SiteSettingsCategory.Type.COOKIES)) {
+             screen.removePreference(screen.findPreference(COOKIE_INFO_TEXT_KEY));
+         }
++        if (!(mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE))) {
++            screen.removePreference(screen.findPreference(TIMEOVERRIDE_INFO_TEXT));
++        }
+ 
+         if (permissionBlockedByOs) {
+             maybeShowOsWarning(screen);
+@@ -1035,6 +1087,15 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+         triStateToggle.initialize(setting, descriptionIds);
+     }
+ 
++    private void configureTimeOverrideStateToggle(
++            TimezoneOverrideSiteSettingsPreference timeOverrideStateToggle) {
++        timeOverrideStateToggle.setOnPreferenceChangeListener(this);
++        @ContentSettingValues
++        int setting = WebsitePreferenceBridge.getDefaultContentSetting(
++                getSiteSettingsDelegate().getBrowserContextHandle(), ContentSettingsType.TIMEZONE_OVERRIDE);
++        timeOverrideStateToggle.initialize(setting, getSiteSettingsDelegate().getBrowserContextHandle());
++    }
++
+     private void configureBinaryToggle(ChromeSwitchPreference binaryToggle, int contentType) {
+         binaryToggle.setOnPreferenceChangeListener(this);
+         binaryToggle.setTitle(ContentSettingsResources.getTitle(contentType));
+@@ -1118,9 +1179,11 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
+ 
+         CharSequence[] descriptions = new String[2];
+         descriptions[0] =
+-                getString(ContentSettingsResources.getSiteSummary(ContentSettingValues.ALLOW));
++                getString(ContentSettingsResources.getSiteSummary(contentSettingsType,
++                                                                  ContentSettingValues.ALLOW));
+         descriptions[1] =
+-                getString(ContentSettingsResources.getSiteSummary(ContentSettingValues.BLOCK));
++                getString(ContentSettingsResources.getSiteSummary(contentSettingsType,
++                                                                  ContentSettingValues.BLOCK));
+ 
+         return new AlertDialog.Builder(getContext(), R.style.Theme_Chromium_AlertDialog)
+                 .setPositiveButton(R.string.cancel, null)
+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
+@@ -127,6 +127,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+                 return "popup_permission_list";
+             case ContentSettingsType.SOUND:
+                 return "sound_permission_list";
++            case ContentSettingsType.TIMEZONE_OVERRIDE:
++                return "timezone_override_permission_list";
+             case ContentSettingsType.AR:
+                 return "ar_permission_list";
+             case ContentSettingsType.MEDIASTREAM_CAMERA:
+@@ -886,11 +888,13 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+         if (value == null) return;
+         setUpPreferenceCommon(preference, value);
+ 
++        int content_type = getContentSettingsTypeFromPreferenceKey(preference.getKey());
++
+         ChromeSwitchPreference switchPreference = (ChromeSwitchPreference) preference;
+         switchPreference.setChecked(value == ContentSettingValues.ALLOW);
+         switchPreference.setSummary(isEmbargoed
+                         ? getString(R.string.automatically_blocked)
+-                        : getString(ContentSettingsResources.getCategorySummary(value)));
++                        : getString(ContentSettingsResources.getCategorySummary(content_type, value)));
+         switchPreference.setOnPreferenceChangeListener(this);
+         @ContentSettingsType
+         int contentType = getContentSettingsTypeFromPreferenceKey(preference.getKey());
+@@ -1109,7 +1113,7 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
+             preference.setSummary(getDSECategorySummary(permission));
+         } else {
+             preference.setSummary(
+-                    getString(ContentSettingsResources.getCategorySummary(permission)));
++                    getString(ContentSettingsResources.getCategorySummary(type, permission)));
+         }
+         preference.setIcon(getContentSettingsIcon(type, permission));
+ 
+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
+@@ -117,7 +117,7 @@ public class SiteSettings
+             } else if (Type.AUTO_DARK_WEB_CONTENT == prefCategory) {
+                 p.setSummary(ContentSettingsResources.getAutoDarkWebContentListSummary(checked));
+             } else if (requiresTriStateSetting) {
+-                p.setSummary(ContentSettingsResources.getCategorySummary(setting));
++                p.setSummary(ContentSettingsResources.getCategorySummary(contentType, setting));
+             } else {
+                 p.setSummary(ContentSettingsResources.getCategorySummary(contentType, checked));
+             }
+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
+@@ -42,7 +42,8 @@ public class SiteSettingsCategory {
+             Type.CLIPBOARD, Type.COOKIES, Type.IDLE_DETECTION, Type.DEVICE_LOCATION,
+             Type.JAVASCRIPT, Type.MICROPHONE, Type.NFC, Type.NOTIFICATIONS, Type.POPUPS,
+             Type.PROTECTED_MEDIA, Type.SENSORS, Type.SOUND, Type.USB, Type.VIRTUAL_REALITY,
+-            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE})
++            Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE,
++            Type.TIMEZONE_OVERRIDE})
+     @Retention(RetentionPolicy.SOURCE)
+     public @interface Type {
+         // All updates here must also be reflected in {@link #preferenceKey(int)
+@@ -72,10 +73,11 @@ public class SiteSettingsCategory {
+         int USE_STORAGE = 22;
+         int AUTO_DARK_WEB_CONTENT = 23;
+         int REQUEST_DESKTOP_SITE = 24;
++        int TIMEZONE_OVERRIDE = 25;
+         /**
+          * Number of handled categories used for calculating array sizes.
+          */
+-        int NUM_ENTRIES = 25;
++        int NUM_ENTRIES = 26;
+     }
+ 
+     private final BrowserContextHandle mBrowserContextHandle;
+@@ -198,6 +200,8 @@ public class SiteSettingsCategory {
+                 return ContentSettingsType.USB_GUARD;
+             case Type.VIRTUAL_REALITY:
+                 return ContentSettingsType.VR;
++            case Type.TIMEZONE_OVERRIDE:
++                return ContentSettingsType.TIMEZONE_OVERRIDE;
+             // case Type.ALL_SITES
+             // case Type.USE_STORAGE
+             default:
+@@ -276,6 +280,8 @@ public class SiteSettingsCategory {
+                 return "use_storage";
+             case Type.VIRTUAL_REALITY:
+                 return "virtual_reality";
++            case Type.TIMEZONE_OVERRIDE:
++                return "timezone_override";
+             default:
+                 assert false;
+                 return "";
+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
+@@ -119,4 +119,6 @@ public interface SiteSettingsDelegate {
+      * Dismisses the Privacy Sandbox snackbar, if active.
+      */
+     void dismissPrivacySandboxSnackbar();
++
++    void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity);
+ }
+diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java
+new file mode 100755
+--- /dev/null
++++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java
+@@ -0,0 +1,193 @@
++/*
++    This file is part of Bromite.
++
++    Bromite 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.
++
++    Bromite is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License
++    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
++*/
++
++package org.chromium.components.browser_ui.site_settings;
++
++import org.chromium.base.Log;
++import android.content.Context;
++import android.content.Intent;
++import android.content.DialogInterface;
++import android.view.View;
++import android.widget.RadioGroup;
++import android.widget.Button;
++import android.widget.TextView;
++import android.util.AttributeSet;
++import android.widget.ArrayAdapter;
++import android.widget.ListView;
++import android.view.LayoutInflater;
++import android.widget.AdapterView;
++import android.graphics.Color;
++
++import androidx.preference.Preference;
++import androidx.preference.PreferenceViewHolder;
++import androidx.appcompat.app.AlertDialog;
++
++import org.chromium.content_public.browser.BrowserContextHandle;
++import org.chromium.components.content_settings.ContentSettingValues;
++import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
++import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
++import org.chromium.components.browser_ui.widget.RadioButtonWithEditText;
++import org.chromium.components.browser_ui.widget.TintedDrawable;
++
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.TimeZone;
++
++/**
++ * TimezoneOverride Preference for SiteSettings.
++ */
++public class TimezoneOverrideSiteSettingsPreference
++        extends Preference implements RadioGroup.OnCheckedChangeListener,
++                                      RadioButtonWithEditText.OnTextChangeListener {
++    private @ContentSettingValues int mSetting = ContentSettingValues.DEFAULT;
++    private RadioButtonWithDescription mAllowed;
++    private RadioButtonWithEditText mAsk;
++    private RadioButtonWithDescription mBlocked;
++    private RadioGroup mRadioGroup;
++    private TextView mSelectButton;
++
++    private String currentSelected;
++
++    private BrowserContextHandle mBrowserContextHandle;
++
++    public TimezoneOverrideSiteSettingsPreference(Context context, AttributeSet attrs) {
++        super(context, attrs);
++
++        setLayoutResource(R.layout.timezoneoverride_site_settings_preference);
++        setSelectable(false);
++    }
++
++    public void initialize(@ContentSettingValues int setting, BrowserContextHandle browserContextHandle) {
++        mSetting = setting;
++        mBrowserContextHandle = browserContextHandle;
++    }
++
++    public @ContentSettingValues int getCheckedSetting() {
++        return mSetting;
++    }
++
++    @Override
++    public void onCheckedChanged(RadioGroup group, int checkedId) {
++        if (mAllowed.isChecked()) {
++            mSetting = ContentSettingValues.ALLOW;
++        } else if (mAsk.isChecked()) {
++            mSetting = ContentSettingValues.ASK;
++        } else if (mBlocked.isChecked()) {
++            mSetting = ContentSettingValues.BLOCK;
++        }
++
++        callChangeListener(mSetting);
++    }
++
++    @Override
++    public void onBindViewHolder(PreferenceViewHolder holder) {
++        super.onBindViewHolder(holder);
++
++        mAllowed = (RadioButtonWithDescription) holder.findViewById(R.id.allowed);
++        mAsk = (RadioButtonWithEditText) holder.findViewById(R.id.ask);
++        mBlocked = (RadioButtonWithDescription) holder.findViewById(R.id.blocked);
++        mRadioGroup = (RadioGroup) holder.findViewById(R.id.radio_button_layout);
++        mRadioGroup.setOnCheckedChangeListener(this);
++
++        mAsk.setPrimaryText(WebsitePreferenceBridge.getCustomTimezone(mBrowserContextHandle));
++        mAsk.addTextChangeListener(this);
++
++        ListView listView = (ListView)holder.findViewById(R.id.listView);
++
++        mSelectButton = (TextView) holder.findViewById(R.id.select_button);
++        mSelectButton.setOnClickListener(view -> {
++          showSelectTimeZoneDialog();
++        });
++
++        RadioButtonWithDescription radioButton = findRadioButton(mSetting);
++        if (radioButton != null) radioButton.setChecked(true);
++    }
++
++    private RadioButtonWithDescription findRadioButton(@ContentSettingValues int setting) {
++        if (setting == ContentSettingValues.ALLOW) {
++            return mAllowed;
++        } else if (setting == ContentSettingValues.ASK) {
++            return mAsk;
++        } else if (setting == ContentSettingValues.BLOCK) {
++            return mBlocked;
++        } else {
++            return null;
++        }
++    }
++
++    public void onTextChanged(CharSequence newText) {
++        WebsitePreferenceBridge.setCustomTimezone(mBrowserContextHandle, newText.toString());
++    }
++
++    private void showSelectTimeZoneDialog() {
++        LayoutInflater inflater =
++                (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
++        View view = inflater.inflate(R.layout.time_zone_select_dialog, null);
++
++        ListView listView = view.findViewById(R.id.listView);
++        ArrayList<String> timezones = new ArrayList<>(Arrays.asList(TimeZone.getAvailableIDs()));
++        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, android.R.id.text1, timezones);
++        listView.setAdapter(adapter);
++
++        currentSelected = String.valueOf(mAsk.getPrimaryText());
++        listView.post(new Runnable()
++        {
++          public void run()
++          {
++            for (int j = 0; j < timezones.size(); j++) {
++                if (currentSelected.equals(timezones.get(j))) {
++                    listView.requestFocusFromTouch();
++                    listView.setSelection(j);
++                    adapter.notifyDataSetChanged();
++                    break;
++                }
++            }
++          }
++        });
++
++        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
++            @Override
++            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
++                currentSelected = timezones.get(i);
++                listView.setSelected(true);
++            }
++        });
++
++        DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
++            @Override
++            public void onClick(DialogInterface dialog, int button) {
++                if (button == AlertDialog.BUTTON_POSITIVE) {
++                    mAsk.setPrimaryText(currentSelected);
++                } else {
++                    dialog.dismiss();
++                }
++            }
++        };
++
++        AlertDialog.Builder alert =
++                new AlertDialog.Builder(getContext(), R.style.Theme_Chromium_AlertDialog);
++        AlertDialog alertDialog =
++                alert.setTitle(R.string.website_settings_select_dialog_title)
++                        .setView(view)
++                        .setPositiveButton(
++                                R.string.website_settings_select_dialog_button, onClickListener)
++                        .setNegativeButton(R.string.cancel, onClickListener)
++                        .create();
++        alertDialog.getDelegate().setHandleNativeActionModesEnabled(false);
++        alertDialog.show();
++    }
++}
+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
+@@ -229,6 +229,16 @@ public final class Website implements Serializable {
+             } else {
+                 RecordUserAction.record("SoundContentSetting.UnmuteBy.SiteSettings");
+             }
++        } else if (type == ContentSettingsType.TIMEZONE_OVERRIDE) {
++            // It is possible to set the permission without having an existing exception,
++            // because we can show the ALLOW state even when this permission is set to the
++            // default. In that case, just set an exception now to ALLOW to enable changing the
++            // permission.
++            if (exception == null) {
++                exception = new ContentSettingException(
++                        ContentSettingsType.TIMEZONE_OVERRIDE, getAddress().getHost(), value, "");
++                setContentSettingException(type, exception);
++            }
+         }
+         // We want to call setContentSetting even after explicitly setting
+         // mContentSettingException above because this will trigger the actual change
+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
+@@ -67,6 +67,7 @@ public class WebsitePermissionsFetcher {
+             case ContentSettingsType.JAVASCRIPT_JIT:
+             case ContentSettingsType.POPUPS:
+             case ContentSettingsType.SOUND:
++            case ContentSettingsType.TIMEZONE_OVERRIDE:
+                 return WebsitePermissionsType.CONTENT_SETTING_EXCEPTION;
+             case ContentSettingsType.AR:
+             case ContentSettingsType.CLIPBOARD_READ_WRITE:
+@@ -147,6 +148,8 @@ public class WebsitePermissionsFetcher {
+         for (@ContentSettingsType int type = 0; type < ContentSettingsType.NUM_TYPES; type++) {
+             addFetcherForContentSettingsType(queue, type);
+         }
++        queue.add(new ExceptionInfoFetcher(ContentSettingsType.TIMEZONE_OVERRIDE));
++
+         queue.add(new PermissionsAvailableCallbackRunner(callback));
+         queue.next();
+     }
+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
+@@ -231,6 +231,8 @@ public class WebsitePreferenceBridge {
+         switch (contentSettingsType) {
+             case ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER:
+                 return true;
++            case ContentSettingsType.TIMEZONE_OVERRIDE:
++                return true;
+             default:
+                 return false;
+         }
+@@ -361,6 +363,14 @@ public class WebsitePreferenceBridge {
+                 contentSettingType, primaryPattern, secondaryPattern, setting);
+     }
+ 
++    public static String getCustomTimezone(BrowserContextHandle browserContextHandle) {
++        return WebsitePreferenceBridgeJni.get().getCustomTimezone(browserContextHandle);
++    }
++
++    public static void setCustomTimezone(BrowserContextHandle browserContextHandle, String timezone) {
++        WebsitePreferenceBridgeJni.get().setCustomTimezone(browserContextHandle, timezone);
++    }
++
+     @NativeMethods
+     public interface Natives {
+         boolean isNotificationEmbargoedForOrigin(
+@@ -424,5 +434,7 @@ public class WebsitePreferenceBridge {
+         boolean isContentSettingManagedByCustodian(
+                 BrowserContextHandle browserContextHandle, int contentSettingType);
+         boolean getLocationAllowedByPolicy(BrowserContextHandle browserContextHandle);
++        String getCustomTimezone(BrowserContextHandle browserContextHandle);
++        void setCustomTimezone(BrowserContextHandle browserContextHandle, String timezone);
+     }
+ }
+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
+@@ -947,3 +947,19 @@ static jboolean JNI_WebsitePreferenceBridge_GetLocationAllowedByPolicy(
+              ->GetDefaultContentSetting(ContentSettingsType::GEOLOCATION,
+                                         nullptr) == CONTENT_SETTING_ALLOW;
+ }
++
++static void JNI_WebsitePreferenceBridge_SetCustomTimezone(
++    JNIEnv* env,
++    const JavaParamRef<jobject>& jbrowser_context_handle,
++    const JavaParamRef<jstring>& timezone) {
++  std::string new_timezone = ConvertJavaStringToUTF8(env, timezone);
++  GetHostContentSettingsMap(jbrowser_context_handle)->SetTimezoneOverrideValue(new_timezone);
++}
++
++static base::android::ScopedJavaLocalRef<jstring> JNI_WebsitePreferenceBridge_GetCustomTimezone(
++    JNIEnv* env,
++    const JavaParamRef<jobject>& jbrowser_context_handle) {
++  std::string timezone;
++  GetHostContentSettingsMap(jbrowser_context_handle)->GetTimezoneOverrideValue(timezone);
++  return ConvertUTF8ToJavaString(env, timezone);
++}
+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
+@@ -72,6 +72,9 @@
+   <message name="IDS_AUTO_DARK_WEB_CONTENT_TITLE" desc="Title of the permission auto darken web content [CHAR_LIMIT=32]">
+     Dark theme for sites
+   </message>
++  <message name="IDS_TIMEZONE_OVERRIDE_PERMISSION_TITLE" desc="Title of the permission to use TimeZone Override [CHAR-LIMIT=32]">
++    Timezone override
++  </message>
+   <message name="IDS_DESKTOP_SITE_TITLE" desc="Title of the permission to request the desktop view of a site by default [CHAR_LIMIT=32]">
+     Desktop site
+   </message>
+@@ -480,6 +483,38 @@
+     Block sites from playing protected content
+   </message>
+ 
++  <!-- Timezone override -->
++  <message name="IDS_WEBSITE_SETTINGS_TIMEOVERRIDE_INFO" desc="Primary text explaining the timezone override feature.">
++    Override timezone with a custom or random one, or use the system timezone
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_ALLOWED_TEXT" desc="Primary text explaining that sites are allowed to access the system timezone.">
++    None (use system timezone)
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_RANDOM_TEXT" desc="Primary text for random timezone override.">
++    Random
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_ALLOWED" desc="Summary text explaining that sites are allowed to access the system timezone.">
++    System timezone
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_CUSTOM" desc="Summary text explaining that sites use custom timezone.">
++    Custom timezone
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_CUSTOM_HINT" desc="Hint text for edit custom timezone.">
++    Specify a custom timezone (default UTC)
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_RANDOM" desc="Summary text for random timezone override.">
++    Random (for each page)
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_SELECT_BUTTON" desc="Primary button text for choosing a timezone from a list.">
++    Choose Timezone...
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_SELECT_DIALOG_TITLE" desc="Text of the dialog to choose a timezone.">
++    Choose Timezone
++  </message>
++  <message name="IDS_WEBSITE_SETTINGS_SELECT_DIALOG_BUTTON" desc="Primary button text in the dialog used to choose the custom timezone.">
++    Select
++  </message>
++
+   <!-- Sound -->
+ 
+   <message name="IDS_WEBSITE_SETTINGS_CATEGORY_SOUND_ALLOWED" desc="Summary text explaining that sites are allowed to play sound and that it is the recommended setting.">
+diff --git a/components/content_settings/core/browser/content_settings_pref_provider.cc b/components/content_settings/core/browser/content_settings_pref_provider.cc
+--- a/components/content_settings/core/browser/content_settings_pref_provider.cc
++++ b/components/content_settings/core/browser/content_settings_pref_provider.cc
+@@ -78,6 +78,8 @@ void PrefProvider::RegisterProfilePrefs(
+                                      info->GetPrefRegistrationFlags());
+   }
+ 
++  registry->RegisterStringPref(prefs::kContentSettingsCustomTimezone, std::string());
++
+   // Obsolete prefs ----------------------------------------------------------
+ 
+   // These prefs have been removed, but need to be registered so they can
+@@ -157,6 +159,10 @@ PrefProvider::PrefProvider(PrefService* prefs,
+     event_args->set_number_of_exceptions(
+         num_exceptions);  // PrefProvider::PrefProvider.
+   });
++
++  custom_timezone_ =
++    prefs_->GetString(
++          prefs::kContentSettingsCustomTimezone);
+ }
+ 
+ PrefProvider::~PrefProvider() {
+@@ -284,4 +290,14 @@ void PrefProvider::SetClockForTesting(base::Clock* clock) {
+   clock_ = clock;
+ }
+ 
++void PrefProvider::GetPrefTimezoneOverrideValue(std::string& timezone) const {
++  timezone = custom_timezone_;
++}
++
++void PrefProvider::SetPrefTimezoneOverrideValue(const std::string& timezone) {
++  prefs_->SetString(
++        prefs::kContentSettingsCustomTimezone, timezone);
++  custom_timezone_ = timezone;
++}
++
+ }  // namespace content_settings
+diff --git a/components/content_settings/core/browser/content_settings_pref_provider.h b/components/content_settings/core/browser/content_settings_pref_provider.h
+--- a/components/content_settings/core/browser/content_settings_pref_provider.h
++++ b/components/content_settings/core/browser/content_settings_pref_provider.h
+@@ -66,6 +66,9 @@ class PrefProvider : public UserModifiableProvider {
+ 
+   ContentSettingsPref* GetPref(ContentSettingsType type) const;
+ 
++  void GetPrefTimezoneOverrideValue(std::string& timezone) const;
++  void SetPrefTimezoneOverrideValue(const std::string& timezone);
++
+  private:
+   friend class DeadlockCheckerObserver;  // For testing.
+ 
+@@ -97,6 +100,7 @@ class PrefProvider : public UserModifiableProvider {
+   base::ThreadChecker thread_checker_;
+ 
+   base::Clock* clock_;
++  std::string custom_timezone_;
+ };
+ 
+ }  // namespace content_settings
+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
+@@ -638,6 +638,18 @@ void ContentSettingsRegistry::Init() {
+            ContentSettingsInfo::INHERIT_IN_INCOGNITO,
+            ContentSettingsInfo::PERSISTENT,
+            ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS);
++
++  Register(ContentSettingsType::TIMEZONE_OVERRIDE, "timezone-override", CONTENT_SETTING_ALLOW,
++           WebsiteSettingsInfo::SYNCABLE,
++           AllowlistedSchemes(kChromeUIScheme, kChromeDevToolsScheme),
++           ValidSettings(CONTENT_SETTING_ALLOW,   // use system time
++                         CONTENT_SETTING_ASK,     // custom timezone, default UTC
++                         CONTENT_SETTING_BLOCK),  // random
++           WebsiteSettingsInfo::SINGLE_ORIGIN_WITH_EMBEDDED_EXCEPTIONS_SCOPE,
++           WebsiteSettingsRegistry::PLATFORM_ANDROID,
++           ContentSettingsInfo::INHERIT_IN_INCOGNITO,
++           ContentSettingsInfo::PERSISTENT,
++           ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS);
+ }
+ 
+ void ContentSettingsRegistry::Register(
+diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
+--- a/components/content_settings/core/browser/content_settings_utils.cc
++++ b/components/content_settings/core/browser/content_settings_utils.cc
+@@ -154,6 +154,13 @@ void GetRendererContentSettingRules(const HostContentSettingsMap* map,
+                              &(rules->script_rules));
+   map->GetSettingsForOneType(ContentSettingsType::POPUPS,
+                              &(rules->popup_redirect_rules));
++
++  // pass custom timezone rules and value to the render process
++  map->GetSettingsForOneType(ContentSettingsType::TIMEZONE_OVERRIDE,
++                             &(rules->timezone_override_rules));
++  std::string timezone;
++  map->GetTimezoneOverrideValue(timezone);
++  rules->timezone_override_value = timezone;
+ }
+ 
+ bool IsMorePermissive(ContentSetting a, ContentSetting b) {
+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
+@@ -588,6 +588,14 @@ void HostContentSettingsMap::SetClockForTesting(base::Clock* clock) {
+     provider->SetClockForTesting(clock);
+ }
+ 
++void HostContentSettingsMap::GetTimezoneOverrideValue(std::string& timezone) const {
++  GetPrefProvider()->GetPrefTimezoneOverrideValue(timezone);
++}
++
++void HostContentSettingsMap::SetTimezoneOverrideValue(const std::string& timezone) {
++  GetPrefProvider()->SetPrefTimezoneOverrideValue(timezone);
++}
++
+ void HostContentSettingsMap::RecordExceptionMetrics() {
+   auto* content_setting_registry =
+       content_settings::ContentSettingsRegistry::GetInstance();
+diff --git a/components/content_settings/core/browser/host_content_settings_map.h b/components/content_settings/core/browser/host_content_settings_map.h
+--- a/components/content_settings/core/browser/host_content_settings_map.h
++++ b/components/content_settings/core/browser/host_content_settings_map.h
+@@ -332,6 +332,9 @@ class HostContentSettingsMap : public content_settings::Observer,
+     allow_invalid_secondary_pattern_for_testing_ = allow;
+   }
+ 
++  void GetTimezoneOverrideValue(std::string& timezone) const;
++  void SetTimezoneOverrideValue(const std::string& timezone);
++
+  private:
+   friend class base::RefCountedThreadSafe<HostContentSettingsMap>;
+   friend class content_settings::TestUtils;
+diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
+--- a/components/content_settings/core/common/content_settings.cc
++++ b/components/content_settings/core/common/content_settings.cc
+@@ -190,7 +190,8 @@ bool RendererContentSettingRules::IsRendererContentSetting(
+          content_type == ContentSettingsType::CLIENT_HINTS ||
+          content_type == ContentSettingsType::POPUPS ||
+          content_type == ContentSettingsType::MIXEDSCRIPT ||
+-         content_type == ContentSettingsType::AUTO_DARK_WEB_CONTENT;
++         content_type == ContentSettingsType::AUTO_DARK_WEB_CONTENT ||
++         content_type == ContentSettingsType::TIMEZONE_OVERRIDE;
+ }
+ 
+ RendererContentSettingRules::RendererContentSettingRules() {}
+diff --git a/components/content_settings/core/common/content_settings.h b/components/content_settings/core/common/content_settings.h
+--- a/components/content_settings/core/common/content_settings.h
++++ b/components/content_settings/core/common/content_settings.h
+@@ -80,6 +80,8 @@ struct RendererContentSettingRules {
+   ContentSettingsForOneType popup_redirect_rules;
+   ContentSettingsForOneType mixed_content_rules;
+   ContentSettingsForOneType auto_dark_content_rules;
++  ContentSettingsForOneType timezone_override_rules;
++  std::string timezone_override_value;
+ };
+ 
+ namespace content_settings {
+diff --git a/components/content_settings/core/common/content_settings.mojom b/components/content_settings/core/common/content_settings.mojom
+--- a/components/content_settings/core/common/content_settings.mojom
++++ b/components/content_settings/core/common/content_settings.mojom
+@@ -78,4 +78,6 @@ struct RendererContentSettingRules {
+   array<ContentSettingPatternSource> popup_redirect_rules;
+   array<ContentSettingPatternSource> mixed_content_rules;
+   array<ContentSettingPatternSource> auto_dark_content_rules;
++  array<ContentSettingPatternSource> timezone_override_rules;
++  string timezone_override_value;
+ };
+diff --git a/components/content_settings/core/common/content_settings_mojom_traits.cc b/components/content_settings/core/common/content_settings_mojom_traits.cc
+--- a/components/content_settings/core/common/content_settings_mojom_traits.cc
++++ b/components/content_settings/core/common/content_settings_mojom_traits.cc
+@@ -101,7 +101,9 @@ bool StructTraits<content_settings::mojom::RendererContentSettingRulesDataView,
+          data.ReadScriptRules(&out->script_rules) &&
+          data.ReadPopupRedirectRules(&out->popup_redirect_rules) &&
+          data.ReadMixedContentRules(&out->mixed_content_rules) &&
+-         data.ReadAutoDarkContentRules(&out->auto_dark_content_rules);
++         data.ReadAutoDarkContentRules(&out->auto_dark_content_rules) &&
++         data.ReadTimezoneOverrideRules(&out->timezone_override_rules) &&
++         data.ReadTimezoneOverrideValue(&out->timezone_override_value);
+ }
+ 
+ }  // namespace mojo
+diff --git a/components/content_settings/core/common/content_settings_mojom_traits.h b/components/content_settings/core/common/content_settings_mojom_traits.h
+--- a/components/content_settings/core/common/content_settings_mojom_traits.h
++++ b/components/content_settings/core/common/content_settings_mojom_traits.h
+@@ -150,6 +150,16 @@ struct StructTraits<
+     return r.auto_dark_content_rules;
+   }
+ 
++  static const std::vector<ContentSettingPatternSource>& timezone_override_rules(
++      const RendererContentSettingRules& r) {
++    return r.timezone_override_rules;
++  }
++
++  static const std::string& timezone_override_value(
++      const RendererContentSettingRules& r) {
++    return r.timezone_override_value;
++  }
++
+   static bool Read(
+       content_settings::mojom::RendererContentSettingRulesDataView data,
+       RendererContentSettingRules* out);
+diff --git a/components/content_settings/core/common/content_settings_types.h b/components/content_settings/core/common/content_settings_types.h
+--- a/components/content_settings/core/common/content_settings_types.h
++++ b/components/content_settings/core/common/content_settings_types.h
+@@ -230,6 +230,9 @@ enum class ContentSettingsType : int32_t {
+   // by the File System Access API.
+   FILE_SYSTEM_LAST_PICKED_DIRECTORY,
+ 
++  // Content setting for timezone customization functionality.
++  TIMEZONE_OVERRIDE,
++
+   // Controls access to the getDisplayMedia API when {preferCurrentTab: true}
+   // is specified.
+   // TODO(crbug.com/1150788): Also apply this when getDisplayMedia() is called
+diff --git a/components/content_settings/core/common/pref_names.cc b/components/content_settings/core/common/pref_names.cc
+--- a/components/content_settings/core/common/pref_names.cc
++++ b/components/content_settings/core/common/pref_names.cc
+@@ -144,4 +144,7 @@ const char kQuietNotificationPermissionUiDisabledTime[] =
+ const char kNotificationsVibrateEnabled[] = "notifications.vibrate_enabled";
+ #endif
+ 
++const char kContentSettingsCustomTimezone[] =
++    "profile.content_settings.custom_timezone";
++
+ }  // namespace prefs
+diff --git a/components/content_settings/core/common/pref_names.h b/components/content_settings/core/common/pref_names.h
+--- a/components/content_settings/core/common/pref_names.h
++++ b/components/content_settings/core/common/pref_names.h
+@@ -77,6 +77,8 @@ extern const char kQuietNotificationPermissionUiDisabledTime[];
+ extern const char kNotificationsVibrateEnabled[];
+ #endif
+ 
++extern const char kContentSettingsCustomTimezone[];
++
+ }  // namespace prefs
+ 
+ #endif  // COMPONENTS_CONTENT_SETTINGS_CORE_COMMON_PREF_NAMES_H_
+diff --git a/components/content_settings/renderer/content_settings_agent_impl.cc b/components/content_settings/renderer/content_settings_agent_impl.cc
+--- a/components/content_settings/renderer/content_settings_agent_impl.cc
++++ b/components/content_settings/renderer/content_settings_agent_impl.cc
+@@ -8,8 +8,10 @@
+ 
+ #include "base/bind.h"
+ #include "base/feature_list.h"
++#include "base/logging.h"
+ #include "base/metrics/histogram_macros.h"
+ #include "base/strings/string_number_conversions.h"
++#include "base/rand_util.h"
+ #include "components/content_settings/core/common/content_settings.h"
+ #include "components/content_settings/core/common/content_settings.mojom.h"
+ #include "components/content_settings/core/common/content_settings_pattern.h"
+@@ -31,6 +33,10 @@
+ #include "third_party/blink/public/web/web_local_frame.h"
+ #include "third_party/blink/public/web/web_local_frame_client.h"
+ #include "third_party/blink/public/web/web_view.h"
++#include "third_party/blink/renderer/core/inspector/locale_controller.h"
++#include "third_party/blink/renderer/core/timezone/timezone_controller.h"
++#include "third_party/icu/source/common/unicode/strenum.h"
++#include "third_party/icu/source/i18n/unicode/timezone.h"
+ #include "url/gurl.h"
+ #include "url/origin.h"
+ #include "url/url_constants.h"
+@@ -44,6 +50,8 @@ using blink::WebURL;
+ using blink::WebView;
+ using content::DocumentState;
+ 
++std::unique_ptr<blink::TimeZoneController::TimeZoneOverride> timezone_override_;
++
+ namespace content_settings {
+ namespace {
+ 
+@@ -363,6 +371,10 @@ bool ContentSettingsAgentImpl::AllowScript(bool enabled_per_settings) {
+   allow = allow || IsAllowlistedForContentSettings();
+ 
+   cached_script_permissions_[frame] = allow;
++
++  if (allow)
++    UpdateOverrides();
++
+   return allow;
+ }
+ 
+@@ -498,4 +510,81 @@ bool ContentSettingsAgentImpl::IsAllowlistedForContentSettings() const {
+   return false;
+ }
+ 
++bool ContentSettingsAgentImpl::UpdateOverrides() {
++  // Evaluate the content setting rules
++  ContentSetting setting = CONTENT_SETTING_ALLOW;
++
++  if (content_setting_rules_) {
++    blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
++
++    setting = GetContentSettingFromRules(
++        content_setting_rules_->timezone_override_rules, frame,
++        url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL());
++  }
++  return UpdateTimeZoneOverride(
++      setting, content_setting_rules_->timezone_override_value);
++  //&& UpdateLocaleOverride(setting);
++}
++
++bool ContentSettingsAgentImpl::UpdateTimeZoneOverride(
++    ContentSetting setting,
++    const std::string& timezone_override_value) {
++  // base/i18n/icu_util.cc # 329
++
++  /* timezone_id: third_party/icu/source/i18n/timezone.cpp
++      We first try to lookup the zone ID in our system list.  If this
++      * fails, we try to parse it as a custom string GMT[+-]hh:mm.  If
++      * all else fails, we return GMT, which is probably not what the
++      * user wants, but at least is a functioning TimeZone object.
++      */
++  String timezone_id;
++
++  if (setting == CONTENT_SETTING_ALLOW) {
++    // system time
++    if (timezone_override_) {
++      timezone_override_.reset();
++    }
++    return true;
++  } else if (setting == CONTENT_SETTING_BLOCK) {
++    // timezone random
++    UErrorCode ec = U_ZERO_ERROR;
++    int32_t rawOffset = base::RandInt(-12, 11) * 3600 * 1000;
++    icu::StringEnumeration* timezones = icu::TimeZone::createEnumeration(
++        rawOffset);  // Obtain timezones by GMT timezone offset
++    if (timezones) {
++      const char* tzID;
++      int32_t length;
++      if ((tzID = timezones->next(&length, ec)) != NULL) {
++        timezone_id = String(tzID);
++      }
++      delete timezones;
++    }
++  } else if (setting == CONTENT_SETTING_ASK) {
++    if (timezone_override_value.empty())
++      timezone_id = "Europe/London";
++    else
++      timezone_id = String(timezone_override_value.c_str());
++  }
++
++  if (blink::TimeZoneController::HasTimeZoneOverride() == false) {
++    timezone_override_.reset();
++    timezone_override_ =
++        blink::TimeZoneController::SetTimeZoneOverride(timezone_id);
++    if (!timezone_override_) {
++      LOG(WARNING) << "UpdateTimeZoneOverride - Invalid timezone id '"
++                   << timezone_id << "'";
++      return false;
++    } else {
++      LOG(INFO)
++          << "UpdateTimeZoneOverride - setting to '"
++          << timezone_id << "'";
++      return true;
++    }
++  } else {
++    LOG(INFO)
++        << "UpdateTimeZoneOverride: already set";
++    return false;
++  }
++}
++
+ }  // namespace content_settings
+diff --git a/components/content_settings/renderer/content_settings_agent_impl.h b/components/content_settings/renderer/content_settings_agent_impl.h
+--- a/components/content_settings/renderer/content_settings_agent_impl.h
++++ b/components/content_settings/renderer/content_settings_agent_impl.h
+@@ -185,6 +185,10 @@ class ContentSettingsAgentImpl
+   std::unique_ptr<Delegate> delegate_;
+ 
+   mojo::AssociatedReceiverSet<mojom::ContentSettingsAgent> receivers_;
++
++  bool UpdateOverrides();
++  bool UpdateTimeZoneOverride(ContentSetting setting, const std::string& timezone_override_value);
++  bool UpdateLocaleOverride(ContentSetting setting);
+ };
+ 
+ }  // namespace content_settings
+diff --git a/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java b/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
+--- a/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
++++ b/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
+@@ -143,4 +143,7 @@ public class WebLayerSiteSettingsDelegate
+ 
+     @Override
+     public void dismissPrivacySandboxSnackbar() {}
++
++    @Override
++    public void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity) {}
+ }
+-- 
+2.20.1
+