LeOSium_webview/LeOS/patches/Experimental-user-scripts-s...

10511 lines
383 KiB
Diff
Raw Normal View History

2023-11-18 11:46:19 +01:00
From: uazo <uazo@users.noreply.github.com>
Date: Fri, 13 Aug 2021 17:10:47 +0000
Subject: Experimental user scripts support
Activate the user scripts functionality for Android,
as it is available in the Desktop version.
It is possible to add user scripts in two ways: by
selecting files from the picker in the settings or
downloading the scripts and opening them from downloads
(only if such files end with '.user.js').
New imported scripts are disabled by default: they
can be activated via the UI.
Parsed user script headers are: name, version, description,
include, exclude, match, exclude_match (only http and
https), run-at (document-start, document-end,
document-idle), homepage, url_source
The UI also allows you to see the source of the script.
See also: components/user_scripts/README.md
Requires patch: Adds-support-for-writing-URIs.patch
Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
---
chrome/android/BUILD.gn | 7 +-
.../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 | 7 +
chrome/browser/about_flags.cc | 7 +
chrome/browser/flag_descriptions.cc | 5 +
chrome/browser/flag_descriptions.h | 3 +
chrome/browser/prefs/browser_prefs.cc | 4 +
chrome/browser/profiles/BUILD.gn | 3 +
...hrome_browser_main_extra_parts_profiles.cc | 7 +
chrome/browser/profiles/profile_manager.cc | 13 +
chrome/browser/profiles/renderer_updater.cc | 19 +-
chrome/browser/profiles/renderer_updater.h | 3 +
.../webui/chrome_web_ui_controller_factory.cc | 5 +
chrome/chrome_paks.gni | 2 +
chrome/common/renderer_configuration.mojom | 1 +
chrome/renderer/BUILD.gn | 6 +
.../chrome_content_renderer_client.cc | 55 ++
.../renderer/chrome_render_thread_observer.cc | 5 +
components/components_strings.grd | 1 +
components/user_scripts/README.md | 150 ++++
components/user_scripts/android/BUILD.gn | 84 ++
.../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 | 87 ++
.../user_scripts/FragmentWindowAndroid.java | 90 ++
.../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 | 212 +++++
.../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 | 77 ++
.../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 | 8 +
.../user-script-ui/user-scripts-ui.html | 14 +
.../user-script-ui/user-scripts-ui.js | 9 +
.../browser/ui/user_scripts_ui.cc | 146 ++++
.../user_scripts/browser/ui/user_scripts_ui.h | 37 +
.../browser/user_script_loader.cc | 715 ++++++++++++++++
.../user_scripts/browser/user_script_loader.h | 168 ++++
.../browser/user_script_pref_info.cc | 34 +
.../browser/user_script_pref_info.h | 72 ++
.../user_scripts/browser/user_script_prefs.cc | 275 ++++++
.../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 | 48 ++
components/user_scripts/common/constants.h | 15 +
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 | 70 ++
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 | 335 ++++++++
.../user_scripts/common/url_pattern_set.h | 160 ++++
components/user_scripts/common/user_script.cc | 329 +++++++
components/user_scripts/common/user_script.h | 403 +++++++++
.../common/user_scripts_features.cc | 32 +
.../common/user_scripts_features.h | 36 +
components/user_scripts/common/view_type.cc | 39 +
components/user_scripts/common/view_type.h | 48 ++
components/user_scripts/renderer/BUILD.gn | 65 ++
.../renderer/extension_frame_helper.cc | 95 +++
.../renderer/extension_frame_helper.h | 90 ++
.../user_scripts/renderer/injection_host.cc | 12 +
.../user_scripts/renderer/injection_host.h | 41 +
.../renderer/resources/greasemonkey_api.js | 82 ++
.../user_scripts_renderer_resources.grd | 14 +
.../user_scripts/renderer/script_context.cc | 191 +++++
.../user_scripts/renderer/script_context.h | 67 ++
.../user_scripts/renderer/script_injection.cc | 293 +++++++
.../user_scripts/renderer/script_injection.h | 155 ++++
.../renderer/script_injection_manager.cc | 415 +++++++++
.../renderer/script_injection_manager.h | 100 +++
.../user_scripts/renderer/script_injector.h | 96 +++
.../user_scripts/renderer/scripts_run_info.cc | 31 +
.../user_scripts/renderer/scripts_run_info.h | 69 ++
.../renderer/user_script_injector.cc | 227 +++++
.../renderer/user_script_injector.h | 86 ++
.../user_scripts/renderer/user_script_set.cc | 266 ++++++
.../user_scripts/renderer/user_script_set.h | 101 +++
.../renderer/user_script_set_manager.cc | 77 ++
.../renderer/user_script_set_manager.h | 61 ++
.../renderer/user_scripts_dispatcher.cc | 36 +
.../renderer/user_scripts_dispatcher.h | 49 ++
.../renderer/user_scripts_renderer_client.cc | 108 +++
.../renderer/user_scripts_renderer_client.h | 38 +
.../renderer/web_ui_injection_host.cc | 40 +
.../renderer/web_ui_injection_host.h | 27 +
.../strings/userscripts_strings.grdp | 54 ++
tools/gritsettings/resource_ids.spec | 6 +
108 files changed, 9491 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_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
@@ -179,7 +179,11 @@ if (current_toolchain == default_toolchain) {
sources = chrome_java_resources
sources += [ "//chrome/android/java/res_app/layout/main.xml" ]
- deps = [
+ # 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" ]
+
+ deps += [
":chrome_base_module_resources",
":ui_locale_string_resources",
"//chrome/android/webapk/libs/common:splash_resources",
@@ -582,6 +586,7 @@ if (current_toolchain == default_toolchain) {
"//components/ukm/android:java",
"//components/url_formatter/android:url_formatter_java",
"//components/user_prefs/android:java",
+ "//components/user_scripts/android:java",
"//components/variations:variations_java",
"//components/variations/android:variations_java",
"//components/version_info/android:version_constants_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
@@ -115,6 +115,11 @@
android:key="useragent_settings"
android:order="20"
android:title="@string/prefs_useragent_settings"/>
+ <Preference
+ android:fragment="org.chromium.components.user_scripts.UserScriptsPreferences"
+ android:key="userscripts_settings"
+ android:order="20"
+ android:title="@string/prefs_userscripts_settings"/>
<Preference
android:fragment="org.chromium.chrome.browser.language.settings.LanguageSettings"
android:key="languages"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -75,6 +75,7 @@ import org.chromium.ui.UiUtils;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.widget.Toast;
import org.chromium.url.GURL;
+import org.chromium.chrome.browser.user_scripts.UserScriptsUtils;
import java.io.File;
@@ -456,6 +457,11 @@ public class DownloadUtils {
public static boolean openFile(String filePath, String mimeType, String downloadGuid,
OTRProfileID otrProfileID, String originalUrl, String referrer,
@DownloadOpenSource int source, Context context) {
+ if (UserScriptsUtils.getInstance().openFile(filePath, mimeType, downloadGuid,
+ originalUrl, referrer,
+ getUriForItem(filePath))) {
+ return true;
+ }
DownloadMetrics.recordDownloadOpen(source, mimeType);
DownloadManagerService service = DownloadManagerService.getDownloadManagerService();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -124,6 +124,8 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import org.chromium.chrome.browser.user_scripts.UserScriptsUtils;
+
/**
* Handles the initialization dependences of the browser process. This is meant to handle the
* initialization that is not tied to any particular Activity, and the logic that should only be
@@ -331,6 +333,7 @@ public class ProcessInitializationHandler {
DefaultBrowserInfo.initBrowserFetcher();
+ UserScriptsUtils.Initialize();
AfterStartupTaskUtils.setStartupComplete();
PartnerBrowserCustomizations.getInstance().setOnInitializeAsyncFinished(
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -23,6 +23,7 @@ import("//chrome/common/features.gni")
import("//components/feed/features.gni")
import("//components/offline_pages/buildflags/features.gni")
import("//device/vr/buildflags/buildflags.gni")
+import("//components/user_scripts/android/java_sources.gni")
# Only used for testing, should not be shipped to end users.
if (enable_offline_pages_harness) {
@@ -60,3 +61,5 @@ if (enable_cardboard) {
"java/src/org/chromium/chrome/browser/vr/VrCompositorDelegateProviderImpl.java",
]
}
+
+chrome_java_sources += userscripts_java_sources
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3684,6 +3684,13 @@ static_library("browser") {
]
deps += [ "//chrome/android/modules/dev_ui/provider:native" ]
}
+ if (is_android) {
+ deps += [
+ "//components/user_scripts/common",
+ "//components/user_scripts/browser",
+ "//components/user_scripts/android",
+ ]
+ }
} else {
#!is_android
sources += [
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -165,6 +165,7 @@
#include "components/ui_devtools/switches.h"
#include "components/variations/service/google_groups_updater_service.h"
#include "components/variations/variations_switches.h"
+#include "components/user_scripts/common/user_scripts_features.h"
#include "components/version_info/version_info.h"
#include "components/viz/common/features.h"
#include "components/viz/common/switches.h"
@@ -8622,6 +8623,12 @@ const FeatureEntry kFeatureEntries[] = {
FEATURE_VALUE_TYPE(ash::features::kClipboardHistoryWebContentsPaste)},
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_ANDROID)
+ {"enable-userscripts-log", flag_descriptions::kEnableLoggingUserScriptsName,
+ flag_descriptions::kEnableLoggingUserScriptsDescription, kOsDesktop | kOsAndroid,
+ FEATURE_VALUE_TYPE(user_scripts::features::kEnableLoggingUserScripts)},
+#endif
+
#if BUILDFLAG(IS_WIN)
{"enable-media-foundation-video-capture",
flag_descriptions::kEnableMediaFoundationVideoCaptureName,
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
@@ -7598,6 +7598,11 @@ const char kUseOutOfProcessVideoDecodingDescription[] =
"enabled in ash-chrome).";
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+const char kEnableLoggingUserScriptsName[] = "Enable logging user scripts component";
+const char kEnableLoggingUserScriptsDescription[] =
+ "Enables logging for troubleshooting feature. "
+ "Enabling logs may make browsing slower.";
+
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_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
@@ -4358,6 +4358,9 @@ extern const char kQuickCommandsDescription[];
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
// defined (OS_FUCHSIA)
+extern const char kEnableLoggingUserScriptsName[];
+extern const char kEnableLoggingUserScriptsDescription[];
+
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_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
@@ -270,6 +270,7 @@
#include "components/permissions/contexts/geolocation_permission_context_android.h"
#include "components/query_tiles/tile_service_prefs.h"
#include "components/webapps/browser/android/install_prompt_prefs.h"
+#include "components/user_scripts/browser/user_script_prefs.h"
#else // BUILDFLAG(IS_ANDROID)
#include "chrome/browser/cart/cart_service.h"
#include "chrome/browser/companion/core/promo_handler.h"
@@ -1716,6 +1717,9 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
translate::TranslatePrefs::RegisterProfilePrefs(registry);
omnibox::RegisterProfilePrefs(registry);
ZeroSuggestProvider::RegisterProfilePrefs(registry);
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsPrefs::RegisterProfilePrefs(registry);
+#endif
#if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
promos_utils::RegisterProfilePrefs(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
@@ -60,6 +60,9 @@ source_set("profile") {
"//content/public/browser",
"//extensions/buildflags",
]
+ if (is_android) {
+ deps += [ "//components/user_scripts/browser" ]
+ }
if (enable_extensions) {
deps += [ "//extensions/browser" ]
}
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
@@ -494,6 +494,10 @@
#include "chrome/browser/offline_pages/request_coordinator_factory.h"
#endif
+#if BUILDFLAG(IS_ANDROID)
+#include "components/user_scripts/browser/userscripts_browser_client.h"
+#endif
+
namespace chrome {
void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
@@ -1206,6 +1210,9 @@ void ChromeBrowserMainExtraPartsProfiles::
#endif
WebDataServiceFactory::GetInstance();
webrtc_event_logging::WebRtcEventLogManagerKeyedServiceFactory::GetInstance();
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsBrowserClient::GetInstance();
+#endif
}
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
@@ -109,6 +109,10 @@
#include "extensions/common/manifest.h"
#endif
+#if BUILDFLAG(IS_ANDROID)
+#include "components/user_scripts/browser/userscripts_browser_client.h"
+#endif
+
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
#include "chrome/browser/sessions/app_session_service_factory.h"
#include "chrome/browser/sessions/session_service_factory.h"
@@ -1534,6 +1538,15 @@ void ProfileManager::DoFinalInitForServices(Profile* profile,
#endif
#endif
+
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsBrowserClient* userscript_client =
+ user_scripts::UserScriptsBrowserClient::GetInstance();
+ if (userscript_client) {
+ userscript_client->SetProfile(profile);
+ }
+#endif
+
#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
@@ -39,6 +39,8 @@
#include "chrome/browser/signin/bound_session_credentials/bound_session_cookie_refresh_service_factory.h"
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
+#include "components/user_scripts/browser/user_script_prefs.h"
+
RendererUpdater::RendererUpdater(Profile* profile)
: profile_(profile),
is_off_the_record_(profile_->IsOffTheRecord()),
@@ -77,6 +79,9 @@ RendererUpdater::RendererUpdater(Profile* profile)
force_youtube_restrict_.Init(policy::policy_prefs::kForceYouTubeRestrict,
pref_service);
allowed_domains_for_apps_.Init(prefs::kAllowedDomainsForApps, pref_service);
+#if BUILDFLAG(IS_ANDROID)
+ activate_userscripts_.Init(user_scripts::prefs::kUserScriptsEnabled, pref_service);
+#endif
pref_change_registrar_.Init(pref_service);
pref_change_registrar_.Add(
@@ -91,6 +96,12 @@ RendererUpdater::RendererUpdater(Profile* profile)
prefs::kAllowedDomainsForApps,
base::BindRepeating(&RendererUpdater::UpdateAllRenderers,
base::Unretained(this)));
+#if BUILDFLAG(IS_ANDROID)
+ pref_change_registrar_.Add(
+ user_scripts::prefs::kUserScriptsEnabled,
+ base::BindRepeating(&RendererUpdater::UpdateAllRenderers,
+ base::Unretained(this)));
+#endif
}
RendererUpdater::~RendererUpdater() {
@@ -244,5 +255,11 @@ chrome::mojom::DynamicParamsPtr RendererUpdater::CreateRendererDynamicParams()
GetBoundSessionThrottlerParams(),
#endif
force_google_safesearch_.GetValue(), force_youtube_restrict_.GetValue(),
- allowed_domains_for_apps_.GetValue());
+ allowed_domains_for_apps_.GetValue(),
+#if BUILDFLAG(IS_ANDROID)
+ activate_userscripts_.GetValue()
+#else
+ false
+#endif
+ );
}
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
@@ -109,6 +109,9 @@ class RendererUpdater : public KeyedService,
// Prefs that we sync to the renderers.
BooleanPrefMember force_google_safesearch_;
+#if BUILDFLAG(IS_ANDROID)
+ BooleanPrefMember activate_userscripts_;
+#endif
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
@@ -107,6 +107,7 @@
#include "components/signin/public/base/signin_buildflags.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/supervised_user/core/common/buildflags.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"
@@ -535,6 +536,10 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui,
return &NewWebUI<UserActionsUI>;
if (url.host_piece() == chrome::kChromeUIVersionHost)
return &NewWebUI<VersionUI>;
+#if BUILDFLAG(IS_ANDROID)
+ if (url.host_piece() == user_scripts::kChromeUIUserScriptsHost)
+ return &NewWebUI<user_scripts::UserScriptsUI>;
+#endif
#if !BUILDFLAG(IS_ANDROID)
#if !BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -121,6 +121,7 @@ template("chrome_extra_paks") {
"$root_gen_dir/net/net_resources.pak",
"$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
"$root_gen_dir/third_party/blink/public/resources/inspector_overlay_resources.pak",
+ "$root_gen_dir/chrome/userscripts_browser_resources.pak",
"$root_gen_dir/ui/resources/webui_resources.pak",
]
deps = [
@@ -138,6 +139,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
@@ -29,6 +29,7 @@ struct DynamicParams {
bool force_safe_search = true;
int32 youtube_restrict = 0;
string allowed_domains_for_apps;
+ bool allow_userscript = false;
};
// Allows the renderer to notify the browser process that requests in renderer
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -234,6 +234,12 @@ static_library("renderer") {
"//v8",
]
+ if (is_android) {
+ deps += [
+ "//components/user_scripts/renderer",
+ ]
+ }
+
data_deps = [ "//tools/v8_context_snapshot" ]
configs += [ "//build/config/compiler:wexit_time_destructors" ]
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
@@ -256,6 +256,11 @@
#include "chrome/renderer/cco/multiline_detector.h"
#endif
+#if BUILDFLAG(IS_ANDROID)
+#include "components/user_scripts/common/user_scripts_features.h"
+#include "components/user_scripts/renderer/user_scripts_renderer_client.h"
+#endif
+
using autofill::AutofillAgent;
using autofill::PasswordAutofillAgent;
using autofill::PasswordGenerationAgent;
@@ -432,6 +437,14 @@ void ChromeContentRendererClient::RenderThreadStarted() {
WebString::FromASCII(extensions::kExtensionScheme));
#endif
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsRendererClient* userscript_client =
+ user_scripts::UserScriptsRendererClient::GetInstance();
+ if (userscript_client) {
+ userscript_client->RenderThreadStarted(GetChromeObserver());
+ }
+#endif
+
#if BUILDFLAG(ENABLE_SPELLCHECK)
if (!spellcheck_)
InitSpellCheck();
@@ -605,6 +618,15 @@ void ChromeContentRendererClient::RenderFrameCreated(
render_frame, registry);
#endif
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsRendererClient* userscript_client =
+ user_scripts::UserScriptsRendererClient::GetInstance();
+ if (userscript_client) {
+ userscript_client->RenderFrameCreated(
+ render_frame, registry);
+ }
+#endif
+
#if BUILDFLAG(ENABLE_PPAPI)
new PepperHelper(render_frame);
#endif
@@ -1607,6 +1629,17 @@ void ChromeContentRendererClient::RunScriptsAtDocumentStart(
render_frame);
// |render_frame| might be dead by now.
#endif
+#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ 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);
+ }
+#endif
}
void ChromeContentRendererClient::RunScriptsAtDocumentEnd(
@@ -1616,6 +1649,17 @@ void ChromeContentRendererClient::RunScriptsAtDocumentEnd(
render_frame);
// |render_frame| might be dead by now.
#endif
+#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ 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);
+ }
+#endif
}
void ChromeContentRendererClient::RunScriptsAtDocumentIdle(
@@ -1625,6 +1669,17 @@ void ChromeContentRendererClient::RunScriptsAtDocumentIdle(
render_frame);
// |render_frame| might be dead by now.
#endif
+#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+ 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);
+ }
+#endif
}
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
@@ -54,6 +54,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(IS_CHROMEOS_ASH)
#include "chrome/renderer/ash_merge_session_loader_throttle.h"
#endif
@@ -217,6 +219,9 @@ void ChromeRenderThreadObserver::SetConfiguration(
chrome::mojom::DynamicParamsPtr params) {
base::AutoLock lock(dynamic_params_lock_);
dynamic_params_ = std::move(params);
+#if BUILDFLAG(IS_ANDROID)
+ user_scripts::UserScriptsRendererClient::GetInstance()->ConfigurationUpdated();
+#endif
}
void ChromeRenderThreadObserver::OnRendererConfigurationAssociatedRequest(
diff --git a/components/components_strings.grd b/components/components_strings.grd
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -336,6 +336,7 @@
<part file="user_education_strings.grdp" />
<part file="version_ui_strings.grdp" />
<part file="webapps_strings.grdp" />
+ <part file="user_scripts/strings/userscripts_strings.grdp" />
<if expr="use_blink">
<part file="history_clusters_strings.grdp" />
diff --git a/components/user_scripts/README.md b/components/user_scripts/README.md
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/README.md
@@ -0,0 +1,150 @@
+# Userscripts support for Bromite
+
+UserScript support is under user setting currently disabled by default: when disabled, no code that can impact navigation safety is active.
+
+Activation allows the use of userscripts in Bromite. It is possible to add them in two ways:
+- by selecting files from the file picker in the settings
+- downloading the scripts and opening it from downloads (only if ends with .user.js)
+The new imported scripts are disabled by default: they can be activated via the menu visible on the ui.
+
+Userscript support is currently the one provided by the desktop version. The enabled headers are:
+
+- `@name`
+- `@version`
+- `@description`
+- `@url` or `@homepage`
+- `@include`, `@exclude`, `@match`, `@exclude_match` for the url pattern (only http e https)
+- `@run-at`
+ - `document-start`
+ Start the script after the documentElement is created, but before anything else happens
+ - `document-end`
+ Start the script after the entire document is parsed. Same as DOMContentLoaded
+ - `document-idle`
+ Start the script sometime after DOMContentLoaded, as soon as the document is "idle". Currently this uses the simple heuristic of: min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no particular injection point is guaranteed
+
+The url-patterns are so defined:
+```
+// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>'
+// <scheme> := '*' | 'http' | 'https'
+// <host> := '*' | <IPv4 address> | [<IPv6 address>] |
+// '*.' <anychar except '/' and '*'>+
+// <port> := [':' ('*' | <port number between 0 and 65535>)]
+// <path> := '/' <any chars>
+//
+// * Host is not used when the scheme is 'file'.
+// * The path can have embedded '*' characters which act as glob wildcards.
+// * '<all_urls>' is a special pattern that matches any valid URL that contains
+// a valid scheme (as specified by valid_schemes_).
+// * The '*' scheme pattern excludes file URLs.
+//
+// Examples of valid patterns:
+// - http://*/*
+// - http://*/foo*
+// - https://*.google.com/foo*bar
+// - file://monkey*
+// - http://127.0.0.1/*
+// - http://[2607:f8b0:4005:805::200e]/*
+//
+// Examples of invalid patterns:
+// - http://* -- path not specified
+// - http://*foo/bar -- * not allowed as substring of host component
+// - http://foo.*.bar/baz -- * must be first component
+// - http:/bar -- scheme separator not found
+// - foo://* -- invalid scheme
+// - chrome:// -- we don't support chrome internal URLs
+```
+
+---
+## **Beware of the scripts you enter: they can be a source of security problems, you are injecting code into your navigation**.
+---
+## Technical aspects
+
+`user_scripts/common` and `user_scripts/renderer` is the closest to the current chromium code: few changes there, mostly eliminated the superfluous extension related code.
+
+In `user_scripts/browser` you find the actual management (in the browser process) and in `android` basically the settings ui.
+
+At startup it tries to read all files in the `userscripts folder` in `/data/user/0/org.bromite.bromite/app_chrome/userscripts`: this could be a critical process because a crash would prevent the browser from opening, and that's why there is a crash counter that automatically disables the feature after three attempts if it encounters a problem during startup.
+
+The java ui allows userscript management: addition, deletion and activation/deactivation. Any errors while reading the scripts are presented to the user: scripts with errors cannot be activated. There is also the visualization of the script source and the open of its homepage (if foreseen) in incognito browsing.
+
+There is also support for an on-line help at https://github.com/bromite/bromite/wiki/UserScripts.
+
+
+Entry points are `components/user_scripts/browser/userscripts_browser_client.cc` and `components/user_scripts/renderer/user_scripts_renderer_client.cc`: the two attach to the browser and the renderer process.
+
+for userscripts_browser_client.cc
+- `chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc`
+builds the browser side. also gpu process passes here, but the call is avoided.
+
+- `chrome/browser/profiles/profile_manager.cc`
+set the profile
+
+for renderer/user_scripts_renderer_client.cc
+- `chrome/renderer/chrome_content_renderer_client.cc`
+at the renderer side
+
+the two sides have a life of their own and can communicate only via ipc, the renderer does not have access to the disk while the browser does only via its own task runner file (`components/user_scripts/browser/file_task_runner.cc`).
+
+## BROWSER PROCESS
+Once the profile is set, it istance the `components/user_scripts/browser/user_script_loader.cc` and starts it.
+This loads all the files in the folder into the runner file and interprets them. Control then passes to
+`components/user_scripts/browser/user_script_prefs.cc` which verifies through the default profile what the user wants active.
+At that point it passes through IPC to the renderer only the list of active scripts.
+
+## RENDERER PROCESS
+Each time a frame is created, the script pattern is checked and it is injected into the three stages (START, IDLE, END).
+The logic is all in `components/user_scripts/renderer/user_script_set.cc`.
+
+## Simple example
+Here you find a working example that eliminates the google popup, useful in always incognito:
+```
+// ==UserScript==
+// @name Remove Google Consent
+// @namespace google
+// @version 0.0.1
+// @description Autohide Accepts Cookies
+// @author uazo
+// @match https://*.google.com/search?*
+// @grant none
+// @run-at document-start
+// ==/UserScript==
+
+(function() {
+ 'use strict';
+
+ var prepareStyleSheet = function() {
+ var style = document.createElement('style');
+ //style.setAttribute('media', 'screen');
+ style.appendChild(document.createTextNode(''));
+ document.head.appendChild(style);
+ style.sheet.insertRule('body { overflow:scroll !important;position:unset !important }');
+ };
+
+ var hideConsent = function() {
+ document.getElementById("lb").style.display = "none";
+ };
+
+ var checkElementThenRun = function(selector, func) {
+ var el = document.querySelector(selector);
+ if ( el == null ) {
+ if (window.requestAnimationFrame != undefined) {
+ window.requestAnimationFrame(function(){ checkElementThenRun(selector, func)});
+ } else {
+ document.addEventListener('readystatechange', function(e) {
+ if (document.readyState == 'complete') {
+ func();
+ }
+ });
+ }
+ } else {
+ func();
+ }
+ }
+
+ document.cookie = 'CONSENT=YES+IT.it+V13+BX;domain=.google.com';
+ checkElementThenRun('head', prepareStyleSheet);
+ checkElementThenRun('#lb', hideConsent);
+})();
+```
+
+See also: https://github.com/bromite/bromite/pull/857
diff --git a/components/user_scripts/android/BUILD.gn b/components/user_scripts/android/BUILD.gn
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/android/BUILD.gn
@@ -0,0 +1,84 @@
+# This file is part of Bromite.
+
+# Bromite is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# Bromite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+
+import("//build/config/android/rules.gni")
+import("//third_party/jni_zero/jni_zero.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",
+ "//build/android:build_java",
+ "//third_party/androidx:androidx_fragment_fragment_java",
+ "//third_party/androidx:androidx_recyclerview_recyclerview_java",
+ "//third_party/androidx:androidx_annotation_annotation_java",
+ "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
+ "//third_party/androidx:androidx_preference_preference_java",
+ "//third_party/androidx:androidx_core_core_java",
+ "//ui/android:ui_java",
+ ]
+ srcjar_deps = [ ":user_scripts_jni_headers" ]
+ resources_package = "org.chromium.components.user_scripts"
+}
+
+source_set("android") {
+ sources = [
+ "user_scripts_bridge.cc",
+ "user_scripts_bridge.h",
+ ]
+ deps = [
+ ":user_scripts_jni_headers",
+ "//base",
+ "//components/user_scripts/browser",
+ "//components/permissions",
+ "//content/public/browser",
+ ]
+}
diff --git a/components/user_scripts/android/java/res/layout/accept_script_item.xml b/components/user_scripts/android/java/res/layout/accept_script_item.xml
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/res/layout/accept_script_item.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical" >
+
+ <LinearLayout
+ android:layout_marginStart="0dp"
+ android:paddingStart="@dimen/draggable_list_item_padding"
+ android:paddingEnd="@dimen/draggable_list_item_padding"
+ style="@style/ListItemContainer">
+
+ <Switch
+ android:id="@+id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="15dp"
+ android:focusable="false"
+ android:background="@null" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PreferenceTitle" />
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PreferenceSummary" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="5dp"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="5dp"
+ style="@style/PreferenceSummary"
+ android:text="@string/scripts_item_version"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/version"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PreferenceSummary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="5dp"
+ style="@style/PreferenceSummary"
+ android:text="@string/scripts_item_filename"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/file"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/PreferenceSummary" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/url_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="5dp"
+ style="@style/PreferenceSummary"
+ android:text="@string/scripts_item_url"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/url"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:focusable="true"
+ android:linksClickable="true" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <org.chromium.components.browser_ui.widget.listmenu.ListMenuButton
+ android:id="@+id/more"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/default_list_row_padding"
+ android:paddingEnd="@dimen/default_list_row_padding"
+ android:background="@null"
+ android:src="@drawable/ic_more_vert_24dp"
+ app:menuMaxWidth="@dimen/pref_scripts_item_popup_width"
+ app:tint="@color/default_icon_color_tint_list"
+ tools:ignore="ContentDescription" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/error_layout"
+ android:layout_marginStart="0dp"
+ android:paddingStart="@dimen/draggable_list_item_padding"
+ android:paddingEnd="2dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/ListItemContainer">
+
+ <org.chromium.ui.widget.ChromeImageView
+ android:id="@+id/icon"
+ android:layout_width="40dp"
+ android:layout_height="wrap_content"
+ android:paddingEnd="15dp"
+ android:adjustViewBounds="true"
+ android:importantForAccessibility="no"
+ app:srcCompat="@drawable/ic_error_outline_red_24dp"/>
+
+ <TextView
+ android:id="@+id/error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="#F00"
+ style="@style/PreferenceSummary" />
+
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/components/user_scripts/android/java/res/layout/accept_script_list.xml b/components/user_scripts/android/java/res/layout/accept_script_list.xml
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/res/layout/accept_script_list.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<androidx.recyclerview.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/script_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/components/user_scripts/android/java/res/layout/scripts_preference.xml b/components/user_scripts/android/java/res/layout/scripts_preference.xml
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/res/layout/scripts_preference.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/accept_scripts_list_container"
+ style="@style/PreferenceLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
+ android:padding="0dp"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/draggable_list_item_padding"
+ android:text="@string/scripts_list_description" />
+
+ <FrameLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/add_script"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:gravity="center_vertical"
+ android:padding="@dimen/draggable_list_item_padding"
+ android:paddingStart="@dimen/pref_scripts_add_button_padding"
+ android:drawablePadding="@dimen/pref_scripts_add_button_padding"
+ android:text="@string/add_script"
+ style="@style/PreferenceTitle" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/components/user_scripts/android/java/res/values/dimens.xml b/components/user_scripts/android/java/res/values/dimens.xml
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/android/java/res/values/dimens.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+
+ <dimen name="pref_scripts_add_button_padding">24dp</dimen>
+ <dimen name="pref_scripts_item_popup_width">260dp</dimen>
+
+</resources>
diff --git a/components/user_scripts/android/java/res/xml/userscripts_preferences.xml b/components/user_scripts/android/java/res/xml/userscripts_preferences.xml
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/res/xml/userscripts_preferences.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+ android:key="enabled_switch"
+ android:title="@string/option_userscript_flag"
+ android:summaryOn="@string/option_userscript_flag_on"
+ android:summaryOff="@string/option_userscript_flag_off" />
+
+ <org.chromium.components.user_scripts.ScriptListPreference
+ android:key="script_list"
+ android:layout="@layout/scripts_preference"
+ android:widgetLayout="@layout/accept_script_list" />
+
+</PreferenceScreen>
diff --git a/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java b/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java
@@ -0,0 +1,87 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.chrome.browser.user_scripts;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Browser;
+import android.provider.MediaStore;
+import android.net.Uri;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ContentUriUtils;
+import org.chromium.base.IntentUtils;
+
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
+import org.chromium.components.browser_ui.settings.SettingsLauncher;
+
+import org.chromium.components.user_scripts.UserScriptsPreferences;
+import org.chromium.components.user_scripts.UserScriptsBridge;
+import org.chromium.components.user_scripts.IUserScriptsUtils;
+
+public class UserScriptsUtils implements IUserScriptsUtils
+{
+ private static UserScriptsUtils instance;
+
+ private UserScriptsUtils() {}
+
+ public static void Initialize() {
+ instance = new UserScriptsUtils();
+ UserScriptsBridge.registerUtils(instance);
+ }
+
+ public static UserScriptsUtils getInstance() {
+ if (instance == null) Initialize();
+ 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();
+
+ String visibleName = filePath;
+ if (ContentUriUtils.isContentUri(visibleName)) {
+ visibleName = ContentUriUtils.getDisplayName(contentUri, context,
+ MediaStore.MediaColumns.DISPLAY_NAME);
+ }
+
+ if (visibleName.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);
+ }
+}
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,90 @@
+// 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.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;
+
+import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate;
+
+/**
+ * Implements intent sending for a fragment based window. This should be created when
+ * onAttach() is called on the fragment, and destroyed when onDetach() is called.
+ */
+public class FragmentWindowAndroid extends WindowAndroid {
+ private Fragment mFragment;
+
+ private static class TrackerDelegateImpl implements Delegate {
+ private final Fragment mFragment;
+ // This WeakReference is purely to avoid gc churn of creating a new WeakReference in
+ // every getActivity call. It is not needed for correctness.
+ private ImmutableWeakReference<Activity> mActivityWeakRefHolder;
+
+ /**
+ * Create an instance of delegate for the given fragment that will own the
+ * IntentRequestTracker.
+ * @param fragment The fragment that owns the IntentRequestTracker.
+ */
+ private TrackerDelegateImpl(Fragment fragment) {
+ mFragment = fragment;
+ }
+
+ @Override
+ public boolean startActivityForResult(Intent intent, int requestCode) {
+ mFragment.startActivityForResult(intent, requestCode, null);
+ return true;
+ }
+
+ @Override
+ public boolean startIntentSenderForResult(IntentSender intentSender, int requestCode) {
+ try {
+ mFragment.startIntentSenderForResult(
+ intentSender, requestCode, new Intent(), 0, 0, 0, null);
+ } catch (IntentSender.SendIntentException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void finishActivity(int requestCode) {
+ Activity activity = getActivity().get();
+ if (activity == null) return;
+ activity.finishActivity(requestCode);
+ }
+
+ @Override
+ public final WeakReference<Activity> getActivity() {
+ if (mActivityWeakRefHolder == null
+ || mActivityWeakRefHolder.get() != mFragment.getActivity()) {
+ mActivityWeakRefHolder = new ImmutableWeakReference<>(mFragment.getActivity());
+ }
+ return mActivityWeakRefHolder;
+ }
+ }
+
+ FragmentWindowAndroid(Context context, Fragment fragment) {
+ super(context, IntentRequestTracker.createFromDelegate(new TrackerDelegateImpl(fragment)));
+ mFragment = fragment;
+
+ setKeyboardDelegate(new ActivityKeyboardVisibilityDelegate(getActivity()));
+ setAndroidPermissionDelegate(new ActivityAndroidPermissionDelegate(getActivity()));
+ }
+}
diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/IUserScriptsUtils.java
@@ -0,0 +1,22 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+public interface IUserScriptsUtils {
+ public void openSourceFile(String scriptKey);
+}
\ No newline at end of file
diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptInfo.java
@@ -0,0 +1,37 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+import java.time.LocalDateTime;
+
+public class ScriptInfo {
+ public String Key;
+ public String Name;
+ public String Description;
+ public String Version;
+ public String FilePath;
+ public String UrlSource;
+
+ public boolean Enabled;
+ public LocalDateTime InstallTime;
+
+ public String ParserError;
+ public boolean ForceDisabled;
+
+ public ScriptInfo() {}
+}
diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListBaseAdapter.java
@@ -0,0 +1,163 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Switch;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import org.chromium.components.browser_ui.widget.dragreorder.DragReorderableListAdapter;
+import org.chromium.components.browser_ui.widget.dragreorder.DragStateDelegate;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenuButtonDelegate;
+import org.chromium.ui.widget.ChromeImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScriptListBaseAdapter extends DragReorderableListAdapter<ScriptInfo> {
+
+ class ItemClickListener {
+ void onScriptOnOff(boolean Enabled) {}
+ void onScriptClicked() {}
+ }
+
+ static class ScriptInfoRowViewHolder extends ViewHolder {
+ private TextView mTitle;
+ private TextView mDescription;
+ private TextView mVersion;
+ private TextView mFile;
+ private TextView mUrl;
+ private TextView mError;
+ private Switch mSwitch;
+ private ChromeImageView mIcon;
+ private LinearLayout mErrorLayout;
+ private LinearLayout mUrlContainer;
+
+ private ListMenuButton mMoreButton;
+
+ private CompoundButton.OnCheckedChangeListener mOnOffListener;
+
+ ScriptInfoRowViewHolder(View view) {
+ super(view);
+
+ mSwitch = view.findViewById(R.id.switch_widget);
+ mTitle = view.findViewById(R.id.title);
+ mDescription = view.findViewById(R.id.description);
+ mVersion = view.findViewById(R.id.version);
+ mFile = view.findViewById(R.id.file);
+ mUrl = view.findViewById(R.id.url);
+ mUrlContainer = view.findViewById(R.id.url_container);
+ mError = view.findViewById(R.id.error);
+ mIcon = view.findViewById(R.id.icon);
+ mErrorLayout = view.findViewById(R.id.error_layout);
+
+ mMoreButton = view.findViewById(R.id.more);
+ }
+
+ protected void updateScriptInfo(ScriptInfo item) {
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch.setChecked(item.Enabled);
+ mSwitch.setOnCheckedChangeListener(mOnOffListener);
+
+ mSwitch.setEnabled(true);
+ if (item.ForceDisabled) {
+ mSwitch.setEnabled(false);
+ }
+
+ mTitle.setText(item.Name);
+ mDescription.setText(item.Description);
+ mVersion.setText(item.Version);
+ mFile.setText(item.Key);
+ mUrl.setText(item.UrlSource);
+ mError.setText(item.ParserError);
+
+ mUrl.setVisibility(View.VISIBLE);
+ if (item.UrlSource == null || item.UrlSource.isEmpty()) {
+ mUrlContainer.setVisibility(View.GONE);
+ }
+ mErrorLayout.setVisibility(View.VISIBLE);
+ if (item.ParserError == null || item.ParserError.isEmpty()) {
+ mErrorLayout.setVisibility(View.GONE);
+ }
+ }
+
+ void setMenuButtonDelegate(@NonNull ListMenuButtonDelegate delegate) {
+ mMoreButton.setVisibility(View.VISIBLE);
+ mMoreButton.setDelegate(delegate);
+ // Set item row end padding 0 when MenuButton is visible.
+ ViewCompat.setPaddingRelative(itemView, ViewCompat.getPaddingStart(itemView),
+ itemView.getPaddingTop(), 0, itemView.getPaddingBottom());
+ }
+
+ void setItemListener(@NonNull ItemClickListener listener) {
+ mOnOffListener = (buttonView, isChecked) -> listener.onScriptOnOff(isChecked);
+ mSwitch.setOnCheckedChangeListener(mOnOffListener);
+ itemView.setOnClickListener(view -> listener.onScriptClicked());
+ }
+ }
+
+ ScriptListBaseAdapter(Context context) {
+ super(context);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
+ View row = LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.accept_script_item, viewGroup, false);
+ return new ScriptInfoRowViewHolder(row);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder viewHolder, int i) {
+ ((ScriptInfoRowViewHolder) viewHolder).updateScriptInfo(mElements.get(i));
+ }
+
+ void setDisplayedScriptInfo(List<ScriptInfo> values) {
+ mElements = new ArrayList<>(values);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ protected void setOrder(List<ScriptInfo> order) {
+ }
+
+ @Override
+ protected boolean isActivelyDraggable(ViewHolder viewHolder) {
+ return isPassivelyDraggable(viewHolder);
+ }
+
+ @Override
+ protected boolean isPassivelyDraggable(ViewHolder viewHolder) {
+ return viewHolder instanceof ScriptInfoRowViewHolder;
+ }
+}
diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/ScriptListPreference.java
@@ -0,0 +1,171 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+import static org.chromium.components.browser_ui.widget.listmenu.BasicListMenu.buildMenuListItem;
+import static org.chromium.components.browser_ui.widget.listmenu.BasicListMenu.buildMenuListItemWithEndIcon;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Browser;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.base.ActivityWindowAndroid;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ContextUtils;
+import org.chromium.components.browser_ui.widget.TintedDrawable;
+import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
+import org.chromium.components.browser_ui.widget.listmenu.ListMenuItemProperties;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+
+import org.chromium.components.user_scripts.ScriptListBaseAdapter;
+import org.chromium.components.user_scripts.FragmentWindowAndroid;
+
+import java.util.List;
+
+public class ScriptListPreference extends Preference {
+ private static class ScriptListAdapter
+ extends ScriptListBaseAdapter {
+ private final Context mContext;
+
+ ScriptListAdapter(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ super.onBindViewHolder(holder, position);
+
+ final ScriptInfo info = getItemByPosition(position);
+
+ ModelList menuItems = new ModelList();
+
+ menuItems.add(buildMenuListItem(R.string.remove, 0, 0, true));
+ menuItems.add(buildMenuListItem(R.string.scripts_view_source, 0, 0,
+ info.ParserError == null || info.ParserError.isEmpty()));
+
+ ListMenu.Delegate delegate = (model) -> {
+ int textId = model.get(ListMenuItemProperties.TITLE_ID);
+ if (textId == R.string.remove) {
+ UserScriptsBridge.RemoveScript(info.Key);
+ } else if (textId == R.string.scripts_view_source) {
+ UserScriptsBridge.getUtils().openSourceFile(info.Key);
+ }
+ };
+ ((ScriptInfoRowViewHolder) holder)
+ .setMenuButtonDelegate(() -> new BasicListMenu(mContext, menuItems, delegate));
+ ((ScriptInfoRowViewHolder) holder)
+ .setItemListener(new ScriptListBaseAdapter.ItemClickListener() {
+ @Override
+ public void onScriptOnOff(boolean Enabled) {
+ UserScriptsBridge.SetScriptEnabled(info.Key, Enabled);
+ }
+
+ @Override
+ public void onScriptClicked() {}
+ });
+ }
+
+ // @Override
+ public void onDataUpdated(boolean enabled) {
+ List<ScriptInfo> list = UserScriptsBridge.getUserScriptItems();
+ if (enabled == false) {
+ for (ScriptInfo script : list) {
+ script.ForceDisabled = true;
+ }
+ }
+ setDisplayedScriptInfo(list);
+ }
+ }
+
+ private TextView mAddButton;
+ private RecyclerView mRecyclerView;
+ private ScriptListAdapter mAdapter;
+ private FragmentWindowAndroid mWindowAndroid;
+
+ public ScriptListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAdapter = new ScriptListAdapter(context);
+ }
+
+ public void setWindowAndroid(FragmentWindowAndroid windowAndroid) {
+ mWindowAndroid = windowAndroid;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mAddButton = (TextView) holder.findViewById(R.id.add_script);
+ mAddButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ TintedDrawable.constructTintedDrawable(
+ getContext(), R.drawable.plus, R.color.default_control_color_active_baseline),
+ 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();
+ }
+ }
+}
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,212 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.lang.ref.WeakReference;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+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.ContentUriUtils;
+import org.chromium.base.Log;
+import org.chromium.ui.base.WindowAndroid;
+
+import org.chromium.components.user_scripts.ScriptListPreference;
+import org.chromium.components.user_scripts.IUserScriptsUtils;
+
+@JNINamespace("user_scripts")
+public class UserScriptsBridge {
+ private static final String TAG = "UserScript";
+
+ static WeakReference<ScriptListPreference> observer;
+
+ private static IUserScriptsUtils utilInstance;
+
+ public static void registerUtils(IUserScriptsUtils instance) {
+ utilInstance = instance;
+ }
+
+ public static IUserScriptsUtils getUtils() {
+ return utilInstance;
+ }
+
+ public static boolean isEnabled() {
+ return UserScriptsBridgeJni.get().isEnabled();
+ }
+
+ public static void setEnabled(boolean enabled) {
+ UserScriptsBridgeJni.get().setEnabled(enabled);
+ }
+
+ public static void RemoveScript(String key) {
+ UserScriptsBridgeJni.get().removeScript(key);
+ }
+
+ public static void SetScriptEnabled(String key,
+ boolean enabled) {
+ UserScriptsBridgeJni.get().setScriptEnabled(key, enabled);
+ }
+
+ public static void Reload() {
+ UserScriptsBridgeJni.get().reload();
+ }
+
+ public static void SelectAndAddScriptFromFile(WindowAndroid window) {
+ Context context = window.getContext().get();
+
+ Intent fileSelector = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ fileSelector.addCategory(Intent.CATEGORY_OPENABLE);
+ fileSelector.setType("*/*");
+ fileSelector.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ window.showIntent(fileSelector,
+ new WindowAndroid.IntentCallback() {
+ @Override
+ public void onIntentCompleted(int resultCode, Intent data) {
+ if (data == null) return;
+ Uri filePath = data.getData();
+ TryToInstall(context, filePath.toString());
+ }
+ },
+ null);
+ }
+
+ public static void TryToInstall(Context context, String ScriptFullPath) {
+ DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which){
+ case DialogInterface.BUTTON_POSITIVE:
+ UserScriptsBridgeJni.get().tryToInstall(ScriptFullPath);
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ break;
+ }
+ }
+ };
+
+ String scriptName = ScriptFullPath;
+ if (ContentUriUtils.isContentUri(scriptName)) {
+ scriptName = ContentUriUtils.getFilePathFromContentUri(Uri.parse(scriptName));
+ if (scriptName == null) {
+ // fallback to content uri name if fail
+ scriptName = ContentUriUtils.getDisplayName(Uri.parse(ScriptFullPath), context,
+ MediaStore.MediaColumns.DISPLAY_NAME);
+ }
+ }
+
+ String message = context.getString(R.string.ask_to_install, scriptName);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setMessage(message)
+ .setPositiveButton(context.getString(R.string.yes), dialogClickListener)
+ .setNegativeButton(context.getString(R.string.no), dialogClickListener)
+ .show();
+ }
+
+ public static List<ScriptInfo> getUserScriptItems() {
+ List<ScriptInfo> list = new ArrayList<>();
+ try {
+ String json = UserScriptsBridgeJni.get().getScriptsInfo();
+
+ JSONObject jsonObject = new JSONObject(json);
+
+ JSONArray scripts = jsonObject.names();
+ if (scripts != null) {
+ Log.i(TAG, "User Scripts Loaded: %s", json);
+ Log.i(TAG, "Totals scripts: %s", Integer.toString(scripts.length()));
+ for (int i = 0; i < scripts.length(); i++) {
+ String key = (String) scripts.get(i);
+ JSONObject script = jsonObject.getJSONObject(key);
+
+ ScriptInfo si = new ScriptInfo();
+ si.Key = key;
+ list.add(si);
+
+ if(script.has("name")) si.Name = script.getString("name");
+ if(script.has("description")) si.Description = script.getString("description");
+ if(script.has("version")) si.Version = script.getString("version");
+ if(script.has("file_path")) si.FilePath = script.getString("file_path");
+ if(script.has("url_source")) si.UrlSource = script.getString("url_source");
+ if(script.has("parser_error")) si.ParserError = script.getString("parser_error");
+ if(script.has("force_disabled")) si.ForceDisabled = script.getBoolean("force_disabled");;
+ si.Enabled = script.getBoolean("enabled");
+ }
+ } else {
+ Log.i(TAG, "User Scripts list empty");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "User Scripts Load Error", e.toString());
+ }
+ return list;
+ }
+
+ public static void RegisterLoadCallback(ScriptListPreference caller) {
+ UserScriptsBridgeJni.get().registerLoadCallback();
+ observer = new WeakReference<ScriptListPreference>(caller);
+ }
+
+ @CalledByNative
+ private static void shouldRefreshUserScriptList() {
+ ScriptListPreference reference = observer.get();
+ if (reference != null) {
+ reference.NotifyScriptsChanged();
+ }
+ }
+
+ @CalledByNative
+ private static void onUserScriptLoaded(boolean result, String error) {
+ ScriptListPreference reference = observer.get();
+ if (reference != null) {
+ reference.OnUserScriptLoaded(result, error);
+ }
+ }
+
+ @NativeMethods
+ interface Natives {
+ boolean isEnabled();
+ void setEnabled(boolean enabled);
+
+ String getScriptsInfo();
+
+ void removeScript(String scriptKey);
+ void setScriptEnabled(String scriptKey, boolean enabled);
+
+ void reload();
+ void selectAndAddScriptFromFile(WindowAndroid window);
+ void tryToInstall(String scriptFullPath);
+
+ void registerLoadCallback();
+ }
+
+}
diff --git a/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/android/java/src/org/chromium/components/user_scripts/UserScriptsPreferences.java
@@ -0,0 +1,116 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.components.user_scripts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.net.Uri;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import org.chromium.base.Log;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.base.ActivityWindowAndroid;
+
+import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
+import org.chromium.components.browser_ui.settings.SettingsUtils;
+import org.chromium.components.browser_ui.settings.TextMessagePreference;
+
+import org.chromium.components.user_scripts.UserScriptsBridge;
+import org.chromium.components.user_scripts.FragmentWindowAndroid;
+
+public class UserScriptsPreferences
+ extends PreferenceFragmentCompat
+ implements SettingsUtils.ISupportHelpAndFeedback {
+
+ private static final String PREF_ENABLED_SWITCH = "enabled_switch";
+ private static final String PREF_SCRIPTLISTPREFERENCE = "script_list";
+
+ public static final String EXTRA_SCRIPT_FILE = "org.chromium.chrome.preferences.script_file";
+
+ private FragmentWindowAndroid mWindowAndroid;
+
+ @Override
+ public void onDestroy() {
+ if (mWindowAndroid != null) mWindowAndroid.destroy();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ getActivity().setTitle(R.string.prefs_userscripts_settings);
+ SettingsUtils.addPreferencesFromResource(this, R.xml.userscripts_preferences);
+
+ ChromeSwitchPreference enabledSwitch =
+ (ChromeSwitchPreference) findPreference(PREF_ENABLED_SWITCH);
+ ScriptListPreference listPreference =
+ (ScriptListPreference) findPreference(PREF_SCRIPTLISTPREFERENCE);
+
+ boolean enabled = UserScriptsBridge.isEnabled();
+ enabledSwitch.setChecked(enabled);
+ listPreference.setEnabled(enabled);
+ enabledSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+ UserScriptsBridge.setEnabled((boolean) newValue);
+ listPreference.setEnabled((boolean) newValue);
+ return true;
+ });
+
+ mWindowAndroid = new FragmentWindowAndroid(getContext(), this);
+ listPreference.setWindowAndroid(mWindowAndroid);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // handle picker callback from SelectFileDialog
+ mWindowAndroid.getIntentRequestTracker().onActivityResult(requestCode, resultCode, data);
+ }
+
+ public void onHelpAndFeebackPressed() {
+ Context context = getContext();
+
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/bromite/bromite/wiki/UserScripts"));
+ // Let Chromium know that this intent is from Chromium, so that it does not close the app when
+ // the user presses 'back' button.
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
+ intent.setPackage(context.getPackageName());
+ context.startActivity(intent);
+ }
+
+ public static Bundle createFragmentArgsForInstall(String filePath) {
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putSerializable(EXTRA_SCRIPT_FILE, filePath);
+ return fragmentArgs;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ String scriptToInstall = (String)getArguments().getSerializable(EXTRA_SCRIPT_FILE);
+ if (scriptToInstall != null) {
+ UserScriptsBridge.TryToInstall(getContext(), scriptToInstall);
+ }
+ }
+}
diff --git a/components/user_scripts/android/java_sources.gni b/components/user_scripts/android/java_sources.gni
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/java_sources.gni
@@ -0,0 +1,18 @@
+# This file is part of Bromite.
+
+# Bromite is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# Bromite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+
+userscripts_java_sources = [
+ "//components/user_scripts/android/java/src/org/chromium/chrome/browser/user_scripts/UserScriptsUtils.java",
+]
diff --git a/components/user_scripts/android/user_scripts_bridge.cc b/components/user_scripts/android/user_scripts_bridge.cc
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/user_scripts_bridge.cc
@@ -0,0 +1,173 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include <jni.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <sstream>
+#include <iterator>
+
+#include "base/android/callback_android.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "ui/android/window_android.h"
+
+#include "components/user_scripts/android/user_scripts_jni_headers/UserScriptsBridge_jni.h"
+#include "../browser/userscripts_browser_client.h"
+#include "user_scripts_bridge.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF16ToJavaString;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using content::BrowserContext;
+
+namespace {
+
+user_scripts::UserScriptsBrowserClient* GetUserScriptsBrowserClient() {
+ return user_scripts::UserScriptsBrowserClient::GetInstance();
+}
+
+class CallbackObserver : public user_scripts::UserScriptLoader::Observer {
+ private:
+ void OnScriptsLoaded(user_scripts::UserScriptLoader* loader,
+ content::BrowserContext* browser_context) override {
+ user_scripts::ShouldRefreshUserScriptList(base::android::AttachCurrentThread());
+ }
+
+ void OnUserScriptLoaded(user_scripts::UserScriptLoader* loader,
+ bool result, const std::string& error) override {
+ user_scripts::OnUserScriptLoaded(base::android::AttachCurrentThread(),
+ result, error);
+ }
+
+ void OnUserScriptLoaderDestroyed(user_scripts::UserScriptLoader* loader) override {}
+};
+
+CallbackObserver* g_userscripts_loader_observer = NULL;
+
+}
+
+namespace user_scripts {
+
+static jboolean JNI_UserScriptsBridge_IsEnabled(
+ JNIEnv* env) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return false;
+ return client->GetPrefs()->IsEnabled();
+}
+
+static void JNI_UserScriptsBridge_SetEnabled(
+ JNIEnv* env,
+ jboolean is_enabled) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+ client->GetPrefs()->SetEnabled(is_enabled);
+ client->GetLoader()->StartLoad();
+}
+
+static base::android::ScopedJavaLocalRef<jstring> JNI_UserScriptsBridge_GetScriptsInfo(
+ JNIEnv* env) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return ConvertUTF8ToJavaString(env, {});
+
+ std::string json = client->GetPrefs()->GetScriptsInfo();
+ return ConvertUTF8ToJavaString(env, json);
+}
+
+static void JNI_UserScriptsBridge_RemoveScript(
+ JNIEnv* env,
+ const JavaParamRef<jstring>& jscript_key) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ std::string script_key = base::android::ConvertJavaStringToUTF8(jscript_key);
+ client->GetLoader()->RemoveScript(script_key);
+}
+
+static void JNI_UserScriptsBridge_SetScriptEnabled(
+ JNIEnv* env,
+ const JavaParamRef<jstring>& jscript_key,
+ jboolean is_enabled) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ std::string script_key = base::android::ConvertJavaStringToUTF8(jscript_key);
+ client->GetLoader()->SetScriptEnabled(script_key, is_enabled);
+}
+
+static void JNI_UserScriptsBridge_Reload(
+ JNIEnv* env) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ client->GetLoader()->StartLoad();
+ user_scripts::ShouldRefreshUserScriptList(env);
+}
+
+static void JNI_UserScriptsBridge_SelectAndAddScriptFromFile(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jwindow_android) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ client->GetLoader()->SelectAndAddScriptFromFile(
+ ui::WindowAndroid::FromJavaWindowAndroid(jwindow_android));
+}
+
+static void JNI_UserScriptsBridge_TryToInstall(JNIEnv* env,
+ const JavaParamRef<jstring>& jscript_path) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ std::string script_path = base::android::ConvertJavaStringToUTF8(jscript_path);
+ base::FilePath path(script_path);
+
+ client->GetLoader()->TryToInstall(path);
+}
+
+static void JNI_UserScriptsBridge_RegisterLoadCallback(
+ JNIEnv* env) {
+ user_scripts::UserScriptsBrowserClient* client = GetUserScriptsBrowserClient();
+ if (client == NULL) return;
+
+ if (g_userscripts_loader_observer == NULL) {
+ g_userscripts_loader_observer = new CallbackObserver();
+ client->GetLoader()->AddObserver(g_userscripts_loader_observer);
+ }
+}
+
+static void ShouldRefreshUserScriptList(JNIEnv* env) {
+ Java_UserScriptsBridge_shouldRefreshUserScriptList(env);
+}
+
+static void OnUserScriptLoaded(JNIEnv* env,
+ bool result, const std::string& error) {
+ base::android::ScopedJavaLocalRef<jstring> j_error =
+ base::android::ConvertUTF8ToJavaString(env, error);
+
+ Java_UserScriptsBridge_onUserScriptLoaded(env, result, j_error);
+}
+
+}
diff --git a/components/user_scripts/android/user_scripts_bridge.h b/components/user_scripts/android/user_scripts_bridge.h
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/android/user_scripts_bridge.h
@@ -0,0 +1,31 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_
+#define COMPONENTS_USERSCRIPS_HELPER_ANDROID_H_
+
+#include <jni.h>
+
+namespace user_scripts {
+
+static void ShouldRefreshUserScriptList(JNIEnv* env);
+static void OnUserScriptLoaded(JNIEnv* env,
+ bool result, const std::string& error);
+
+} // namespace user_scripts
+
+#endif
\ No newline at end of file
diff --git a/components/user_scripts/browser/BUILD.gn b/components/user_scripts/browser/BUILD.gn
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/BUILD.gn
@@ -0,0 +1,77 @@
+# This file is part of Bromite.
+
+# Bromite is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# Bromite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+
+import("//build/config/features.gni")
+import("//tools/grit/grit_rule.gni")
+
+group("browser") {
+ public_deps = [
+ "//components/user_scripts/browser:browser_sources",
+ ]
+}
+
+source_set("browser_sources") {
+ visibility = [ "./*" ]
+
+ sources = [
+ "file_task_runner.cc",
+ "file_task_runner.h",
+ "userscripts_browser_client.cc",
+ "userscripts_browser_client.h",
+ "user_script_loader.cc",
+ "user_script_loader.h",
+ "user_script_prefs.cc",
+ "user_script_prefs.h",
+ "user_script_pref_info.cc",
+ "user_script_pref_info.h",
+ "ui/user_scripts_ui.h",
+ "ui/user_scripts_ui.cc",
+ ]
+
+ deps = [
+ ":userscripts_browser_resources",
+ "//base:i18n",
+ "//components/keyed_service/content",
+ "//components/keyed_service/core",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//content/public/browser",
+ "//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",
+ ]
+}
+
+grit("userscripts_browser_resources") {
+ source = "resources/browser_resources.grd"
+
+ output_dir = "$root_gen_dir/chrome"
+ outputs = [
+ "grit/userscripts_browser_resources.h",
+ "userscripts_browser_resources.pak",
+ ]
+}
diff --git a/components/user_scripts/browser/file_task_runner.cc b/components/user_scripts/browser/file_task_runner.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/file_task_runner.cc
@@ -0,0 +1,40 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "file_task_runner.h"
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/lazy_thread_pool_task_runner.h"
+#include "base/task/task_traits.h"
+
+namespace user_scripts {
+
+namespace {
+
+base::LazyThreadPoolSequencedTaskRunner g_us_task_runner =
+ LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(
+ base::TaskTraits(base::MayBlock(),
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ base::TaskPriority::USER_VISIBLE));
+
+} // namespace
+
+scoped_refptr<base::SequencedTaskRunner> GetUserScriptsFileTaskRunner() {
+ return g_us_task_runner.Get();
+}
+
+} // namespace user_scripts
diff --git a/components/user_scripts/browser/file_task_runner.h b/components/user_scripts/browser/file_task_runner.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/file_task_runner.h
@@ -0,0 +1,34 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
+#define USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/task/task_traits.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace user_scripts {
+
+scoped_refptr<base::SequencedTaskRunner> GetUserScriptsFileTaskRunner();
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_BROWSER_FILE_TASK_RUNNER_H_
diff --git a/components/user_scripts/browser/resources/browser_resources.grd b/components/user_scripts/browser/resources/browser_resources.grd
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/resources/browser_resources.grd
@@ -0,0 +1,14 @@
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+ <outputs>
+ <output filename="grit/userscripts_browser_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="userscripts_browser_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_USER_SCRIPTS_HTML" file="user-script-ui\user-scripts-ui.html" type="BINDATA" />
+ <include name="IDR_USER_SCRIPTS_JS" file="user-script-ui\user-scripts-ui.js" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
\ No newline at end of file
diff --git a/components/user_scripts/browser/resources/user-script-ui/BUILD.gn b/components/user_scripts/browser/resources/user-script-ui/BUILD.gn
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/resources/user-script-ui/BUILD.gn
@@ -0,0 +1,8 @@
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_library("view_script_source") {
+ sources = [ "user-scripts-ui.js" ]
+ deps = [
+ "//ui/webui/resources:library",
+ ]
+}
diff --git a/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/resources/user-script-ui/user-scripts-ui.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Userscript View Source</title>
+ <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+ <script type="module" src="user-scripts-ui.js"></script>
+</head>
+<body>
+ <pre id="content">
+ Loading Script Source file...
+ </pre>
+</body>
+</html>
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.js';
+import {$} from 'chrome://resources/js/util_ts.js';
+
+document.addEventListener('DOMContentLoaded', function() {
+ const urlParams = new URLSearchParams(window.location.search);
+ sendWithPromise('requestSource', urlParams.get('key')).then(textContent => {
+ $('content').textContent = textContent[0].content;
+ });
+});
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,146 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+
+#include <memory>
+
+#include "base/json/json_string_value_serializer.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(const UserScriptsUIHandler&) = delete;
+ UserScriptsUIHandler& operator=(const UserScriptsUIHandler&) = delete;
+ UserScriptsUIHandler();
+ ~UserScriptsUIHandler() override;
+
+ // content::WebUIMessageHandler:
+ void RegisterMessages() override;
+
+ private:
+ void HandleRequestSource(const base::Value::List& args);
+ void OnScriptsLoaded(
+ const std::string callback_id,
+ const std::string script_key,
+ std::unique_ptr<user_scripts::UserScriptList> user_scripts);
+
+ std::unique_ptr<user_scripts::UserScriptList> loaded_scripts_;
+
+ base::WeakPtrFactory<UserScriptsUIHandler> weak_factory_{this};
+};
+
+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(const base::Value::List& args) {
+ AllowJavascript();
+ if (args.size() < 2) return;
+
+ std::string callback_id = args[0].GetString();
+ std::string script_key = args[1].GetString();
+ if (script_key.empty()) {
+ std::string json = "Missing key value.";
+ ResolveJavascriptCallback(base::Value(callback_id), base::Value(json));
+ return;
+ }
+
+ user_scripts::UserScriptsBrowserClient* client = user_scripts::UserScriptsBrowserClient::GetInstance();
+ if (client == NULL) {
+ std::string json = "User scripts disabled.";
+ ResolveJavascriptCallback(base::Value(callback_id), base::Value(json));
+ } else {
+ std::unique_ptr<user_scripts::UserScriptList> scripts_to_load =
+ std::move(loaded_scripts_);
+ scripts_to_load->clear();
+
+ client->GetLoader()->LoadScripts(std::move(scripts_to_load),
+ base::BindOnce(
+ &UserScriptsUIHandler::OnScriptsLoaded,
+ weak_factory_.GetWeakPtr(),
+ callback_id, script_key)
+ );
+ }
+}
+
+void UserScriptsUIHandler::OnScriptsLoaded(
+ const std::string callback_id,
+ const std::string script_key,
+ std::unique_ptr<user_scripts::UserScriptList> user_scripts) {
+ loaded_scripts_ = std::move(user_scripts);
+
+ base::Value::List response;
+ for (const std::unique_ptr<user_scripts::UserScript>& script : *loaded_scripts_) {
+ if (script->key() == script_key) {
+ base::Value::Dict scriptData;
+ for (const std::unique_ptr<user_scripts::UserScript::File>& js_file :
+ script->js_scripts()) {
+ base::StringPiece contents = js_file->GetContent();
+ scriptData.Set("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::CreateAndAdd(
+ Profile::FromWebUI(web_ui), kChromeUIUserScriptsHost);
+ html_source->SetDefaultResource(IDR_USER_SCRIPTS_HTML);
+ html_source->AddResourcePath("user-scripts-ui.js", IDR_USER_SCRIPTS_JS);
+ web_ui->AddMessageHandler(std::make_unique<UserScriptsUIHandler>());
+}
+
+UserScriptsUI::~UserScriptsUI() {
+}
+
+}
diff --git a/components/user_scripts/browser/ui/user_scripts_ui.h b/components/user_scripts/browser/ui/user_scripts_ui.h
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/ui/user_scripts_ui.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_UI_USER_SCRIPTS_UI_H_
+#define USERSCRIPTS_BROWSER_UI_USER_SCRIPTS_UI_H_
+
+#include "content/public/browser/web_ui_controller.h"
+
+namespace user_scripts {
+
+const char kChromeUIUserScriptsHost[] = "user-scripts";
+
+class UserScriptsUI : public content::WebUIController {
+ public:
+ UserScriptsUI(const UserScriptsUI&) = delete;
+ UserScriptsUI& operator=(const UserScriptsUI&) = delete;
+ explicit UserScriptsUI(content::WebUI* web_ui);
+ ~UserScriptsUI() override;
+};
+
+}
+
+#endif
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,715 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "user_script_loader.h"
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/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"
+#include "../common/host_id.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");
+
+// 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::IsAsciiWhitespace(temp[0]))
+ return false;
+
+ base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
+ return true;
+}
+
+} // namespace
+
+// static
+bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text,
+ std::unique_ptr<UserScript>& script,
+ bool *found_metadata,
+ std::string& error_message) {
+ // http://wiki.greasespot.net/Metadata_block
+ base::StringPiece line;
+ size_t line_start = 0;
+ size_t line_end = line_start;
+ *found_metadata = false;
+
+ while (line_start < script_text.length()) {
+ line_end = script_text.find('\n', line_start);
+
+ // Handle the case where there is no trailing newline in the file.
+ if (line_end == std::string::npos)
+ line_end = script_text.length() - 1;
+
+ line = base::StringPiece(script_text.data() + line_start,
+ line_end - line_start);
+
+ if (!*found_metadata) {
+ if (base::StartsWith(line, kUserScriptBegin))
+ *found_metadata = true;
+ } else {
+ if (base::StartsWith(line, kUserScriptEnd))
+ break;
+
+ std::string value;
+ if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
+ // We escape some characters that MatchPattern() considers special.
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ script->add_glob(value);
+ } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ script->add_exclude_glob(value);
+ } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
+ script->set_name_space(value);
+ } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
+ script->set_name(value);
+ } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
+ base::Version version(value);
+ if (version.IsValid())
+ script->set_version(version.GetString());
+ } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
+ script->set_description(value);
+ } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
+ URLPattern pattern(UserScript::ValidUserScriptSchemes());
+ if (URLPattern::ParseResult::kSuccess != pattern.Parse(value)) {
+ error_message = "Invalid UserScript Schema " + value;
+ return false;
+ }
+ script->add_url_pattern(pattern);
+ } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
+ URLPattern exclude(UserScript::ValidUserScriptSchemes());
+ if (URLPattern::ParseResult::kSuccess != exclude.Parse(value)) {
+ error_message = "Invalid UserScript Schema " + value;
+ return false;
+ }
+ script->add_exclude_url_pattern(exclude);
+ } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
+ if (value == kRunAtDocumentStartValue)
+ script->set_run_location(UserScript::DOCUMENT_START);
+ else if (value == kRunAtDocumentEndValue)
+ script->set_run_location(UserScript::DOCUMENT_END);
+ else if (value == kRunAtDocumentIdleValue)
+ script->set_run_location(UserScript::DOCUMENT_IDLE);
+ else {
+ error_message = "Invalid RunAtDeclaration " + value;
+ return false;
+ }
+ } else if (GetDeclarationValue(line, kUrlSourceDeclaration, &value) ||
+ GetDeclarationValue(line, kUrlHomePageDeclaration, &value)) {
+ script->set_url_source(value);
+ } else if (GetDeclarationValue(line, kParserError, &value)) {
+ script->set_parser_error(value);
+ } else if (GetDeclarationValue(line, kForceDisabled, &value)) {
+ script->set_force_disabled();
+ }
+
+ // TODO(aa): Handle more types of metadata.
+ }
+
+ line_start = line_end + 1;
+ }
+
+ // If no patterns were specified, default to @include *. This is what
+ // Greasemonkey does.
+ if (script->globs().empty() && script->url_patterns().is_empty())
+ script->add_glob("*");
+
+ return true;
+}
+
+// static
+bool LoadUserScriptFromFile(
+ const base::FilePath& user_script_path, const GURL& original_url,
+ std::unique_ptr<UserScript>& script,
+ bool* found_metadata,
+ std::u16string* error) {
+
+ base::File infile;
+ if (user_script_path.IsContentUri()) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: path " << user_script_path << " is a content uri";
+
+ infile = OpenContentUriForRead(user_script_path);
+ } else {
+ infile = base::File(user_script_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ }
+
+ if (!infile.IsValid()) {
+ base::File::Error out_error = infile.error_details();
+ *error = u"Cannot open script source. Error: " +
+ base::ASCIIToUTF16(base::File::ErrorToString(out_error));
+ return false;
+ }
+
+ auto length = infile.GetLength();
+ if (length<=0) {
+ *error = u"File is empty.";
+ return false;
+ }
+
+ auto buffer = std::vector<char>(length);
+ int bytes_read = infile.Read(0, buffer.data(), length);
+ if (bytes_read == -1) {
+ *error = u"Could not read source file.";
+ return false;
+ }
+
+ std::string content(buffer.begin(), buffer.end());
+ if (!base::IsStringUTF8(content)) {
+ *error = u"User script must be UTF8 encoded.";
+ return false;
+ }
+
+ std::string detailed_error;
+ bool parseResult = UserScriptLoader::ParseMetadataHeader(content, script,
+ found_metadata, detailed_error);
+ if (parseResult == false || *found_metadata == false) {
+ std::u16string detailed_error16;
+ base::UTF8ToUTF16(detailed_error.c_str(), detailed_error.length(), &detailed_error16);
+ *error = base::StrCat({u"Invalid script header. ", detailed_error16});
+ return false;
+ }
+
+ script->set_match_origin_as_fallback(MatchOriginAsFallbackBehavior::kNever);
+
+ std::unique_ptr<UserScript::File> file(new UserScript::File());
+ file->set_content(content);
+
+ // 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);
+
+ file->set_url(GURL(base::StrCat({"https://userscripts/file/", key, ".js"})));
+
+ script->js_scripts().push_back(std::move(file));
+
+ // add into key the filename
+ // this value is used in ui to discriminate scripts
+ script->set_key(user_script_path.BaseName().value());
+ return true;
+}
+
+// static
+bool GetOrCreatePath(base::FilePath& path) {
+ base::PathService::Get(base::DIR_ANDROID_APP_DATA, &path);
+ path = path.AppendASCII("userscripts");
+
+ // create snippets directory if not exists
+ if (!base::PathExists(path)) {
+ LOG(INFO) << "Path " << path << " doesn't exists. Creating";
+ base::File::Error error = base::File::FILE_OK;
+ if (!base::CreateDirectoryAndGetError(path, &error)) {
+ LOG(ERROR) <<
+ "UserScriptLoader: failed to create directory: " << path
+ << " with error code " << error;
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+void LoadUserScripts(UserScriptList* user_scripts_list) {
+ base::FilePath path;
+ if (GetOrCreatePath(path) == false) return;
+
+ // enumerate all files from script path
+ // we accept all files, but we check if it's a real
+ // userscript
+ base::FileEnumerator dir_enum(
+ path,
+ /*recursive=*/false, base::FileEnumerator::FILES);
+ base::FilePath full_name;
+ while (full_name = dir_enum.Next(), !full_name.empty()) {
+ std::unique_ptr<UserScript> userscript(new UserScript());
+ userscript->set_id(UserScript::GenerateUserScriptID());
+ userscript->set_host_id(HostID(HostID::HostType::EXTENSIONS,
+ "_" + base::NumberToString(userscript->id())));
+
+ std::u16string error;
+ bool found_metadata;
+ if (LoadUserScriptFromFile(full_name, GURL(), userscript, &found_metadata, &error)) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: Found user script " << userscript->name() <<
+ "-" << userscript->version() <<
+ "-" << userscript->description();
+
+ userscript->set_file_path(full_name.AsUTF8Unsafe());
+ user_scripts_list->push_back(std::move(userscript));
+ } else {
+ LOG(ERROR) << "UserScriptLoader: load error " << error;
+ }
+ }
+}
+
+UserScriptLoader::UserScriptLoader(BrowserContext* browser_context,
+ UserScriptsPrefs* prefs)
+ : loaded_scripts_(new UserScriptList()),
+ ready_(false),
+ browser_context_(browser_context),
+ prefs_(prefs) {}
+
+UserScriptLoader::~UserScriptLoader() {
+ for (auto& observer : observers_)
+ observer.OnUserScriptLoaderDestroyed(this);
+}
+
+void UserScriptLoader::OnRenderProcessHostCreated(
+ content::RenderProcessHost* process_host) {
+ if (initial_load_complete()) {
+ SendUpdate(process_host, shared_memory_);
+ }
+}
+
+void UserScriptLoader::SetReady(bool ready) {
+ bool was_ready = ready_;
+ ready_ = ready;
+ if (ready_ && !was_ready)
+ AttemptLoad();
+}
+
+void UserScriptLoader::AttemptLoad() {
+ int tryOut = prefs_->GetCurrentStartupTryout();
+ if (tryOut >= 3) {
+ LOG(INFO) << "UserScriptLoader: Possible crash detected. UserScript disabled";
+ prefs_->SetEnabled(false);
+ } else {
+ prefs_->StartupTryout(tryOut+1);
+ StartLoad();
+ }
+}
+
+void UserScriptLoader::StartLoad() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: StartLoad";
+
+ // Reload any loaded scripts, and clear out |loaded_scripts_| to indicate that
+ // the scripts aren't currently ready.
+ std::unique_ptr<UserScriptList> scripts_to_load = std::move(loaded_scripts_);
+ scripts_to_load->clear();
+
+ if (prefs_->IsEnabled()) {
+ LoadScripts(std::move(scripts_to_load),
+ base::BindOnce(&UserScriptLoader::OnScriptsLoaded,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ OnScriptsLoaded(std::move(scripts_to_load));
+ }
+}
+
+// static
+base::ReadOnlySharedMemoryRegion UserScriptLoader::Serialize(
+ const UserScriptList& scripts) {
+ base::Pickle pickle;
+ pickle.WriteUInt32(scripts.size());
+ for (const std::unique_ptr<UserScript>& script : scripts) {
+ // TODO(aa): This can be replaced by sending content script metadata to
+ // renderers along with other extension data in ExtensionMsg_Loaded.
+ // See crbug.com/70516.
+ script->Pickle(&pickle);
+ // Write scripts as 'data' so that we can read it out in the slave without
+ // allocating a new string.
+ for (const std::unique_ptr<UserScript::File>& js_file :
+ script->js_scripts()) {
+ base::StringPiece contents = js_file->GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+ for (const std::unique_ptr<UserScript::File>& css_file :
+ script->css_scripts()) {
+ base::StringPiece contents = css_file->GetContent();
+ pickle.WriteData(contents.data(), contents.length());
+ }
+ }
+
+ // Create the shared memory object.
+ base::MappedReadOnlyRegion shared_memory =
+ base::ReadOnlySharedMemoryRegion::Create(pickle.size());
+ if (!shared_memory.IsValid())
+ return {};
+
+ // Copy the pickle to shared memory.
+ memcpy(shared_memory.mapping.memory(), pickle.data(), pickle.size());
+ return std::move(shared_memory.region);
+}
+
+void UserScriptLoader::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptLoader::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserScriptLoader::OnScriptsLoaded(
+ std::unique_ptr<UserScriptList> user_scripts) {
+
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: OnScriptsLoaded";
+
+ // Check user preferences for loaded user scripts
+ prefs_->CompareWithPrefs(*user_scripts);
+ loaded_scripts_ = std::move(user_scripts);
+
+ base::ReadOnlySharedMemoryRegion shared_memory;
+ shared_memory =
+ UserScriptLoader::Serialize(*loaded_scripts_);
+
+ if (!shared_memory.IsValid()) {
+ // This can happen if we run out of file descriptors. In that case, we
+ // have a choice between silently omitting all user scripts for new tabs,
+ // by nulling out shared_memory_, or only silently omitting new ones by
+ // leaving the existing object in place. The second seems less bad, even
+ // though it removes the possibility that freeing the shared memory block
+ // would open up enough FDs for long enough for a retry to succeed.
+
+ // Pretend the extension change didn't happen.
+ return;
+ }
+
+ // We've got scripts ready to go.
+ shared_memory_ = std::move(shared_memory);
+
+ for (content::RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ SendUpdate(i.GetCurrentValue(), shared_memory_);
+ }
+
+ // DCHECK(false); trying crash
+ prefs_->StartupTryout(0);
+
+ for (auto& observer : observers_)
+ observer.OnScriptsLoaded(this, browser_context_);
+}
+
+void UserScriptLoader::SendUpdate(
+ content::RenderProcessHost* process,
+ const base::ReadOnlySharedMemoryRegion& shared_memory) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: SendUpdate";
+
+ // If the process is being started asynchronously, early return. We'll end up
+ // calling InitUserScripts when it's created which will call this again.
+ base::ProcessHandle handle = process->GetProcess().Handle();
+ if (!handle)
+ return;
+
+ base::ReadOnlySharedMemoryRegion region_for_process =
+ shared_memory.Duplicate();
+ if (!region_for_process.IsValid())
+ return;
+
+ process->Send(new ExtensionMsg_UpdateUserScripts(
+ std::move(region_for_process)));
+}
+
+void LoadScriptsOnFileTaskRunner(
+ std::unique_ptr<UserScriptList> user_scripts,
+ UserScriptLoader::LoadScriptsCallback callback) {
+ DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
+ DCHECK(user_scripts.get());
+
+ // load user scripts from path
+ LoadUserScripts(user_scripts.get());
+
+ // Explicit priority to prevent unwanted task priority inheritance.
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
+ ->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback), std::move(user_scripts)));
+}
+
+void UserScriptLoader::LoadScripts(
+ std::unique_ptr<UserScriptList> user_scripts,
+ LoadScriptsCallback callback) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ GetUserScriptsFileTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&LoadScriptsOnFileTaskRunner, std::move(user_scripts),
+ std::move(callback)));
+}
+
+void RemoveScriptsOnFileTaskRunner(
+ const std::string& script_id,
+ UserScriptLoader::RemoveScriptCallback callback) {
+ DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
+
+ base::FilePath path;
+ if (GetOrCreatePath(path)) {
+ base::FilePath file = path.Append(script_id);
+ if (base::DeleteFile(file) == false) {
+ LOG(ERROR) <<
+ "ERROR: failed to delete file : " << path;
+ }
+ }
+
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
+ ->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback)));
+}
+
+void UserScriptLoader::OnScriptRemoved() {
+ StartLoad();
+}
+
+void UserScriptLoader::RemoveScript(const std::string& script_id) {
+ if (!prefs_->IsEnabled()) return;
+ prefs_->RemoveScriptFromPrefs(script_id);
+
+ GetUserScriptsFileTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&RemoveScriptsOnFileTaskRunner,
+ std::move(script_id),
+ base::BindOnce(&UserScriptLoader::OnScriptRemoved,
+ weak_factory_.GetWeakPtr())));
+}
+
+void UserScriptLoader::SetScriptEnabled(const std::string& script_id, bool is_enabled) {
+ if (!prefs_->IsEnabled()) return;
+ prefs_->SetScriptEnabled(script_id, is_enabled);
+ StartLoad();
+}
+
+void UserScriptLoader::SelectAndAddScriptFromFile(ui::WindowAndroid* nativeWindow) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!prefs_->IsEnabled()) return;
+
+ dialog_ = ui::SelectFileDialog::Create(
+ this, std::make_unique<ChromeSelectFilePolicy>(nullptr /*web_contents*/));
+
+ ui::SelectFileDialog::FileTypeInfo allowed_file_info;
+ allowed_file_info.extensions = {{FILE_PATH_LITERAL("js")}};
+ allowed_file_info.allowed_paths =
+ ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
+ base::FilePath suggested_name;
+
+ std::vector<std::u16string> types;
+ types.push_back(u"*/*"); /*= java SelectFileDialog.ALL_TYPES*/
+ std::pair<std::vector<std::u16string>, bool> accept_types = std::make_pair(
+ types, false /*use_media_capture*/);
+
+ dialog_->SelectFile(
+ ui::SelectFileDialog::SELECT_OPEN_FILE,
+ std::u16string() /* dialog title*/, suggested_name, &allowed_file_info,
+ 0 /* file type index */, std::string() /* default file extension */,
+ nativeWindow,
+ &accept_types /* params */);
+}
+
+
+void LoadScriptFromPathOnFileTaskRunner(
+ const base::FilePath& path,
+ const std::string& display_name,
+ UserScriptLoader::LoadSingleScriptCallback callback ) {
+ DCHECK(GetUserScriptsFileTaskRunner()->RunsTasksInCurrentSequence());
+
+ std::unique_ptr<UserScript> userscript(new UserScript());
+ std::u16string error;
+ bool found_metadata = false;
+ bool loaded = LoadUserScriptFromFile(path, GURL(), userscript, &found_metadata, &error);
+
+ bool result = loaded;
+ if (result || found_metadata) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: found " << userscript->name() <<
+ "-" << userscript->version() <<
+ "-" << userscript->description();
+ base::FilePath destination;
+ if (GetOrCreatePath(destination) == false) {
+ error = u"Cannot create destination.";
+ } else {
+ // we need an unique file name
+ if (display_name.empty() == false) {
+ userscript->set_key(display_name);
+ }
+
+ // filename is original filename or display_name
+ std::string file_name(userscript->key());
+ base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_');
+ destination = destination.Append(file_name);
+
+ if (destination.ReferencesParent()) {
+ error = u"Invalid file name.";
+ result = false;
+ } else if (base::PathExists(destination)) {
+ error = u"User script already loaded.";
+ result = false;
+ } else {
+ if (loaded) {
+ // if is a correct userscript, copy it
+ result = base::CopyFile(path, destination);
+ if (result == false) {
+ error = u"Copy error.";
+ }
+ } else {
+ // else, there is a parser error
+ // write minimal values and the error string, so UI can show it
+ std::string combined_string = base::StrCat({
+ kUserScriptBegin, "\n",
+ kNamespaceDeclaration, " ", userscript->name_space(), "\n",
+ kNameDeclaration, " ", userscript->name(), "\n",
+ kVersionDeclaration, " ", userscript->version(), "\n",
+ kDescriptionDeclaration, " ", userscript->description(), "\n",
+ kUrlSourceDeclaration, " ", userscript->url_source(), "\n",
+ kParserError, " ", base::UTF16ToASCII(error), "\n",
+ kForceDisabled, " true\n",
+ kUserScriptEnd, "\n"
+ });
+
+ if (!base::WriteFile(destination, combined_string)) {
+ error = u"Cannot write.";
+ result = false;
+ }
+ }
+ }
+ }
+ }
+
+ if (!error.empty()) {
+ LOG(ERROR) << "UserScriptLoader: load error " << error;
+ }
+
+ // return to callback with eventually the error
+ const std::string string_error = base::UTF16ToASCII(error);
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
+ ->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback), result,
+ std::move(string_error)));
+}
+
+void UserScriptLoader::TryToInstall(const base::FilePath& script_path) {
+ if (!prefs_->IsEnabled()) return;
+
+ std::u16string file_display_name;
+ base::MaybeGetFileDisplayName(script_path, &file_display_name);
+
+ std::string display_name = script_path.BaseName().value();
+ if (base::IsStringASCII(file_display_name))
+ display_name = base::UTF16ToASCII(file_display_name);
+
+ GetUserScriptsFileTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &LoadScriptFromPathOnFileTaskRunner,
+ script_path, display_name,
+ base::BindOnce(
+ &UserScriptLoader::LoadScriptFromPathOnFileTaskRunnerCallback,
+ weak_factory_.GetWeakPtr()
+ )
+ ));
+}
+
+void UserScriptLoader::FileSelected(
+ const base::FilePath& path, int index, void* params) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptLoader: FileSelected " << path;
+
+ UserScriptLoader::TryToInstall(path);
+}
+
+void UserScriptLoader::LoadScriptFromPathOnFileTaskRunnerCallback(
+ bool result, const std::string& error) {
+ for (auto& observer : observers_)
+ observer.OnUserScriptLoaded(this, result, error);
+
+ StartLoad();
+}
+
+void UserScriptLoader::FileSelectionCanceled(
+ void* params) {
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/browser/user_script_loader.h b/components/user_scripts/browser/user_script_loader.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/user_script_loader.h
@@ -0,0 +1,168 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
+#define USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/compiler_specific.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:
+ UserScriptLoader(const UserScriptLoader&) = delete;
+ UserScriptLoader& operator=(const UserScriptLoader&) = delete;
+ using LoadScriptsCallback =
+ base::OnceCallback<void(std::unique_ptr<UserScriptList>)>;
+ using LoadSingleScriptCallback =
+ base::OnceCallback<void(bool result, const std::string& error)>;
+
+ using RemoveScriptCallback =
+ base::OnceCallback<void()>;
+ class Observer {
+ public:
+ virtual void OnScriptsLoaded(UserScriptLoader* loader,
+ content::BrowserContext* browser_context) = 0;
+ virtual void OnUserScriptLoaderDestroyed(UserScriptLoader* loader) = 0;
+ virtual void OnUserScriptLoaded(UserScriptLoader* loader,
+ bool result, const std::string& error) = 0;
+ };
+
+ // Parses the includes out of |script| and returns them in |includes|.
+ static bool ParseMetadataHeader(const base::StringPiece& script_text,
+ std::unique_ptr<UserScript>& script,
+ bool *found_metadata,
+ std::string& error_message);
+
+ UserScriptLoader(content::BrowserContext* browser_context,
+ UserScriptsPrefs* prefs);
+ //const HostID& host_id);
+ ~UserScriptLoader() override;
+
+ // Initiates procedure to start loading scripts on the file thread.
+ void StartLoad();
+
+ // Returns true if we have any scripts ready.
+ bool initial_load_complete() const { return shared_memory_.IsValid(); }
+
+ // Pickle user scripts and return pointer to the shared memory.
+ static base::ReadOnlySharedMemoryRegion Serialize(
+ const user_scripts::UserScriptList& scripts);
+
+ // Adds or removes observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Sets the flag if the initial set of hosts has finished loading; if it's
+ // set to be true, calls AttempLoad() to bootstrap.
+ void SetReady(bool ready);
+
+ void RemoveScript(const std::string& script_id);
+ void SetScriptEnabled(const std::string& script_id, bool is_enabled);
+
+ void SelectAndAddScriptFromFile(ui::WindowAndroid* wa);
+ void TryToInstall(const base::FilePath& script_path);
+
+ void LoadScripts(std::unique_ptr<UserScriptList> user_scripts,
+ LoadScriptsCallback callback);
+
+ protected:
+ content::BrowserContext* browser_context() const { return browser_context_; }
+
+ UserScriptsPrefs* prefs() const { return prefs_; }
+
+ private:
+ void OnRenderProcessHostCreated(
+ content::RenderProcessHost* process_host) override;
+
+ // Attempts to initiate a load.
+ void AttemptLoad();
+
+ // Called once we have finished loading the scripts on the file thread.
+ void OnScriptsLoaded(std::unique_ptr<UserScriptList> user_scripts);
+
+ // Sends the renderer process a new set of user scripts. If
+ // |changed_hosts| is not empty, this signals that only the scripts from
+ // those hosts should be updated. Otherwise, all hosts will be
+ // updated.
+ void SendUpdate(content::RenderProcessHost* process,
+ const base::ReadOnlySharedMemoryRegion& shared_memory);
+
+ // Contains the scripts that were found the last time scripts were updated.
+ base::ReadOnlySharedMemoryRegion shared_memory_;
+
+ // List of scripts that are currently loaded. This is null when a load is in
+ // progress.
+ std::unique_ptr<UserScriptList> loaded_scripts_;
+
+ // If the initial set of hosts has finished loading.
+ bool ready_;
+
+ // The browser_context for which the scripts managed here are installed.
+ raw_ptr<content::BrowserContext> browser_context_;
+
+ // Manage load and store from preferences
+ raw_ptr<UserScriptsPrefs> prefs_;
+
+ // The associated observers.
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ void OnScriptRemoved();
+
+ // Manage file dialog requests
+ scoped_refptr<ui::SelectFileDialog> dialog_;
+ void FileSelected(const base::FilePath& path,
+ int index, void* params) override;
+ void FileSelectionCanceled(void* params) override;
+ void LoadScriptFromPathOnFileTaskRunnerCallback(
+ bool result, const std::string& error );
+
+ base::WeakPtrFactory<UserScriptLoader> weak_factory_{this};
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_BROWSER_USER_SCRIPT_LOADER_H_
diff --git a/components/user_scripts/browser/user_script_pref_info.cc b/components/user_scripts/browser/user_script_pref_info.cc
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/user_script_pref_info.cc
@@ -0,0 +1,34 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "user_script_pref_info.h"
+
+namespace user_scripts {
+
+UserScriptsListPrefs::ScriptInfo::ScriptInfo(const std::string& name,
+ const std::string& description,
+ const base::Time& install_time,
+ bool enabled)
+ : install_time(install_time),
+ enabled(enabled),
+ name_(name),
+ description_(description) {}
+
+UserScriptsListPrefs::ScriptInfo::ScriptInfo(const ScriptInfo& other) = default;
+UserScriptsListPrefs::ScriptInfo::~ScriptInfo() = default;
+
+}
diff --git a/components/user_scripts/browser/user_script_pref_info.h b/components/user_scripts/browser/user_script_pref_info.h
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/user_script_pref_info.h
@@ -0,0 +1,72 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
+#define USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
+
+#include "base/values.h"
+#include "base/time/time.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+namespace user_scripts {
+
+class UserScriptsListPrefs : public KeyedService {
+ public:
+ struct ScriptInfo {
+ ScriptInfo(const std::string& name,
+ const std::string& description,
+ const base::Time& install_time,
+ bool enabled);
+ ScriptInfo(const ScriptInfo& other);
+ ~ScriptInfo();
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ const std::string& description() const { return description_; }
+ void set_description(const std::string& description) { description_ = description; }
+
+ const std::string& version() const { return version_; }
+ void set_version(const std::string& version) { version_ = version; }
+
+ const std::string& file_path() const { return file_path_; }
+ void set_file_path(const std::string& file_path) { file_path_ = file_path; }
+
+ const std::string& url_source() const { return url_source_; }
+ void set_url_source(const std::string& url_source) { url_source_ = url_source; }
+
+ const std::string& parser_error() const { return parser_error_; }
+ void set_parser_error(const std::string& parser_error) { parser_error_ = parser_error; }
+
+ base::Time install_time;
+ bool enabled;
+
+ bool force_disabled;
+
+ private:
+ std::string name_;
+ std::string description_;
+ std::string version_;
+ std::string file_path_;
+ std::string url_source_;
+ std::string parser_error_;
+ };
+};
+
+}
+
+#endif // USERSCRIPTS_BROWSER_USERSCRIPT_PREF_INFO_H_
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,275 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include <map>
+
+#include "base/values.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/json/json_writer.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "services/preferences/public/cpp/dictionary_value_update.h"
+#include "services/preferences/public/cpp/scoped_pref_update.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 ::prefs::ScopedDictionaryPrefUpdate {
+ public:
+ PrefUpdate(PrefService* service,
+ const std::string& id,
+ const std::string& path)
+ : ::prefs::ScopedDictionaryPrefUpdate(service, path), id_(id) {}
+
+ PrefUpdate(const PrefUpdate&) = delete;
+ PrefUpdate& operator=(const PrefUpdate&) = delete;
+ ~PrefUpdate() override = default;
+
+ std::unique_ptr<::prefs::DictionaryValueUpdate> Get() override {
+ std::unique_ptr<::prefs::DictionaryValueUpdate> dict =
+ ScopedDictionaryPrefUpdate::Get();
+ std::unique_ptr<::prefs::DictionaryValueUpdate> dict_item;
+ if (!dict->GetDictionaryWithoutPathExpansion(id_, &dict_item)) {
+ dict_item = dict->SetDictionaryWithoutPathExpansion(id_, base::Value::Dict());
+ }
+ return dict_item;
+ }
+
+ private:
+ const std::string id_;
+};
+
+bool GetInt64FromPref(const base::Value* dict,
+ const std::string& key,
+ int64_t* value) {
+ DCHECK(dict);
+ const std::string* value_str = dict->GetDict().FindString(key);
+ if (!value_str) {
+ VLOG(2) << "Can't find key in local pref dictionary. Invalid key: " << key
+ << ".";
+ return false;
+ }
+
+ if (!base::StringToInt64(*value_str, value)) {
+ VLOG(2) << "Can't change string to int64_t. Invalid string value: "
+ << *value_str << ".";
+ return false;
+ }
+
+ return true;
+}
+
+}
+
+UserScriptsPrefs::UserScriptsPrefs(
+ PrefService* prefs)
+ : prefs_(prefs) {
+}
+
+// static
+void UserScriptsPrefs::RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterBooleanPref(prefs::kUserScriptsEnabled, false);
+ registry->RegisterIntegerPref(kUserScriptsStartup, 0);
+ registry->RegisterDictionaryPref(kUserScriptsList);
+}
+
+bool UserScriptsPrefs::IsEnabled() {
+ return prefs_->GetBoolean(prefs::kUserScriptsEnabled);
+}
+
+void UserScriptsPrefs::SetEnabled(bool enabled) {
+ prefs_->SetBoolean(prefs::kUserScriptsEnabled, enabled);
+ prefs_->CommitPendingWrite();
+}
+
+void UserScriptsPrefs::StartupTryout(int number) {
+ prefs_->SetInteger(kUserScriptsStartup, number);
+ prefs_->CommitPendingWrite();
+}
+
+int UserScriptsPrefs::GetCurrentStartupTryout() {
+ return prefs_->GetInteger(kUserScriptsStartup);
+}
+
+void UserScriptsPrefs::CompareWithPrefs(UserScriptList& user_scripts) {
+ if (IsEnabled() == false) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScriptsPrefs: disabled by user";
+
+ user_scripts.clear();
+ return;
+ }
+
+ std::vector<std::string> all_scripts;
+
+ auto it = user_scripts.begin();
+ while (it != user_scripts.end())
+ {
+ std::string key = it->get()->key();
+ all_scripts.push_back(key);
+
+ std::unique_ptr<UserScriptsListPrefs::ScriptInfo> scriptInfo =
+ UserScriptsPrefs::CreateScriptInfoFromPrefs(key);
+
+ // add or update prefs
+ scriptInfo->set_name(it->get()->name());
+ scriptInfo->set_description(it->get()->description());
+ scriptInfo->set_version(it->get()->version());
+ scriptInfo->set_file_path(it->get()->file_path());
+ scriptInfo->set_url_source(it->get()->url_source());
+ scriptInfo->set_parser_error(it->get()->parser_error());
+ scriptInfo->force_disabled = (it->get()->force_disabled());
+
+ PrefUpdate update(prefs_, key, kUserScriptsList);
+ std::unique_ptr<::prefs::DictionaryValueUpdate> script_dict = update.Get();
+
+ script_dict->SetString(kScriptName, scriptInfo->name());
+ script_dict->SetString(kScriptDescription, scriptInfo->description());
+ script_dict->SetBoolean(kScriptIsEnabled, scriptInfo->enabled);
+ script_dict->SetString(kScriptVersion, scriptInfo->version());
+ script_dict->SetString(kScriptFilePath, scriptInfo->file_path());
+ script_dict->SetString(kScriptUrlSource, scriptInfo->url_source());
+ script_dict->SetString(kScriptParserError, scriptInfo->parser_error());
+ script_dict->SetBoolean(kScriptForceDisabled, scriptInfo->force_disabled);
+
+ std::string install_time_str =
+ base::NumberToString(scriptInfo->install_time.ToInternalValue());
+ script_dict->SetString(kScriptInstallTime, install_time_str);
+
+ if (!scriptInfo->enabled) {
+ it = user_scripts.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // remove script from prefs if no more present
+ std::vector<std::string> all_scripts_to_remove;
+ const base::Value::Dict& dict = prefs_->GetDict(kUserScriptsList).Clone();
+ for (const auto script_it : dict) {
+ const std::string& key = script_it.first;
+
+ if (std::find(all_scripts.begin(), all_scripts.end(), key) == all_scripts.end()) {
+ all_scripts_to_remove.push_back(key);
+ }
+ }
+
+ ::prefs::ScopedDictionaryPrefUpdate update(prefs_, kUserScriptsList);
+ std::unique_ptr<::prefs::DictionaryValueUpdate> update_dict = update.Get();
+ for (auto key : all_scripts_to_remove) {
+ update_dict->RemoveWithoutPathExpansion(key, nullptr);
+ }
+
+ return;
+}
+
+std::string UserScriptsPrefs::GetScriptsInfo() {
+ std::string json_string;
+
+ const base::Value::Dict& dict = prefs_->GetDict(kUserScriptsList);
+
+ base::JSONWriter::WriteWithOptions(
+ dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
+ base::TrimWhitespaceASCII(json_string, base::TRIM_ALL, &json_string);
+
+ return json_string;
+}
+
+std::unique_ptr<UserScriptsListPrefs::ScriptInfo> UserScriptsPrefs::CreateScriptInfoFromPrefs(
+ const std::string& script_id) const {
+
+ auto scriptInfo = std::make_unique<UserScriptsListPrefs::ScriptInfo>(
+ script_id, "", base::Time::Now(), false);
+
+ const base::Value::Dict& scripts = prefs_->GetDict(kUserScriptsList);
+ if (scripts.empty())
+ return scriptInfo;
+
+ const base::Value* script = scripts.Find(script_id);
+ if (!script)
+ return scriptInfo;
+
+ const base::Value::Dict& dict = script->GetDict();
+ const std::string* name = dict.FindString(kScriptName);
+ const std::string* description = dict.FindString(kScriptDescription);
+ const std::string* version = dict.FindString(kScriptVersion);
+ const std::string* file_path = dict.FindString(kScriptFilePath);
+ const std::string* url_source = dict.FindString(kScriptUrlSource);
+ const std::string* parser_error = dict.FindString(kScriptParserError);
+
+ scriptInfo->set_name( name ? *name : "no name" );
+ scriptInfo->set_description( description ? *description : "no description" );
+ scriptInfo->set_version( version ? *version : "no version" );
+ scriptInfo->enabled = dict.FindBool(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 = dict.FindBool(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) {
+ ::prefs::ScopedDictionaryPrefUpdate update(prefs_, kUserScriptsList);
+ std::unique_ptr<::prefs::DictionaryValueUpdate> const update_dict = update.Get();
+ update_dict->RemoveWithoutPathExpansion(script_id, nullptr);
+}
+
+void UserScriptsPrefs::SetScriptEnabled(const std::string& script_id, bool is_enabled) {
+ PrefUpdate update(prefs_, script_id, kUserScriptsList);
+ std::unique_ptr<::prefs::DictionaryValueUpdate> script_dict = update.Get();
+ bool force_disabled;
+ script_dict->GetBoolean(kScriptForceDisabled, &force_disabled);
+ if (force_disabled) is_enabled = false;
+ script_dict->SetBoolean(kScriptIsEnabled, is_enabled);
+}
+
+}
diff --git a/components/user_scripts/browser/user_script_prefs.h b/components/user_scripts/browser/user_script_prefs.h
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/browser/user_script_prefs.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
+#define USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
+
+#include "content/public/browser/browser_context.h"
+#include "components/prefs/pref_service.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+#include "user_script_pref_info.h"
+#include "../common/user_script.h"
+
+namespace user_scripts {
+
+namespace prefs {
+ extern const char kUserScriptsEnabled[];
+}
+
+class UserScriptsPrefs {
+ public:
+ UserScriptsPrefs(
+ PrefService* prefs);
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ bool IsEnabled();
+ void SetEnabled(bool enabled);
+
+ void StartupTryout(int number);
+ int GetCurrentStartupTryout();
+
+ void CompareWithPrefs(UserScriptList& user_scripts);
+
+ std::string GetScriptsInfo();
+ void RemoveScriptFromPrefs(const std::string& script_id);
+ void SetScriptEnabled(const std::string& script_id, bool is_enabled);
+
+ std::unique_ptr<UserScriptsListPrefs::ScriptInfo> CreateScriptInfoFromPrefs(
+ const std::string& script_id) const;
+
+ private:
+ raw_ptr<PrefService> prefs_;
+};
+
+}
+
+#endif // USERSCRIPTS_BROWSER_USERSCRIPT_PREFS_H_
diff --git a/components/user_scripts/browser/userscripts_browser_client.cc b/components/user_scripts/browser/userscripts_browser_client.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/userscripts_browser_client.cc
@@ -0,0 +1,78 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "userscripts_browser_client.h"
+
+#include "base/logging.h"
+
+#include "chrome/browser/browser_process.h"
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+
+#include "../common/user_scripts_features.h"
+#include "user_script_loader.h"
+#include "file_task_runner.h"
+#include "user_script_prefs.h"
+
+namespace user_scripts {
+
+namespace {
+
+// remember: was ExtensionsBrowserClient
+UserScriptsBrowserClient* g_userscripts_browser_client = NULL;
+
+} // namespace
+
+UserScriptsBrowserClient::UserScriptsBrowserClient() {}
+
+UserScriptsBrowserClient::~UserScriptsBrowserClient() = default;
+
+// static
+UserScriptsBrowserClient* UserScriptsBrowserClient::GetInstance() {
+ // only for browser process
+ if (!g_browser_process)
+ return NULL;
+
+ // singleton
+ if (g_userscripts_browser_client)
+ return g_userscripts_browser_client;
+
+ // make file task runner
+ GetUserScriptsFileTaskRunner().get();
+
+ // new instance singleton
+ g_userscripts_browser_client = new UserScriptsBrowserClient();
+
+ return g_userscripts_browser_client;
+}
+
+void UserScriptsBrowserClient::SetProfile(content::BrowserContext* context) {
+ browser_context_ = context;
+
+ prefs_ =
+ std::make_unique<user_scripts::UserScriptsPrefs>(
+ static_cast<Profile*>(context)->GetPrefs());
+
+ userscript_loader_ =
+ std::make_unique<user_scripts::UserScriptLoader>(browser_context_, prefs_.get());
+ if (prefs_->IsEnabled()) {
+ userscript_loader_->SetReady(true);
+ }
+}
+
+} // namespace user_scripts
diff --git a/components/user_scripts/browser/userscripts_browser_client.h b/components/user_scripts/browser/userscripts_browser_client.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/browser/userscripts_browser_client.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
+#define USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "content/public/browser/browser_context.h"
+
+#include "../common/user_script.h"
+#include "user_script_loader.h"
+#include "user_script_prefs.h"
+
+namespace user_scripts {
+
+class UserScriptsBrowserClient {
+ public:
+ UserScriptsBrowserClient();
+ UserScriptsBrowserClient(const UserScriptsBrowserClient&) = delete;
+ UserScriptsBrowserClient& operator=(const UserScriptsBrowserClient&) = delete;
+ virtual ~UserScriptsBrowserClient();
+
+ // Returns the single instance of |this|.
+ static UserScriptsBrowserClient* GetInstance();
+
+ void SetProfile(content::BrowserContext* context);
+
+ user_scripts::UserScriptsPrefs* GetPrefs() {
+ return prefs_.get();
+ }
+
+ user_scripts::UserScriptLoader* GetLoader() {
+ return userscript_loader_.get();
+ }
+
+ private:
+ std::unique_ptr<UserScriptList> scripts_;
+ raw_ptr<content::BrowserContext> browser_context_;
+ std::unique_ptr<user_scripts::UserScriptsPrefs> prefs_;
+ std::unique_ptr<user_scripts::UserScriptLoader> userscript_loader_;
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_BROWSER_USERSCRIPTS_BROWSER_CLIENT_H_
diff --git a/components/user_scripts/common/BUILD.gn b/components/user_scripts/common/BUILD.gn
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/BUILD.gn
@@ -0,0 +1,48 @@
+# 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 = [
+ "//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,15 @@
+// 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_
+
+namespace user_scripts {
+
+// The origin of injected CSS.
+enum CSSOrigin { /*CSS_ORIGIN_AUTHOR,*/ CSS_ORIGIN_USER };
+
+} // namespace user_scripts
+
+#endif // USERSCRIPTS_COMMON_CONSTANTS_H_
diff --git a/components/user_scripts/common/error_utils.cc b/components/user_scripts/common/error_utils.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/error_utils.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "error_utils.h"
+
+#include <initializer_list>
+
+#include "base/check_op.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace user_scripts {
+
+namespace {
+
+std::string FormatErrorMessageInternal(
+ base::StringPiece format,
+ std::initializer_list<base::StringPiece> args) {
+ std::string format_str = format.as_string();
+ base::StringTokenizer tokenizer(format_str, "*");
+ tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS);
+
+ std::vector<base::StringPiece> result_pieces;
+ auto* args_it = args.begin();
+ while (tokenizer.GetNext()) {
+ if (!tokenizer.token_is_delim()) {
+ result_pieces.push_back(tokenizer.token_piece());
+ continue;
+ }
+
+ CHECK_NE(args_it, args.end())
+ << "More placeholders (*) than substitutions.";
+
+ // Substitute the argument.
+ result_pieces.push_back(*args_it);
+ args_it++;
+ }
+
+ // Not all substitutions were consumed.
+ CHECK_EQ(args_it, args.end()) << "Fewer placeholders (*) than substitutions.";
+
+ return base::JoinString(result_pieces, "" /* separator */);
+}
+
+} // namespace
+
+std::string ErrorUtils::FormatErrorMessage(base::StringPiece format,
+ base::StringPiece s1) {
+ return FormatErrorMessageInternal(format, {s1});
+}
+
+} // namespace user_scripts
diff --git a/components/user_scripts/common/error_utils.h b/components/user_scripts/common/error_utils.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/error_utils.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_ERROR_UTILS_H_
+#define USERSCRIPTS_COMMON_ERROR_UTILS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace user_scripts {
+
+class ErrorUtils {
+ public:
+ // Creates an error messages from a pattern.
+ static std::string FormatErrorMessage(base::StringPiece format,
+ base::StringPiece s1);
+
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_COMMON_ERROR_UTILS_H_
diff --git a/components/user_scripts/common/extension_message_generator.cc b/components/user_scripts/common/extension_message_generator.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/extension_message_generator.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "components/user_scripts/common/extension_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "components/user_scripts/common/extension_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "components/user_scripts/common/extension_message_generator.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "components/user_scripts/common/extension_message_generator.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "components/user_scripts/common/extension_message_generator.h"
+} // namespace IPC
diff --git a/components/user_scripts/common/extension_message_generator.h b/components/user_scripts/common/extension_message_generator.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/extension_message_generator.h
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Multiply-included file, hence no include guard.
+
+#undef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+#include "extension_messages.h"
+#ifndef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+#error "Failed to include header extension_messages.h"
+#endif
diff --git a/components/user_scripts/common/extension_messages.cc b/components/user_scripts/common/extension_messages.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/extension_messages.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extension_messages.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "content/public/common/common_param_traits.h"
+
+namespace IPC {
+
+void ParamTraits<HostID>::Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.type());
+ WriteParam(m, p.id());
+}
+
+bool ParamTraits<HostID>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ HostID::HostType type;
+ std::string id;
+ if (!ReadParam(m, iter, &type))
+ return false;
+ if (!ReadParam(m, iter, &id))
+ return false;
+ *r = HostID(type, id);
+ return true;
+}
+
+void ParamTraits<HostID>::Log(
+ const param_type& p, std::string* l) {
+ LogParam(p.type(), l);
+ LogParam(p.id(), l);
+}
+
+} // namespace IPC
diff --git a/components/user_scripts/common/extension_messages.h b/components/user_scripts/common/extension_messages.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/extension_messages.h
@@ -0,0 +1,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.
+
+// IPC messages for extensions.
+
+#ifndef USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+#define USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/values.h"
+#include "content/public/common/common_param_traits.h"
+#include "constants.h"
+#include "host_id.h"
+#include "ipc/ipc_message_start.h"
+#include "ipc/ipc_message_macros.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+#define IPC_MESSAGE_START ExtensionMsgStart
+
+IPC_ENUM_TRAITS_MAX_VALUE(HostID::HostType, HostID::HOST_TYPE_LAST)
+
+// Singly-included section for custom IPC traits.
+#ifndef INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+#define INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+
+namespace IPC {
+
+template <>
+struct ParamTraits<HostID> {
+ typedef HostID param_type;
+ static void Write(base::Pickle* m, const param_type& p);
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+
+} // namespace IPC
+
+#endif // INTERNAL_USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
+
+// Notification that the user scripts have been updated. It has one
+// ReadOnlySharedMemoryRegion argument consisting of the pickled script data.
+// This memory region is valid in the context of the renderer.
+// If |owner| is not empty, then the shared memory handle refers to |owner|'s
+// programmatically-defined scripts. Otherwise, the handle refers to all
+// hosts' statically defined scripts. So far, only extension-hosts support
+// statically defined scripts; WebUI-hosts don't.
+// If |changed_hosts| is not empty, only the host in that set will
+// be updated. Otherwise, all hosts that have scripts in the shared memory
+// region will be updated. Note that the empty set => all hosts case is not
+// supported for per-extension programmatically-defined script regions; in such
+// regions, the owner is expected to list itself as the only changed host.
+// If |whitelisted_only| is true, this process should only run whitelisted
+// scripts and not all user scripts.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateUserScripts,
+ base::ReadOnlySharedMemoryRegion)
+
+#endif // USERSCRIPTS_COMMON_EXTENSION_MESSAGES_H_
diff --git a/components/user_scripts/common/host_id.cc b/components/user_scripts/common/host_id.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/host_id.cc
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "host_id.h"
+
+#include <tuple>
+
+HostID::HostID()
+ : type_(HostType::EXTENSIONS) {
+}
+
+HostID::HostID(HostType type, const std::string& id)
+ : type_(type), id_(id) {
+}
+
+HostID::HostID(const HostID& host_id)
+ : type_(host_id.type()),
+ id_(host_id.id()) {
+}
+
+HostID::~HostID() {
+}
+
+bool HostID::operator<(const HostID& host_id) const {
+ return std::tie(type_, id_) < std::tie(host_id.type_, host_id.id_);
+}
+
+bool HostID::operator==(const HostID& host_id) const {
+ return type_ == host_id.type_ && id_ == host_id.id_;
+}
diff --git a/components/user_scripts/common/host_id.h b/components/user_scripts/common/host_id.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/host_id.h
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_HOST_ID_H_
+#define USERSCRIPTS_COMMON_HOST_ID_H_
+
+#include <string>
+
+// IDs of hosts who own user scripts.
+// A HostID is immutable after creation.
+struct HostID {
+ enum HostType { EXTENSIONS, WEBUI, HOST_TYPE_LAST = WEBUI };
+
+ HostID();
+ HostID(HostType type, const std::string& id);
+ HostID(const HostID& host_id);
+ ~HostID();
+
+ bool operator<(const HostID& host_id) const;
+ bool operator==(const HostID& host_id) const;
+
+ HostType type() const { return type_; }
+ const std::string& id() const { return id_; }
+
+ private:
+ // The type of the host.
+ HostType type_;
+
+ // Similar to extension_id, host_id is a unique indentifier for a host,
+ // e.g., an Extension or WebUI.
+ std::string id_;
+};
+
+#endif // USERSCRIPTS_COMMON_HOST_ID_H_
diff --git a/components/user_scripts/common/script_constants.h b/components/user_scripts/common/script_constants.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/script_constants.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
+#define USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
+
+namespace user_scripts {
+
+// Whether to fall back to matching the origin for frames where the URL
+// cannot be matched directly, such as those with about: or data: schemes.
+enum class MatchOriginAsFallbackBehavior {
+ // Never fall back on the origin; this means scripts will never match on
+ // these frames.
+ kNever,
+ // Match the origin only for about:-scheme frames, and then climb the frame
+ // tree to find an appropriate ancestor to get a full URL (including path).
+ // This is for supporting the "match_about_blank" key.
+ // TODO(devlin): I wonder if we could simplify this to be "MatchForAbout",
+ // and not worry about climbing the frame tree. It would be a behavior
+ // change, but I wonder how many extensions it would impact in practice.
+ kMatchForAboutSchemeAndClimbTree,
+ // Match the origin as a fallback whenever applicable. This won't have a
+ // corresponding path.
+ kAlways,
+};
+
+// TODO(devlin): Move the other non-UserScript-specific constants like
+// RunLocation and InjectionType from UserScript into here.
+
+}
+
+#endif // USERSCRIPTS_COMMON_SCRIPT_CONSTANTS_H_
diff --git a/components/user_scripts/common/url_pattern.cc b/components/user_scripts/common/url_pattern.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/url_pattern.cc
@@ -0,0 +1,803 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "url_pattern.h"
+
+#include <stddef.h>
+
+#include <ostream>
+
+#include "base/logging.h"
+#include "base/strings/pattern.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "content/public/common/url_constants.h"
+#include "constants.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/url_util.h"
+
+const char URLPattern::kAllUrlsPattern[] = "<all_urls>";
+
+namespace {
+
+// TODO(aa): What about more obscure schemes like javascript: ?
+// Note: keep this array in sync with kValidSchemeMasks.
+const char* const kValidSchemes[] = {
+ url::kHttpScheme, url::kHttpsScheme,
+ url::kFileScheme, url::kFtpScheme,
+ /*content::kChromeUIScheme,*/ /*extensions::kExtensionScheme,*/
+ url::kFileSystemScheme, url::kWsScheme,
+ url::kWssScheme, url::kDataScheme,
+ url::kUrnScheme,
+};
+
+const int kValidSchemeMasks[] = {
+ URLPattern::SCHEME_HTTP, URLPattern::SCHEME_HTTPS,
+ URLPattern::SCHEME_FILE, URLPattern::SCHEME_FTP,
+ /*URLPattern::SCHEME_CHROMEUI,*/ /*URLPattern::SCHEME_EXTENSION,*/
+ URLPattern::SCHEME_FILESYSTEM, URLPattern::SCHEME_WS,
+ URLPattern::SCHEME_WSS, URLPattern::SCHEME_DATA,
+ URLPattern::SCHEME_URN,
+};
+
+static_assert(std::size(kValidSchemes) == std::size(kValidSchemeMasks),
+ "must keep these arrays in sync");
+
+const char kParseSuccess[] = "Success.";
+const char kParseErrorMissingSchemeSeparator[] = "Missing scheme separator.";
+const char kParseErrorInvalidScheme[] = "Invalid scheme.";
+const char kParseErrorWrongSchemeType[] = "Wrong scheme type.";
+const char kParseErrorEmptyHost[] = "Host can not be empty.";
+const char kParseErrorInvalidHostWildcard[] = "Invalid host wildcard.";
+const char kParseErrorEmptyPath[] = "Empty path.";
+const char kParseErrorInvalidPort[] = "Invalid port.";
+const char kParseErrorInvalidHost[] = "Invalid host.";
+
+// Message explaining each URLPattern::ParseResult.
+const char* const kParseResultMessages[] = {
+ kParseSuccess,
+ kParseErrorMissingSchemeSeparator,
+ kParseErrorInvalidScheme,
+ kParseErrorWrongSchemeType,
+ kParseErrorEmptyHost,
+ kParseErrorInvalidHostWildcard,
+ kParseErrorEmptyPath,
+ kParseErrorInvalidPort,
+ kParseErrorInvalidHost,
+};
+
+static_assert(static_cast<int>(URLPattern::ParseResult::kNumParseResults) ==
+ std::size(kParseResultMessages),
+ "must add message for each parse result");
+
+const char kPathSeparator[] = "/";
+
+bool IsStandardScheme(base::StringPiece scheme) {
+ // "*" gets the same treatment as a standard scheme.
+ if (scheme == "*")
+ return true;
+
+ return url::IsStandard(scheme.data(),
+ url::Component(0, static_cast<int>(scheme.length())));
+}
+
+bool IsValidPortForScheme(base::StringPiece scheme, base::StringPiece port) {
+ if (port == "*")
+ return true;
+
+ // Only accept non-wildcard ports if the scheme uses ports.
+ if (url::DefaultPortForScheme(scheme.data(), scheme.length()) ==
+ url::PORT_UNSPECIFIED) {
+ return false;
+ }
+
+ int parsed_port = url::PORT_UNSPECIFIED;
+ if (!base::StringToInt(port, &parsed_port))
+ return false;
+ return (parsed_port >= 0) && (parsed_port < 65536);
+}
+
+// Returns |path| with the trailing wildcard stripped if one existed.
+//
+// The functions that rely on this (OverlapsWith and Contains) are only
+// called for the patterns inside URLPatternSet. In those cases, we know that
+// the path will have only a single wildcard at the end. This makes figuring
+// out overlap much easier. It seems like there is probably a computer-sciency
+// way to solve the general case, but we don't need that yet.
+base::StringPiece StripTrailingWildcard(base::StringPiece path) {
+ if (base::EndsWith(path, "*"))
+ path.remove_suffix(1);
+ return path;
+}
+
+// Removes trailing dot from |host_piece| if any.
+base::StringPiece CanonicalizeHostForMatching(base::StringPiece host_piece) {
+ if (base::EndsWith(host_piece, "."))
+ host_piece.remove_suffix(1);
+ return host_piece;
+}
+
+} // namespace
+
+// static
+bool URLPattern::IsValidSchemeForExtensions(base::StringPiece scheme) {
+ for (size_t i = 0; i < std::size(kValidSchemes); ++i) {
+ if (scheme == kValidSchemes[i])
+ return true;
+ }
+ return false;
+}
+
+// static
+int URLPattern::GetValidSchemeMaskForExtensions() {
+ int result = 0;
+ for (size_t i = 0; i < std::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 ipv6_host_end_pos = host_and_port.find(']');
+ if (ipv6_host_end_pos == base::StringPiece::npos)
+ return ParseResult::kInvalidHost;
+ if (ipv6_host_end_pos == 1)
+ return ParseResult::kEmptyHost;
+
+ if (ipv6_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[ipv6_host_end_pos + 1] != ':')
+ return ParseResult::kInvalidHost;
+
+ port_separator_pos = ipv6_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 < std::size(kValidSchemes); ++i) {
+ if (scheme == kValidSchemes[i] && (valid_schemes_ & kValidSchemeMasks[i]))
+ return true;
+ }
+
+ return false;
+}
+
+void URLPattern::SetPath(base::StringPiece path) {
+ spec_.clear();
+ path_.assign(path.data(), path.size());
+ path_escaped_ = path_;
+ base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&path_escaped_, 0, "?", "\\?");
+}
+
+bool URLPattern::SetPort(base::StringPiece port) {
+ spec_.clear();
+ if (IsValidPortForScheme(scheme_, port)) {
+ port_.assign(port.data(), port.size());
+ return true;
+ }
+ return false;
+}
+
+bool URLPattern::MatchesURL(const GURL& test) const {
+ // Invalid URLs can never match.
+ if (!test.is_valid())
+ return false;
+
+ const GURL* test_url = &test;
+ bool has_inner_url = test.inner_url() != nullptr;
+
+ if (has_inner_url) {
+ if (!test.SchemeIsFileSystem())
+ return false; // The only nested URLs we handle are filesystem URLs.
+ test_url = test.inner_url();
+ }
+
+ // Ensure the scheme matches first, since <all_urls> may not match this URL if
+ // the scheme is excluded.
+ if (!MatchesScheme(test_url->scheme_piece()))
+ return false;
+
+ if (match_all_urls_)
+ return true;
+
+ // Unless |match_all_urls_| is true, the grammar only permits matching
+ // URLs with nonempty paths.
+ if (!test.has_path())
+ return false;
+
+ std::string path_for_request = test.PathForRequest();
+ if (has_inner_url) {
+ path_for_request = base::StringPrintf("%s%s", test_url->path_piece().data(),
+ path_for_request.c_str());
+ }
+
+ return MatchesSecurityOriginHelper(*test_url) &&
+ MatchesPath(path_for_request);
+}
+
+bool URLPattern::MatchesSecurityOrigin(const GURL& test) const {
+ const GURL* test_url = &test;
+ bool has_inner_url = test.inner_url() != NULL;
+
+ if (has_inner_url) {
+ if (!test.SchemeIsFileSystem())
+ return false; // The only nested URLs we handle are filesystem URLs.
+ test_url = test.inner_url();
+ }
+
+ if (!MatchesScheme(test_url->scheme()))
+ return false;
+
+ if (match_all_urls_)
+ return true;
+
+ return MatchesSecurityOriginHelper(*test_url);
+}
+
+bool URLPattern::MatchesScheme(base::StringPiece test) const {
+ if (!IsValidScheme(test))
+ return false;
+
+ return scheme_ == "*" || test == scheme_;
+}
+
+bool URLPattern::MatchesHost(base::StringPiece host) const {
+ // TODO(devlin): This is a bit sad. Parsing urls is expensive. However, it's
+ // important that we do this conversion to a GURL in order to canonicalize the
+ // host (the pattern's host_ already is canonicalized from Parse()). We can't
+ // just do string comparison.
+ return MatchesHost(
+ GURL(base::StringPrintf("%s%s%s/", url::kHttpScheme,
+ url::kStandardSchemeSeparator, host.data())));
+}
+
+bool URLPattern::MatchesHost(const GURL& test) const {
+ base::StringPiece test_host(CanonicalizeHostForMatching(test.host_piece()));
+ const base::StringPiece pattern_host(CanonicalizeHostForMatching(host_));
+
+ // If the hosts are exactly equal, we have a match.
+ if (test_host == pattern_host)
+ return true;
+
+ // If we're matching subdomains, and we have no host in the match pattern,
+ // that means that we're matching all hosts, which means we have a match no
+ // matter what the test host is.
+ if (match_subdomains_ && pattern_host.empty())
+ return true;
+
+ // Otherwise, we can only match if our match pattern matches subdomains.
+ if (!match_subdomains_)
+ return false;
+
+ // We don't do subdomain matching against IP addresses, so we can give up now
+ // if the test host is an IP address.
+ if (test.HostIsIPAddress())
+ return false;
+
+ // Check if the test host is a subdomain of our host.
+ if (test_host.length() <= (pattern_host.length() + 1))
+ return false;
+
+ if (!base::EndsWith(test_host, pattern_host))
+ return false;
+
+ return test_host[test_host.length() - pattern_host.length() - 1] == '.';
+}
+
+bool URLPattern::MatchesEffectiveTld(
+ net::registry_controlled_domains::PrivateRegistryFilter private_filter,
+ net::registry_controlled_domains::UnknownRegistryFilter unknown_filter)
+ const {
+ // Check if it matches all urls or is a pattern like http://*/*.
+ if (match_all_urls_ || (match_subdomains_ && host_.empty()))
+ return true;
+
+ // If this doesn't even match subdomains, it can't possibly be a TLD wildcard.
+ if (!match_subdomains_)
+ return false;
+
+ // If there was more than just a TLD in the host (e.g., *.foobar.com), it
+ // doesn't match all hosts in an effective TLD.
+ if (net::registry_controlled_domains::HostHasRegistryControlledDomain(
+ host_, unknown_filter, private_filter)) {
+ return false;
+ }
+
+ // At this point the host could either be just a TLD ("com") or some unknown
+ // TLD-like string ("notatld"). To disambiguate between them construct a
+ // fake URL, and check the registry.
+ //
+ // If we recognized this TLD, then this is a pattern like *.com, and it
+ // matches an effective TLD.
+ return net::registry_controlled_domains::HostHasRegistryControlledDomain(
+ "notatld." + host_, unknown_filter, private_filter);
+}
+
+bool URLPattern::MatchesSingleOrigin() const {
+ // Strictly speaking, the port is part of the origin, but in URLPattern it
+ // defaults to *. It's not very interesting anyway, so leave it out.
+ return !MatchesEffectiveTld() && scheme_ != "*" && !match_subdomains_;
+}
+
+bool URLPattern::MatchesPath(base::StringPiece test) const {
+ // Make the behaviour of OverlapsWith consistent with MatchesURL, which is
+ // need to match hosted apps on e.g. 'google.com' also run on 'google.com/'.
+ // The below if is a no-copy way of doing (test + "/*" == path_escaped_).
+ if (path_escaped_.length() == test.length() + 2 &&
+ base::StartsWith(path_escaped_.c_str(), test) &&
+ base::EndsWith(path_escaped_, "/*")) {
+ return true;
+ }
+
+ return base::MatchPattern(test, path_escaped_);
+}
+
+const std::string& URLPattern::GetAsString() const {
+ if (!spec_.empty())
+ return spec_;
+
+ if (match_all_urls_) {
+ spec_ = kAllUrlsPattern;
+ return spec_;
+ }
+
+ bool standard_scheme = IsStandardScheme(scheme_);
+
+ std::string spec = scheme_ +
+ (standard_scheme ? url::kStandardSchemeSeparator : ":");
+
+ if (scheme_ != url::kFileScheme && standard_scheme) {
+ if (match_subdomains_) {
+ spec += "*";
+ if (!host_.empty())
+ spec += ".";
+ }
+
+ if (!host_.empty())
+ spec += host_;
+
+ if (port_ != "*") {
+ spec += ":";
+ spec += port_;
+ }
+ }
+
+ if (!path_.empty())
+ spec += path_;
+
+ spec_ = std::move(spec);
+ return spec_;
+}
+
+bool URLPattern::OverlapsWith(const URLPattern& other) const {
+ if (match_all_urls() || other.match_all_urls())
+ return true;
+ return (MatchesAnyScheme(other.GetExplicitSchemes()) ||
+ other.MatchesAnyScheme(GetExplicitSchemes()))
+ && (MatchesHost(other.host()) || other.MatchesHost(host()))
+ && (MatchesPortPattern(other.port()) || other.MatchesPortPattern(port()))
+ && (MatchesPath(StripTrailingWildcard(other.path())) ||
+ other.MatchesPath(StripTrailingWildcard(path())));
+}
+
+bool URLPattern::Contains(const URLPattern& other) const {
+ // Important: it's not enough to just check match_all_urls(); we also need to
+ // make sure that the schemes in this pattern are a superset of those in
+ // |other|.
+ if (match_all_urls() &&
+ (valid_schemes_ & other.valid_schemes_) == other.valid_schemes_) {
+ return true;
+ }
+
+ return MatchesAllSchemes(other.GetExplicitSchemes()) &&
+ MatchesHost(other.host()) &&
+ (!other.match_subdomains_ || match_subdomains_) &&
+ MatchesPortPattern(other.port()) &&
+ MatchesPath(StripTrailingWildcard(other.path()));
+}
+
+absl::optional<URLPattern> URLPattern::CreateIntersection(
+ const URLPattern& other) const {
+ // Easy case: Schemes don't overlap. Return nullopt.
+ int intersection_schemes = URLPattern::SCHEME_NONE;
+ if (valid_schemes_ == URLPattern::SCHEME_ALL)
+ intersection_schemes = other.valid_schemes_;
+ else if (other.valid_schemes_ == URLPattern::SCHEME_ALL)
+ intersection_schemes = valid_schemes_;
+ else
+ intersection_schemes = valid_schemes_ & other.valid_schemes_;
+
+ if (intersection_schemes == URLPattern::SCHEME_NONE)
+ return absl::nullopt;
+
+ {
+ // In a few cases, we can (mostly) return a copy of one of the patterns.
+ // This can happen when either:
+ // - The URLPattern's are identical (possibly excluding valid_schemes_)
+ // - One of the patterns has match_all_urls() equal to true.
+ // NOTE(devlin): Theoretically, we could use Contains() instead of
+ // match_all_urls() here. However, Contains() strips the trailing wildcard
+ // from the path, which could yield the incorrect result.
+ const URLPattern* copy_source = nullptr;
+ if (*this == other || other.match_all_urls())
+ copy_source = this;
+ else if (match_all_urls())
+ copy_source = &other;
+
+ if (copy_source) {
+ // NOTE: equality checks don't take into account valid_schemes_, and
+ // schemes can be different in the case of match_all_urls() as well, so
+ // we can't always just return *copy_source.
+ if (intersection_schemes == copy_source->valid_schemes_)
+ return *copy_source;
+ URLPattern result(intersection_schemes);
+ ParseResult parse_result = result.Parse(copy_source->GetAsString());
+ CHECK_EQ(ParseResult::kSuccess, parse_result);
+ return result;
+ }
+ }
+
+ // No more easy cases. Go through component by component to find the patterns
+ // that intersect.
+
+ // Note: Alias the function type (rather than using auto) because
+ // MatchesHost() is overloaded.
+ using match_function_type = bool (URLPattern::*)(base::StringPiece) const;
+
+ auto get_intersection = [this, &other](base::StringPiece own_str,
+ base::StringPiece other_str,
+ match_function_type match_function,
+ base::StringPiece* out) {
+ if ((this->*match_function)(other_str)) {
+ *out = other_str;
+ return true;
+ }
+ if ((other.*match_function)(own_str)) {
+ *out = own_str;
+ return true;
+ }
+ return false;
+ };
+
+ base::StringPiece scheme;
+ base::StringPiece host;
+ base::StringPiece port;
+ base::StringPiece path;
+ // If any pieces fail to overlap, then there is no intersection.
+ if (!get_intersection(scheme_, other.scheme_, &URLPattern::MatchesScheme,
+ &scheme) ||
+ !get_intersection(host_, other.host_, &URLPattern::MatchesHost, &host) ||
+ !get_intersection(port_, other.port_, &URLPattern::MatchesPortPattern,
+ &port) ||
+ !get_intersection(path_, other.path_, &URLPattern::MatchesPath, &path)) {
+ return absl::nullopt;
+ }
+
+ // Only match subdomains if both patterns match subdomains.
+ base::StringPiece subdomains;
+ if (match_subdomains_ && other.match_subdomains_) {
+ // The host may be empty (e.g., in the case of *://*/* - in that case, only
+ // append '*' instead of '*.'.
+ subdomains = host.empty() ? "*" : "*.";
+ }
+
+ base::StringPiece scheme_separator =
+ IsStandardScheme(scheme) ? url::kStandardSchemeSeparator : ":";
+
+ std::string pattern_str = base::StrCat(
+ {scheme, scheme_separator, subdomains, host, ":", port, path});
+
+ URLPattern pattern(intersection_schemes);
+ ParseResult result = pattern.Parse(pattern_str);
+ // TODO(devlin): I don't think there's any way this should ever fail, but
+ // use a CHECK() to flush any cases out. If nothing crops up, downgrade this
+ // to a DCHECK in M72.
+ CHECK_EQ(ParseResult::kSuccess, result);
+
+ return pattern;
+}
+
+bool URLPattern::MatchesAnyScheme(
+ const std::vector<std::string>& schemes) const {
+ for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
+ if (MatchesScheme(*i))
+ return true;
+ }
+
+ return false;
+}
+
+bool URLPattern::MatchesAllSchemes(
+ const std::vector<std::string>& schemes) const {
+ for (auto i = schemes.cbegin(); i != schemes.cend(); ++i) {
+ if (!MatchesScheme(*i))
+ return false;
+ }
+
+ return true;
+}
+
+bool URLPattern::MatchesSecurityOriginHelper(const GURL& test) const {
+ // Ignore hostname if scheme is file://.
+ if (scheme_ != url::kFileScheme && !MatchesHost(test))
+ return false;
+
+ if (!MatchesPortPattern(base::NumberToString(test.EffectiveIntPort())))
+ return false;
+
+ return true;
+}
+
+bool URLPattern::MatchesPortPattern(base::StringPiece port) const {
+ return port_ == "*" || port_ == port;
+}
+
+std::vector<std::string> URLPattern::GetExplicitSchemes() const {
+ std::vector<std::string> result;
+
+ if (scheme_ != "*" && !match_all_urls_ && IsValidScheme(scheme_)) {
+ result.push_back(scheme_);
+ return result;
+ }
+
+ for (size_t i = 0; i < std::size(kValidSchemes); ++i) {
+ if (MatchesScheme(kValidSchemes[i])) {
+ result.push_back(kValidSchemes[i]);
+ }
+ }
+
+ return result;
+}
+
+std::vector<URLPattern> URLPattern::ConvertToExplicitSchemes() const {
+ std::vector<std::string> explicit_schemes = GetExplicitSchemes();
+ std::vector<URLPattern> result;
+
+ for (std::vector<std::string>::const_iterator i = explicit_schemes.begin();
+ i != explicit_schemes.end(); ++i) {
+ URLPattern temp = *this;
+ temp.SetScheme(*i);
+ temp.SetMatchAllURLs(false);
+ result.push_back(temp);
+ }
+
+ return result;
+}
+
+// static
+const char* URLPattern::GetParseResultString(
+ URLPattern::ParseResult parse_result) {
+ return kParseResultMessages[static_cast<int>(parse_result)];
+}
diff --git a/components/user_scripts/common/url_pattern.h b/components/user_scripts/common/url_pattern.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/url_pattern.h
@@ -0,0 +1,302 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef USERSCRIPTS_COMMON_URL_PATTERN_H_
+#define USERSCRIPTS_COMMON_URL_PATTERN_H_
+
+#include <functional>
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+
+class GURL;
+
+// A pattern that can be used to match URLs. A URLPattern is a very restricted
+// subset of URL syntax:
+//
+// <url-pattern> := <scheme>://<host><port><path> | '<all_urls>'
+// <scheme> := '*' | 'http' | 'https' | 'file' | 'ftp' | 'chrome' |
+// 'chrome-extension' | 'filesystem'
+// <host> := '*' | <IPv4 address> | [<IPv6 address>] |
+// '*.' <anychar except '/' and '*'>+
+// <port> := [':' ('*' | <port number between 0 and 65535>)]
+// <path> := '/' <any chars>
+//
+// * Host is not used when the scheme is 'file'.
+// * The path can have embedded '*' characters which act as glob wildcards.
+// * '<all_urls>' is a special pattern that matches any valid URL that contains
+// a valid scheme (as specified by valid_schemes_).
+// * The '*' scheme pattern excludes file URLs.
+//
+// Examples of valid patterns:
+// - http://*/*
+// - http://*/foo*
+// - https://*.google.com/foo*bar
+// - file://monkey*
+// - http://127.0.0.1/*
+// - http://[2607:f8b0:4005:805::200e]/*
+//
+// Examples of invalid patterns:
+// - http://* -- path not specified
+// - http://*foo/bar -- * not allowed as substring of host component
+// - http://foo.*.bar/baz -- * must be first component
+// - http:/bar -- scheme separator not found
+// - foo://* -- invalid scheme
+// - chrome:// -- we don't support chrome internal URLs
+class URLPattern {
+ public:
+ // A collection of scheme bitmasks for use with valid_schemes.
+ enum SchemeMasks {
+ SCHEME_NONE = 0,
+ SCHEME_HTTP = 1 << 0,
+ SCHEME_HTTPS = 1 << 1,
+ SCHEME_FILE = 1 << 2,
+ SCHEME_FTP = 1 << 3,
+ SCHEME_CHROMEUI = 1 << 4,
+ SCHEME_EXTENSION = 1 << 5,
+ SCHEME_FILESYSTEM = 1 << 6,
+ SCHEME_WS = 1 << 7,
+ SCHEME_WSS = 1 << 8,
+ SCHEME_DATA = 1 << 9,
+ SCHEME_URN = 1 << 10,
+
+ // IMPORTANT!
+ // SCHEME_ALL will match every scheme, including chrome://, chrome-
+ // extension://, about:, etc. Because this has lots of security
+ // implications, third-party extensions should usually not be able to get
+ // access to URL patterns initialized this way. If there is a reason
+ // for violating this general rule, document why this it safe.
+ SCHEME_ALL = -1,
+ };
+
+ // Error codes returned from Parse().
+ enum class ParseResult {
+ kSuccess = 0,
+ kMissingSchemeSeparator,
+ kInvalidScheme,
+ kWrongSchemeSeparator,
+ kEmptyHost,
+ kInvalidHostWildcard,
+ kEmptyPath,
+ kInvalidPort,
+ kInvalidHost,
+ kNumParseResults,
+ };
+
+ // The <all_urls> string pattern.
+ static const char kAllUrlsPattern[];
+
+ // Returns true if the given |scheme| is considered valid for extensions.
+ static bool IsValidSchemeForExtensions(base::StringPiece scheme);
+
+ // Returns the mask for all schemes considered valid for extensions.
+ static int GetValidSchemeMaskForExtensions();
+
+ explicit URLPattern(int valid_schemes);
+
+ // Convenience to construct a URLPattern from a string. If the string is not
+ // known ahead of time, use Parse() instead, which returns success or failure.
+ URLPattern(int valid_schemes, base::StringPiece pattern);
+
+ URLPattern();
+ URLPattern(const URLPattern& other);
+ URLPattern(URLPattern&& other);
+ ~URLPattern();
+
+ URLPattern& operator=(const URLPattern& other);
+ URLPattern& operator=(URLPattern&& other);
+
+ bool operator<(const URLPattern& other) const;
+ bool operator>(const URLPattern& other) const;
+ bool operator==(const URLPattern& other) const;
+
+ // Initializes this instance by parsing the provided string. Returns
+ // URLPattern::ParseResult::kSuccess on success, or an error code otherwise.
+ // On failure, this instance will have some intermediate values and is in an
+ // invalid state.
+ ParseResult Parse(base::StringPiece pattern_str);
+
+ // Gets the bitmask of valid schemes.
+ int valid_schemes() const { return valid_schemes_; }
+ void SetValidSchemes(int valid_schemes);
+
+ // Gets the host the pattern matches. This can be an empty string if the
+ // pattern matches all hosts (the input was <scheme>://*/<whatever>).
+ const std::string& host() const { return host_; }
+ void SetHost(base::StringPiece host);
+
+ // Gets whether to match subdomains of host().
+ bool match_subdomains() const { return match_subdomains_; }
+ void SetMatchSubdomains(bool val);
+
+ // Gets the path the pattern matches with the leading slash. This can have
+ // embedded asterisks which are interpreted using glob rules.
+ const std::string& path() const { return path_; }
+ void SetPath(base::StringPiece path);
+
+ // Returns true if this pattern matches all (valid) urls.
+ bool match_all_urls() const { return match_all_urls_; }
+ void SetMatchAllURLs(bool val);
+
+ // Sets the scheme for pattern matches. This can be a single '*' if the
+ // pattern matches all valid schemes (as defined by the valid_schemes_
+ // property). Returns false on failure (if the scheme is not valid).
+ bool SetScheme(base::StringPiece scheme);
+ // Note: You should use MatchesScheme() instead of this getter unless you
+ // absolutely need the exact scheme. This is exposed for testing.
+ const std::string& scheme() const { return scheme_; }
+
+ // Returns true if the specified scheme can be used in this URL pattern, and
+ // false otherwise. Uses valid_schemes_ to determine validity.
+ bool IsValidScheme(base::StringPiece scheme) const;
+
+ // Returns true if this instance matches the specified URL. Always returns
+ // false for invalid URLs.
+ bool MatchesURL(const GURL& test) const;
+
+ // Returns true if this instance matches the specified security origin.
+ bool MatchesSecurityOrigin(const GURL& test) const;
+
+ // Returns true if |test| matches our scheme.
+ // Note that if test is "filesystem", this may fail whereas MatchesURL
+ // may succeed. MatchesURL is smart enough to look at the inner_url instead
+ // of the outer "filesystem:" part.
+ bool MatchesScheme(base::StringPiece test) const;
+
+ // Returns true if |test| matches our host.
+ bool MatchesHost(base::StringPiece test) const;
+ bool MatchesHost(const GURL& test) const;
+
+ // Returns true if |test| matches our path.
+ bool MatchesPath(base::StringPiece test) const;
+
+ // Returns true if the pattern matches all patterns in an (e)TLD. This
+ // includes patterns like *://*.com/*, *://*.co.uk/*, etc. A pattern that
+ // matches all domains (e.g., *://*/*) will return true.
+ // |private_filter| specifies whether private registries (like appspot.com)
+ // should be considered; if included, patterns like *://*.appspot.com/* will
+ // return true. By default, we exclude private registries (so *.appspot.com
+ // returns false).
+ // Note: This is an expensive method, and should be used sparingly!
+ // You should probably use URLPatternSet::ShouldWarnAllHosts(), which is
+ // cached.
+ bool MatchesEffectiveTld(
+ net::registry_controlled_domains::PrivateRegistryFilter private_filter =
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES,
+ net::registry_controlled_domains::UnknownRegistryFilter unknown_filter =
+ net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES) const;
+
+ // Returns true if the pattern only matches a single origin. The pattern may
+ // include a path.
+ bool MatchesSingleOrigin() const;
+
+ // Sets the port. Returns false if the port is invalid.
+ bool SetPort(base::StringPiece port);
+ const std::string& port() const { return port_; }
+
+ // Returns a string representing this instance.
+ const std::string& GetAsString() const;
+
+ // Determines whether there is a URL that would match this instance and
+ // another instance. This method is symmetrical: Calling
+ // other.OverlapsWith(this) would result in the same answer.
+ bool OverlapsWith(const URLPattern& other) const;
+
+ // Returns true if this pattern matches all possible URLs that |other| can
+ // match. For example, http://*.google.com encompasses http://www.google.com.
+ bool Contains(const URLPattern& other) const;
+
+ // Creates a new URLPattern that represents the intersection of this
+ // URLPattern with the |other|, or base::nullopt if no intersection exists.
+ // For instance, given the patterns http://*.google.com/* and
+ // *://maps.google.com/*, the intersection is http://maps.google.com/*.
+ // NOTES:
+ // - Though scheme intersections are supported, the serialization of
+ // URLPatternSet does not record them. Be sure that this is safe for your
+ // use cases.
+ // - Path intersection is done on a best-effort basis. If one path clearly
+ // contains another, it will be handled correctly, but this method does not
+ // deal with cases like /*a* and /*b* (where technically the intersection
+ // is /*a*b*|/*b*a*); the intersection returned for that case will be empty.
+ absl::optional<URLPattern> CreateIntersection(const URLPattern& other) const;
+
+ // Converts this URLPattern into an equivalent set of URLPatterns that don't
+ // use a wildcard in the scheme component. If this URLPattern doesn't use a
+ // wildcard scheme, then the returned set will contain one element that is
+ // equivalent to this instance.
+ std::vector<URLPattern> ConvertToExplicitSchemes() const;
+
+ static bool EffectiveHostCompare(const URLPattern& a, const URLPattern& b) {
+ if (a.match_all_urls_ && b.match_all_urls_)
+ return false;
+ return a.host_.compare(b.host_) < 0;
+ }
+
+ // Used for origin comparisons in a std::set.
+ class EffectiveHostCompareFunctor {
+ public:
+ bool operator()(const URLPattern& a, const URLPattern& b) const {
+ return EffectiveHostCompare(a, b);
+ }
+ };
+
+ // Get an error string for a ParseResult.
+ static const char* GetParseResultString(URLPattern::ParseResult parse_result);
+
+ private:
+ // Returns true if any of the |schemes| items matches our scheme.
+ bool MatchesAnyScheme(const std::vector<std::string>& schemes) const;
+
+ // Returns true if all of the |schemes| items matches our scheme.
+ bool MatchesAllSchemes(const std::vector<std::string>& schemes) const;
+
+ bool MatchesSecurityOriginHelper(const GURL& test) const;
+
+ // Returns true if our port matches the |port| pattern (it may be "*").
+ bool MatchesPortPattern(base::StringPiece port) const;
+
+ // If the URLPattern contains a wildcard scheme, returns a list of
+ // equivalent literal schemes, otherwise returns the current scheme.
+ std::vector<std::string> GetExplicitSchemes() const;
+
+ // A bitmask containing the schemes which are considered valid for this
+ // pattern. Parse() uses this to decide whether a pattern contains a valid
+ // scheme.
+ int valid_schemes_;
+
+ // True if this is a special-case "<all_urls>" pattern.
+ bool match_all_urls_;
+
+ // The scheme for the pattern.
+ std::string scheme_;
+
+ // The host without any leading "*" components.
+ std::string host_;
+
+ // Whether we should match subdomains of the host. This is true if the first
+ // component of the pattern's host was "*".
+ bool match_subdomains_;
+
+ // The port.
+ std::string port_;
+
+ // The path to match. This is everything after the host of the URL, or
+ // everything after the scheme in the case of file:// URLs.
+ std::string path_;
+
+ // The path with "?" and "\" characters escaped for use with the
+ // MatchPattern() function.
+ std::string path_escaped_;
+
+ // A string representing this URLPattern.
+ mutable std::string spec_;
+};
+
+std::ostream& operator<<(std::ostream& out, const URLPattern& url_pattern);
+
+typedef std::vector<URLPattern> URLPatternList;
+
+#endif // USERSCRIPTS_COMMON_URL_PATTERN_H_
diff --git a/components/user_scripts/common/url_pattern_set.cc b/components/user_scripts/common/url_pattern_set.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/url_pattern_set.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "url_pattern_set.h"
+
+#include <iterator>
+#include <ostream>
+
+#include "base/containers/contains.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "error_utils.h"
+#include "url_pattern.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "user_scripts_features.h"
+
+namespace user_scripts {
+
+namespace {
+
+const char kInvalidURLPatternError[] = "Invalid url pattern '*'";
+
+} // namespace
+
+// static
+URLPatternSet URLPatternSet::CreateDifference(const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ return URLPatternSet(base::STLSetDifference<std::set<URLPattern>>(
+ set1.patterns_, set2.patterns_));
+}
+
+// static
+URLPatternSet URLPatternSet::CreateIntersection(
+ const URLPatternSet& set1,
+ const URLPatternSet& set2,
+ IntersectionBehavior intersection_behavior) {
+ // Note: leverage return value optimization; always return the same object.
+ URLPatternSet result;
+
+ if (intersection_behavior == IntersectionBehavior::kStringComparison) {
+ // String comparison just relies on STL set behavior, which looks at the
+ // string representation.
+ result = URLPatternSet(base::STLSetIntersection<std::set<URLPattern>>(
+ set1.patterns_, set2.patterns_));
+ return result;
+ }
+
+ // Look for a semantic intersection.
+
+ // Step 1: Iterate over each set. Find any patterns that are completely
+ // contained by the other (thus being necessarily present in any intersection)
+ // and add them, collecting the others in a set of unique items.
+ // Note: Use a collection of pointers for the uniques to avoid excessive
+ // copies. Since these are owned by the URLPatternSet passed in, which is
+ // const, this should be safe.
+ std::vector<const URLPattern*> unique_set1;
+ for (const URLPattern& pattern : set1) {
+ if (set2.ContainsPattern(pattern))
+ result.patterns_.insert(pattern);
+ else
+ unique_set1.push_back(&pattern);
+ }
+ std::vector<const URLPattern*> unique_set2;
+ for (const URLPattern& pattern : set2) {
+ if (set1.ContainsPattern(pattern))
+ result.patterns_.insert(pattern);
+ else
+ unique_set2.push_back(&pattern);
+ }
+
+ // If we're just looking for patterns contained by both, we're done.
+ if (intersection_behavior == IntersectionBehavior::kPatternsContainedByBoth)
+ return result;
+
+ DCHECK_EQ(IntersectionBehavior::kDetailed, intersection_behavior);
+
+ // Step 2: Iterate over all the unique patterns and find the intersections
+ // they have with the other patterns.
+ for (const auto* pattern : unique_set1) {
+ for (const auto* pattern2 : unique_set2) {
+ absl::optional<URLPattern> intersection =
+ pattern->CreateIntersection(*pattern2);
+ if (intersection)
+ result.patterns_.insert(std::move(*intersection));
+ }
+ }
+
+ return result;
+}
+
+// static
+URLPatternSet URLPatternSet::CreateUnion(const URLPatternSet& set1,
+ const URLPatternSet& set2) {
+ return URLPatternSet(
+ base::STLSetUnion<std::set<URLPattern>>(set1.patterns_, set2.patterns_));
+}
+
+// static
+URLPatternSet URLPatternSet::CreateUnion(
+ const std::vector<URLPatternSet>& sets) {
+ URLPatternSet result;
+ if (sets.empty())
+ return result;
+
+ // N-way union algorithm is basic O(nlog(n)) merge algorithm.
+ //
+ // Do the first merge step into a working set so that we don't mutate any of
+ // the input.
+ // TODO(devlin): Looks like this creates a bunch of copies; we can probably
+ // clean that up.
+ std::vector<URLPatternSet> working;
+ for (size_t i = 0; i < sets.size(); i += 2) {
+ if (i + 1 < sets.size())
+ working.push_back(CreateUnion(sets[i], sets[i + 1]));
+ else
+ working.push_back(sets[i].Clone());
+ }
+
+ for (size_t skip = 1; skip < working.size(); skip *= 2) {
+ for (size_t i = 0; i < (working.size() - skip); i += skip) {
+ URLPatternSet u = CreateUnion(working[i], working[i + skip]);
+ working[i].patterns_.swap(u.patterns_);
+ }
+ }
+
+ result.patterns_.swap(working[0].patterns_);
+ return result;
+}
+
+URLPatternSet::URLPatternSet() = default;
+
+URLPatternSet::URLPatternSet(URLPatternSet&& rhs) = default;
+
+URLPatternSet::URLPatternSet(const std::set<URLPattern>& patterns)
+ : patterns_(patterns) {}
+
+URLPatternSet::~URLPatternSet() = default;
+
+URLPatternSet& URLPatternSet::operator=(URLPatternSet&& rhs) = default;
+
+bool URLPatternSet::operator==(const URLPatternSet& other) const {
+ return patterns_ == other.patterns_;
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const URLPatternSet& url_pattern_set) {
+ out << "{ ";
+
+ auto iter = url_pattern_set.patterns().cbegin();
+ if (!url_pattern_set.patterns().empty()) {
+ out << *iter;
+ ++iter;
+ }
+
+ for (;iter != url_pattern_set.patterns().end(); ++iter)
+ out << ", " << *iter;
+
+ if (!url_pattern_set.patterns().empty())
+ out << " ";
+
+ out << "}";
+ return out;
+}
+
+URLPatternSet URLPatternSet::Clone() const {
+ return URLPatternSet(patterns_);
+}
+
+bool URLPatternSet::is_empty() const {
+ return patterns_.empty();
+}
+
+size_t URLPatternSet::size() const {
+ return patterns_.size();
+}
+
+bool URLPatternSet::AddPattern(const URLPattern& pattern) {
+ return patterns_.insert(pattern).second;
+}
+
+void URLPatternSet::AddPatterns(const URLPatternSet& set) {
+ patterns_.insert(set.patterns().begin(),
+ set.patterns().end());
+}
+
+void URLPatternSet::ClearPatterns() {
+ patterns_.clear();
+}
+
+bool URLPatternSet::AddOrigin(int valid_schemes, const GURL& origin) {
+ if (origin.is_empty())
+ return false;
+ const url::Origin real_origin = url::Origin::Create(origin);
+ DCHECK(real_origin.IsSameOriginWith(url::Origin::Create(
+ origin.DeprecatedGetOriginAsURL())));
+ 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;
+}
+
+base::Value::List URLPatternSet::ToValue() const {
+ base::Value::List result;
+ for (const auto& pattern : patterns_) {
+ base::Value pattern_str_value(pattern.GetAsString());
+ if (!base::Contains(result, pattern_str_value))
+ result.Append(std::move(pattern_str_value));
+ }
+ return result;
+}
+
+bool URLPatternSet::Populate(const std::vector<std::string>& patterns,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error) {
+ ClearPatterns();
+ for (size_t i = 0; i < patterns.size(); ++i) {
+ URLPattern pattern(valid_schemes);
+ if (pattern.Parse(patterns[i]) != URLPattern::ParseResult::kSuccess) {
+ if (error) {
+ *error = ErrorUtils::FormatErrorMessage(kInvalidURLPatternError,
+ patterns[i]);
+ } else {
+ LOG(ERROR) << "Invalid url pattern: " << patterns[i];
+ }
+ return false;
+ }
+ if (!allow_file_access && pattern.MatchesScheme(url::kFileScheme)) {
+ pattern.SetValidSchemes(
+ pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
+ }
+ AddPattern(pattern);
+ }
+ return true;
+}
+
+std::unique_ptr<std::vector<std::string>> URLPatternSet::ToStringVector()
+ const {
+ std::unique_ptr<std::vector<std::string>> value(new std::vector<std::string>);
+ for (auto i = patterns_.cbegin(); i != patterns_.cend(); ++i) {
+ value->push_back(i->GetAsString());
+ }
+ return value;
+}
+
+bool URLPatternSet::Populate(const base::Value::List& value,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error) {
+ std::vector<std::string> patterns;
+ for (const base::Value& pattern : value) {
+ const std::string* item = pattern.GetIfString();
+ if (!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,160 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_URL_PATTERN_SET_H_
+#define USERSCRIPTS_COMMON_URL_PATTERN_SET_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <memory>
+#include <set>
+
+#include "base/values.h"
+#include "url_pattern.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace user_scripts {
+
+// Represents the set of URLs an extension uses for web content.
+class URLPatternSet {
+ public:
+ URLPatternSet(const URLPatternSet&) = delete;
+ URLPatternSet& operator=(const URLPatternSet&) = delete;
+ typedef std::set<URLPattern>::const_iterator const_iterator;
+ typedef std::set<URLPattern>::iterator iterator;
+
+ // Returns |set1| - |set2|.
+ static URLPatternSet CreateDifference(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ enum class IntersectionBehavior {
+ // For the following descriptions, consider the two URLPatternSets:
+ // Set 1: {"https://example.com/*", "https://*.google.com/*", "http://*/*"}
+ // Set 2: {"https://example.com/*", "https://google.com/maps",
+ // "*://chromium.org/*"}
+
+ // Only includes patterns that are exactly in both sets. The intersection of
+ // the two sets above is {"https://example.com/*"}, since that is the only
+ // pattern that appears exactly in each.
+ kStringComparison,
+
+ // Includes patterns that are effectively contained by both sets. The
+ // intersection of the two sets above is
+ // {
+ // "https://example.com/*" (contained exactly by each set)
+ // "https://google.com/maps" (contained exactly by set 2 and a strict
+ // subset of https://*.google.com/* in set 1)
+ // }
+ kPatternsContainedByBoth,
+
+ // Includes patterns that are contained by both sets and creates new
+ // patterns to represent the intersection of any others. The intersection of
+ // the two sets above is
+ // {
+ // "https://example.com/*" (contained exactly by each set)
+ // "https://google.com/maps" (contained exactly by set 2 and a strict
+ // subset of https://*.google.com/* in set 1)
+ // "http://chromium.org/*" (the overlap between "http://*/*" in set 1 and
+ // *://chromium.org/*" in set 2).
+ // }
+ // Note that this is the most computationally expensive - potentially
+ // O(n^2) - since it can require comparing each pattern in one set to every
+ // pattern in the other set.
+ kDetailed,
+ };
+
+ // Returns the intersection of |set1| and |set2| according to
+ // |intersection_behavior|.
+ static URLPatternSet CreateIntersection(
+ const URLPatternSet& set1,
+ const URLPatternSet& set2,
+ IntersectionBehavior intersection_behavior);
+
+ // Returns the union of |set1| and |set2|.
+ static URLPatternSet CreateUnion(const URLPatternSet& set1,
+ const URLPatternSet& set2);
+
+ // Returns the union of all sets in |sets|.
+ static URLPatternSet CreateUnion(const std::vector<URLPatternSet>& sets);
+
+ URLPatternSet();
+ URLPatternSet(URLPatternSet&& rhs);
+ explicit URLPatternSet(const std::set<URLPattern>& patterns);
+ ~URLPatternSet();
+
+ URLPatternSet& operator=(URLPatternSet&& rhs);
+ bool operator==(const URLPatternSet& rhs) const;
+
+ bool is_empty() const;
+ size_t size() const;
+ const std::set<URLPattern>& patterns() const { return patterns_; }
+ const_iterator begin() const { return patterns_.begin(); }
+ const_iterator end() const { return patterns_.end(); }
+ iterator erase(iterator iter) { return patterns_.erase(iter); }
+
+ // Returns a copy of this URLPatternSet; not instrumented as a copy
+ // constructor to avoid accidental/unnecessary copies.
+ URLPatternSet Clone() const;
+
+ // Adds a pattern to the set. Returns true if a new pattern was inserted,
+ // false if the pattern was already in the set.
+ bool AddPattern(const URLPattern& pattern);
+
+ // Adds all patterns from |set| into this.
+ void AddPatterns(const URLPatternSet& set);
+
+ void ClearPatterns();
+
+ // Adds a pattern based on |origin| to the set.
+ bool AddOrigin(int valid_schemes, const GURL& origin);
+
+ // Returns true if every URL that matches |set| is matched by this. In other
+ // words, if every pattern in |set| is encompassed by a pattern in this.
+ bool Contains(const URLPatternSet& set) const;
+
+ // Returns true if any pattern in this set encompasses |pattern|.
+ bool ContainsPattern(const URLPattern& pattern) const;
+
+ // Test if the extent contains a URL.
+ bool MatchesURL(const GURL& url) const;
+
+ // Test if the extent matches all URLs (for example, <all_urls>).
+ bool MatchesAllURLs() const;
+
+ bool MatchesSecurityOrigin(const GURL& origin) const;
+
+ // Returns true if there is a single URL that would be in two extents.
+ bool OverlapsWith(const URLPatternSet& other) const;
+
+ // Converts to and from Value for serialization to preferences.
+ base::Value::List ToValue() const;
+ bool Populate(const base::Value::List& value,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error);
+
+ // Converts to and from a vector of strings.
+ std::unique_ptr<std::vector<std::string>> ToStringVector() const;
+ bool Populate(const std::vector<std::string>& patterns,
+ int valid_schemes,
+ bool allow_file_access,
+ std::string* error);
+
+ private:
+ // The list of URL patterns that comprise the extent.
+ std::set<URLPattern> patterns_;
+};
+
+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,329 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "user_script.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/command_line.h"
+#include "base/pickle.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "user_scripts_features.h"
+
+namespace {
+
+// This cannot be a plain int or int64_t because we need to generate unique IDs
+// from multiple threads.
+base::AtomicSequenceNumber g_user_script_id_generator;
+
+bool UrlMatchesGlobs(const std::vector<std::string>* globs,
+ const GURL& url) {
+ for (auto glob = globs->cbegin(); glob != globs->cend(); ++glob) {
+ if (base::MatchPattern(url.spec(), *glob))
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+namespace user_scripts {
+
+// The bitmask for valid user script injectable schemes used by URLPattern.
+enum {
+ kValidUserScriptSchemes = //URLPattern::SCHEME_CHROMEUI |
+ URLPattern::SCHEME_HTTP |
+ URLPattern::SCHEME_HTTPS
+ //| URLPattern::SCHEME_FILE |
+ //URLPattern::SCHEME_FTP
+};
+
+// static
+const char UserScript::kFileExtension[] = ".user.js";
+
+// static
+int UserScript::GenerateUserScriptID() {
+ return g_user_script_id_generator.GetNext();
+}
+
+bool UserScript::IsURLUserScript(const GURL& url,
+ const std::string& mime_type) {
+ return base::EndsWith(url.ExtractFileName(), kFileExtension,
+ base::CompareCase::INSENSITIVE_ASCII) &&
+ mime_type != "text/html";
+}
+
+// static
+int UserScript::ValidUserScriptSchemes(bool canExecuteScriptEverywhere) {
+ if (canExecuteScriptEverywhere)
+ return URLPattern::SCHEME_ALL;
+ int valid_schemes = kValidUserScriptSchemes;
+ // if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ // switches::kExtensionsOnChromeURLs)) {
+ // valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
+ // }
+ return valid_schemes;
+}
+
+UserScript::File::File(const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const GURL& url)
+ : extension_root_(extension_root),
+ relative_path_(relative_path),
+ url_(url) {
+}
+
+UserScript::File::File() {}
+
+UserScript::File::File(const File& other)
+ : extension_root_(other.extension_root_),
+ relative_path_(other.relative_path_),
+ url_(other.url_),
+ external_content_(other.external_content_),
+ content_(other.content_),
+ key_(other.key_) {}
+
+UserScript::File::~File() {}
+
+UserScript::UserScript() = default;
+UserScript::~UserScript() = default;
+
+void UserScript::add_url_pattern(const URLPattern& pattern) {
+ url_set_.AddPattern(pattern);
+}
+
+void UserScript::add_exclude_url_pattern(const URLPattern& pattern) {
+ exclude_url_set_.AddPattern(pattern);
+}
+
+bool UserScript::MatchesURL(const GURL& url) const {
+ // Since the injecton is also provided for native pages,
+ // we must verify that the render process does not include
+ // scripts in the schema that are not allowed
+
+ // we allow only URLPattern::SCHEME_HTTP(S)
+ URLPattern pattern(kValidUserScriptSchemes);
+ pattern.Parse(url.spec());
+ if (!pattern.IsValidScheme(pattern.scheme()))
+ return false;
+
+ 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->WriteString(name_);
+ pickle->WriteBool(emulate_greasemonkey());
+ pickle->WriteBool(match_all_frames());
+ pickle->WriteInt(static_cast<int>(match_origin_as_fallback()));
+ pickle->WriteBool(is_incognito_enabled());
+
+ PickleHostID(pickle, host_id_);
+ pickle->WriteInt(consumer_instance_type());
+ PickleGlobs(pickle, globs_);
+ PickleGlobs(pickle, exclude_globs_);
+ PickleURLPatternSet(pickle, url_set_);
+ PickleURLPatternSet(pickle, exclude_url_set_);
+ PickleScripts(pickle, js_scripts_);
+ PickleScripts(pickle, css_scripts_);
+}
+
+void UserScript::PickleGlobs(base::Pickle* pickle,
+ const std::vector<std::string>& globs) const {
+ pickle->WriteUInt32(globs.size());
+ for (auto glob = globs.cbegin(); glob != globs.cend(); ++glob) {
+ pickle->WriteString(*glob);
+ }
+}
+
+void UserScript::PickleHostID(base::Pickle* pickle,
+ const HostID& host_id) const {
+ pickle->WriteInt(host_id.type());
+ pickle->WriteString(host_id.id());
+}
+
+void UserScript::PickleURLPatternSet(base::Pickle* pickle,
+ const URLPatternSet& pattern_list) const {
+ pickle->WriteUInt32(pattern_list.patterns().size());
+ for (auto pattern = pattern_list.begin(); pattern != pattern_list.end();
+ ++pattern) {
+ pickle->WriteInt(pattern->valid_schemes());
+ pickle->WriteString(pattern->GetAsString());
+ }
+}
+
+void UserScript::PickleScripts(base::Pickle* pickle,
+ const FileList& scripts) const {
+ pickle->WriteUInt32(scripts.size());
+ for (const std::unique_ptr<File>& file : scripts)
+ file->Pickle(pickle);
+}
+
+void UserScript::Unpickle(const base::Pickle& pickle,
+ base::PickleIterator* iter) {
+ // Read the run location.
+ int run_location = 0;
+ CHECK(iter->ReadInt(&run_location));
+ CHECK(run_location >= 0 && run_location < RUN_LOCATION_LAST);
+ run_location_ = static_cast<RunLocation>(run_location);
+
+ CHECK(iter->ReadInt(&user_script_id_));
+ CHECK(iter->ReadString(&name_));
+ CHECK(iter->ReadBool(&emulate_greasemonkey_));
+ CHECK(iter->ReadBool(&match_all_frames_));
+ int match_origin_as_fallback_int = 0;
+ CHECK(iter->ReadInt(&match_origin_as_fallback_int));
+ match_origin_as_fallback_ =
+ static_cast<MatchOriginAsFallbackBehavior>(match_origin_as_fallback_int);
+ CHECK(iter->ReadBool(&incognito_enabled_));
+
+ UnpickleHostID(pickle, iter, &host_id_);
+
+ int consumer_instance_type = 0;
+ CHECK(iter->ReadInt(&consumer_instance_type));
+ consumer_instance_type_ =
+ static_cast<ConsumerInstanceType>(consumer_instance_type);
+
+ UnpickleGlobs(pickle, iter, &globs_);
+ UnpickleGlobs(pickle, iter, &exclude_globs_);
+ UnpickleURLPatternSet(pickle, iter, &url_set_);
+ UnpickleURLPatternSet(pickle, iter, &exclude_url_set_);
+ UnpickleScripts(pickle, iter, &js_scripts_);
+ UnpickleScripts(pickle, iter, &css_scripts_);
+}
+
+void UserScript::UnpickleGlobs(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ std::vector<std::string>* globs) {
+ uint32_t num_globs = 0;
+ CHECK(iter->ReadUInt32(&num_globs));
+ globs->clear();
+ for (uint32_t i = 0; i < num_globs; ++i) {
+ std::string glob;
+ CHECK(iter->ReadString(&glob));
+ globs->push_back(glob);
+ }
+}
+
+void UserScript::UnpickleHostID(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ HostID* host_id) {
+ int type = 0;
+ std::string id;
+ CHECK(iter->ReadInt(&type));
+ CHECK(iter->ReadString(&id));
+ *host_id = HostID(static_cast<HostID::HostType>(type), id);
+}
+
+void UserScript::UnpickleURLPatternSet(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ URLPatternSet* pattern_list) {
+ uint32_t num_patterns = 0;
+ CHECK(iter->ReadUInt32(&num_patterns));
+
+ pattern_list->ClearPatterns();
+ for (uint32_t i = 0; i < num_patterns; ++i) {
+ int valid_schemes;
+ CHECK(iter->ReadInt(&valid_schemes));
+
+ std::string pattern_str;
+ CHECK(iter->ReadString(&pattern_str));
+
+ URLPattern pattern(kValidUserScriptSchemes);
+ URLPattern::ParseResult result = pattern.Parse(pattern_str);
+ CHECK(URLPattern::ParseResult::kSuccess == result)
+ << URLPattern::GetParseResultString(result) << " "
+ << pattern_str.c_str();
+
+ pattern.SetValidSchemes(valid_schemes);
+ pattern_list->AddPattern(pattern);
+ }
+}
+
+void UserScript::UnpickleScripts(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ FileList* scripts) {
+ uint32_t num_files = 0;
+ CHECK(iter->ReadUInt32(&num_files));
+ scripts->clear();
+ for (uint32_t i = 0; i < num_files; ++i) {
+ std::unique_ptr<File> file(new File());
+ file->Unpickle(pickle, iter);
+ scripts->push_back(std::move(file));
+ }
+}
+
+UserScriptIDPair::UserScriptIDPair(int id, const HostID& host_id)
+ : id(id), host_id(host_id) {}
+
+UserScriptIDPair::UserScriptIDPair(int id) : id(id), host_id(HostID()) {}
+
+bool operator<(const UserScriptIDPair& a, const UserScriptIDPair& b) {
+ return a.id < b.id;
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/common/user_script.h b/components/user_scripts/common/user_script.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/user_script.h
@@ -0,0 +1,403 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_USER_SCRIPT_H_
+#define USERSCRIPTS_COMMON_USER_SCRIPT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/strings/string_piece.h"
+#include "script_constants.h"
+#include "host_id.h"
+#include "url_pattern.h"
+#include "url_pattern_set.h"
+#include "url/gurl.h"
+
+namespace base {
+class Pickle;
+class PickleIterator;
+}
+
+namespace user_scripts {
+
+// Represents a user script, either a standalone one, or one that is part of an
+// extension.
+class UserScript {
+ public:
+ UserScript(const UserScript&) = delete;
+ UserScript& operator=(const UserScript&) = delete;
+ // The file extension for standalone user scripts.
+ static const char kFileExtension[];
+
+ static int GenerateUserScriptID();
+
+ // Check if a URL should be treated as a user script and converted to an
+ // extension.
+ static bool IsURLUserScript(const GURL& url, const std::string& mime_type);
+
+ // Get the valid user script schemes for the current process. If
+ // canExecuteScriptEverywhere is true, this will return ALL_SCHEMES.
+ static int ValidUserScriptSchemes(bool canExecuteScriptEverywhere = false);
+
+ // TODO(rdevlin.cronin) This and RunLocation don't really belong here, since
+ // they are used for more than UserScripts (e.g., tabs.executeScript()).
+ // The type of injected script.
+ enum InjectionType {
+ // A content script specified in the extension's manifest.
+ CONTENT_SCRIPT,
+ // A script injected via, e.g. tabs.executeScript().
+ //PROGRAMMATIC_SCRIPT
+ };
+ // The last type of injected script; used for enum verification in IPC.
+ // Update this if you add more injected script types!
+ static const InjectionType INJECTION_TYPE_LAST = CONTENT_SCRIPT/*PROGRAMMATIC_SCRIPT*/;
+
+ // Locations that user scripts can be run inside the document.
+ // The three run locations must strictly follow each other in both load order
+ // (i.e., start *always* comes before end) and numerically, as we use
+ // arithmetic checking (e.g., curr == last + 1). So, no bitmasks here!!
+ enum RunLocation {
+ UNDEFINED,
+ DOCUMENT_START, // After the documentElement is created, but before
+ // anything else happens.
+ DOCUMENT_END, // After the entire document is parsed. Same as
+ // DOMContentLoaded.
+ DOCUMENT_IDLE, // Sometime after DOMContentLoaded, as soon as the document
+ // is "idle". Currently this uses the simple heuristic of:
+ // min(DOM_CONTENT_LOADED + TIMEOUT, ONLOAD), but no
+ // particular injection point is guaranteed.
+ RUN_DEFERRED, // The user script's injection was deferred for permissions
+ // reasons, and was executed at a later time.
+ BROWSER_DRIVEN, // The user script will be injected when triggered by an
+ // IPC in the browser process.
+ RUN_LOCATION_LAST // Leave this as the last item.
+ };
+
+ // Holds script file info.
+ class File {
+ public:
+ File(const base::FilePath& extension_root,
+ const base::FilePath& relative_path,
+ const GURL& url);
+ File();
+ File(const File& other);
+ ~File();
+
+ const base::FilePath& extension_root() const { return extension_root_; }
+ const base::FilePath& relative_path() const { return relative_path_; }
+
+ const GURL& url() const { return url_; }
+ void set_url(const GURL& url) { url_ = url; }
+
+ // If external_content_ is set returns it as content otherwise it returns
+ // content_
+ const base::StringPiece GetContent() const {
+ if (external_content_.data())
+ return external_content_;
+ else
+ return content_;
+ }
+ void set_external_content(const base::StringPiece& content) {
+ external_content_ = content;
+ }
+ void set_content(const base::StringPiece& content) {
+ content_.assign(content.begin(), content.end());
+ }
+
+ const std::string& key() const { return key_; }
+ void set_key(const std::string& key) {
+ key_ = key;
+ }
+
+ // Serialization support. The content and FilePath members will not be
+ // serialized!
+ void Pickle(base::Pickle* pickle) const;
+ void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
+
+ private:
+ // Where the script file lives on the disk. We keep the path split so that
+ // it can be localized at will.
+ base::FilePath extension_root_;
+ base::FilePath relative_path_;
+
+ // The url to this script file.
+ GURL url_;
+
+ // The script content. It can be set to either loaded_content_ or
+ // externally allocated string.
+ base::StringPiece external_content_;
+
+ // Set when the content is loaded by LoadContent
+ std::string content_;
+
+ std::string key_;
+ };
+
+ using FileList = std::vector<std::unique_ptr<File>>;
+
+ // Type of a API consumer instance that user scripts will be injected on.
+ enum ConsumerInstanceType { TAB, WEBVIEW };
+
+ // Constructor. Default the run location to document end, which is like
+ // Greasemonkey and probably more useful for typical scripts.
+ UserScript();
+ ~UserScript();
+
+ // Performs a copy of all fields except file contents.
+ // static std::unique_ptr<UserScript> CopyMetadataFrom(const UserScript& other);
+
+ const std::string& name_space() const { return name_space_; }
+ void set_name_space(const std::string& name_space) {
+ name_space_ = name_space;
+ }
+
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ const std::string& version() const { return version_; }
+ void set_version(const std::string& version) {
+ version_ = version;
+ }
+
+ const std::string& key() const { return key_; }
+ void set_key(const std::string& key) {
+ key_ = key;
+ }
+
+ const std::string& file_path() const { return file_path_; }
+ void set_file_path(const std::string& file_path) {
+ file_path_ = file_path;
+ }
+
+ const std::string& url_source() const { return url_source_; }
+ void set_url_source(const std::string& url_source) {
+ url_source_ = url_source;
+ }
+
+ const std::string& description() const { return description_; }
+ void set_description(const std::string& description) {
+ description_ = description;
+ }
+
+ const std::string& parser_error() const { return parser_error_; }
+ void set_parser_error(const std::string& parser_error) {
+ parser_error_ = parser_error;
+ }
+
+ bool force_disabled() const { return force_disabled_; }
+ void set_force_disabled() {
+ force_disabled_ = true;
+ }
+
+ // The place in the document to run the script.
+ RunLocation run_location() const { return run_location_; }
+ void set_run_location(RunLocation location) { run_location_ = location; }
+
+ // Whether to emulate greasemonkey when running this script.
+ bool emulate_greasemonkey() const { return emulate_greasemonkey_; }
+ void set_emulate_greasemonkey(bool val) { emulate_greasemonkey_ = val; }
+
+ // Whether to match all frames, or only the top one.
+ bool match_all_frames() const { return match_all_frames_; }
+ void set_match_all_frames(bool val) { match_all_frames_ = val; }
+
+ // Whether to match the origin as a fallback if the URL cannot be used
+ // directly.
+ MatchOriginAsFallbackBehavior match_origin_as_fallback() const {
+ return match_origin_as_fallback_;
+ }
+ void set_match_origin_as_fallback(MatchOriginAsFallbackBehavior val) {
+ match_origin_as_fallback_ = val;
+ }
+
+ // The globs, if any, that determine which pages this script runs against.
+ // These are only used with "standalone" Greasemonkey-like user scripts.
+ const std::vector<std::string>& globs() const { return globs_; }
+ void add_glob(const std::string& glob) { globs_.push_back(glob); }
+ void clear_globs() { globs_.clear(); }
+ const std::vector<std::string>& exclude_globs() const {
+ return exclude_globs_;
+ }
+ void add_exclude_glob(const std::string& glob) {
+ exclude_globs_.push_back(glob);
+ }
+ void clear_exclude_globs() { exclude_globs_.clear(); }
+
+ // The URLPatterns, if any, that determine which pages this script runs
+ // against.
+ const URLPatternSet& url_patterns() const { return url_set_; }
+ void add_url_pattern(const URLPattern& pattern);
+ const URLPatternSet& exclude_url_patterns() const {
+ return exclude_url_set_;
+ }
+ void add_exclude_url_pattern(const URLPattern& pattern);
+
+ // List of js scripts for this user script
+ FileList& js_scripts() { return js_scripts_; }
+ const FileList& js_scripts() const { return js_scripts_; }
+
+ // List of css scripts for this user script
+ FileList& css_scripts() { return css_scripts_; }
+ const FileList& css_scripts() const { return css_scripts_; }
+
+ const std::string& extension_id() const { return host_id_.id(); }
+
+ const HostID& host_id() const { return host_id_; }
+ void set_host_id(const HostID& host_id) { host_id_ = host_id; }
+
+ const ConsumerInstanceType& consumer_instance_type() const {
+ return consumer_instance_type_;
+ }
+ void set_consumer_instance_type(
+ const ConsumerInstanceType& consumer_instance_type) {
+ consumer_instance_type_ = consumer_instance_type;
+ }
+
+ int id() const { return user_script_id_; }
+ void set_id(int id) { user_script_id_ = id; }
+
+ // TODO(lazyboy): Incognito information is extension specific, it doesn't
+ // belong here. We should be able to determine this in the renderer/ where it
+ // is used.
+ bool is_incognito_enabled() const { return incognito_enabled_; }
+ void set_incognito_enabled(bool enabled) { incognito_enabled_ = enabled; }
+
+ // Returns true if the script should be applied to the specified URL, false
+ // otherwise.
+ bool MatchesURL(const GURL& url) const;
+
+ // Returns true if the script should be applied to the given
+ // |effective_document_url|. It is the caller's responsibility to calculate
+ // |effective_document_url| based on match_origin_as_fallback().
+ bool MatchesDocument(const GURL& effective_document_url,
+ bool is_subframe) const;
+
+ // Serializes the UserScript into a pickle. The content of the scripts and
+ // paths to UserScript::Files will not be serialized!
+ void Pickle(base::Pickle* pickle) const;
+
+ // Deserializes the script from a pickle. Note that this always succeeds
+ // because presumably we were the one that pickled it, and we did it
+ // correctly.
+ void Unpickle(const base::Pickle& pickle, base::PickleIterator* iter);
+
+ private:
+ // base::Pickle helper functions used to pickle the individual types of
+ // components.
+ void PickleGlobs(base::Pickle* pickle,
+ const std::vector<std::string>& globs) const;
+ void PickleHostID(base::Pickle* pickle, const HostID& host_id) const;
+ void PickleURLPatternSet(base::Pickle* pickle,
+ const URLPatternSet& pattern_list) const;
+ void PickleScripts(base::Pickle* pickle, const FileList& scripts) const;
+
+ // Unpickle helper functions used to unpickle individual types of components.
+ void UnpickleGlobs(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ std::vector<std::string>* globs);
+ void UnpickleHostID(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ HostID* host_id);
+ void UnpickleURLPatternSet(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ URLPatternSet* pattern_list);
+ void UnpickleScripts(const base::Pickle& pickle,
+ base::PickleIterator* iter,
+ FileList* scripts);
+
+ // The location to run the script inside the document.
+ RunLocation run_location_ = DOCUMENT_IDLE;
+
+ // The namespace of the script. This is used by Greasemonkey in the same way
+ // as XML namespaces. Only used when parsing Greasemonkey-style scripts.
+ std::string name_space_;
+
+ // The script's name. Only used when parsing Greasemonkey-style scripts.
+ std::string name_;
+
+ // A longer description. Only used when parsing Greasemonkey-style scripts.
+ std::string description_;
+
+ // Parser error to show to user
+ std::string parser_error_;
+
+ // A version number of the script. Only used when parsing Greasemonkey-style
+ // scripts.
+ std::string version_;
+
+ // Greasemonkey-style globs that determine pages to inject the script into.
+ // These are only used with standalone scripts.
+ std::vector<std::string> globs_;
+ std::vector<std::string> exclude_globs_;
+
+ // URLPatterns that determine pages to inject the script into. These are
+ // only used with scripts that are part of extensions.
+ URLPatternSet url_set_;
+ URLPatternSet exclude_url_set_;
+
+ // List of js scripts defined in content_scripts
+ FileList js_scripts_;
+
+ // List of css scripts defined in content_scripts
+ FileList css_scripts_;
+
+ // internal key of scripts
+ std::string key_;
+
+ std::string file_path_;
+
+ // url source of script
+ std::string url_source_;
+
+ // The ID of the host this script is a part of. The |ID| of the
+ // |host_id| can be empty if the script is a "standlone" user script.
+ HostID host_id_;
+
+ // The type of the consumer instance that the script will be injected.
+ ConsumerInstanceType consumer_instance_type_ = TAB;
+
+ // The globally-unique id associated with this user script. -1 indicates
+ // "invalid".
+ int user_script_id_ = -1;
+
+ // Whether we should try to emulate Greasemonkey's APIs when running this
+ // script.
+ bool emulate_greasemonkey_ = false;
+
+ // Whether the user script should run in all frames, or only just the top one.
+ bool match_all_frames_ = false;
+
+ // Whether the user script should run in frames whose initiator / precursor
+ // origin matches a match pattern, if an appropriate URL cannot be found for
+ // the frame for matching purposes, such as in the case of about:, data:, and
+ // other schemes.
+ MatchOriginAsFallbackBehavior match_origin_as_fallback_ =
+ MatchOriginAsFallbackBehavior::kNever;
+
+ // True if the script should be injected into an incognito tab.
+ bool incognito_enabled_ = false;
+
+ // Script cannot be enabled
+ bool force_disabled_ = false;
+};
+
+// Information we need while removing scripts from a UserScriptLoader.
+struct UserScriptIDPair {
+ UserScriptIDPair(int id, const HostID& host_id);
+ explicit UserScriptIDPair(int id);
+
+ int id;
+ HostID host_id;
+};
+
+bool operator<(const UserScriptIDPair& a, const UserScriptIDPair& b);
+
+using UserScriptList = std::vector<std::unique_ptr<UserScript>>;
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_COMMON_USER_SCRIPT_H_
diff --git a/components/user_scripts/common/user_scripts_features.cc b/components/user_scripts/common/user_scripts_features.cc
new file mode 100644
--- /dev/null
+++ b/components/user_scripts/common/user_scripts_features.cc
@@ -0,0 +1,32 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "user_scripts_features.h"
+
+#include "build/build_config.h"
+
+namespace user_scripts {
+
+namespace features {
+
+BASE_FEATURE(kEnableLoggingUserScripts,
+ "EnableLoggingUserScripts",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
+}
+
+}
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,36 @@
+/*
+ This file is part of Bromite.
+
+ Bromite is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bromite is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef USERSCRIPTS_COMMON_USERSCRIPTS_FEATURES_H_
+#define USERSCRIPTS_COMMON_USERSCRIPTS_FEATURES_H_
+
+// This file defines all the base::FeatureList features for the Password Manager
+// module.
+
+#include "base/feature_list.h"
+
+namespace user_scripts {
+
+namespace features {
+
+BASE_DECLARE_FEATURE(kEnableLoggingUserScripts);
+
+}
+
+}
+
+#endif
diff --git a/components/user_scripts/common/view_type.cc b/components/user_scripts/common/view_type.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/view_type.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "view_type.h"
+
+#include "base/strings/string_piece.h"
+
+namespace user_scripts {
+
+bool GetViewTypeFromString(const std::string& view_type,
+ ViewType* view_type_out) {
+ // TODO(devlin): This map doesn't contain the following values:
+ // - VIEW_TYPE_BACKGROUND_CONTENTS
+ // - VIEW_TYPE_COMPONENT
+ // - VIEW_TYPE_EXTENSION_GUEST
+ // Why? Is it just because we don't expose those types to JS?
+ static const struct {
+ ViewType type;
+ base::StringPiece name;
+ } constexpr kTypeMap[] = {
+ // {VIEW_TYPE_APP_WINDOW, "APP_WINDOW"},
+ // {VIEW_TYPE_EXTENSION_BACKGROUND_PAGE, "BACKGROUND"},
+ // {VIEW_TYPE_EXTENSION_DIALOG, "EXTENSION_DIALOG"},
+ // {VIEW_TYPE_EXTENSION_POPUP, "POPUP"},
+ {VIEW_TYPE_TAB_CONTENTS, "TAB"},
+ };
+
+ for (const auto& entry : kTypeMap) {
+ if (entry.name == view_type) {
+ *view_type_out = entry.type;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/common/view_type.h b/components/user_scripts/common/view_type.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/common/view_type.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_COMMON_VIEW_TYPE_H_
+#define USERSCRIPTS_COMMON_VIEW_TYPE_H_
+
+#include <string>
+
+namespace user_scripts {
+
+// Icky RTTI used by a few systems to distinguish the host type of a given
+// WebContents.
+//
+// Do not change or reuse the the entry values in this list as this is used in
+// ExtensionViewType enum in tools/metrics/histograms/enums.xml.
+//
+// TODO(aa): Remove this and teach those systems to keep track of their own
+// data.
+enum ViewType {
+ VIEW_TYPE_INVALID = 0,
+ // VIEW_TYPE_APP_WINDOW = 1,
+ // VIEW_TYPE_BACKGROUND_CONTENTS = 2,
+
+ // // For custom parts of Chrome if no other type applies.
+ // VIEW_TYPE_COMPONENT = 3,
+
+ // VIEW_TYPE_EXTENSION_BACKGROUND_PAGE = 4,
+ // VIEW_TYPE_EXTENSION_DIALOG = 5,
+ // VIEW_TYPE_EXTENSION_GUEST = 6,
+ // VIEW_TYPE_EXTENSION_POPUP = 7,
+
+ // Panels were removed in https://crbug.com/571511.
+ // DEPRECATED_VIEW_TYPE_PANEL = 8,
+
+ VIEW_TYPE_TAB_CONTENTS = 9,
+
+ VIEW_TYPE_LAST = VIEW_TYPE_TAB_CONTENTS
+};
+
+// Matches the |view_type| to the corresponding ViewType, and populates
+// |view_type_out|. Returns true if a match is found.
+bool GetViewTypeFromString(const std::string& view_type,
+ ViewType* view_type_out);
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_COMMON_VIEW_TYPE_H_
diff --git a/components/user_scripts/renderer/BUILD.gn b/components/user_scripts/renderer/BUILD.gn
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/BUILD.gn
@@ -0,0 +1,65 @@
+# 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.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,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extension_frame_helper.h"
+
+#include <set>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "base/timer/elapsed_timer.h"
+#include "content/public/renderer/render_frame.h"
+#include "../common/constants.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/web/web_console_message.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_settings.h"
+#include "third_party/blink/public/web/web_view.h"
+
+namespace user_scripts {
+
+namespace {
+
+base::LazyInstance<std::set<const ExtensionFrameHelper*>>::DestructorAtExit
+ g_frame_helpers = LAZY_INSTANCE_INITIALIZER;
+
+// Runs every callback in |callbacks_to_be_run_and_cleared| while |frame_helper|
+// is valid, and clears |callbacks_to_be_run_and_cleared|.
+void RunCallbacksWhileFrameIsValid(
+ base::WeakPtr<ExtensionFrameHelper> frame_helper,
+ std::vector<base::OnceClosure>* callbacks_to_be_run_and_cleared) {
+ // The JavaScript code can cause re-entrancy. To avoid a deadlock, don't run
+ // callbacks that are added during the iteration.
+ std::vector<base::OnceClosure> callbacks;
+ callbacks_to_be_run_and_cleared->swap(callbacks);
+ for (auto& callback : callbacks) {
+ std::move(callback).Run();
+ if (!frame_helper.get())
+ return; // Frame and ExtensionFrameHelper invalidated by callback.
+ }
+}
+
+} // namespace
+
+ExtensionFrameHelper::ExtensionFrameHelper(content::RenderFrame* render_frame)
+ : content::RenderFrameObserver(render_frame),
+ content::RenderFrameObserverTracker<ExtensionFrameHelper>(render_frame),
+ tab_id_(-1) {
+ g_frame_helpers.Get().insert(this);
+}
+
+ExtensionFrameHelper::~ExtensionFrameHelper() {
+ g_frame_helpers.Get().erase(this);
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentStart(
+ base::OnceClosure callback) {
+ document_element_created_callbacks_.push_back(std::move(callback));
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentEnd(
+ base::OnceClosure callback) {
+ document_load_finished_callbacks_.push_back(std::move(callback));
+}
+
+void ExtensionFrameHelper::ScheduleAtDocumentIdle(
+ base::OnceClosure callback) {
+ document_idle_callbacks_.push_back(std::move(callback));
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentStart() {
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_element_created_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentEnd() {
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_load_finished_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::RunScriptsAtDocumentIdle() {
+ RunCallbacksWhileFrameIsValid(weak_ptr_factory_.GetWeakPtr(),
+ &document_idle_callbacks_);
+ // |this| might be dead by now.
+}
+
+void ExtensionFrameHelper::OnDestruct() {
+ delete this;
+}
+
+} // namespace user_scripts
diff --git a/components/user_scripts/renderer/extension_frame_helper.h b/components/user_scripts/renderer/extension_frame_helper.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/extension_frame_helper.h
@@ -0,0 +1,90 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_EXTENSION_FRAME_HELPER_H_
+#define USERSCRIPTS_RENDERER_EXTENSION_FRAME_HELPER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "../common/view_type.h"
+#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
+#include "v8/include/v8.h"
+
+struct ExtensionMsg_ExternalConnectionInfo;
+struct ExtensionMsg_TabConnectionInfo;
+
+namespace base {
+class ListValue;
+}
+
+namespace user_scripts {
+
+class Dispatcher;
+struct Message;
+struct PortId;
+class ScriptContext;
+
+// RenderFrame-level plumbing for extension features.
+class ExtensionFrameHelper
+ : public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<ExtensionFrameHelper> {
+ public:
+ ExtensionFrameHelper(const ExtensionFrameHelper&) = delete;
+ ExtensionFrameHelper& operator=(const ExtensionFrameHelper&) = delete;
+ ExtensionFrameHelper(content::RenderFrame* render_frame /*,
+ Dispatcher* extension_dispatcher*/);
+ ~ExtensionFrameHelper() override;
+
+ int tab_id() const { return tab_id_; }
+
+ // Called when the document element has been inserted in this frame. This
+ // method may invoke untrusted JavaScript code that invalidate the frame and
+ // this ExtensionFrameHelper.
+ void RunScriptsAtDocumentStart();
+
+ // Called after the DOMContentLoaded event has fired.
+ void RunScriptsAtDocumentEnd();
+
+ // Called before the window.onload event is fired.
+ void RunScriptsAtDocumentIdle();
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentStart
+ // notification. Only call this when you are certain that there will be such a
+ // notification, e.g. from RenderFrameObserver::DidCreateDocumentElement.
+ // Otherwise the callback is never invoked, or invoked for a document that you
+ // were not expecting.
+ void ScheduleAtDocumentStart(base::OnceClosure callback);
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentEnd call.
+ void ScheduleAtDocumentEnd(base::OnceClosure callback);
+
+ // Schedule a callback, to be run at the next RunScriptsAtDocumentIdle call.
+ void ScheduleAtDocumentIdle(base::OnceClosure callback);
+
+ private:
+
+ void OnDestruct() override;
+
+ // The id of the tab the render frame is attached to.
+ int tab_id_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentStart notification.
+ std::vector<base::OnceClosure> document_element_created_callbacks_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentEnd notification.
+ std::vector<base::OnceClosure> document_load_finished_callbacks_;
+
+ // Callbacks to be run at the next RunScriptsAtDocumentIdle notification.
+ std::vector<base::OnceClosure> document_idle_callbacks_;
+
+ base::WeakPtrFactory<ExtensionFrameHelper> weak_ptr_factory_{this};
+};
+
+} // 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,41 @@
+// 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 "../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 InjectionHost&) = delete;
+ InjectionHost& operator=(const InjectionHost&) = delete;
+ 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_;
+};
+
+#endif // USERSCRIPTS_RENDERER_INJECTION_HOST_H_
diff --git a/components/user_scripts/renderer/resources/greasemonkey_api.js b/components/user_scripts/renderer/resources/greasemonkey_api.js
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/resources/greasemonkey_api.js
@@ -0,0 +1,82 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// -----------------------------------------------------------------------------
+// NOTE: If you change this file you need to touch renderer_resources.grd to
+// have your change take effect.
+// -----------------------------------------------------------------------------
+
+// Partial implementation of the Greasemonkey API, see:
+// http://wiki.greasespot.net/Greasemonkey_Manual:APIs
+
+function GM_addStyle(css) {
+ var parent = document.getElementsByTagName("head")[0];
+ if (!parent) {
+ parent = document.documentElement;
+ }
+ var style = document.createElement("style");
+ style.type = "text/css";
+ var textNode = document.createTextNode(css);
+ style.appendChild(textNode);
+ parent.appendChild(style);
+}
+
+function GM_xmlhttpRequest(details) {
+ function setupEvent(xhr, url, eventName, callback) {
+ xhr[eventName] = function () {
+ var isComplete = xhr.readyState == 4;
+ var responseState = {
+ responseText: xhr.responseText,
+ readyState: xhr.readyState,
+ responseHeaders: isComplete ? xhr.getAllResponseHeaders() : "",
+ status: isComplete ? xhr.status : 0,
+ statusText: isComplete ? xhr.statusText : "",
+ finalUrl: isComplete ? url : ""
+ };
+ callback(responseState);
+ };
+ }
+
+ var xhr = new XMLHttpRequest();
+ var eventNames = ["onload", "onerror", "onreadystatechange"];
+ for (var i = 0; i < eventNames.length; i++ ) {
+ var eventName = eventNames[i];
+ if (eventName in details) {
+ setupEvent(xhr, details.url, eventName, details[eventName]);
+ }
+ }
+
+ xhr.open(details.method, details.url);
+
+ if (details.overrideMimeType) {
+ xhr.overrideMimeType(details.overrideMimeType);
+ }
+ if (details.headers) {
+ for (var header in details.headers) {
+ xhr.setRequestHeader(header, details.headers[header]);
+ }
+ }
+ xhr.send(details.data ? details.data : null);
+}
+
+function GM_openInTab(url) {
+ window.open(url, "");
+}
+
+function GM_log(message) {
+ window.console.log(message);
+}
+
+(function() {
+ function generateGreasemonkeyStub(name) {
+ return function() {
+ console.log("%s is not supported.", name);
+ };
+ }
+
+ var apis = ["GM_getValue", "GM_setValue", "GM_registerMenuCommand"];
+ for (var i = 0, api; api = apis[i]; i++) {
+ window[api] = generateGreasemonkeyStub(api);
+ }
+})();
diff --git a/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd b/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+ <outputs>
+ <output filename="grit/user_scripts_renderer_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="user_scripts_renderer_resources.pak" type="data_package" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <include name="IDR_GREASEMONKEY_API_JS" file="greasemonkey_api.js" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/components/user_scripts/renderer/script_context.cc b/components/user_scripts/renderer/script_context.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/script_context.cc
@@ -0,0 +1,191 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "script_context.h"
+
+#include "base/command_line.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/contains.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "../common/constants.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_document_loader.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_navigation_params.h"
+#include "v8/include/v8.h"
+
+namespace user_scripts {
+
+namespace {
+
+GURL GetEffectiveDocumentURL(
+ blink::WebLocalFrame* frame,
+ const GURL& document_url,
+ MatchOriginAsFallbackBehavior match_origin_as_fallback,
+ bool allow_inaccessible_parents) {
+ auto should_consider_origin = [document_url, match_origin_as_fallback]() {
+ switch (match_origin_as_fallback) {
+ case MatchOriginAsFallbackBehavior::kNever:
+ return false;
+ case MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree:
+ return document_url.SchemeIs(url::kAboutScheme);
+ case MatchOriginAsFallbackBehavior::kAlways:
+ // TODO(devlin): Add more schemes here - blob, filesystem, etc.
+ return document_url.SchemeIs(url::kAboutScheme) ||
+ document_url.SchemeIs(url::kDataScheme);
+ }
+
+ NOTREACHED();
+ };
+
+ // If we don't need to consider the origin, we're done.
+ if (!should_consider_origin())
+ return document_url;
+
+ // Get the "security origin" for the frame. For about: frames, this is the
+ // origin of that of the controlling frame - e.g., an about:blank frame on
+ // https://example.com will have the security origin of https://example.com.
+ // Other frames, like data: frames, will have an opaque origin. For these,
+ // we can get the precursor origin.
+ const blink::WebSecurityOrigin web_frame_origin = frame->GetSecurityOrigin();
+ const url::Origin frame_origin = web_frame_origin;
+ const url::SchemeHostPort& tuple_or_precursor_tuple =
+ frame_origin.GetTupleOrPrecursorTupleIfOpaque();
+
+ // When there's no valid tuple (which can happen in the case of e.g. a
+ // browser-initiated navigation to an opaque URL), there's no origin to
+ // fallback to. Bail.
+ if (!tuple_or_precursor_tuple.IsValid())
+ return document_url;
+
+ const url::Origin origin_or_precursor_origin =
+ url::Origin::Create(tuple_or_precursor_tuple.GetURL());
+
+ if (!allow_inaccessible_parents &&
+ !web_frame_origin.CanAccess(
+ blink::WebSecurityOrigin(origin_or_precursor_origin))) {
+ // The frame can't access its precursor. Bail.
+ return document_url;
+ }
+
+ // Looks like the initiator origin is an appropriate fallback!
+
+ if (match_origin_as_fallback == MatchOriginAsFallbackBehavior::kAlways) {
+ // The easy case! We use the origin directly. We're done.
+ return origin_or_precursor_origin.GetURL();
+ }
+
+ DCHECK_EQ(MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree,
+ match_origin_as_fallback);
+
+ // Unfortunately, in this case, we have to climb the frame tree. This is for
+ // match patterns that are associated with paths as well, not just origins.
+ // For instance, if an extension wants to run on google.com/maps/* with
+ // match_about_blank true, then it should run on about:-scheme frames created
+ // by google.com/maps, but not about:-scheme frames created by google.com
+ // (which is what the precursor tuple origin would be).
+
+ // Traverse the frame/window hierarchy to find the closest non-about:-page
+ // with the same origin as the precursor and return its URL.
+ // Note: This can return the incorrect result, e.g. if a parent frame
+ // navigates a grandchild frame.
+ blink::WebFrame* parent = frame;
+ GURL parent_url;
+ blink::WebDocument parent_document;
+ base::flat_set<blink::WebFrame*> already_visited_frames;
+ do {
+ already_visited_frames.insert(parent);
+ if (parent->Parent())
+ parent = parent->Parent();
+ else
+ parent = parent->Opener();
+
+ // Avoid an infinite loop - see https://crbug.com/568432 and
+ // https://crbug.com/883526.
+ if (base::Contains(already_visited_frames, parent))
+ return document_url;
+
+ parent_document = parent && parent->IsWebLocalFrame()
+ ? parent->ToWebLocalFrame()->GetDocument()
+ : blink::WebDocument();
+
+ // We reached the end of the ancestral chain without finding a valid parent,
+ // or found a remote web frame (in which case, it's a different origin).
+ // Bail and use the original URL.
+ if (parent_document.IsNull())
+ return document_url;
+
+ url::SchemeHostPort parent_tuple_or_precursor_tuple =
+ url::Origin(parent->GetSecurityOrigin())
+ .GetTupleOrPrecursorTupleIfOpaque();
+ if (!parent_tuple_or_precursor_tuple.IsValid() ||
+ parent_tuple_or_precursor_tuple != tuple_or_precursor_tuple) {
+ // The parent has a different tuple origin than frame; this could happen
+ // in edge cases where a parent navigates an iframe or popup of a child
+ // frame at a different origin. [1] In this case, bail, since we can't
+ // find a full URL (i.e., one including the path) with the same security
+ // origin to use for the frame in question.
+ // [1] Consider a frame tree like:
+ // <html> <!--example.com-->
+ // <iframe id="a" src="a.com">
+ // <iframe id="b" src="b.com"></iframe>
+ // </iframe>
+ // </html>
+ // Frame "a" is cross-origin from the top-level frame, and so the
+ // example.com top-level frame can't directly access frame "b". However,
+ // it can navigate it through
+ // window.frames[0].frames[0].location.href = 'about:blank';
+ // In that case, the precursor origin tuple origin of frame "b" would be
+ // example.com, but the parent tuple origin is a.com.
+ // Note that usually, this would have bailed earlier with a remote frame,
+ // but it may not if we're at the process limit.
+ return document_url;
+ }
+
+ parent_url = GURL(parent_document.Url());
+ } while (parent_url.SchemeIs(url::kAboutScheme));
+
+ DCHECK(!parent_url.is_empty());
+ DCHECK(!parent_document.IsNull());
+
+ // We should know that the frame can access the parent document (unless we
+ // explicitly allow it not to), since it has the same tuple origin as the
+ // frame, and we checked the frame access above.
+ DCHECK(allow_inaccessible_parents ||
+ web_frame_origin.CanAccess(parent_document.GetSecurityOrigin()));
+ return parent_url;
+}
+
+} // namespace
+
+// static
+GURL ScriptContext::GetDocumentLoaderURLForFrame(
+ const blink::WebLocalFrame* frame) {
+ return GURL(frame->GetDocument().Url());
+}
+
+// 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,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_SCRIPT_CONTEXT_H_
+#define USERSCRIPTS_RENDERER_SCRIPT_CONTEXT_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/threading/thread_checker.h"
+#include "base/unguessable_token.h"
+#include "../common/script_constants.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:
+ ScriptContext(const ScriptContext&) = delete;
+ ScriptContext& operator=(const ScriptContext&) = delete;
+ // 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);
+};
+
+} // 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,293 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "script_injection.h"
+
+#include <map>
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/lazy_instance.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 "scripts_run_info.h"
+#include "third_party/blink/public/platform/web_isolated_world_info.h"
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "url/gurl.h"
+
+namespace user_scripts {
+
+namespace {
+
+using IsolatedWorldMap = std::map<std::string, int>;
+base::LazyInstance<IsolatedWorldMap>::DestructorAtExit g_isolated_worlds =
+ LAZY_INSTANCE_INITIALIZER;
+
+const int64_t kInvalidRequestId = -1;
+
+// Gets the isolated world ID to use for the given |injection_host|. If no
+// isolated world has been created for that |injection_host| one will be created
+// and initialized.
+int GetIsolatedWorldIdForInstance(const InjectionHost* injection_host) {
+ static int g_next_isolated_world_id = 1; // Embedder isolated worlds can use IDs in [1, 1<<29).
+
+ IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ int id = 0;
+ const std::string& key = injection_host->id().id();
+ auto iter = isolated_worlds.find(key);
+ if (iter != isolated_worlds.end()) {
+ id = iter->second;
+ } else {
+ id = g_next_isolated_world_id++;
+ // This map will tend to pile up over time, but realistically, you're never
+ // going to have enough injection hosts for it to matter.
+ isolated_worlds[key] = id;
+ }
+
+ blink::WebIsolatedWorldInfo info;
+ info.security_origin =
+ blink::WebSecurityOrigin::Create(injection_host->url());
+ info.human_readable_name = blink::WebString::FromUTF8(injection_host->name());
+ info.stable_id = blink::WebString::FromUTF8(key);
+
+ const std::string* csp = injection_host->GetContentSecurityPolicy();
+ if (csp)
+ info.content_security_policy = blink::WebString::FromUTF8(*csp);
+
+ // Even though there may be an existing world for this |injection_host|'s key,
+ // the properties may have changed (e.g. due to an extension update).
+ // Overwrite any existing entries.
+ blink::SetIsolatedWorldInfo(id, info);
+
+ return id;
+}
+
+} // namespace
+
+// Watches for the deletion of a RenderFrame, after which is_valid will return
+// false.
+class ScriptInjection::FrameWatcher : public content::RenderFrameObserver {
+ public:
+ FrameWatcher(const FrameWatcher&) = delete;
+ FrameWatcher& operator=(const FrameWatcher&) = delete;
+ 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_;
+};
+
+// static
+std::string ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id) {
+ const IsolatedWorldMap& isolated_worlds = g_isolated_worlds.Get();
+
+ for (const auto& iter : isolated_worlds) {
+ if (iter.second == isolated_world_id)
+ return iter.first;
+ }
+ return std::string();
+}
+
+// static
+void ScriptInjection::RemoveIsolatedWorld(const std::string& host_id) {
+ g_isolated_worlds.Get().erase(host_id);
+}
+
+ScriptInjection::ScriptInjection(
+ std::unique_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ std::unique_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location,
+ bool log_activity)
+ : injector_(std::move(injector)),
+ render_frame_(render_frame),
+ injection_host_(std::move(injection_host)),
+ run_location_(run_location),
+ request_id_(kInvalidRequestId),
+ complete_(false),
+ did_inject_js_(false),
+ log_activity_(log_activity),
+ frame_watcher_(new FrameWatcher(render_frame, this)) {
+ CHECK(injection_host_.get());
+}
+
+ScriptInjection::~ScriptInjection() {
+ if (!complete_)
+ NotifyWillNotInject(ScriptInjector::WONT_INJECT);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ CompletionCallback async_completion_callback) {
+ if (current_location < run_location_)
+ return INJECTION_WAITING; // Wait for the right location.
+
+ if (request_id_ != kInvalidRequestId) {
+ // We're waiting for permission right now, try again later.
+ return INJECTION_WAITING;
+ }
+
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED; // We're done.
+ }
+
+ InjectionResult result = Inject(scripts_run_info);
+ // If the injection is blocked, we need to set the manager so we can
+ // notify it upon completion.
+ if (result == INJECTION_BLOCKED)
+ async_completion_callback_ = std::move(async_completion_callback);
+ return result;
+}
+
+ScriptInjection::InjectionResult ScriptInjection::OnPermissionGranted(
+ ScriptsRunInfo* scripts_run_info) {
+ if (!injection_host_) {
+ NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED);
+ return INJECTION_FINISHED;
+ }
+
+ return Inject(scripts_run_info);
+}
+
+void ScriptInjection::OnHostRemoved() {
+ injection_host_.reset(nullptr);
+}
+
+void ScriptInjection::NotifyWillNotInject(
+ ScriptInjector::InjectFailureReason reason) {
+ complete_ = true;
+ injector_->OnWillNotInject(reason, render_frame_);
+}
+
+ScriptInjection::InjectionResult ScriptInjection::Inject(
+ ScriptsRunInfo* scripts_run_info) {
+ DCHECK(injection_host_);
+ //DCHECK(scripts_run_info);
+ DCHECK(!complete_);
+ bool should_inject_js = injector_->ShouldInjectJs(
+ run_location_, scripts_run_info->executing_scripts[host_id().id()]);
+ bool should_inject_css = injector_->ShouldInjectCss(
+ run_location_, scripts_run_info->injected_stylesheets[host_id().id()]);
+
+ // This can happen if the extension specified a script to
+ // be run in multiple rules, and the script has already run.
+ // See crbug.com/631247.
+ if (!should_inject_js && !should_inject_css) {
+ return INJECTION_FINISHED;
+ }
+
+ if (should_inject_js)
+ InjectJs(&(scripts_run_info->executing_scripts[host_id().id()]),
+ &(scripts_run_info->num_js));
+ if (should_inject_css)
+ InjectCss(&(scripts_run_info->injected_stylesheets[host_id().id()]),
+ &(scripts_run_info->num_css));
+
+ complete_ = did_inject_js_ || !should_inject_js;
+
+ if (complete_) {
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ } else {
+ ++scripts_run_info->num_blocking_js;
+ }
+
+ return complete_ ? INJECTION_FINISHED : INJECTION_BLOCKED;
+}
+
+void ScriptInjection::InjectJs(std::set<std::string>* executing_scripts,
+ size_t* num_injected_js_scripts) {
+ DCHECK(!did_inject_js_);
+ std::vector<blink::WebScriptSource> sources = injector_->GetJsSources(
+ run_location_, executing_scripts, num_injected_js_scripts);
+ DCHECK(!sources.empty());
+ int world_id = GetIsolatedWorldIdForInstance(injection_host_.get());
+
+ 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::mojom::EvaluationTiming execution_option =
+ should_execute_asynchronously
+ ? blink::mojom::EvaluationTiming::kAsynchronous
+ : blink::mojom::EvaluationTiming::kSynchronous;
+
+ render_frame_->GetWebFrame()->RequestExecuteScript(
+ world_id, sources, blink::mojom::UserActivationOption::kDoNotActivate,
+ execution_option,
+ blink::mojom::LoadEventBlockingOption::kBlock,
+ base::BindOnce(&ScriptInjection::OnJsInjectionCompleted,
+ weak_ptr_factory_.GetWeakPtr()),
+ blink::BackForwardCacheAware::kPossiblyDisallow,
+ blink::mojom::WantResultOption::kNoResult, blink::mojom::PromiseResultOption::kDoNotWait);
+ }
+
+void ScriptInjection::OnJsInjectionCompleted(
+ absl::optional<base::Value> results,
+ base::TimeTicks start_time) {
+ DCHECK(!did_inject_js_);
+
+ execution_result_ = std::move(results);
+ did_inject_js_ = true;
+
+ // If |async_completion_callback_| is set, it means the script finished
+ // asynchronously, and we should run it.
+ if (!async_completion_callback_.is_null()) {
+ complete_ = true;
+ injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
+ render_frame_);
+ // Warning: this object can be destroyed after this line!
+ std::move(async_completion_callback_).Run(this);
+ }
+}
+
+void ScriptInjection::InjectCss(std::set<std::string>* injected_stylesheets,
+ size_t* num_injected_stylesheets) {
+ std::vector<blink::WebString> css_sources = injector_->GetCssSources(
+ run_location_, injected_stylesheets, num_injected_stylesheets);
+ blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
+ // Default CSS origin is "author", but can be overridden to "user" by scripts.
+ absl::optional<CSSOrigin> css_origin = injector_->GetCssOrigin();
+ blink::WebCssOrigin blink_css_origin =
+ css_origin && *css_origin == CSS_ORIGIN_USER
+ ? blink::WebCssOrigin::kUser
+ : blink::WebCssOrigin::kAuthor;
+ blink::WebStyleSheetKey style_sheet_key;
+ if (const absl::optional<std::string>& injection_key =
+ injector_->GetInjectionKey())
+ style_sheet_key = blink::WebString::FromASCII(*injection_key);
+ for (const blink::WebString& css : css_sources)
+ web_frame->GetDocument().InsertStyleSheet(css, &style_sheet_key,
+ blink_css_origin);
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/renderer/script_injection.h b/components/user_scripts/renderer/script_injection.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/script_injection.h
@@ -0,0 +1,155 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_SCRIPT_INJECTION_H_
+#define USERSCRIPTS_RENDERER_SCRIPT_INJECTION_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "../common/user_script.h"
+#include "injection_host.h"
+#include "script_injector.h"
+#include "third_party/blink/public/web/web_script_execution_callback.h"
+
+struct HostID;
+
+namespace content {
+class RenderFrame;
+}
+
+namespace v8 {
+class Value;
+template <class T> class Local;
+}
+
+namespace user_scripts {
+struct ScriptsRunInfo;
+
+// A script wrapper which is aware of whether or not it is allowed to execute,
+// and contains the implementation to do so.
+class ScriptInjection {
+ public:
+ ScriptInjection(const ScriptInjection&) = delete;
+ ScriptInjection& operator=(const ScriptInjection&) = delete;
+ enum InjectionResult {
+ INJECTION_FINISHED,
+ INJECTION_BLOCKED,
+ INJECTION_WAITING
+ };
+
+ using CompletionCallback = base::OnceCallback<void(ScriptInjection*)>;
+
+ // Return the id of the injection host associated with the given world.
+ static std::string GetHostIdForIsolatedWorld(int world_id);
+
+ // Remove the isolated world associated with the given injection host.
+ static void RemoveIsolatedWorld(const std::string& host_id);
+
+ ScriptInjection(std::unique_ptr<ScriptInjector> injector,
+ content::RenderFrame* render_frame,
+ std::unique_ptr<const InjectionHost> injection_host,
+ UserScript::RunLocation run_location,
+ bool log_activity);
+ ~ScriptInjection();
+
+ // Try to inject the script at the |current_location|. This returns
+ // INJECTION_FINISHED if injection has injected or will never inject, returns
+ // INJECTION_BLOCKED if injection is running asynchronously and has not
+ // finished yet, returns INJECTION_WAITING if injections is delayed (either
+ // for permission purposes or because |current_location| is not the designated
+ // |run_location_|).
+ // If INJECTION_BLOCKED is returned, |async_completion_callback| will be
+ // called upon completion.
+ InjectionResult TryToInject(
+ UserScript::RunLocation current_location,
+ ScriptsRunInfo* scripts_run_info,
+ CompletionCallback async_completion_callback);
+
+ // Called when permission for the given injection has been granted.
+ // Returns INJECTION_FINISHED if injection has injected or will never inject,
+ // returns INJECTION_BLOCKED if injection is ran asynchronously.
+ InjectionResult OnPermissionGranted(ScriptsRunInfo* scripts_run_info);
+
+ // Resets the pointer of the injection host when the host is gone.
+ void OnHostRemoved();
+
+ void invalidate_render_frame() { render_frame_ = nullptr; }
+
+ // Accessors.
+ content::RenderFrame* render_frame() const { return render_frame_; }
+ const HostID& host_id() const { return injection_host_->id(); }
+ int64_t request_id() const { return request_id_; }
+
+ // Called when JS injection for the given frame has been completed or
+ // cancelled.
+ void OnJsInjectionCompleted(absl::optional<base::Value> results,
+ base::TimeTicks start_time);
+
+ private:
+ class FrameWatcher;
+
+ // Sends a message to the browser to request permission to inject.
+ void RequestPermissionFromBrowser();
+
+ // Injects the script. Returns INJECTION_FINISHED if injection has finished,
+ // otherwise INJECTION_BLOCKED.
+ InjectionResult Inject(ScriptsRunInfo* scripts_run_info);
+
+ // Inject any JS scripts into the frame for the injection.
+ void InjectJs(std::set<std::string>* executing_scripts,
+ size_t* num_injected_js_scripts);
+
+ // Inject any CSS source into the frame for the injection.
+ void InjectCss(std::set<std::string>* injected_stylesheets,
+ size_t* num_injected_stylesheets);
+
+ // Notify that we will not inject, and mark it as acknowledged.
+ void NotifyWillNotInject(ScriptInjector::InjectFailureReason reason);
+
+ // The injector for this injection.
+ std::unique_ptr<ScriptInjector> injector_;
+
+ // The RenderFrame into which this should inject the script.
+ content::RenderFrame* render_frame_;
+
+ // The associated injection host.
+ std::unique_ptr<const InjectionHost> injection_host_;
+
+ // The location in the document load at which we inject the script.
+ UserScript::RunLocation run_location_;
+
+ // This injection's request id. This will be -1 unless the injection is
+ // currently waiting on permission.
+ int64_t request_id_;
+
+ // Whether or not the injection is complete, either via injecting the script
+ // or because it will never complete.
+ bool complete_;
+
+ // Whether or not the injection successfully injected JS.
+ bool did_inject_js_;
+
+ // Whether or not we should log dom activity for this injection.
+ bool log_activity_;
+
+ // Results storage.
+ absl::optional<base::Value> execution_result_;
+
+ // The callback to run upon completing asynchronously.
+ CompletionCallback async_completion_callback_;
+
+ // A helper class to hold the render frame and watch for its deletion.
+ std::unique_ptr<FrameWatcher> frame_watcher_;
+
+ base::WeakPtrFactory<ScriptInjection> weak_ptr_factory_{this};
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_RENDERER_SCRIPT_INJECTION_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,415 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "script_injection_manager.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/feature_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "base/logging.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_thread.h"
+#include "extension_frame_helper.h"
+#include "../common/host_id.h"
+#include "script_injection.h"
+#include "scripts_run_info.h"
+#include "web_ui_injection_host.h"
+#include "ipc/ipc_message_macros.h"
+#include "third_party/blink/public/platform/web_url_error.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "url/gurl.h"
+#include "../common/user_scripts_features.h"
+
+namespace user_scripts {
+
+namespace {
+
+// The length of time to wait after the DOM is complete to try and run user
+// scripts.
+const int kScriptIdleTimeoutInMs = 200;
+
+// Returns the RunLocation that follows |run_location|.
+UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
+ switch (run_location) {
+ case UserScript::DOCUMENT_START:
+ return UserScript::DOCUMENT_END;
+ case UserScript::DOCUMENT_END:
+ return UserScript::DOCUMENT_IDLE;
+ case UserScript::DOCUMENT_IDLE:
+ return UserScript::RUN_LOCATION_LAST;
+ case UserScript::UNDEFINED:
+ case UserScript::RUN_DEFERRED:
+ case UserScript::BROWSER_DRIVEN:
+ case UserScript::RUN_LOCATION_LAST:
+ break;
+ }
+ NOTREACHED();
+ return UserScript::RUN_LOCATION_LAST;
+}
+
+} // namespace
+
+class ScriptInjectionManager::RFOHelper : public content::RenderFrameObserver {
+ public:
+ RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager);
+ ~RFOHelper() override;
+
+ // commit @9f2aac4
+ void Initialize();
+
+ private:
+ // RenderFrameObserver implementation.
+ void DidCreateNewDocument() override;
+ void DidCreateDocumentElement() override;
+ void DidFailProvisionalLoad() override;
+ void DidDispatchDOMContentLoadedEvent() override;
+ void WillDetach() override;
+ void OnDestruct() override;
+ void OnStop() override;
+
+ // Tells the ScriptInjectionManager to run tasks associated with
+ // document_idle.
+ void RunIdle();
+
+ void StartInjectScripts(UserScript::RunLocation run_location);
+
+ // Indicate that the frame is no longer valid because it is starting
+ // a new load or closing.
+ void InvalidateAndResetFrame(bool force_reset);
+
+ // The owning ScriptInjectionManager.
+ ScriptInjectionManager* manager_;
+
+ bool should_run_idle_ = true; // commit @9f2aac4
+
+ base::WeakPtrFactory<RFOHelper> weak_factory_{this};
+};
+
+ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame* render_frame,
+ ScriptInjectionManager* manager)
+ : content::RenderFrameObserver(render_frame),
+ manager_(manager),
+ should_run_idle_(true) {}
+
+ScriptInjectionManager::RFOHelper::~RFOHelper() {
+}
+
+
+void ScriptInjectionManager::RFOHelper::Initialize() {
+ // Set up for the initial empty document, for which the Document created
+ // events do not happen as it's already present.
+ DidCreateNewDocument();
+ // The initial empty document for a main frame may have scripts attached to it
+ // but we do not want to invalidate the frame and lose them when the next
+ // document loads. For example the IncognitoApiTest.IncognitoSplitMode test
+ // does `chrome.tabs.create()` with a script to be run, which is added to the
+ // frame before it navigates, so it needs to be preserved. However scripts in
+ // child frames are expected to be run inside the initial empty document. For
+ // example the ExecuteScriptApiTest.FrameWithHttp204 test creates a child
+ // frame at about:blank and expects to run injected scripts inside it.
+ // This is all quite inconsistent however tests both depend on us queuing and
+ // not queueing the DOCUMENT_START events in the initial empty document.
+ if (!render_frame()->IsMainFrame()) {
+ DidCreateDocumentElement();
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
+ // A new document is going to be shown, so invalidate the old document state.
+ // Don't force-reset the frame, because it is possible that a script injection
+ // was scheduled before the page was loaded, e.g. by navigating to a
+ // javascript: URL before the page has loaded.
+ constexpr bool kForceReset = false;
+ InvalidateAndResetFrame(kForceReset);
+}
+
+void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: DidCreateDocumentElement -> DOCUMENT_START";
+
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentStart(
+ base::BindOnce(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_START));
+}
+
+void ScriptInjectionManager::RFOHelper::DidFailProvisionalLoad() {
+ auto it = manager_->frame_statuses_.find(render_frame());
+ if (it != manager_->frame_statuses_.end() &&
+ it->second == UserScript::DOCUMENT_START) {
+ // Since the provisional load failed, the frame stays at its previous loaded
+ // state and origin (or the parent's origin for new/about:blank frames).
+ // Reset the frame to DOCUMENT_IDLE in order to reflect that the frame is
+ // done loading, and avoid any deadlock in the system.
+ //
+ // We skip injection of DOCUMENT_END and DOCUMENT_IDLE scripts, because the
+ // injections closely follow the DOMContentLoaded (and onload) events, which
+ // are not triggered after a failed provisional load.
+ // This assumption is verified in the checkDOMContentLoadedEvent subtest of
+ // ExecuteScriptApiTest.FrameWithHttp204 (browser_tests).
+ constexpr bool kForceReset = true;
+ InvalidateAndResetFrame(kForceReset);
+ should_run_idle_ = false;
+ manager_->frame_statuses_[render_frame()] = UserScript::DOCUMENT_IDLE;
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::DidDispatchDOMContentLoadedEvent() {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: DidDispatchDOMContentLoadedEvent -> DOCUMENT_END";
+
+ DCHECK(content::RenderThread::Get());
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentEnd(
+ base::BindOnce(&ScriptInjectionManager::RFOHelper::StartInjectScripts,
+ weak_factory_.GetWeakPtr(), UserScript::DOCUMENT_END));
+
+ // We try to run idle in two places: a delayed task here and in response to
+ // ContentRendererClient::RunScriptsAtDocumentIdle(). DidDispatchDOMContentLoadedEvent()
+ // corresponds to completing the document's load, whereas
+ // RunScriptsAtDocumentIdle() corresponds to completing the document and all
+ // subresources' load (but before the window.onload event). We don't want to
+ // hold up script injection for a particularly slow subresource, so we set a
+ // delayed task from here - but if we finish everything before that point
+ // (i.e., RunScriptsAtDocumentIdle() is triggered), then there's no reason to
+ // keep waiting.
+ render_frame()
+ ->GetTaskRunner(blink::TaskType::kInternalDefault)
+ ->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()),
+ base::Milliseconds(kScriptIdleTimeoutInMs));
+
+ ExtensionFrameHelper::Get(render_frame())
+ ->ScheduleAtDocumentIdle(
+ base::BindOnce(&ScriptInjectionManager::RFOHelper::RunIdle,
+ weak_factory_.GetWeakPtr()));
+}
+
+void ScriptInjectionManager::RFOHelper::WillDetach() {
+ // The frame is closing - invalidate.
+ constexpr bool kForceReset = true;
+ InvalidateAndResetFrame(kForceReset);
+}
+
+void ScriptInjectionManager::RFOHelper::OnDestruct() {
+ manager_->RemoveObserver(this);
+}
+
+void ScriptInjectionManager::RFOHelper::OnStop() {
+ // If the navigation request fails (e.g. 204/205/downloads), notify the
+ // extension to avoid keeping the frame in a START state indefinitely which
+ // leads to deadlocks.
+ DidFailProvisionalLoad();
+}
+
+void ScriptInjectionManager::RFOHelper::RunIdle() {
+ // Only notify the manager if the frame hasn't already had idle run since the
+ // task to RunIdle() was posted.
+ if (should_run_idle_) {
+ should_run_idle_ = false;
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: RunIdle -> DOCUMENT_IDLE";
+ manager_->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE);
+ }
+}
+
+void ScriptInjectionManager::RFOHelper::StartInjectScripts(
+ UserScript::RunLocation run_location) {
+ manager_->StartInjectScripts(render_frame(), run_location);
+}
+
+void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame(
+ bool force_reset) {
+ // Invalidate any pending idle injections, and reset the frame inject on idle.
+ weak_factory_.InvalidateWeakPtrs();
+ // We reset to inject on idle, because the frame can be reused (in the case of
+ // navigation).
+ should_run_idle_ = true;
+
+ // Reset the frame if either |force_reset| is true, or if the manager is
+ // keeping track of the state of the frame (in which case we need to clean it
+ // up).
+ if (force_reset || manager_->frame_statuses_.count(render_frame()) != 0)
+ manager_->InvalidateForFrame(render_frame());
+}
+
+ScriptInjectionManager::ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager)
+ : user_script_set_manager_(user_script_set_manager),
+ user_script_set_manager_observation_(this) {
+ user_script_set_manager_observation_.Observe(user_script_set_manager_);
+}
+
+ScriptInjectionManager::~ScriptInjectionManager() {
+ for (const auto& injection : pending_injections_)
+ injection->invalidate_render_frame();
+ for (const auto& injection : running_injections_)
+ injection->invalidate_render_frame();
+}
+
+void ScriptInjectionManager::OnRenderFrameCreated(
+ content::RenderFrame* render_frame) {
+ rfo_helpers_.push_back(std::make_unique<RFOHelper>(render_frame, this));
+ rfo_helpers_.back()->Initialize(); // commit @9f2aac4
+}
+
+void ScriptInjectionManager::OnInjectionFinished(
+ ScriptInjection* injection) {
+ auto iter =
+ std::find_if(running_injections_.begin(), running_injections_.end(),
+ [injection](const std::unique_ptr<ScriptInjection>& mode) {
+ return injection == mode.get();
+ });
+ if (iter != running_injections_.end())
+ running_injections_.erase(iter);
+}
+
+void ScriptInjectionManager::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts) {
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if (changed_hosts.count((*iter)->host_id()) > 0)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+}
+
+void ScriptInjectionManager::RemoveObserver(RFOHelper* helper) {
+ for (auto iter = rfo_helpers_.begin(); iter != rfo_helpers_.end(); ++iter) {
+ if (iter->get() == helper) {
+ rfo_helpers_.erase(iter);
+ break;
+ }
+ }
+}
+
+void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame* frame) {
+ // If the frame invalidated is the frame being injected into, we need to
+ // note it.
+ active_injection_frames_.erase(frame);
+
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame)
+ iter = pending_injections_.erase(iter);
+ else
+ ++iter;
+ }
+
+ frame_statuses_.erase(frame);
+}
+
+void ScriptInjectionManager::StartInjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ auto iter = frame_statuses_.find(frame);
+ // We also don't execute if we detect that the run location is somehow out of
+ // order. This can happen if:
+ // - The first run location reported for the frame isn't DOCUMENT_START, or
+ // - The run location reported doesn't immediately follow the previous
+ // reported run location.
+ // We don't want to run because extensions may have requirements that scripts
+ // running in an earlier run location have run by the time a later script
+ // runs. Better to just not run.
+ // Note that we check run_location > NextRunLocation() in the second clause
+ // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
+ // can happen multiple times, so we can receive earlier/equal run locations.
+ if ((iter == frame_statuses_.end() &&
+ run_location != UserScript::DOCUMENT_START) ||
+ (iter != frame_statuses_.end() &&
+ run_location > NextRunLocation(iter->second))) {
+ // We also invalidate the frame, because the run order of pending injections
+ // may also be bad.
+ InvalidateForFrame(frame);
+ return;
+ } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
+ // Certain run location signals (like DidCreateDocumentElement) can happen
+ // multiple times. Ignore the subsequent signals.
+ return;
+ }
+
+ // Otherwise, all is right in the world, and we can get on with the
+ // injections!
+ frame_statuses_[frame] = run_location;
+ InjectScripts(frame, run_location);
+}
+
+void ScriptInjectionManager::InjectScripts(
+ content::RenderFrame* frame,
+ UserScript::RunLocation run_location) {
+ // Find any injections that want to run on the given frame.
+ ScriptInjectionVector frame_injections;
+ for (auto iter = pending_injections_.begin();
+ iter != pending_injections_.end();) {
+ if ((*iter)->render_frame() == frame) {
+ frame_injections.push_back(std::move(*iter));
+ iter = pending_injections_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ // Add any injections for user scripts.
+ int tab_id = ExtensionFrameHelper::Get(frame)->tab_id();
+ user_script_set_manager_->GetAllInjections(&frame_injections, frame, tab_id,
+ run_location);
+
+ // Note that we are running in |frame|.
+ active_injection_frames_.insert(frame);
+
+ ScriptsRunInfo scripts_run_info(frame, run_location);
+
+ for (auto iter = frame_injections.begin(); iter != frame_injections.end();) {
+ // It's possible for thScriptsRunInfoe frame to be invalidated in the course of injection
+ // (if a script removes its own frame, for example). If this happens, abort.
+ if (!active_injection_frames_.count(frame))
+ break;
+ std::unique_ptr<ScriptInjection> injection(std::move(*iter));
+ iter = frame_injections.erase(iter);
+ TryToInject(std::move(injection), run_location, &scripts_run_info);
+ }
+
+ // We are done running in the frame.
+ active_injection_frames_.erase(frame);
+
+ scripts_run_info.LogRun(activity_logging_enabled_);
+}
+
+void ScriptInjectionManager::TryToInject(
+ std::unique_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info) {
+ // Try to inject the script. If the injection is waiting (i.e., for
+ // permission), add it to the list of pending injections. If the injection
+ // has blocked, add it to the list of running injections.
+ // The Unretained below is safe because this object owns all the
+ // ScriptInjections, so is guaranteed to outlive them.
+ switch (injection->TryToInject(
+ run_location, scripts_run_info,
+ base::BindOnce(&ScriptInjectionManager::OnInjectionFinished,
+ base::Unretained(this)))) {
+ case ScriptInjection::INJECTION_WAITING:
+ pending_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_BLOCKED:
+ running_injections_.push_back(std::move(injection));
+ break;
+ case ScriptInjection::INJECTION_FINISHED:
+ break;
+ }
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/renderer/script_injection_manager.h b/components/user_scripts/renderer/script_injection_manager.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/script_injection_manager.h
@@ -0,0 +1,100 @@
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#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:
+ ScriptInjectionManager(const ScriptInjectionManager&) = delete;
+ ScriptInjectionManager& operator=(const ScriptInjectionManager&) = delete;
+ explicit ScriptInjectionManager(
+ UserScriptSetManager* user_script_set_manager);
+ virtual ~ScriptInjectionManager();
+
+ // Notifies that a new render view has been created.
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ // Removes pending injections of the unloaded extension.
+ //void OnExtensionUnloaded(const std::string& extension_id);
+
+ void set_activity_logging_enabled(bool enabled) {
+ activity_logging_enabled_ = enabled;
+ }
+
+ private:
+ // A RenderFrameObserver implementation which watches the various render
+ // frames in order to notify the ScriptInjectionManager of different
+ // document load states and IPCs.
+ class RFOHelper;
+
+ using FrameStatusMap =
+ std::map<content::RenderFrame*, UserScript::RunLocation>;
+
+ using ScriptInjectionVector = std::vector<std::unique_ptr<ScriptInjection>>;
+
+ // Notifies that an injection has been finished.
+ void OnInjectionFinished(ScriptInjection* injection);
+
+ // UserScriptSetManager::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) override;
+
+ // Notifies that an RFOHelper should be removed.
+ void RemoveObserver(RFOHelper* helper);
+
+ // Invalidate any pending tasks associated with |frame|.
+ void InvalidateForFrame(content::RenderFrame* frame);
+
+ // Starts the process to inject appropriate scripts into |frame|.
+ void StartInjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Actually injects the scripts into |frame|.
+ void InjectScripts(content::RenderFrame* frame,
+ UserScript::RunLocation run_location);
+
+ // Try to inject and store injection if it has not finished.
+ void TryToInject(std::unique_ptr<ScriptInjection> injection,
+ UserScript::RunLocation run_location,
+ ScriptsRunInfo* scripts_run_info);
+
+ // The map of active web frames to their corresponding statuses. The
+ // RunLocation of the frame corresponds to the last location that has ran.
+ FrameStatusMap frame_statuses_;
+
+ // The frames currently being injected into, so long as that frame is valid.
+ std::set<content::RenderFrame*> active_injection_frames_;
+
+ // The collection of RFOHelpers.
+ std::vector<std::unique_ptr<RFOHelper>> rfo_helpers_;
+
+ // The set of UserScripts associated with extensions. Owned by the Dispatcher.
+ UserScriptSetManager* user_script_set_manager_;
+
+ // Pending injections which are waiting for either the proper run location or
+ // user consent.
+ ScriptInjectionVector pending_injections_;
+
+ // Running injections which are waiting for async callbacks from blink.
+ ScriptInjectionVector running_injections_;
+
+ // Whether or not dom activity should be logged for scripts injected.
+ bool activity_logging_enabled_ = false;
+
+ base::ScopedObservation<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observation_{this};
+};
+
+}
diff --git a/components/user_scripts/renderer/script_injector.h b/components/user_scripts/renderer/script_injector.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/script_injector.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
+#define USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "../common/constants.h"
+#include "../common/user_script.h"
+#include "third_party/blink/public/web/web_script_source.h"
+
+class InjectionHost;
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace user_scripts {
+
+// The pseudo-delegate class for a ScriptInjection that provides all necessary
+// information about how to inject the script, including what code to inject and
+// when (run location), but without any injection logic.
+class ScriptInjector {
+ public:
+ // The possible reasons for not injecting the script.
+ enum InjectFailureReason {
+ EXTENSION_REMOVED, // The extension was removed before injection.
+ NOT_ALLOWED, // The script is not allowed to inject.
+ WONT_INJECT // The injection won't inject because the user rejected
+ // (or just did not accept) the injection.
+ };
+
+ virtual ~ScriptInjector() {}
+
+ // Returns the script type of this particular injection.
+ virtual UserScript::InjectionType script_type() const = 0;
+
+ // Returns true if the script is running inside a user gesture.
+ virtual bool IsUserGesture() const = 0;
+
+ // Returns the CSS origin of this injection.
+ virtual absl::optional<CSSOrigin> GetCssOrigin() const = 0;
+
+ // Returns the key for this injection, if it's a CSS injection.
+ virtual const absl::optional<std::string> GetInjectionKey() const = 0;
+
+ // Returns true if the script expects results.
+ virtual bool ExpectsResults() const = 0;
+
+ // Returns true if the script should inject JS source at the given
+ // |run_location|.
+ virtual bool ShouldInjectJs(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& executing_scripts) const = 0;
+
+ // Returns true if the script should inject CSS at the given |run_location|.
+ virtual bool ShouldInjectCss(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& injected_stylesheets) const = 0;
+
+ // Returns the javascript sources to inject at the given |run_location|.
+ // Only called if ShouldInjectJs() is true.
+ virtual std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* executing_scripts,
+ size_t* num_injected_js_scripts) const = 0;
+
+ // Returns the css to inject at the given |run_location|.
+ // Only called if ShouldInjectCss() is true.
+ virtual std::vector<blink::WebString> GetCssSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* injected_stylesheets,
+ size_t* num_injected_stylesheets) const = 0;
+
+ // Notifies the script that injection has completed, with a possibly-populated
+ // list of results (depending on whether or not ExpectsResults() was true).
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnInjectionComplete(
+ absl::optional<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) = 0;
+
+ // Notifies the script that injection will never occur.
+ // |render_frame| contains the render frame, or null if the frame was
+ // invalidated.
+ virtual void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) = 0;
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_RENDERER_SCRIPT_INJECTOR_H_
diff --git a/components/user_scripts/renderer/scripts_run_info.cc b/components/user_scripts/renderer/scripts_run_info.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/scripts_run_info.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "scripts_run_info.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "script_context.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+namespace user_scripts {
+
+ScriptsRunInfo::ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location)
+ : num_css(0u),
+ num_js(0u),
+ num_blocking_js(0u),
+ routing_id_(render_frame->GetRoutingID()),
+ run_location_(location),
+ frame_url_(ScriptContext::GetDocumentLoaderURLForFrame(
+ render_frame->GetWebFrame())) {}
+
+ScriptsRunInfo::~ScriptsRunInfo() {
+}
+
+void ScriptsRunInfo::LogRun(bool send_script_activity) {
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/renderer/scripts_run_info.h b/components/user_scripts/renderer/scripts_run_info.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/scripts_run_info.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_SCRIPTS_RUN_INFO_H_
+#define USERSCRIPTS_RENDERER_SCRIPTS_RUN_INFO_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/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 {
+ ScriptsRunInfo(const ScriptsRunInfo&) = delete;
+ ScriptsRunInfo& operator=(const ScriptsRunInfo&) = delete;
+ // Map of extensions IDs to the executing script paths.
+ typedef std::map<std::string, std::set<std::string> > ExecutingScriptsMap;
+
+ ScriptsRunInfo(content::RenderFrame* render_frame,
+ UserScript::RunLocation location);
+ ~ScriptsRunInfo();
+
+ // The number of CSS scripts injected.
+ size_t num_css;
+ // The number of JS scripts injected.
+ size_t num_js;
+ // The number of blocked JS scripts injected.
+ size_t num_blocking_js;
+ // A map of extension ids to executing script paths.
+ ExecutingScriptsMap executing_scripts;
+ // A map of extension ids to injected stylesheet paths.
+ ExecutingScriptsMap injected_stylesheets;
+ // The elapsed time since the ScriptsRunInfo was constructed.
+ base::ElapsedTimer timer;
+
+ // Log information about a given script run. If |send_script_activity| is
+ // true, this also informs the browser of the script run.
+ void LogRun(bool send_script_activity);
+
+ static void LogLongInjectionTaskTime(UserScript::RunLocation run_location,
+ const base::TimeDelta& elapsed);
+
+ private:
+ // The routinig id to use to notify the browser of any injections. Since the
+ // frame may be deleted in injection, we don't hold on to a reference to it
+ // directly.
+ int routing_id_;
+
+ // The run location at which injection is happening.
+ UserScript::RunLocation run_location_;
+
+ // The url of the frame, preserved for the same reason as the routing id.
+ GURL frame_url_;
+};
+
+} // 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,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "user_script_injector.h"
+
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/lazy_instance.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "components/user_scripts/renderer/grit/user_scripts_renderer_resources.h"
+#include "injection_host.h"
+#include "script_context.h"
+#include "scripts_run_info.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "url/gurl.h"
+
+namespace user_scripts {
+
+namespace {
+
+struct RoutingInfoKey {
+ int routing_id;
+ int script_id;
+
+ RoutingInfoKey(int routing_id, int script_id)
+ : routing_id(routing_id), script_id(script_id) {}
+
+ bool operator<(const RoutingInfoKey& other) const {
+ return std::tie(routing_id, script_id) <
+ std::tie(other.routing_id, other.script_id);
+ }
+};
+
+using RoutingInfoMap = std::map<RoutingInfoKey, bool>;
+
+// A map records whether a given |script_id| from a webview-added user script
+// is allowed to inject on the render of given |routing_id|.
+// Once a script is added, the decision of whether or not allowed to inject
+// won't be changed.
+// After removed by the webview, the user scipt will also be removed
+// from the render. Therefore, there won't be any query from the same
+// |script_id| and |routing_id| pair.
+// base::LazyInstance<RoutingInfoMap>::DestructorAtExit g_routing_info_map =
+// LAZY_INSTANCE_INITIALIZER;
+
+// Greasemonkey API source that is injected with the scripts.
+struct GreasemonkeyApiJsString {
+ GreasemonkeyApiJsString();
+ blink::WebScriptSource GetSource() const;
+
+ private:
+ blink::WebString source_;
+};
+
+// The below constructor, monstrous as it is, just makes a WebScriptSource from
+// the GreasemonkeyApiJs resource.
+GreasemonkeyApiJsString::GreasemonkeyApiJsString() {
+ std::string greasemonky_api_js(
+ ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
+ IDR_GREASEMONKEY_API_JS));
+ source_ = blink::WebString::FromUTF8(greasemonky_api_js);
+}
+
+blink::WebScriptSource GreasemonkeyApiJsString::GetSource() const {
+ return blink::WebScriptSource(source_);
+}
+
+base::LazyInstance<GreasemonkeyApiJsString>::Leaky g_greasemonkey_api =
+ LAZY_INSTANCE_INITIALIZER;
+
+bool ShouldInjectScripts(const UserScript::FileList& scripts,
+ const std::set<std::string>& injected_files) {
+ for (const std::unique_ptr<UserScript::File>& file : scripts) {
+ // Check if the script is already injected.
+ if (injected_files.count(file->url().path()) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+UserScriptInjector::UserScriptInjector(const UserScript* script,
+ UserScriptSet* script_list)
+ : script_(script),
+ user_script_set_(script_list),
+ script_id_(script_->id()),
+ user_script_set_observer_(this) {
+ user_script_set_observer_.Observe(script_list);
+}
+
+UserScriptInjector::~UserScriptInjector() {
+}
+
+void UserScriptInjector::OnUserScriptsUpdated(
+ const std::set<HostID>& changed_hosts,
+ const UserScriptList& scripts) {
+ // When user scripts are updated, all the old script pointers are invalidated.
+ script_ = nullptr;
+ // If the host causing this injection changed, then this injection
+ // will be removed, and there's no guarantee the backing script still exists.
+ // if (changed_hosts.count(host_id_) > 0)
+ // return;
+
+ for (const std::unique_ptr<UserScript>& script : scripts) {
+ if (script->id() == script_id_) {
+ script_ = script.get();
+ break;
+ }
+ }
+ // If |host_id_| wasn't in |changed_hosts|, then the script for this injection
+ // should be guaranteed to exist.
+ DCHECK(script_);
+}
+
+UserScript::InjectionType UserScriptInjector::script_type() const {
+ return UserScript::CONTENT_SCRIPT;
+}
+
+bool UserScriptInjector::IsUserGesture() const {
+ return false;
+}
+
+bool UserScriptInjector::ExpectsResults() const {
+ return false;
+}
+
+absl::optional<CSSOrigin> UserScriptInjector::GetCssOrigin() const {
+ return absl::nullopt;
+}
+
+const absl::optional<std::string> UserScriptInjector::GetInjectionKey() const {
+ return absl::nullopt;
+}
+
+bool UserScriptInjector::ShouldInjectJs(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& executing_scripts) const {
+ return script_ && script_->run_location() == run_location &&
+ !script_->js_scripts().empty() &&
+ ShouldInjectScripts(script_->js_scripts(), executing_scripts);
+}
+
+bool UserScriptInjector::ShouldInjectCss(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& injected_stylesheets) const {
+ return script_ && run_location == UserScript::DOCUMENT_START &&
+ !script_->css_scripts().empty() &&
+ ShouldInjectScripts(script_->css_scripts(), injected_stylesheets);
+}
+
+std::vector<blink::WebScriptSource> UserScriptInjector::GetJsSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* executing_scripts,
+ size_t* num_injected_js_scripts) const {
+ DCHECK(script_);
+ std::vector<blink::WebScriptSource> sources;
+
+ DCHECK_EQ(script_->run_location(), run_location);
+
+ const UserScript::FileList& js_scripts = script_->js_scripts();
+ sources.reserve(js_scripts.size() +
+ (script_->emulate_greasemonkey() ? 1 : 0));
+ // Emulate Greasemonkey API for scripts that were converted to extension
+ // user scripts.
+ if (script_->emulate_greasemonkey())
+ sources.push_back(g_greasemonkey_api.Get().GetSource());
+ for (const std::unique_ptr<UserScript::File>& file : js_scripts) {
+ const GURL& script_url = file->url();
+ // Check if the script is already injected.
+ if (executing_scripts->count(script_url.path()) != 0)
+ continue;
+
+ sources.push_back(blink::WebScriptSource(
+ user_script_set_->GetJsSource(*file, script_->emulate_greasemonkey()),
+ script_url));
+
+ (*num_injected_js_scripts) += 1;
+ executing_scripts->insert(script_url.path());
+ }
+
+ return sources;
+}
+
+std::vector<blink::WebString> UserScriptInjector::GetCssSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* injected_stylesheets,
+ size_t* num_injected_stylesheets) const {
+ DCHECK(script_);
+ DCHECK_EQ(UserScript::DOCUMENT_START, run_location);
+
+ std::vector<blink::WebString> sources;
+
+ const UserScript::FileList& css_scripts = script_->css_scripts();
+ sources.reserve(css_scripts.size());
+ for (const std::unique_ptr<UserScript::File>& file : script_->css_scripts()) {
+ const std::string& stylesheet_path = file->url().path();
+ // Check if the stylesheet is already injected.
+ if (injected_stylesheets->count(stylesheet_path) != 0)
+ continue;
+
+ sources.push_back(user_script_set_->GetCssSource(*file));
+ (*num_injected_stylesheets) += 1;
+ injected_stylesheets->insert(stylesheet_path);
+ }
+ return sources;
+}
+
+void UserScriptInjector::OnInjectionComplete(
+ absl::optional<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) {}
+
+void UserScriptInjector::OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) {
+}
+
+} // namespace extensions
diff --git a/components/user_scripts/renderer/user_script_injector.h b/components/user_scripts/renderer/user_script_injector.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/user_script_injector.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_USER_SCRIPT_INJECTOR_H_
+#define USERSCRIPTS_RENDERER_USER_SCRIPT_INJECTOR_H_
+
+#include <memory>
+#include <string>
+
+#include "base/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 UserScriptInjector&) = delete;
+ UserScriptInjector& operator=(const UserScriptInjector&) = delete;
+ UserScriptInjector(const UserScript* user_script,
+ UserScriptSet* user_script_set);
+ ~UserScriptInjector() override;
+
+ private:
+ // UserScriptSet::Observer implementation.
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const UserScriptList& scripts) override;
+
+ // ScriptInjector implementation.
+ UserScript::InjectionType script_type() const override;
+ bool IsUserGesture() const override;
+ absl::optional<CSSOrigin> GetCssOrigin() const override;
+ const absl::optional<std::string> GetInjectionKey() const override;
+ bool ExpectsResults() const override;
+ bool ShouldInjectJs(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& executing_scripts) const override;
+ bool ShouldInjectCss(
+ UserScript::RunLocation run_location,
+ const std::set<std::string>& injected_stylesheets) const override;
+ std::vector<blink::WebScriptSource> GetJsSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* executing_scripts,
+ size_t* num_injected_js_scripts) const override;
+ std::vector<blink::WebString> GetCssSources(
+ UserScript::RunLocation run_location,
+ std::set<std::string>* injected_stylesheets,
+ size_t* num_injected_stylesheets) const override;
+ void OnInjectionComplete(absl::optional<base::Value> execution_result,
+ UserScript::RunLocation run_location,
+ content::RenderFrame* render_frame) override;
+ void OnWillNotInject(InjectFailureReason reason,
+ content::RenderFrame* render_frame) override;
+
+ // The associated user script. Owned by the UserScriptInjector that created
+ // this object.
+ const UserScript* script_;
+
+ // The UserScriptSet that eventually owns the UserScript this
+ // UserScriptInjector points to.
+ // Outlives |this|.
+ UserScriptSet* const user_script_set_;
+
+ // The id of the associated user script. We cache this because when we update
+ // the |script_| associated with this injection, the old referance may be
+ // deleted.
+ int script_id_;
+
+ base::ScopedObservation<UserScriptSet, UserScriptSet::Observer>
+ user_script_set_observer_{this};
+};
+
+} // 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,266 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "user_script_set.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/debug/alias.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/strcat.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "injection_host.h"
+#include "script_context.h"
+#include "script_injection.h"
+#include "user_script_injector.h"
+#include "web_ui_injection_host.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/gurl.h"
+#include "../common/user_scripts_features.h"
+
+namespace user_scripts {
+
+namespace {
+
+// These two strings are injected before and after the Greasemonkey API and
+// user script to wrap it in an anonymous scope.
+const char kUserScriptHead[] = "(function (unsafeWindow) {\n";
+const char kUserScriptTail[] = "\n})(window);";
+// Maximum number of total content scripts we allow (across all extensions).
+// The limit exists to diagnose https://crbug.com/723381. The number is
+// arbitrarily chosen.
+// TODO(lazyboy): Remove when the bug is fixed.
+const uint32_t kNumScriptsArbitraryMax = 100000u;
+
+GURL GetDocumentUrlForFrame(blink::WebLocalFrame* frame) {
+ GURL data_source_url = ScriptContext::GetDocumentLoaderURLForFrame(frame);
+ if (!data_source_url.is_empty() && frame->IsViewSourceModeEnabled()) {
+ data_source_url = GURL(content::kViewSourceScheme + std::string(":") +
+ data_source_url.spec());
+ }
+
+ return data_source_url;
+}
+
+} // namespace
+
+UserScriptSet::UserScriptSet() {}
+
+UserScriptSet::~UserScriptSet() {
+}
+
+void UserScriptSet::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSet::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserScriptSet::GetInjections(
+ std::vector<std::unique_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ bool log_activity) {
+ GURL document_url = GetDocumentUrlForFrame(render_frame->GetWebFrame());
+ for (const std::unique_ptr<UserScript>& script : scripts_) {
+ std::unique_ptr<ScriptInjection> injection = GetInjectionForScript(
+ script.get(), render_frame, tab_id, run_location, document_url,
+ /* is_declarative, */ log_activity);
+ if (injection.get())
+ injections->push_back(std::move(injection));
+ }
+}
+
+bool UserScriptSet::UpdateUserScripts(
+ base::ReadOnlySharedMemoryRegion shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only) {
+ bool only_inject_incognito = false;
+ //ExtensionsRendererClient::Get()->IsIncognitoProcess();
+
+ // Create the shared memory mapping.
+ shared_memory_mapping_ = shared_memory.Map();
+ if (!shared_memory.IsValid())
+ return false;
+
+ // First get the size of the memory block.
+ const base::Pickle::Header* pickle_header =
+ shared_memory_mapping_.GetMemoryAs<base::Pickle::Header>();
+ if (!pickle_header)
+ return false;
+
+ // Now read in the rest of the block.
+ size_t pickle_size =
+ sizeof(base::Pickle::Header) + pickle_header->payload_size;
+
+ // Unpickle scripts.
+ uint32_t num_scripts = 0;
+ auto memory = shared_memory_mapping_.GetMemoryAsSpan<char>(pickle_size);
+ if (!memory.size())
+ return false;
+
+ base::Pickle pickle(memory.data(), pickle_size);
+ base::PickleIterator iter(pickle);
+ base::debug::Alias(&pickle_size);
+ CHECK(iter.ReadUInt32(&num_scripts));
+
+ // Sometimes the shared memory contents seem to be corrupted
+ // (https://crbug.com/723381). Set an arbitrary max limit to the number of
+ // scripts so that we don't add OOM noise to crash reports.
+ CHECK_LT(num_scripts, kNumScriptsArbitraryMax);
+
+ scripts_.clear();
+ script_sources_.clear();
+ scripts_.reserve(num_scripts);
+ for (uint32_t i = 0; i < num_scripts; ++i) {
+ std::unique_ptr<UserScript> script(new UserScript());
+ script->Unpickle(pickle, &iter);
+
+ // Note that this is a pointer into shared memory. We don't own it. It gets
+ // cleared up when the last renderer or browser process drops their
+ // reference to the shared memory.
+ for (size_t j = 0; j < script->js_scripts().size(); ++j) {
+ const char* body = NULL;
+ size_t 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;
+ size_t body_length = 0;
+ CHECK(iter.ReadData(&body, &body_length));
+ script->css_scripts()[j]->set_external_content(
+ base::StringPiece(body, body_length));
+ }
+
+ if (only_inject_incognito && !script->is_incognito_enabled())
+ continue; // This script shouldn't run in an incognito tab.
+
+ scripts_.push_back(std::move(script));
+ }
+
+ for (auto& observer : observers_)
+ observer.OnUserScriptsUpdated(changed_hosts, scripts_);
+ return true;
+}
+
+void UserScriptSet::AddScript(std::unique_ptr<UserScript> script) {
+ scripts_.push_back(std::move(script));
+}
+
+std::unique_ptr<ScriptInjection> UserScriptSet::GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ //bool is_declarative,
+ bool log_activity) {
+ std::unique_ptr<ScriptInjection> injection;
+ std::unique_ptr<const InjectionHost> injection_host;
+ blink::WebLocalFrame* web_frame = render_frame->GetWebFrame();
+
+ const HostID& host_id = script->host_id();
+ injection_host.reset(new WebUIInjectionHost(host_id));
+
+ GURL effective_document_url =
+ ScriptContext::GetEffectiveDocumentURLForInjection(
+ web_frame, document_url, script->match_origin_as_fallback());
+
+ bool is_subframe = web_frame->Parent();
+ if (!script->MatchesDocument(effective_document_url, is_subframe)) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: No Match name=" << script->name() <<
+ " id=" << script->host_id().id() <<
+ " url=" << effective_document_url.spec();
+ return injection;
+ }
+
+ std::unique_ptr<ScriptInjector> injector(
+ new UserScriptInjector(script, this));
+
+ bool inject_css = !script->css_scripts().empty() &&
+ run_location == UserScript::DOCUMENT_START;
+ bool inject_js =
+ !script->js_scripts().empty() && script->run_location() == run_location;
+ if (inject_css || inject_js) {
+ injection.reset(new ScriptInjection(std::move(injector), render_frame,
+ std::move(injection_host), run_location,
+ log_activity));
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: Match name=" << script->name() << " " <<
+ "url=" << effective_document_url.spec();
+ } else {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts)) {
+ if (script->run_location() != run_location)
+ LOG(INFO) << "UserScripts: run location for name=" << script->name() <<
+ " id=" << script->host_id().id() <<
+ " current " << run_location <<
+ " need " << script->run_location();
+ else
+ LOG(INFO) << "UserScripts: Match but no run name=" << script->name() <<
+ " id=" << script->host_id().id() <<
+ " url=" << effective_document_url.spec();
+ }
+ }
+ return injection;
+}
+
+blink::WebString UserScriptSet::GetJsSource(const UserScript::File& file,
+ bool emulate_greasemonkey) {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts)) {
+ if (emulate_greasemonkey) {
+ LOG(INFO) << "UserScripts: Injecting w/greasemonkey " << file.url();
+ } else {
+ LOG(INFO) << "UserScripts: Injecting " << file.url();
+ }
+ }
+
+ 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);
+ } else {
+ source = blink::WebString::FromUTF8(script_content.data(),
+ script_content.length());
+ }
+ 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,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
+#define USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/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:
+ UserScriptSet(const UserScriptSet&) = delete;
+ UserScriptSet& operator=(const UserScriptSet&) = delete;
+ class Observer {
+ public:
+ // Called when the set of user scripts is updated. |changed_hosts| contains
+ // the hosts whose scripts have been altered. Note that *all* script objects
+ // are invalidated, even if they aren't in |changed_hosts|.
+ virtual void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts,
+ const UserScriptList& scripts) = 0;
+ };
+
+ UserScriptSet();
+ ~UserScriptSet();
+
+ // Adds or removes observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+ void AddScript(std::unique_ptr<UserScript> script);
+
+ // Append any ScriptInjections that should run on the given |render_frame| and
+ // |tab_id|, at the given |run_location|, to |injections|.
+ // |extensions| is passed in to verify the corresponding extension is still
+ // valid.
+ void GetInjections(std::vector<std::unique_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ bool log_activity);
+
+ // Updates scripts given the shared memory region containing user scripts.
+ // Returns true if the scripts were successfully updated.
+ bool UpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory,
+ const std::set<HostID>& changed_hosts,
+ bool whitelisted_only);
+
+ // Returns the contents of a script file.
+ // Note that copying is cheap as this uses WebString.
+ blink::WebString GetJsSource(const UserScript::File& file,
+ bool emulate_greasemonkey);
+ blink::WebString GetCssSource(const UserScript::File& file);
+
+ private:
+ // Returns a new ScriptInjection for the given |script| to execute in the
+ // |render_frame|, or NULL if the script should not execute.
+ std::unique_ptr<ScriptInjection> GetInjectionForScript(
+ const UserScript* script,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location,
+ const GURL& document_url,
+ //bool is_declarative,
+ bool log_activity);
+
+ // Shared memory mapping containing raw script data.
+ base::ReadOnlySharedMemoryMapping shared_memory_mapping_;
+
+ // The UserScripts this injector manages.
+ UserScriptList scripts_;
+
+ // Map of user script file url -> source.
+ std::map<GURL, blink::WebString> script_sources_;
+
+ // The associated observers.
+ base::ObserverList<Observer>::Unchecked observers_;
+};
+
+} // namespace extensions
+
+#endif // USERSCRIPTS_RENDERER_USER_SCRIPT_SET_H_
diff --git a/components/user_scripts/renderer/user_script_set_manager.cc b/components/user_scripts/renderer/user_script_set_manager.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/user_script_set_manager.cc
@@ -0,0 +1,77 @@
+#include "user_script_set_manager.h"
+
+#include "base/logging.h"
+#include "content/public/renderer/render_thread.h"
+#include "../common/host_id.h"
+#include "../common/extension_messages.h"
+#include "../common/user_scripts_features.h"
+#include "user_script_set.h"
+
+namespace user_scripts {
+
+UserScriptSetManager::UserScriptSetManager() {
+ content::RenderThread::Get()->AddObserver(this);
+}
+
+UserScriptSetManager::~UserScriptSetManager() {
+}
+
+void UserScriptSetManager::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserScriptSetManager::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool UserScriptSetManager::OnControlMessageReceived(
+ const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(UserScriptSetManager, message)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void UserScriptSetManager::GetAllInjections(
+ std::vector<std::unique_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location) {
+
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: GetAllInjections";
+
+ // static_scripts_ is UserScriptSet
+ static_scripts_.GetInjections(injections, render_frame, tab_id, run_location,
+ activity_logging_enabled_);
+}
+
+void UserScriptSetManager::OnUpdateUserScripts(
+ base::ReadOnlySharedMemoryRegion shared_memory) {
+ if (!shared_memory.IsValid()) {
+ NOTREACHED() << "Bad scripts handle";
+ return;
+ }
+
+ UserScriptSet* scripts = NULL;
+ scripts = &static_scripts_;
+
+ DCHECK(scripts);
+
+ // If no hosts are included in the set, that indicates that all
+ // hosts were updated. Add them all to the set so that observers and
+ // individual UserScriptSets don't need to know this detail.
+ //const std::set<HostID>* effective_hosts = &changed_hosts;
+ std::set<HostID> all_hosts;
+ const std::set<HostID>* effective_hosts = &all_hosts;
+
+ if (scripts->UpdateUserScripts(std::move(shared_memory), *effective_hosts,
+ false /*whitelisted_only*/)) {
+ for (auto& observer : observers_)
+ observer.OnUserScriptsUpdated(all_hosts /* *effective_hosts*/);
+ }
+}
+
+}
\ No newline at end of file
diff --git a/components/user_scripts/renderer/user_script_set_manager.h b/components/user_scripts/renderer/user_script_set_manager.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/user_script_set_manager.h
@@ -0,0 +1,61 @@
+#ifndef USERSCRIPTS_RENDER_SET_MANAGER_H_
+#define USERSCRIPTS_RENDER_SET_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/observer_list.h"
+#include "content/public/renderer/render_thread_observer.h"
+#include "../common/host_id.h"
+#include "user_script_set.h"
+#include "script_injection.h"
+
+namespace user_scripts {
+
+class UserScriptSetManager : public content::RenderThreadObserver {
+ public:
+ class Observer {
+ public:
+ virtual void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) = 0;
+ };
+
+ UserScriptSetManager();
+
+ ~UserScriptSetManager() override;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Append all injections from |static_scripts| and each of
+ // |programmatic_scripts_| to |injections|.
+ void GetAllInjections(
+ std::vector<std::unique_ptr<ScriptInjection>>* injections,
+ content::RenderFrame* render_frame,
+ int tab_id,
+ UserScript::RunLocation run_location);
+
+private:
+ // content::RenderThreadObserver implementation.
+ bool OnControlMessageReceived(const IPC::Message& message) override;
+
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ // Handle the UpdateUserScripts extension message.
+ void OnUpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory);
+ //, const HostID& host_id,
+ //const std::set<HostID>& changed_hosts,
+ //bool whitelisted_only);
+
+ // Scripts statically defined in extension manifests.
+ UserScriptSet static_scripts_;
+
+ // Whether or not dom activity should be logged for scripts injected.
+ bool activity_logging_enabled_ = false;
+};
+
+}
+
+#endif
diff --git a/components/user_scripts/renderer/user_scripts_dispatcher.cc b/components/user_scripts/renderer/user_scripts_dispatcher.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/user_scripts_dispatcher.cc
@@ -0,0 +1,36 @@
+#include "user_scripts_dispatcher.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "content/public/renderer/render_thread.h"
+#include "extension_frame_helper.h"
+
+namespace user_scripts {
+
+// ex ChromeExtensionsDispatcherDelegate
+UserScriptsDispatcher::UserScriptsDispatcher()
+ : user_script_set_manager_observer_(this) {
+ user_script_set_manager_.reset(new UserScriptSetManager());
+ script_injection_manager_.reset(
+ new ScriptInjectionManager(user_script_set_manager_.get()));
+ user_script_set_manager_observer_.Observe(user_script_set_manager_.get());
+}
+
+UserScriptsDispatcher::~UserScriptsDispatcher() {
+}
+
+void UserScriptsDispatcher::OnRenderThreadStarted(content::RenderThread* thread) {
+}
+
+void UserScriptsDispatcher::OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) {
+}
+
+void UserScriptsDispatcher::OnRenderFrameCreated(content::RenderFrame* render_frame) {
+ script_injection_manager_->OnRenderFrameCreated(render_frame);
+}
+
+}
\ No newline at end of file
diff --git a/components/user_scripts/renderer/user_scripts_dispatcher.h b/components/user_scripts/renderer/user_scripts_dispatcher.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/user_scripts_dispatcher.h
@@ -0,0 +1,49 @@
+#ifndef USERSCRIPTS_RENDER_DISPATCHER_H_
+#define USERSCRIPTS_RENDER_DISPATCHER_H_
+
+#include "user_script_set_manager.h"
+#include "script_injection_manager.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/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:
+ UserScriptsDispatcher(const UserScriptsDispatcher&) = delete;
+ UserScriptsDispatcher& operator=(const UserScriptsDispatcher&) = delete;
+ explicit UserScriptsDispatcher();
+ ~UserScriptsDispatcher() override;
+
+ void OnRenderThreadStarted(content::RenderThread* thread);
+ void OnUserScriptsUpdated(const std::set<HostID>& changed_hosts) override;
+ void OnRenderFrameCreated(content::RenderFrame* render_frame);
+
+ private:
+ std::unique_ptr<UserScriptSetManager> user_script_set_manager_;
+
+ std::unique_ptr<ScriptInjectionManager> script_injection_manager_;
+
+ base::ScopedObservation<UserScriptSetManager, UserScriptSetManager::Observer>
+ user_script_set_manager_observer_{this};
+};
+
+}
+
+#endif
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,108 @@
+#include "user_scripts_renderer_client.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/lazy_instance.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_frame_visitor.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+
+#include "../common/user_scripts_features.h"
+#include "user_scripts_dispatcher.h"
+#include "extension_frame_helper.h"
+
+namespace user_scripts {
+
+// was ChromeExtensionsRendererClient
+UserScriptsRendererClient::UserScriptsRendererClient() {}
+
+UserScriptsRendererClient::~UserScriptsRendererClient() {}
+
+// static
+UserScriptsRendererClient* UserScriptsRendererClient::GetInstance() {
+ static base::LazyInstance<UserScriptsRendererClient>::Leaky client =
+ LAZY_INSTANCE_INITIALIZER;
+ return client.Pointer();
+}
+
+void UserScriptsRendererClient::RenderThreadStarted(
+ ChromeRenderThreadObserver* chrome_observer) {
+ chrome_observer_ = chrome_observer;
+
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: RenderThreadStarted";
+
+ content::RenderThread* thread = content::RenderThread::Get();
+ dispatcher_ = std::make_unique<UserScriptsDispatcher>();
+
+ dispatcher_->OnRenderThreadStarted(thread);
+ thread->AddObserver(dispatcher_.get());
+}
+
+void UserScriptsRendererClient::ConfigurationUpdated() {
+ if (base::FeatureList::IsEnabled(features::kEnableLoggingUserScripts))
+ LOG(INFO) << "UserScripts: Configuration Updated";
+
+ struct WatchFrame : public content::RenderFrameVisitor {
+ bool Visit(content::RenderFrame* frame) override {
+ if (frame)
+ UserScriptsRendererClient::GetInstance()->RenderFrameCreated(frame, NULL);
+ return true; // Continue visiting.
+ }
+ };
+ WatchFrame visitor = {};
+ content::RenderFrame::ForEach(&visitor);
+}
+
+void UserScriptsRendererClient::RenderFrameCreated(
+ content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry) {
+
+ auto params = chrome_observer_->GetDynamicParams();
+ enabled_ = params->allow_userscript;
+ if (!enabled_) return;
+
+ ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
+ if (!frame_helper) {
+ new user_scripts::ExtensionFrameHelper(render_frame);
+ dispatcher_->OnRenderFrameCreated(render_frame);
+ }
+}
+
+void UserScriptsRendererClient::RunScriptsAtDocumentStart(content::RenderFrame* render_frame) {
+ if (!enabled_) 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_) 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_) 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.
+}
+
+}
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,38 @@
+#ifndef USERSCRIPTS_RENDER_CLIENT_H_
+#define USERSCRIPTS_RENDER_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include "user_scripts_dispatcher.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "chrome/renderer/chrome_render_thread_observer.h"
+
+namespace user_scripts {
+
+class UserScriptsRendererClient {
+ public:
+ UserScriptsRendererClient(const UserScriptsRendererClient&) = delete;
+ UserScriptsRendererClient& operator=(const UserScriptsRendererClient&) = delete;
+ UserScriptsRendererClient();
+ ~UserScriptsRendererClient();
+
+ static UserScriptsRendererClient* GetInstance();
+
+ void RenderThreadStarted(ChromeRenderThreadObserver* chrome_observer);
+ void ConfigurationUpdated();
+ void RenderFrameCreated(content::RenderFrame* render_frame,
+ service_manager::BinderRegistry* registry);
+ void RunScriptsAtDocumentStart(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame);
+ void RunScriptsAtDocumentIdle(content::RenderFrame* render_frame);
+
+ private:
+ std::unique_ptr<UserScriptsDispatcher> dispatcher_;
+ bool enabled_ = false;
+ raw_ptr<ChromeRenderThreadObserver> chrome_observer_;
+};
+
+}
+
+#endif
diff --git a/components/user_scripts/renderer/web_ui_injection_host.cc b/components/user_scripts/renderer/web_ui_injection_host.cc
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/web_ui_injection_host.cc
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "web_ui_injection_host.h"
+#include "base/no_destructor.h"
+
+namespace {
+
+// The default secure CSP to be used in order to prevent remote scripts.
+const char kDefaultSecureCSP[] = "script-src 'self'; object-src 'self';";
+
+}
+
+WebUIInjectionHost::WebUIInjectionHost(const HostID& host_id)
+ : InjectionHost(host_id),
+ url_(host_id.id()) {
+}
+
+WebUIInjectionHost::~WebUIInjectionHost() {
+}
+
+const std::string* WebUIInjectionHost::GetContentSecurityPolicy() const {
+ // Use the main world CSP.
+ // return nullptr;
+
+ // The isolated world will use its own CSP which blocks remotely hosted
+ // code.
+ static const base::NoDestructor<std::string> default_isolated_world_csp(
+ kDefaultSecureCSP);
+ return default_isolated_world_csp.get();
+}
+
+const GURL& WebUIInjectionHost::url() const {
+ return url_;
+}
+
+const std::string& WebUIInjectionHost::name() const {
+ return id().id();
+}
diff --git a/components/user_scripts/renderer/web_ui_injection_host.h b/components/user_scripts/renderer/web_ui_injection_host.h
new file mode 100755
--- /dev/null
+++ b/components/user_scripts/renderer/web_ui_injection_host.h
@@ -0,0 +1,27 @@
+// 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 "injection_host.h"
+
+class WebUIInjectionHost : public InjectionHost {
+ public:
+ WebUIInjectionHost(const WebUIInjectionHost&) = delete;
+ WebUIInjectionHost& operator=(const WebUIInjectionHost&) = delete;
+ 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_;
+};
+
+#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,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+
+ <!-- Preferences -->
+ <message name="IDS_PREFS_USERSCRIPTS_SETTINGS"
+ desc="."
+ formatter_data="android_java">
+ User Scripts
+ </message>
+
+ <message name="IDS_OPTION_USERSCRIPT_FLAG" desc="." formatter_data="android_java">
+ Activate User Scripts
+ </message>
+
+ <message name="IDS_OPTION_USERSCRIPT_FLAG_ON" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ ON
+ </message>
+ <message name="IDS_OPTION_USERSCRIPT_FLAG_OFF" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ OFF
+ </message>
+
+ <message name="IDS_ADD_SCRIPT" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ Add script
+ </message>
+ <message name="IDS_SCRIPTS_LIST_DESCRIPTION" desc="." formatter_data="android_java">
+ Experimental support for Greasemonkey-style user scripts.
+ </message>
+
+ <message name="IDS_SCRIPTS_ITEM_VERSION" desc="." formatter_data="android_java">
+ Version:
+ </message>
+ <message name="IDS_SCRIPTS_ITEM_FILENAME" desc="." formatter_data="android_java">
+ File:
+ </message>
+ <message name="IDS_SCRIPTS_ITEM_URL" desc="." formatter_data="android_java">
+ Url:
+ </message>
+
+ <message name="IDS_SCRIPTS_VIEW_SOURCE" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ View source
+ </message>
+
+ <message name="IDS_ASK_TO_INSTALL" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ Do you want to install this user script from following location?
+
+<ph name="FILE">%s</ph>
+
+NOTE: only install user scripts that you have verified are secure, user scripts can steal your credentials and data.
+ </message>
+ <message name="IDS_NO" desc=". [CHAR-LIMIT=32]" formatter_data="android_java">
+ No
+ </message>
+
+</grit-part>
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
@@ -955,6 +955,12 @@
"components/autofill/core/browser/autofill_address_rewriter_resources.grd":{
"includes": [6220]
},
+ "components/user_scripts/renderer/resources/user_scripts_renderer_resources.grd": {
+ "includes": [7000],
+ },
+ "components/user_scripts/browser/resources/browser_resources.grd": {
+ "includes": [7020],
+ },
# END components/ section.
# START ios/ section.
--
2.25.1