diff --git a/build/bromite_patches_list.txt b/build/bromite_patches_list.txt index 86716f6c..9c4009cd 100644 --- a/build/bromite_patches_list.txt +++ b/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 diff --git a/build/patches/Automated-domain-substitution.patch b/build/patches/Automated-domain-substitution.patch index 958a8ecf..be33f089 100644 --- a/build/patches/Automated-domain-substitution.patch +++ b/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[] = diff --git a/build/patches/Disable-third-party-cookies-by-default.patch b/build/patches/Disable-third-party-cookies-by-default.patch new file mode 100644 index 00000000..13adf145 --- /dev/null +++ b/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(CookieControlsMode::kIncognitoOnly), ++ static_cast(CookieControlsMode::kBlockThirdParty), + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + } + +-- +2.20.1 + diff --git a/build/patches/Enable-native-Android-autofill.patch b/build/patches/Enable-native-Android-autofill.patch index b60e6f91..72295937 100644 --- a/build/patches/Enable-native-Android-autofill.patch +++ b/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", diff --git a/build/patches/Experimental-user-scripts-support.patch b/build/patches/Experimental-user-scripts-support.patch new file mode 100644 index 00000000..74256d53 --- /dev/null +++ b/build/patches/Experimental-user-scripts-support.patch @@ -0,0 +1,10639 @@ +From: uazo +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"/> ++ + 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( + #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; + if (url.host_piece() == chrome::kChromeUIVersionHost) + return &NewWebUI; ++ if (url.host_piece() == user_scripts::kChromeUIUserScriptsHost) ++ return &NewWebUI; + + #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 @@ + + + ++ + + + +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: ++``` ++// := :// | '' ++// := '*' | 'http' | 'https' ++// := '*' | | [] | ++// '*.' + ++// := [':' ('*' | )] ++// := '/' ++// ++// * Host is not used when the scheme is 'file'. ++// * The path can have embedded '*' characters which act as glob wildcards. ++// * '' 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 . ++ ++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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ 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 @@ ++ ++ ++ ++ +\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ 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 @@ ++ ++ ++ ++ ++ ++ 24dp ++ 260dp ++ ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 . ++*/ ++ ++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 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 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 . ++*/ ++ ++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 . ++*/ ++ ++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 . ++*/ ++ ++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 { ++ ++ 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 values) { ++ mElements = new ArrayList<>(values); ++ notifyDataSetChanged(); ++ } ++ ++ @Override ++ protected void setOrder(List 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 . ++*/ ++ ++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 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 . ++*/ ++ ++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 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 getUserScriptItems() { ++ List 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(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 . ++*/ ++ ++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 . ++ ++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 . ++*/ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 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& 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& 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& 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& 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 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 . ++*/ ++ ++#ifndef COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_ ++#define COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_ ++ ++#include ++ ++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 . ++ ++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 . ++*/ ++ ++#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 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 . ++*/ ++ ++#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 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ 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 @@ ++ ++ ++ ++ ++ Local State Debug Page ++ ++ ++ ++ ++
++    Loading Script Source file...
++  
++ ++ +\ 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 . ++*/ ++ ++ ++#include ++ ++#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); ++ ++ std::unique_ptr loaded_scripts_; ++ ++ base::WeakPtrFactory 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 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) { ++ loaded_scripts_ = std::move(user_scripts); ++ ++ base::ListValue response; ++ for (const std::unique_ptr& script : *loaded_scripts_) { ++ if (script->key() == script_key) { ++ auto scriptData = std::make_unique(); ++ for (const std::unique_ptr& 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()); ++} ++ ++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 . ++*/ ++ ++#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 . ++*/ ++ ++#include "user_script_loader.h" ++ ++#include ++ ++#include ++#include ++#include ++ ++#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& 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& 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(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 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(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 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& 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& js_file : ++ script->js_scripts()) { ++ base::StringPiece contents = js_file->GetContent(); ++ pickle.WriteData(contents.data(), contents.length()); ++ } ++ for (const std::unique_ptr& 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 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 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 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(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 types; ++ types.push_back(u"*/*"); /*= java SelectFileDialog.ALL_TYPES*/ ++ std::pair, 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(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 . ++*/ ++ ++#ifndef USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_ ++#define USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_ ++ ++#include ++#include ++#include ++ ++#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)>; ++ using LoadSingleScriptCallback = ++ base::OnceCallback; ++ ++ using RemoveScriptCallback = ++ base::OnceCallback; ++ 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& 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 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 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 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::Unchecked observers_; ++ ++ void OnScriptRemoved(); ++ ++ // Manage file dialog requests ++ scoped_refptr 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 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 . ++*/ ++ ++#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 . ++*/ ++ ++#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 . ++*/ ++ ++#include ++ ++#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(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 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 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 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 UserScriptsPrefs::CreateScriptInfoFromPrefs( ++ const std::string& script_id) const { ++ ++ auto scriptInfo = std::make_unique( ++ script_id, "", base::Time::Now(), false); ++ ++ const base::DictionaryValue* scripts = ++ prefs_->GetDictionary(kUserScriptsList); ++ if (!scripts) ++ return scriptInfo; ++ ++ const base::DictionaryValue* script = static_cast( ++ 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 . ++*/ ++ ++#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 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 . ++*/ ++ ++#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( ++ static_cast(context)->GetPrefs()); ++ ++ userscript_loader_ = ++ std::make_unique(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 . ++*/ ++ ++#ifndef USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_ ++#define USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_ ++ ++#include ++#include ++#include ++ ++#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 scripts_; ++ content::BrowserContext* browser_context_; ++ std::unique_ptr prefs_; ++ std::unique_ptr 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 ++ ++#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 args) { ++ std::string format_str = format.as_string(); ++ base::StringTokenizer tokenizer(format_str, "*"); ++ tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS); ++ ++ std::vector 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 ++ ++#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 ++ ++#include ++#include ++ ++#include "content/public/common/common_param_traits.h" ++ ++namespace IPC { ++ ++void ParamTraits::Write(base::Pickle* m, const param_type& p) { ++ WriteParam(m, p.type()); ++ WriteParam(m, p.id()); ++} ++ ++bool ParamTraits::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::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 ++ ++#include ++#include ++#include ++#include ++#include ++ ++#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 { ++ 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 ++ ++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 ++ ++// 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 ++ ++#include ++ ++#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[] = ""; ++ ++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(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(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 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::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& schemes) const { ++ for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) { ++ if (MatchesScheme(*i)) ++ return true; ++ } ++ ++ return false; ++} ++ ++bool URLPattern::MatchesAllSchemes( ++ const std::vector& 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 URLPattern::GetExplicitSchemes() const { ++ std::vector 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::ConvertToExplicitSchemes() const { ++ std::vector explicit_schemes = GetExplicitSchemes(); ++ std::vector result; ++ ++ for (std::vector::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(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 ++#include ++#include ++#include ++ ++#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: ++// ++// := :// | '' ++// := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' | ++// 'chrome-extension' | 'filesystem' ++// := '*' | | [] | ++// '*.' + ++// := [':' ('*' | )] ++// := '/' ++// ++// * Host is not used when the scheme is 'file'. ++// * The path can have embedded '*' characters which act as glob wildcards. ++// * '' 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 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 ://*/). ++ 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 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 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& schemes) const; ++ ++ // Returns true if all of the |schemes| items matches our scheme. ++ bool MatchesAllSchemes(const std::vector& 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 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 "" 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 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 ++#include ++ ++#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>( ++ 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>( ++ 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 unique_set1; ++ for (const URLPattern& pattern : set1) { ++ if (set2.ContainsPattern(pattern)) ++ result.patterns_.insert(pattern); ++ else ++ unique_set1.push_back(&pattern); ++ } ++ std::vector 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 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>(set1.patterns_, set2.patterns_)); ++} ++ ++// static ++URLPatternSet URLPatternSet::CreateUnion( ++ const std::vector& 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 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& 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 URLPatternSet::ToValue() const { ++ std::unique_ptr 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& 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> URLPatternSet::ToStringVector() ++ const { ++ std::unique_ptr> value(new std::vector); ++ 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 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 ++ ++#include ++#include ++#include ++ ++#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::const_iterator const_iterator; ++ typedef std::set::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& sets); ++ ++ URLPatternSet(); ++ URLPatternSet(URLPatternSet&& rhs); ++ explicit URLPatternSet(const std::set& patterns); ++ ~URLPatternSet(); ++ ++ URLPatternSet& operator=(URLPatternSet&& rhs); ++ bool operator==(const URLPatternSet& rhs) const; ++ ++ bool is_empty() const; ++ size_t size() const; ++ const std::set& 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, ). ++ 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 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> ToStringVector() const; ++ bool Populate(const std::vector& patterns, ++ int valid_schemes, ++ bool allow_file_access, ++ std::string* error); ++ ++ private: ++ // The list of URL patterns that comprise the extent. ++ std::set 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 ++#include ++ ++#include ++#include ++ ++#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* 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(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& 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 : 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(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(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(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* 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(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(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 ++#include ++#include ++ ++#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>; ++ ++ // 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 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& globs() const { return globs_; } ++ void add_glob(const std::string& glob) { globs_.push_back(glob); } ++ void clear_globs() { globs_.clear(); } ++ const std::vector& 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& 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* 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 globs_; ++ std::vector 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>; ++ ++} // 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 . ++*/ ++ ++#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 . ++*/ ++ ++#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 ++ ++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 ++ ++#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>::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 frame_helper, ++ std::vector* 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 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(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 ++#include ++ ++#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 { ++ 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 document_element_created_callbacks_; ++ ++ // Callbacks to be run at the next RunScriptsAtDocumentEnd notification. ++ std::vector document_load_finished_callbacks_; ++ ++ // Callbacks to be run at the next RunScriptsAtDocumentIdle notification. ++ std::vector document_idle_callbacks_; ++ ++ base::WeakPtrFactory 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 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: ++ // ++ // ++ // ++ // ++ // 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; ++ ++FrameToDocumentLoader& FrameDocumentLoaderMap() { ++ static base::NoDestructor 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 ++#include ++#include ++#include ++ ++#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 ++#include ++ ++#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; ++base::LazyInstance::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 injection) ++ : ScriptInjectionCallback( ++ base::BindOnce(&TimedScriptInjectionCallback::OnCompleted, ++ base::Unretained(this))), ++ injection_(injection) {} ++ ~TimedScriptInjectionCallback() override {} ++ ++ void OnCompleted(const std::vector>& result) { ++ if (injection_) { ++ base::TimeTicks timestamp(base::TimeTicks::Now()); ++ absl::optional 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 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 injector, ++ content::RenderFrame* render_frame, ++ std::unique_ptr 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* executing_scripts, ++ size_t* num_injected_js_scripts) { ++ DCHECK(!did_inject_js_); ++ std::vector 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 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>& results, ++ absl::optional 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 context = ++ render_frame_->GetWebFrame()->MainWorldScriptContext(); ++ execution_result_ = ++ content::V8ValueConverter::Create()->FromV8Value(results[0], context); ++ } ++ if (!execution_result_.get()) ++ execution_result_ = std::make_unique(); ++ } ++ 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* injected_stylesheets, ++ size_t* num_injected_stylesheets) { ++ std::vector 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 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& 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 ++ ++#include ++#include ++ ++#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 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; ++ ++ // 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 injector, ++ content::RenderFrame* render_frame, ++ std::unique_ptr 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>& results, ++ absl::optional 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* executing_scripts, ++ size_t* num_injected_js_scripts); ++ ++ // Inject any CSS source into the frame for the injection. ++ void InjectCss(std::set* 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 injector_; ++ ++ // The RenderFrame into which this should inject the script. ++ content::RenderFrame* render_frame_; ++ ++ // The associated injection host. ++ std::unique_ptr 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 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 frame_watcher_; ++ ++ base::WeakPtrFactory 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>& result) { ++ std::vector> 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 ++ ++#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>& result)>; ++ ++ explicit ScriptInjectionCallback( ++ CompleteCallback injection_completed_callback); ++ ~ScriptInjectionCallback() override; ++ ++ void Completed(const blink::WebVector>& 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 ++#include ++ ++#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 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(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& mode) { ++ return injection == mode.get(); ++ }); ++ if (iter != running_injections_.end()) ++ running_injections_.erase(iter); ++} ++ ++void ScriptInjectionManager::OnUserScriptsUpdated( ++ const std::set& 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 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 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 ++ ++#include ++#include ++#include ++#include ++ ++#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; ++ ++ using ScriptInjectionVector = std::vector>; ++ ++ // Notifies that an injection has been finished. ++ void OnInjectionFinished(ScriptInjection* injection); ++ ++ // UserScriptSetManager::Observer implementation. ++ void OnUserScriptsUpdated(const std::set& 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 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 active_injection_frames_; ++ ++ // The collection of RFOHelpers. ++ std::vector> 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 ++ 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 ++#include ++ ++#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 GetCssOrigin() const = 0; ++ ++ // Returns the key for this injection, if it's a CSS injection. ++ virtual const absl::optional 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& 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& injected_stylesheets) const = 0; ++ ++ // Returns the javascript sources to inject at the given |run_location|. ++ // Only called if ShouldInjectJs() is true. ++ virtual std::vector GetJsSources( ++ UserScript::RunLocation run_location, ++ std::set* 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 GetCssSources( ++ UserScript::RunLocation run_location, ++ std::set* 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 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 ++ ++#include ++#include ++#include ++ ++#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 > 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 ++#include ++ ++#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; ++ ++// 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::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::Leaky g_greasemonkey_api = ++ LAZY_INSTANCE_INITIALIZER; ++ ++bool ShouldInjectScripts(const UserScript::FileList& scripts, ++ const std::set& injected_files) { ++ for (const std::unique_ptr& 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& 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& 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 UserScriptInjector::GetCssOrigin() const { ++ return absl::nullopt; ++} ++ ++const absl::optional UserScriptInjector::GetInjectionKey() const { ++ return absl::nullopt; ++} ++ ++bool UserScriptInjector::ShouldInjectJs( ++ UserScript::RunLocation run_location, ++ const std::set& 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& injected_stylesheets) const { ++ return script_ && run_location == UserScript::DOCUMENT_START && ++ !script_->css_scripts().empty() && ++ ShouldInjectScripts(script_->css_scripts(), injected_stylesheets); ++} ++ ++std::vector UserScriptInjector::GetJsSources( ++ UserScript::RunLocation run_location, ++ std::set* executing_scripts, ++ size_t* num_injected_js_scripts) const { ++ DCHECK(script_); ++ std::vector 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& 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 UserScriptInjector::GetCssSources( ++ UserScript::RunLocation run_location, ++ std::set* injected_stylesheets, ++ size_t* num_injected_stylesheets) const { ++ DCHECK(script_); ++ DCHECK_EQ(UserScript::DOCUMENT_START, run_location); ++ ++ std::vector sources; ++ ++ const UserScript::FileList& css_scripts = script_->css_scripts(); ++ sources.reserve(css_scripts.size()); ++ for (const std::unique_ptr& 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 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 ++#include ++ ++#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& changed_hosts, ++ const UserScriptList& scripts) override; ++ ++ // ScriptInjector implementation. ++ UserScript::InjectionType script_type() const override; ++ bool IsUserGesture() const override; ++ absl::optional GetCssOrigin() const override; ++ const absl::optional GetInjectionKey() const override; ++ bool ExpectsResults() const override; ++ bool ShouldInjectJs( ++ UserScript::RunLocation run_location, ++ const std::set& executing_scripts) const override; ++ bool ShouldInjectCss( ++ UserScript::RunLocation run_location, ++ const std::set& injected_stylesheets) const override; ++ std::vector GetJsSources( ++ UserScript::RunLocation run_location, ++ std::set* executing_scripts, ++ size_t* num_injected_js_scripts) const override; ++ std::vector GetCssSources( ++ UserScript::RunLocation run_location, ++ std::set* injected_stylesheets, ++ size_t* num_injected_stylesheets) const override; ++ void OnInjectionComplete(std::unique_ptr 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 ++ 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 ++ ++#include ++ ++#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>* 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& script : scripts_) { ++ std::unique_ptr 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& 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(); ++ 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(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 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 script) { ++ scripts_.push_back(std::move(script)); ++} ++ ++std::unique_ptr 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 injection; ++ std::unique_ptr 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 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 ++#include ++#include ++#include ++#include ++ ++#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& 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 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>* 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& 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 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 script_sources_; ++ ++ // The associated observers. ++ base::ObserverList::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>* 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* effective_hosts = &changed_hosts; ++ std::set all_hosts; ++ const std::set* 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 ++#include ++#include ++#include ++ ++#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& 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>* 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::Unchecked observers_; ++ ++ // Handle the UpdateUserScripts extension message. ++ void OnUpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory); ++ //, const HostID& host_id, ++ //const std::set& 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 ++ ++#include ++#include ++#include ++ ++#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& 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 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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& changed_hosts) override; ++ void OnRenderFrameCreated(content::RenderFrame* render_frame); ++ ++ private: ++ std::unique_ptr user_script_set_manager_; ++ ++ std::unique_ptr script_injection_manager_; ++ ++ base::ScopedObservation ++ 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 ++#include ++ ++#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::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(); ++ ++ 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 ++#include ++ ++#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 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 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 @@ ++ ++ ++ ++ ++ ++ User Scripts ++ ++ ++ ++ Activate User Scripts ++ ++ ++ ++ ON ++ ++ ++ OFF ++ ++ ++ ++ Add script ++ ++ ++ Experimental support for Greasemonkey-style user scripts. ++ ++ ++ ++ Version: ++ ++ ++ File: ++ ++ ++ Url: ++ ++ ++ ++ View source ++ ++ ++ ++ Only install user scripts that you have verified are secure, user scripts can steal your credentials and data. ++ ++Do you want to install %s? ++ ++ ++ Yes ++ ++ ++ No ++ ++ ++ +\ 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 + diff --git a/build/patches/Revert-the-removal-of-an-option-to-block-autoplay.patch b/build/patches/Revert-the-removal-of-an-option-to-block-autoplay.patch index 2eeca34f..c85393a6 100644 --- a/build/patches/Revert-the-removal-of-an-option-to-block-autoplay.patch +++ b/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 @@ @@ -181,7 +181,7 @@ index 31260c27852a3..7bae9b4f3afcc 100644 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 mixed_content_rules; array 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 StructTraitsautoplay_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 diff --git a/build/patches/Site-setting-for-images.patch b/build/patches/Site-setting-for-images.patch index dde9bc47..76d75788 100644 --- a/build/patches/Site-setting-for-images.patch +++ b/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 + - + JavaScript -@@ -185,6 +188,9 @@ +@@ -188,6 +191,9 @@ Site %1$sgoogle.com added @@ -379,7 +379,7 @@ index 35c484f1f74b1..99fe74e63ba17 100644 Site URL 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, diff --git a/build/patches/Timezone-customization.patch b/build/patches/Timezone-customization.patch new file mode 100644 index 00000000..9f5a3187 --- /dev/null +++ b/build/patches/Timezone-customization.patch @@ -0,0 +1,1358 @@ +From: uazo +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 @@ + ++ ++ + + + ++ ++ ++ ++ + + + ++ ++ + + + 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 . ++*/ ++ ++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 timezones = new ArrayList<>(Arrays.asList(TimeZone.getAvailableIDs())); ++ ArrayAdapter adapter = new ArrayAdapter(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& jbrowser_context_handle, ++ const JavaParamRef& timezone) { ++ std::string new_timezone = ConvertJavaStringToUTF8(env, timezone); ++ GetHostContentSettingsMap(jbrowser_context_handle)->SetTimezoneOverrideValue(new_timezone); ++} ++ ++static base::android::ScopedJavaLocalRef JNI_WebsitePreferenceBridge_GetCustomTimezone( ++ JNIEnv* env, ++ const JavaParamRef& 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 @@ + + Dark theme for sites + ++ ++ Timezone override ++ + + Desktop site + +@@ -480,6 +483,38 @@ + Block sites from playing protected content + + ++ ++ ++ Override timezone with a custom or random one, or use the system timezone ++ ++ ++ None (use system timezone) ++ ++ ++ Random ++ ++ ++ System timezone ++ ++ ++ Custom timezone ++ ++ ++ Specify a custom timezone (default UTC) ++ ++ ++ Random (for each page) ++ ++ ++ Choose Timezone... ++ ++ ++ Choose Timezone ++ ++ ++ Select ++ ++ + + + +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; + 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 popup_redirect_rules; + array mixed_content_rules; + array auto_dark_content_rules; ++ array 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 StructTraitsscript_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& 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 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_; + + mojo::AssociatedReceiverSet 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 +