diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index de3418cfba..97519e1a80 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -14,6 +14,17 @@ This document uses the following convention to categorize breaking changes: ## Planned Breaking API Changes (43.0) +### Behavior Changed: `chrome.scripting` CSS injection matches more fallback frames + +Extensions using `chrome.scripting.insertCSS()` or `chrome.scripting.removeCSS()` +now follow Chrome's behavior when Electron cannot match a frame's URL directly, +such as with `about:blank` or `data:` frames. If the extension has access to the +page that created the frame, CSS may now be inserted into or removed from those +fallback frames as well. + +Apps or extensions that relied on Electron skipping those frames should narrow their +injection target, frame IDs, or match patterns. + ### Behavior Changed: Dialog methods default to Downloads directory The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided: diff --git a/filenames.gni b/filenames.gni index 186d7d8152..d0dd526308 100644 --- a/filenames.gni +++ b/filenames.gni @@ -772,8 +772,6 @@ filenames = { "shell/browser/extensions/api/resources_private/resources_private_api.h", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h", - "shell/browser/extensions/api/scripting/scripting_api.cc", - "shell/browser/extensions/api/scripting/scripting_api.h", "shell/browser/extensions/api/streams_private/streams_private_api.cc", "shell/browser/extensions/api/streams_private/streams_private_api.h", "shell/browser/extensions/api/tabs/tabs_api.cc", diff --git a/patches/chromium/.patches b/patches/chromium/.patches index aaece12bf1..9bba9e5eef 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -152,4 +152,3 @@ chore_register_node_as_a_dynamic_trace_category_prefix.patch fix_make_macos_text_replacement_work_on_contenteditable.patch fix_allow_reentrancy_on_downloadmanagerimpl_observer_list.patch build_gn_arg_to_support_linker_wrapper_script_on_windows.patch -chore_exclude_upstream_scripting_api_when_building_electron.patch diff --git a/patches/chromium/chore_exclude_upstream_scripting_api_when_building_electron.patch b/patches/chromium/chore_exclude_upstream_scripting_api_when_building_electron.patch deleted file mode 100644 index 92d7492351..0000000000 --- a/patches/chromium/chore_exclude_upstream_scripting_api_when_building_electron.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Electron Scripts -Date: Sat, 25 Apr 2026 21:10:03 -0500 -Subject: chore: exclude upstream scripting API when building Electron - -Electron provides its own implementation of the Scripting API at -shell/browser/extensions/api/scripting/ that includes Electron-specific -modifications. CL 7784831 moved the upstream implementation from -//chrome to //extensions, which caused it to be transitively linked -into Electron, resulting in duplicate symbols. - -Exclude the upstream sources when is_electron_build is set. - -diff --git a/extensions/browser/api/scripting/BUILD.gn b/extensions/browser/api/scripting/BUILD.gn -index 14d18bfbc36673f3eb9ffe0777d34ddd2163af0d..cff84c1a9e713e2845b2331913dde9be730305b1 100644 ---- a/extensions/browser/api/scripting/BUILD.gn -+++ b/extensions/browser/api/scripting/BUILD.gn -@@ -12,6 +12,16 @@ source_set("scripting") { - "scripting_api.cc", - "scripting_api.h", - ] -+ -+ # Electron provides its own scripting API implementation in -+ # shell/browser/extensions/api/scripting/. -+ if (is_electron_build) { -+ sources -= [ -+ "scripting_api.cc", -+ "scripting_api.h", -+ ] -+ } -+ - public_deps = [ - "//extensions/browser:browser_sources", - "//extensions/common/api", diff --git a/shell/browser/extensions/api/BUILD.gn b/shell/browser/extensions/api/BUILD.gn index 9be5160521..857e173a86 100644 --- a/shell/browser/extensions/api/BUILD.gn +++ b/shell/browser/extensions/api/BUILD.gn @@ -14,7 +14,6 @@ function_registration("api_registration") { "//electron/shell/common/extensions/api/action.json", "//electron/shell/common/extensions/api/extension.json", "//electron/shell/common/extensions/api/resources_private.idl", - "//electron/shell/common/extensions/api/scripting.idl", "//electron/shell/common/extensions/api/tabs.json", ] diff --git a/shell/browser/extensions/api/scripting/scripting_api.cc b/shell/browser/extensions/api/scripting/scripting_api.cc deleted file mode 100644 index 7b08dab002..0000000000 --- a/shell/browser/extensions/api/scripting/scripting_api.cc +++ /dev/null @@ -1,1201 +0,0 @@ -// Copyright 2023 Microsoft, GmbH -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "shell/browser/extensions/api/scripting/scripting_api.h" - -#include - -#include "base/check.h" -#include "base/json/json_writer.h" -#include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" -#include "base/types/optional_util.h" -#include "content/public/browser/browser_task_traits.h" -#include "content/public/browser/navigation_controller.h" -#include "content/public/browser/navigation_entry.h" -#include "content/public/browser/web_contents.h" -#include "extensions/browser/extension_api_frame_id_map.h" -#include "extensions/browser/extension_file_task_runner.h" -#include "extensions/browser/extension_registry.h" -#include "extensions/browser/extension_system.h" -#include "extensions/browser/extension_user_script_loader.h" -#include "extensions/browser/extension_util.h" -#include "extensions/browser/load_and_localize_file.h" -#include "extensions/browser/script_executor.h" -#include "extensions/browser/scripting_constants.h" -#include "extensions/browser/user_script_manager.h" -#include "extensions/common/api/extension_types.h" -#include "extensions/common/api/scripting.h" -#include "extensions/common/api/scripts_internal.h" -#include "extensions/common/api/scripts_internal/script_serialization.h" -#include "extensions/common/error_utils.h" -#include "extensions/common/extension.h" -#include "extensions/common/manifest_constants.h" -#include "extensions/common/mojom/css_origin.mojom-shared.h" -#include "extensions/common/mojom/execution_world.mojom-shared.h" -#include "extensions/common/mojom/host_id.mojom.h" -#include "extensions/common/mojom/match_origin_as_fallback.mojom-shared.h" -#include "extensions/common/mojom/run_location.mojom-shared.h" -#include "extensions/common/permissions/permissions_data.h" -#include "extensions/common/user_script.h" -#include "extensions/common/utils/content_script_utils.h" -#include "extensions/common/utils/extension_types_utils.h" -#include "shell/browser/api/electron_api_web_contents.h" -#include "shell/browser/extensions/electron_extension_tab_util.h" -#include "third_party/abseil-cpp/absl/strings/str_format.h" - -namespace extensions { - -namespace { - -constexpr std::string_view kEmptyMatchesError = - "Script with ID '*' must specify 'matches'."; -constexpr char kExactlyOneOfCssAndFilesError[] = - "Exactly one of 'css' and 'files' must be specified."; - -// Note: CSS always injects as soon as possible, so we default to -// document_start. Because of tab loading, there's no guarantee this will -// *actually* inject before page load, but it will at least inject "soon". -constexpr mojom::RunLocation kCSSRunLocation = - mojom::RunLocation::kDocumentStart; - -// Converts the given `style_origin` to a CSSOrigin. -mojom::CSSOrigin ConvertStyleOriginToCSSOrigin( - api::scripting::StyleOrigin style_origin) { - mojom::CSSOrigin css_origin = mojom::CSSOrigin::kAuthor; - switch (style_origin) { - case api::scripting::StyleOrigin::kNone: - case api::scripting::StyleOrigin::kAuthor: - css_origin = mojom::CSSOrigin::kAuthor; - break; - case api::scripting::StyleOrigin::kUser: - css_origin = mojom::CSSOrigin::kUser; - break; - } - - return css_origin; -} - -mojom::ExecutionWorld ConvertExecutionWorld( - api::scripting::ExecutionWorld world) { - mojom::ExecutionWorld execution_world = mojom::ExecutionWorld::kIsolated; - switch (world) { - case api::scripting::ExecutionWorld::kNone: - case api::scripting::ExecutionWorld::kIsolated: - break; // Default to mojom::ExecutionWorld::kIsolated. - case api::scripting::ExecutionWorld::kMain: - execution_world = mojom::ExecutionWorld::kMain; - } - - return execution_world; -} - -std::string InjectionKeyForCode(const mojom::HostID& host_id, - const std::string& code) { - return ScriptExecutor::GenerateInjectionKey(host_id, /*script_url=*/GURL(), - code); -} - -std::string InjectionKeyForFile(const mojom::HostID& host_id, - const GURL& resource_url) { - return ScriptExecutor::GenerateInjectionKey(host_id, resource_url, - /*code=*/std::string()); -} - -std::vector FileSourcesToJSSources( - const Extension& extension, - std::vector file_sources) { - std::vector js_sources; - js_sources.reserve(file_sources.size()); - for (auto& file_source : file_sources) { - js_sources.push_back(mojom::JSSource::New( - std::move(file_source.data), - extension.ResolveExtensionURL(file_source.file_name))); - } - - return js_sources; -} - -std::vector FileSourcesToCSSSources( - const Extension& extension, - std::vector file_sources) { - mojom::HostID host_id(mojom::HostID::HostType::kExtensions, extension.id()); - - std::vector css_sources; - css_sources.reserve(file_sources.size()); - for (auto& file_source : file_sources) { - css_sources.push_back(mojom::CSSSource::New( - std::move(file_source.data), - InjectionKeyForFile( - host_id, extension.ResolveExtensionURL(file_source.file_name)))); - } - - return css_sources; -} - -// Returns an error message string for when an extension cannot access a page it -// is attempting to. -std::string GetCannotAccessPageErrorMessage(const PermissionsData& permissions, - const GURL& url) { - if (permissions.HasAPIPermission(mojom::APIPermissionID::kTab)) { - return ErrorUtils::FormatErrorMessage( - manifest_errors::kCannotAccessPageWithUrl, url.spec()); - } - return manifest_errors::kCannotAccessPage; -} - -// Returns true if the `permissions` allow for injection into the given `frame`. -// If false, populates `error`. -bool HasPermissionToInjectIntoFrame(const PermissionsData& permissions, - int tab_id, - content::RenderFrameHost* frame, - std::string* error) { - GURL committed_url = frame->GetLastCommittedURL(); - if (committed_url.is_empty()) { - if (!frame->IsInPrimaryMainFrame()) { - // We can't check the pending URL for subframes from the //chrome layer. - // Assume the injection is allowed; the renderer has additional checks - // later on. - return true; - } - // Unknown URL, e.g. because no load was committed yet. In this case we look - // for any pending entry on the NavigationController associated with the - // WebContents for the frame. - content::WebContents* web_contents = - content::WebContents::FromRenderFrameHost(frame); - content::NavigationEntry* pending_entry = - web_contents->GetController().GetPendingEntry(); - if (!pending_entry) { - *error = manifest_errors::kCannotAccessPage; - return false; - } - GURL pending_url = pending_entry->GetURL(); - if (pending_url.SchemeIsHTTPOrHTTPS() && - !permissions.CanAccessPage(pending_url, tab_id, error)) { - // This catches the majority of cases where an extension tried to inject - // on a newly-created navigating tab, saving us a potentially-costly IPC - // and, maybe, slightly reducing (but not by any stretch eliminating) an - // attack surface. - *error = GetCannotAccessPageErrorMessage(permissions, pending_url); - return false; - } - - // Otherwise allow for now. The renderer has additional checks and will - // fail the injection if needed. - return true; - } - - // TODO(devlin): Add more schemes here, in line with - // https://crbug.com/55084. - if (committed_url.SchemeIs(url::kAboutScheme) || - committed_url.SchemeIs(url::kDataScheme)) { - url::Origin origin = frame->GetLastCommittedOrigin(); - const url::SchemeHostPort& tuple_or_precursor_tuple = - origin.GetTupleOrPrecursorTupleIfOpaque(); - if (!tuple_or_precursor_tuple.IsValid()) { - *error = GetCannotAccessPageErrorMessage(permissions, committed_url); - return false; - } - - committed_url = tuple_or_precursor_tuple.GetURL(); - } - - return permissions.CanAccessPage(committed_url, tab_id, error); -} - -// Collects the frames for injection. Method will return false if an error is -// encountered. -bool CollectFramesForInjection(const api::scripting::InjectionTarget& target, - content::WebContents* tab, - std::set& frame_ids, - std::set& frames, - std::string* error_out) { - if (target.document_ids) { - for (const auto& id : *target.document_ids) { - ExtensionApiFrameIdMap::DocumentId document_id = - ExtensionApiFrameIdMap::DocumentIdFromString(id); - - if (!document_id) { - *error_out = absl::StrFormat("Invalid document id %s", id); - return false; - } - - content::RenderFrameHost* frame = - ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId( - document_id); - - // If the frame was not found or it matched another tab reject this - // request. - if (!frame || content::WebContents::FromRenderFrameHost(frame) != tab) { - *error_out = absl::StrFormat("No document with id %s in tab with id %d", - id, target.tab_id); - return false; - } - - // Convert the documentId into a frameId since the content will be - // injected synchronously. - frame_ids.insert(ExtensionApiFrameIdMap::GetFrameId(frame)); - frames.insert(frame); - } - } else { - if (target.frame_ids) { - frame_ids.insert(target.frame_ids->begin(), target.frame_ids->end()); - } else { - frame_ids.insert(ExtensionApiFrameIdMap::kTopFrameId); - } - - for (int frame_id : frame_ids) { - content::RenderFrameHost* frame = - ExtensionApiFrameIdMap::GetRenderFrameHostById(tab, frame_id); - if (!frame) { - *error_out = absl::StrFormat("No frame with id %d in tab with id %d", - frame_id, target.tab_id); - return false; - } - frames.insert(frame); - } - } - return true; -} - -// Returns true if the `target` can be accessed with the given `permissions`. -// If the target can be accessed, populates `script_executor_out`, -// `frame_scope_out`, and `frame_ids_out` with the appropriate values; -// if the target cannot be accessed, populates `error_out`. -bool CanAccessTarget(const PermissionsData& permissions, - const api::scripting::InjectionTarget& target, - content::BrowserContext* browser_context, - bool include_incognito_information, - ScriptExecutor** script_executor_out, - ScriptExecutor::FrameScope* frame_scope_out, - std::set* frame_ids_out, - std::string* error_out) { - auto* contents = GetElectronTabById(target.tab_id, browser_context); - if (!contents) { - *error_out = absl::StrFormat("No tab with id: %d", target.tab_id); - return false; - } - - content::WebContents* tab = contents->web_contents(); - - if ((target.all_frames && *target.all_frames == true) && - (target.frame_ids || target.document_ids)) { - *error_out = - "Cannot specify 'allFrames' if either 'frameIds' or 'documentIds' is " - "specified."; - return false; - } - - if (target.frame_ids && target.document_ids) { - *error_out = "Cannot specify both 'frameIds' and 'documentIds'."; - return false; - } - - ScriptExecutor* script_executor = contents->script_executor(); - DCHECK(script_executor); - - ScriptExecutor::FrameScope frame_scope = - target.all_frames && *target.all_frames == true - ? ScriptExecutor::INCLUDE_SUB_FRAMES - : ScriptExecutor::SPECIFIED_FRAMES; - - std::set frame_ids; - std::set frames; - if (!CollectFramesForInjection(target, tab, frame_ids, frames, error_out)) - return false; - - // TODO(devlin): If `allFrames` is true, we error out if the extension - // doesn't have access to the top frame (even if it may inject in child - // frames). This is inconsistent with content scripts (which can execute on - // child frames), but consistent with the old tabs.executeScript() API. - for (content::RenderFrameHost* frame : frames) { - DCHECK_EQ(content::WebContents::FromRenderFrameHost(frame), tab); - if (!HasPermissionToInjectIntoFrame(permissions, target.tab_id, frame, - error_out)) { - return false; - } - } - - *frame_ids_out = std::move(frame_ids); - *frame_scope_out = frame_scope; - *script_executor_out = script_executor; - return true; -} - -api::scripts_internal::SerializedUserScript -ConvertRegisteredContentScriptToSerializedUserScript( - api::scripting::RegisteredContentScript content_script) { - auto convert_execution_world = [](api::scripting::ExecutionWorld world) { - switch (world) { - case api::scripting::ExecutionWorld::kNone: - case api::scripting::ExecutionWorld::kIsolated: - return api::extension_types::ExecutionWorld::kIsolated; - case api::scripting::ExecutionWorld::kMain: - return api::extension_types::ExecutionWorld::kMain; - } - }; - - api::scripts_internal::SerializedUserScript serialized_script; - serialized_script.source = - api::scripts_internal::Source::kDynamicContentScript; - - // Note: IDs have already been prefixed appropriately. - serialized_script.id = std::move(content_script.id); - // Note: `matches` are guaranteed to be non-null. - serialized_script.matches = std::move(*content_script.matches); - serialized_script.exclude_matches = std::move(content_script.exclude_matches); - if (content_script.css) { - serialized_script.css = script_serialization::GetSourcesFromFileNames( - std::move(*content_script.css)); - } - if (content_script.js) { - serialized_script.js = script_serialization::GetSourcesFromFileNames( - std::move(*content_script.js)); - } - serialized_script.all_frames = content_script.all_frames; - serialized_script.match_origin_as_fallback = - content_script.match_origin_as_fallback; - serialized_script.run_at = content_script.run_at; - serialized_script.world = convert_execution_world(content_script.world); - - return serialized_script; -} - -std::unique_ptr ParseUserScript( - content::BrowserContext* browser_context, - const Extension& extension, - bool allowed_in_incognito, - api::scripting::RegisteredContentScript content_script, - std::u16string* error) { - api::scripts_internal::SerializedUserScript serialized_script = - ConvertRegisteredContentScriptToSerializedUserScript( - std::move(content_script)); - - return script_serialization::ParseSerializedUserScript( - serialized_script, extension, allowed_in_incognito, error); -} - -// Converts a UserScript object to a api::scripting::RegisteredContentScript -// object, used for getRegisteredContentScripts. -api::scripting::RegisteredContentScript CreateRegisteredContentScriptInfo( - const UserScript& script) { - CHECK_EQ(UserScript::Source::kDynamicContentScript, script.GetSource()); - - // To convert a `UserScript`, we first go through our script_internal - // serialization; this allows us to do simple conversions and avoid any - // complex logic. - api::scripts_internal::SerializedUserScript serialized_script = - script_serialization::SerializeUserScript(script); - - auto convert_serialized_script_sources = - [](std::vector sources) { - std::vector converted; - converted.reserve(sources.size()); - for (auto& source : sources) { - CHECK(source.file) - << "Content scripts don't allow arbitrary code strings"; - converted.push_back(std::move(*source.file)); - } - return converted; - }; - - auto convert_execution_world = - [](api::extension_types::ExecutionWorld world) { - switch (world) { - case api::extension_types::ExecutionWorld::kNone: - NOTREACHED() - << "Execution world should always be present in serialization."; - case api::extension_types::ExecutionWorld::kIsolated: - return api::scripting::ExecutionWorld::kIsolated; - case api::extension_types::ExecutionWorld::kUserScript: - NOTREACHED() << "ISOLATED worlds are not supported in this API."; - case api::extension_types::ExecutionWorld::kMain: - return api::scripting::ExecutionWorld::kMain; - } - }; - - api::scripting::RegisteredContentScript content_script; - content_script.id = std::move(serialized_script.id); - content_script.matches = std::move(serialized_script.matches); - content_script.exclude_matches = std::move(serialized_script.exclude_matches); - if (serialized_script.css) { - content_script.css = - convert_serialized_script_sources(std::move(*serialized_script.css)); - } - if (serialized_script.js) { - content_script.js = - convert_serialized_script_sources(std::move(*serialized_script.js)); - } - content_script.all_frames = serialized_script.all_frames; - content_script.match_origin_as_fallback = - serialized_script.match_origin_as_fallback; - content_script.run_at = serialized_script.run_at; - content_script.world = convert_execution_world(serialized_script.world); - - return content_script; -} - -} // namespace - -ScriptingExecuteScriptFunction::ScriptingExecuteScriptFunction() = default; -ScriptingExecuteScriptFunction::~ScriptingExecuteScriptFunction() = default; - -ExtensionFunction::ResponseAction ScriptingExecuteScriptFunction::Run() { - std::optional params = - api::scripting::ExecuteScript::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - injection_ = std::move(params->injection); - - // Silently alias `function` to `func` for backwards compatibility. - // TODO(devlin): Remove this in M95. - if (injection_.function) { - if (injection_.func) { - return RespondNow( - Error("Both 'func' and 'function' were specified. " - "Only 'func' should be used.")); - } - injection_.func = std::move(injection_.function); - } - - if ((injection_.files && injection_.func) || - (!injection_.files && !injection_.func)) { - return RespondNow( - Error("Exactly one of 'func' and 'files' must be specified")); - } - - if (injection_.files) { - if (injection_.args) - return RespondNow(Error("'args' may not be used with file injections.")); - - // JS files don't require localization. - constexpr bool kRequiresLocalization = false; - std::string error; - if (!CheckAndLoadFiles( - std::move(*injection_.files), - script_parsing::ContentScriptType::kJs, *extension(), - kRequiresLocalization, - base::BindOnce(&ScriptingExecuteScriptFunction::DidLoadResources, - this), - &error)) { - return RespondNow(Error(std::move(error))); - } - return RespondLater(); - } - - DCHECK(injection_.func); - - // TODO(devlin): This (wrapping a function to create an IIFE) is pretty hacky, - // and along with the JSON-serialization of the arguments to curry in. - // Add support to the ScriptExecutor to better support this case. - std::string args_expression; - if (injection_.args) { - std::vector string_args; - string_args.reserve(injection_.args->size()); - for (const auto& arg : *injection_.args) { - std::string json; - if (!base::JSONWriter::Write(arg, &json)) - return RespondNow(Error("Unserializable argument passed.")); - string_args.push_back(std::move(json)); - } - args_expression = base::JoinString(string_args, ","); - } - - std::string code_to_execute = - absl::StrFormat("(%s)(%s)", *injection_.func, args_expression); - - std::vector sources; - sources.push_back(mojom::JSSource::New(std::move(code_to_execute), GURL())); - - std::string error; - if (!Execute(std::move(sources), &error)) - return RespondNow(Error(std::move(error))); - - return RespondLater(); -} - -void ScriptingExecuteScriptFunction::DidLoadResources( - std::vector file_sources, - std::optional load_error) { - if (load_error) { - Respond(Error(std::move(*load_error))); - return; - } - - DCHECK(!file_sources.empty()); - - std::string error; - if (!Execute(FileSourcesToJSSources(*extension(), std::move(file_sources)), - &error)) { - Respond(Error(std::move(error))); - } -} - -bool ScriptingExecuteScriptFunction::Execute( - std::vector sources, - std::string* error) { - ScriptExecutor* script_executor = nullptr; - ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES; - std::set frame_ids; - if (!CanAccessTarget(*extension()->permissions_data(), injection_.target, - browser_context(), include_incognito_information(), - &script_executor, &frame_scope, &frame_ids, error)) { - return false; - } - - mojom::ExecutionWorld execution_world = - ConvertExecutionWorld(injection_.world); - // scripting.executeScript() doesn't support selecting execution world id. - std::optional execution_world_id = std::nullopt; - bool inject_immediately = injection_.inject_immediately.value_or(false); - - scripting::ExecuteScript( - extension()->id(), std::move(sources), execution_world, - execution_world_id, script_executor, frame_scope, frame_ids, - inject_immediately, user_gesture(), - base::BindOnce(&ScriptingExecuteScriptFunction::OnScriptExecuted, this)); - - return true; -} - -void ScriptingExecuteScriptFunction::OnScriptExecuted( - std::vector frame_results) { - // If only a single frame was included and the injection failed, respond with - // an error. - if (frame_results.size() == 1 && !frame_results[0].error.empty()) { - Respond(Error(std::move(frame_results[0].error))); - return; - } - - // Otherwise, respond successfully. We currently just skip over individual - // frames that failed. In the future, we can bubble up these error messages - // to the extension. - std::vector injection_results; - for (auto& result : frame_results) { - if (!result.error.empty()) - continue; - api::scripting::InjectionResult injection_result; - injection_result.result = std::move(result.value); - injection_result.frame_id = result.frame_id; - if (result.document_id) - injection_result.document_id = result.document_id.ToString(); - - // Put the top frame first; otherwise, any order. - if (result.frame_id == ExtensionApiFrameIdMap::kTopFrameId) { - injection_results.insert(injection_results.begin(), - std::move(injection_result)); - } else { - injection_results.push_back(std::move(injection_result)); - } - } - - Respond(ArgumentList( - api::scripting::ExecuteScript::Results::Create(injection_results))); -} - -ScriptingInsertCSSFunction::ScriptingInsertCSSFunction() = default; -ScriptingInsertCSSFunction::~ScriptingInsertCSSFunction() = default; - -ExtensionFunction::ResponseAction ScriptingInsertCSSFunction::Run() { - std::optional params = - api::scripting::InsertCSS::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - injection_ = std::move(params->injection); - - if ((injection_.files && injection_.css) || - (!injection_.files && !injection_.css)) { - return RespondNow(Error(kExactlyOneOfCssAndFilesError)); - } - - if (injection_.files) { - // CSS files require localization. - constexpr bool kRequiresLocalization = true; - std::string error; - if (!CheckAndLoadFiles( - std::move(*injection_.files), - script_parsing::ContentScriptType::kCss, *extension(), - kRequiresLocalization, - base::BindOnce(&ScriptingInsertCSSFunction::DidLoadResources, this), - &error)) { - return RespondNow(Error(std::move(error))); - } - return RespondLater(); - } - - DCHECK(injection_.css); - - mojom::HostID host_id(mojom::HostID::HostType::kExtensions, - extension()->id()); - - std::vector sources; - sources.push_back( - mojom::CSSSource::New(std::move(*injection_.css), - InjectionKeyForCode(host_id, *injection_.css))); - - std::string error; - if (!Execute(std::move(sources), &error)) { - return RespondNow(Error(std::move(error))); - } - - return RespondLater(); -} - -void ScriptingInsertCSSFunction::DidLoadResources( - std::vector file_sources, - std::optional load_error) { - if (load_error) { - Respond(Error(std::move(*load_error))); - return; - } - - DCHECK(!file_sources.empty()); - std::vector sources = - FileSourcesToCSSSources(*extension(), std::move(file_sources)); - - std::string error; - if (!Execute(std::move(sources), &error)) - Respond(Error(std::move(error))); -} - -bool ScriptingInsertCSSFunction::Execute( - std::vector sources, - std::string* error) { - ScriptExecutor* script_executor = nullptr; - ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES; - std::set frame_ids; - if (!CanAccessTarget(*extension()->permissions_data(), injection_.target, - browser_context(), include_incognito_information(), - &script_executor, &frame_scope, &frame_ids, error)) { - return false; - } - DCHECK(script_executor); - - script_executor->ExecuteScript( - mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()), - mojom::CodeInjection::NewCss(mojom::CSSInjection::New( - std::move(sources), ConvertStyleOriginToCSSOrigin(injection_.origin), - mojom::CSSInjection::Operation::kAdd)), - frame_scope, frame_ids, - mojom::MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree, - kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS, - /* webview_src */ GURL(), - base::BindOnce(&ScriptingInsertCSSFunction::OnCSSInserted, this)); - - return true; -} - -void ScriptingInsertCSSFunction::OnCSSInserted( - std::vector results) { - // If only a single frame was included and the injection failed, respond with - // an error. - if (results.size() == 1 && !results[0].error.empty()) { - Respond(Error(std::move(results[0].error))); - return; - } - - Respond(NoArguments()); -} - -ScriptingRemoveCSSFunction::ScriptingRemoveCSSFunction() = default; -ScriptingRemoveCSSFunction::~ScriptingRemoveCSSFunction() = default; - -ExtensionFunction::ResponseAction ScriptingRemoveCSSFunction::Run() { - std::optional params = - api::scripting::RemoveCSS::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - api::scripting::CSSInjection& injection = params->injection; - - if ((injection.files && injection.css) || - (!injection.files && !injection.css)) { - return RespondNow(Error(kExactlyOneOfCssAndFilesError)); - } - - ScriptExecutor* script_executor = nullptr; - ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES; - std::set frame_ids; - std::string error; - if (!CanAccessTarget(*extension()->permissions_data(), injection.target, - browser_context(), include_incognito_information(), - &script_executor, &frame_scope, &frame_ids, &error)) { - return RespondNow(Error(std::move(error))); - } - DCHECK(script_executor); - - mojom::HostID host_id(mojom::HostID::HostType::kExtensions, - extension()->id()); - std::vector sources; - - if (injection.files) { - std::vector resources; - if (!scripting::GetFileResources(*injection.files, - script_parsing::ContentScriptType::kCss, - *extension(), &resources, &error)) { - return RespondNow(Error(std::move(error))); - } - - // Note: Since we're just removing the CSS, we don't actually need to load - // the file here. It's okay for `code` to be empty in this case. - const std::string empty_code; - sources.reserve(injection.files->size()); - - for (const auto& file : *injection.files) { - sources.push_back(mojom::CSSSource::New( - empty_code, InjectionKeyForFile( - host_id, extension()->ResolveExtensionURL(file)))); - } - } else { - DCHECK(injection.css); - sources.push_back( - mojom::CSSSource::New(std::move(*injection.css), - InjectionKeyForCode(host_id, *injection.css))); - } - - script_executor->ExecuteScript( - std::move(host_id), - mojom::CodeInjection::NewCss(mojom::CSSInjection::New( - std::move(sources), ConvertStyleOriginToCSSOrigin(injection.origin), - mojom::CSSInjection::Operation::kRemove)), - frame_scope, frame_ids, - mojom::MatchOriginAsFallbackBehavior::kMatchForAboutSchemeAndClimbTree, - kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS, - /* webview_src */ GURL(), - base::BindOnce(&ScriptingRemoveCSSFunction::OnCSSRemoved, this)); - - return RespondLater(); -} - -void ScriptingRemoveCSSFunction::OnCSSRemoved( - std::vector results) { - // If only a single frame was included and the injection failed, respond with - // an error. - if (results.size() == 1 && !results[0].error.empty()) { - Respond(Error(std::move(results[0].error))); - return; - } - - Respond(NoArguments()); -} - -ScriptingRegisterContentScriptsFunction:: - ScriptingRegisterContentScriptsFunction() = default; -ScriptingRegisterContentScriptsFunction:: - ~ScriptingRegisterContentScriptsFunction() = default; - -ExtensionFunction::ResponseAction -ScriptingRegisterContentScriptsFunction::Run() { - std::optional params = - api::scripting::RegisterContentScripts::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - std::vector& scripts = - params->scripts; - ExtensionUserScriptLoader* loader = - ExtensionSystem::Get(browser_context()) - ->user_script_manager() - ->GetUserScriptLoaderForExtension(extension()->id()); - - // Create script ids for dynamic content scripts. - std::string error; - std::set existing_script_ids = - loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript); - std::set new_script_ids = scripting::CreateDynamicScriptIds( - scripts, UserScript::Source::kDynamicContentScript, existing_script_ids, - &error); - - if (!error.empty()) { - CHECK(new_script_ids.empty()); - return RespondNow(Error(std::move(error))); - } - - // Parse content scripts. - std::u16string parse_error; - UserScriptList parsed_scripts; - std::set persistent_script_ids; - - bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito( - extension()->id(), browser_context()); - - parsed_scripts.reserve(scripts.size()); - for (auto& script : scripts) { - if (!script.matches) { - return RespondNow(Error(ErrorUtils::FormatErrorMessage( - kEmptyMatchesError, UserScript::TrimPrefixFromScriptID(script.id)))); - } - - // Scripts will persist across sessions by default. - bool persist_across_sessions = - script.persist_across_sessions.value_or(true); - - std::unique_ptr user_script = - ParseUserScript(browser_context(), *extension(), allowed_in_incognito, - std::move(script), &parse_error); - if (!user_script) { - return RespondNow(Error(base::UTF16ToASCII(parse_error))); - } - - if (persist_across_sessions) { - persistent_script_ids.insert(user_script->id()); - } - parsed_scripts.push_back(std::move(user_script)); - } - // The contents of `scripts` have all been std::move()'d. - scripts.clear(); - - // Add new script IDs now in case another call with the same script IDs is - // made immediately following this one. - loader->AddPendingDynamicScriptIDs(std::move(new_script_ids)); - - GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread, - script_parsing::GetSymlinkPolicy(extension()), - std::move(parsed_scripts)), - base::BindOnce(&ScriptingRegisterContentScriptsFunction:: - OnContentScriptFilesValidated, - this, std::move(persistent_script_ids))); - - // Balanced in `OnContentScriptFilesValidated()` or - // `OnContentScriptsRegistered()`. - AddRef(); - return RespondLater(); -} - -void ScriptingRegisterContentScriptsFunction::OnContentScriptFilesValidated( - std::set persistent_script_ids, - scripting::ValidateScriptsResult result) { - // We cannot proceed if the `browser_context` is not valid as the - // `ExtensionSystem` will not exist. - if (!browser_context()) { - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - // We cannot proceed if the extension is uninstalled or unloaded in the middle - // of validating its script files. - ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); - if (!extension() || - !registry->enabled_extensions().Contains(extension_id())) { - // Note: a Respond() is not needed if the system is shutting down or if the - // extension is no longer enabled. - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - auto error = std::move(result.second); - auto scripts = std::move(result.first); - ExtensionUserScriptLoader* loader = - ExtensionSystem::Get(browser_context()) - ->user_script_manager() - ->GetUserScriptLoaderForExtension(extension()->id()); - - if (error.has_value()) { - std::set ids_to_remove; - for (const auto& script : scripts) { - ids_to_remove.insert(script->id()); - } - - loader->RemovePendingDynamicScriptIDs(std::move(ids_to_remove)); - Respond(Error(std::move(*error))); - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - loader->AddDynamicScripts( - std::move(scripts), std::move(persistent_script_ids), - base::BindOnce( - &ScriptingRegisterContentScriptsFunction::OnContentScriptsRegistered, - this)); -} - -void ScriptingRegisterContentScriptsFunction::OnContentScriptsRegistered( - const std::optional& error) { - if (error.has_value()) - Respond(Error(std::move(*error))); - else - Respond(NoArguments()); - Release(); // Matches the `AddRef()` in `Run()`. -} - -ScriptingGetRegisteredContentScriptsFunction:: - ScriptingGetRegisteredContentScriptsFunction() = default; -ScriptingGetRegisteredContentScriptsFunction:: - ~ScriptingGetRegisteredContentScriptsFunction() = default; - -ExtensionFunction::ResponseAction -ScriptingGetRegisteredContentScriptsFunction::Run() { - std::optional params = - api::scripting::GetRegisteredContentScripts::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - const std::optional& filter = - params->filter; - std::set id_filter; - if (filter && filter->ids) { - for (const std::string& id : *(filter->ids)) { - id_filter.insert(scripting::AddPrefixToDynamicScriptId( - id, UserScript::Source::kDynamicContentScript)); - } - } - - ExtensionUserScriptLoader* loader = - ExtensionSystem::Get(browser_context()) - ->user_script_manager() - ->GetUserScriptLoaderForExtension(extension()->id()); - const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts(); - - std::vector script_infos; - std::set persistent_script_ids = - loader->GetPersistentDynamicScriptIDs(); - for (const std::unique_ptr& script : dynamic_scripts) { - if (script->GetSource() != UserScript::Source::kDynamicContentScript) { - continue; - } - - if (!id_filter.empty() && !id_filter.contains(script->id())) { - continue; - } - - auto registered_script = CreateRegisteredContentScriptInfo(*script); - registered_script.persist_across_sessions = - persistent_script_ids.contains(script->id()); - - // Remove the internally used prefix from the `script`'s ID before - // returning. - registered_script.id = script->GetIDWithoutPrefix(); - script_infos.push_back(std::move(registered_script)); - } - - return RespondNow( - ArgumentList(api::scripting::GetRegisteredContentScripts::Results::Create( - script_infos))); -} - -ScriptingUnregisterContentScriptsFunction:: - ScriptingUnregisterContentScriptsFunction() = default; -ScriptingUnregisterContentScriptsFunction:: - ~ScriptingUnregisterContentScriptsFunction() = default; - -ExtensionFunction::ResponseAction -ScriptingUnregisterContentScriptsFunction::Run() { - auto params = - api::scripting::UnregisterContentScripts::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - std::optional& filter = params->filter; - std::optional> ids = std::nullopt; - // TODO(crbug.com/40216362): `ids` should have an empty list when filter ids - // is empty, instead of a nullopt. Otherwise, we are incorrectly removing all - // content scripts when ids is empty. - if (filter && filter->ids && !filter->ids->empty()) { - ids = std::move(filter->ids); - } - - std::string error; - bool removal_triggered = scripting::RemoveScripts( - ids, UserScript::Source::kDynamicContentScript, browser_context(), - extension()->id(), - base::BindOnce(&ScriptingUnregisterContentScriptsFunction:: - OnContentScriptsUnregistered, - this), - &error); - - if (!removal_triggered) { - CHECK(!error.empty()); - return RespondNow(Error(std::move(error))); - } - - return RespondLater(); -} - -void ScriptingUnregisterContentScriptsFunction::OnContentScriptsUnregistered( - const std::optional& error) { - if (error.has_value()) - Respond(Error(std::move(*error))); - else - Respond(NoArguments()); -} - -ScriptingUpdateContentScriptsFunction::ScriptingUpdateContentScriptsFunction() = - default; -ScriptingUpdateContentScriptsFunction:: - ~ScriptingUpdateContentScriptsFunction() = default; - -ExtensionFunction::ResponseAction ScriptingUpdateContentScriptsFunction::Run() { - std::optional params = - api::scripting::UpdateContentScripts::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - - std::vector& scripts_to_update = - params->scripts; - std::string error; - - // Add the prefix for dynamic content scripts onto the IDs of all - // `scripts_to_update` before continuing. - std::set ids_to_update = scripting::CreateDynamicScriptIds( - scripts_to_update, UserScript::Source::kDynamicContentScript, - std::set(), &error); - - if (!error.empty()) { - CHECK(ids_to_update.empty()); - return RespondNow(Error(std::move(error))); - } - - ExtensionUserScriptLoader* loader = - ExtensionSystem::Get(browser_context()) - ->user_script_manager() - ->GetUserScriptLoaderForExtension(extension()->id()); - - std::set updated_script_ids_to_persist; - UserScriptList parsed_scripts = scripting::UpdateScripts( - scripts_to_update, UserScript::Source::kDynamicContentScript, *loader, - base::BindRepeating(&CreateRegisteredContentScriptInfo), - base::BindRepeating(&ScriptingUpdateContentScriptsFunction::ApplyUpdate, - this, &updated_script_ids_to_persist), - &error); - - if (!error.empty()) { - CHECK(parsed_scripts.empty()); - return RespondNow(Error(std::move(error))); - } - - // Add new script IDs now in case another call with the same script IDs is - // made immediately following this one. - loader->AddPendingDynamicScriptIDs(std::move(ids_to_update)); - - GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread, - script_parsing::GetSymlinkPolicy(extension()), - std::move(parsed_scripts)), - base::BindOnce( - &ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated, - this, std::move(updated_script_ids_to_persist))); - - // Balanced in `OnContentScriptFilesValidated()` or - // `OnContentScriptsRegistered()`. - AddRef(); - return RespondLater(); -} - -std::unique_ptr ScriptingUpdateContentScriptsFunction::ApplyUpdate( - std::set* script_ids_to_persist, - api::scripting::RegisteredContentScript& new_script, - api::scripting::RegisteredContentScript& original_script, - std::u16string* parse_error) { - if (new_script.matches) { - original_script.matches = std::move(new_script.matches); - } - - if (new_script.exclude_matches) { - original_script.exclude_matches = std::move(new_script.exclude_matches); - } - - if (new_script.js) { - original_script.js = std::move(new_script.js); - } - - if (new_script.css) { - original_script.css = std::move(new_script.css); - } - - if (new_script.all_frames) { - *original_script.all_frames = *new_script.all_frames; - } - - if (new_script.match_origin_as_fallback) { - *original_script.match_origin_as_fallback = - *new_script.match_origin_as_fallback; - } - - if (new_script.run_at != api::extension_types::RunAt::kNone) { - original_script.run_at = new_script.run_at; - } - - // Note: for the update application, we disregard allowed_in_incognito. - // We'll set it on the resulting scripts. - constexpr bool kAllowedInIncognito = false; - - // Parse content script. - std::unique_ptr parsed_script = - ParseUserScript(browser_context(), *extension(), kAllowedInIncognito, - std::move(original_script), parse_error); - if (!parsed_script) { - return nullptr; - } - - // Persist the updated script if the flag is specified as true, or if the - // original script is persisted and the flag is not specified. - if (new_script.persist_across_sessions.value_or(false) || - (!new_script.persist_across_sessions && - script_ids_to_persist->contains(new_script.id))) { - script_ids_to_persist->insert(new_script.id); - } - - return parsed_script; -} - -void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated( - std::set persistent_script_ids, - scripting::ValidateScriptsResult result) { - // We cannot proceed if the `browser_context` is not valid as the - // `ExtensionSystem` will not exist. - if (!browser_context()) { - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - // We cannot proceed if the extension is uninstalled or unloaded in the middle - // of validating its script files. - ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context()); - if (!extension() || - !registry->enabled_extensions().Contains(extension_id())) { - // Note: a Respond() is not needed if the system is shutting down or if the - // extension is no longer enabled. - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - auto error = std::move(result.second); - auto scripts = std::move(result.first); - ExtensionUserScriptLoader* loader = - ExtensionSystem::Get(browser_context()) - ->user_script_manager() - ->GetUserScriptLoaderForExtension(extension()->id()); - - bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito( - extension()->id(), browser_context()); - - std::set script_ids; - for (const auto& script : scripts) { - script_ids.insert(script->id()); - - script->set_incognito_enabled(allowed_in_incognito); - } - - if (error.has_value()) { - loader->RemovePendingDynamicScriptIDs(script_ids); - Respond(Error(std::move(*error))); - Release(); // Matches the `AddRef()` in `Run()`. - return; - } - - loader->UpdateDynamicScripts( - std::move(scripts), std::move(script_ids), - std::move(persistent_script_ids), - base::BindOnce( - &ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated, - this)); -} - -void ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated( - const std::optional& error) { - if (error.has_value()) - Respond(Error(std::move(*error))); - else - Respond(NoArguments()); - Release(); // Matches the `AddRef()` in `Run()`. -} - -} // namespace extensions diff --git a/shell/browser/extensions/api/scripting/scripting_api.h b/shell/browser/extensions/api/scripting/scripting_api.h deleted file mode 100644 index 15f15f1ebc..0000000000 --- a/shell/browser/extensions/api/scripting/scripting_api.h +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2023 Microsoft, GmbH -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_ -#define ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_ - -#include -#include -#include -#include -#include - -#include "extensions/browser/extension_function.h" -#include "extensions/browser/script_executor.h" -#include "extensions/browser/scripting_utils.h" -#include "extensions/common/api/scripting.h" -#include "extensions/common/mojom/code_injection.mojom.h" -#include "extensions/common/user_script.h" - -namespace extensions { - -class ScriptingExecuteScriptFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.executeScript", SCRIPTING_EXECUTESCRIPT) - - ScriptingExecuteScriptFunction(); - ScriptingExecuteScriptFunction(const ScriptingExecuteScriptFunction&) = - delete; - ScriptingExecuteScriptFunction& operator=( - const ScriptingExecuteScriptFunction&) = delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingExecuteScriptFunction() override; - - // Called when the resource files to be injected has been loaded. - void DidLoadResources(std::vector file_sources, - std::optional load_error); - - // Triggers the execution of `sources` in the appropriate context. - // Returns true on success; on failure, populates `error`. - bool Execute(std::vector sources, std::string* error); - - // Invoked when script execution is complete. - void OnScriptExecuted(std::vector frame_results); - - api::scripting::ScriptInjection injection_; -}; - -class ScriptingInsertCSSFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.insertCSS", SCRIPTING_INSERTCSS) - - ScriptingInsertCSSFunction(); - ScriptingInsertCSSFunction(const ScriptingInsertCSSFunction&) = delete; - ScriptingInsertCSSFunction& operator=(const ScriptingInsertCSSFunction&) = - delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingInsertCSSFunction() override; - - // Called when the resource files to be injected has been loaded. - void DidLoadResources(std::vector file_sources, - std::optional load_error); - - // Triggers the execution of `sources` in the appropriate context. - // Returns true on success; on failure, populates `error`. - bool Execute(std::vector sources, std::string* error); - - // Called when the CSS insertion is complete. - void OnCSSInserted(std::vector results); - - api::scripting::CSSInjection injection_; -}; - -class ScriptingRemoveCSSFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.removeCSS", SCRIPTING_REMOVECSS) - - ScriptingRemoveCSSFunction(); - ScriptingRemoveCSSFunction(const ScriptingRemoveCSSFunction&) = delete; - ScriptingRemoveCSSFunction& operator=(const ScriptingRemoveCSSFunction&) = - delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingRemoveCSSFunction() override; - - // Called when the CSS removal is complete. - void OnCSSRemoved(std::vector results); -}; - -class ScriptingRegisterContentScriptsFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.registerContentScripts", - SCRIPTING_REGISTERCONTENTSCRIPTS) - - ScriptingRegisterContentScriptsFunction(); - ScriptingRegisterContentScriptsFunction( - const ScriptingRegisterContentScriptsFunction&) = delete; - ScriptingRegisterContentScriptsFunction& operator=( - const ScriptingRegisterContentScriptsFunction&) = delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingRegisterContentScriptsFunction() override; - - // Called when script files have been checked. - void OnContentScriptFilesValidated( - std::set persistent_script_ids, - scripting::ValidateScriptsResult result); - - // Called when content scripts have been registered. - void OnContentScriptsRegistered(const std::optional& error); -}; - -class ScriptingGetRegisteredContentScriptsFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.getRegisteredContentScripts", - SCRIPTING_GETREGISTEREDCONTENTSCRIPTS) - - ScriptingGetRegisteredContentScriptsFunction(); - ScriptingGetRegisteredContentScriptsFunction( - const ScriptingGetRegisteredContentScriptsFunction&) = delete; - ScriptingGetRegisteredContentScriptsFunction& operator=( - const ScriptingGetRegisteredContentScriptsFunction&) = delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingGetRegisteredContentScriptsFunction() override; -}; - -class ScriptingUnregisterContentScriptsFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.unregisterContentScripts", - SCRIPTING_UNREGISTERCONTENTSCRIPTS) - - ScriptingUnregisterContentScriptsFunction(); - ScriptingUnregisterContentScriptsFunction( - const ScriptingUnregisterContentScriptsFunction&) = delete; - ScriptingUnregisterContentScriptsFunction& operator=( - const ScriptingUnregisterContentScriptsFunction&) = delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingUnregisterContentScriptsFunction() override; - - // Called when content scripts have been unregistered. - void OnContentScriptsUnregistered(const std::optional& error); -}; - -class ScriptingUpdateContentScriptsFunction : public ExtensionFunction { - public: - DECLARE_EXTENSION_FUNCTION("scripting.updateContentScripts", - SCRIPTING_UPDATECONTENTSCRIPTS) - - ScriptingUpdateContentScriptsFunction(); - ScriptingUpdateContentScriptsFunction( - const ScriptingUpdateContentScriptsFunction&) = delete; - ScriptingUpdateContentScriptsFunction& operator=( - const ScriptingUpdateContentScriptsFunction&) = delete; - - // ExtensionFunction: - ResponseAction Run() override; - - private: - ~ScriptingUpdateContentScriptsFunction() override; - - // Returns a UserScript object by updating the `original_script` with the - // `new_script` given delta. If the updated script cannot be parsed, populates - // `parse_error` and returns nullptr. - std::unique_ptr ApplyUpdate( - std::set* script_ids_to_persist, - api::scripting::RegisteredContentScript& new_script, - api::scripting::RegisteredContentScript& original_script, - std::u16string* parse_error); - - // Called when script files have been checked. - void OnContentScriptFilesValidated( - std::set persistent_script_ids, - scripting::ValidateScriptsResult result); - - // Called when content scripts have been updated. - void OnContentScriptsUpdated(const std::optional& error); -}; - -} // namespace extensions - -#endif // ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_ diff --git a/shell/browser/extensions/electron_extensions_browser_api_provider.cc b/shell/browser/extensions/electron_extensions_browser_api_provider.cc index a066a78740..eecf264727 100644 --- a/shell/browser/extensions/electron_extensions_browser_api_provider.cc +++ b/shell/browser/extensions/electron_extensions_browser_api_provider.cc @@ -8,7 +8,6 @@ #include "extensions/browser/extension_function_registry.h" #include "shell/browser/extensions/api/extension_action/extension_action_api.h" #include "shell/browser/extensions/api/generated_api_registration.h" -#include "shell/browser/extensions/api/scripting/scripting_api.h" #include "shell/browser/extensions/api/tabs/tabs_api.h" namespace extensions { diff --git a/shell/browser/extensions/electron_extensions_browser_client.cc b/shell/browser/extensions/electron_extensions_browser_client.cc index b786b7fe47..15c5223330 100644 --- a/shell/browser/extensions/electron_extensions_browser_client.cc +++ b/shell/browser/extensions/electron_extensions_browser_client.cc @@ -35,6 +35,7 @@ #include "extensions/common/manifest_handlers/devtools_page_handler.h" #include "extensions/common/manifest_url_handlers.h" #include "services/network/public/mojom/url_loader.mojom.h" +#include "shell/browser/api/electron_api_web_contents.h" #include "shell/browser/browser.h" #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_browser_context.h" @@ -42,6 +43,7 @@ #include "shell/browser/extensions/electron_component_extension_resource_manager.h" #include "shell/browser/extensions/electron_extension_host_delegate.h" #include "shell/browser/extensions/electron_extension_system_factory.h" +#include "shell/browser/extensions/electron_extension_tab_util.h" #include "shell/browser/extensions/electron_extension_web_contents_observer.h" #include "shell/browser/extensions/electron_extensions_api_client.h" #include "shell/browser/extensions/electron_extensions_browser_api_provider.h" @@ -381,6 +383,28 @@ ElectronExtensionsBrowserClient::GetExtensionWebContentsObserver( web_contents); } +bool ElectronExtensionsBrowserClient::IsValidTabId( + content::BrowserContext* browser_context, + const int tab_id, + const bool include_incognito, + content::WebContents** web_contents) const { + auto* contents = extensions::GetElectronTabById(tab_id, browser_context); + if (!contents) + return false; + + if (web_contents) + *web_contents = contents->web_contents(); + + return true; +} + +extensions::ScriptExecutor* +ElectronExtensionsBrowserClient::GetScriptExecutorForTab( + content::WebContents& web_contents) { + auto* contents = electron::api::WebContents::From(&web_contents); + return contents ? contents->script_executor() : nullptr; +} + extensions::KioskDelegate* ElectronExtensionsBrowserClient::GetKioskDelegate() { if (!kiosk_delegate_) kiosk_delegate_ = std::make_unique(); diff --git a/shell/browser/extensions/electron_extensions_browser_client.h b/shell/browser/extensions/electron_extensions_browser_client.h index 12ee7bede6..cc50d2b078 100644 --- a/shell/browser/extensions/electron_extensions_browser_client.h +++ b/shell/browser/extensions/electron_extensions_browser_client.h @@ -139,6 +139,12 @@ class ElectronExtensionsBrowserClient content::WebContents* web_contents) override; extensions::ExtensionWebContentsObserver* GetExtensionWebContentsObserver( content::WebContents* web_contents) override; + bool IsValidTabId(content::BrowserContext* browser_context, + int tab_id, + bool include_incognito, + content::WebContents** web_contents) const override; + extensions::ScriptExecutor* GetScriptExecutorForTab( + content::WebContents& web_contents) override; extensions::KioskDelegate* GetKioskDelegate() override; std::string GetApplicationLocale() override; void RegisterBrowserInterfaceBindersForFrame( diff --git a/shell/common/extensions/api/BUILD.gn b/shell/common/extensions/api/BUILD.gn index da96283d4a..c2919b360d 100644 --- a/shell/common/extensions/api/BUILD.gn +++ b/shell/common/extensions/api/BUILD.gn @@ -41,7 +41,6 @@ generated_json_strings("generated_api_json_strings") { "browser_action.json", "extension.json", "resources_private.idl", - "scripting.idl", "tabs.json", ] @@ -62,7 +61,6 @@ generated_json_strings("generated_api_json_strings") { generated_types("generated_api_types") { sources = [ "resources_private.idl", - "scripting.idl", "tabs.json", ] diff --git a/shell/common/extensions/api/_api_features.json b/shell/common/extensions/api/_api_features.json index b5af622579..bee5911393 100644 --- a/shell/common/extensions/api/_api_features.json +++ b/shell/common/extensions/api/_api_features.json @@ -65,15 +65,6 @@ "chrome://print/*" ] }], - "scripting": { - "dependencies": ["permission:scripting"], - "contexts": ["privileged_extension"] - }, - "scripting.globalParams": { - "channel": "trunk", - "dependencies": ["permission:scripting"], - "contexts": ["content_script"] - }, "tabs": { "channel": "stable", "extension_types": ["extension"], diff --git a/shell/common/extensions/api/scripting.idl b/shell/common/extensions/api/scripting.idl deleted file mode 100644 index f029ede3ff..0000000000 --- a/shell/common/extensions/api/scripting.idl +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Use the chrome.scripting API to execute script in different -// contexts. -namespace scripting { - callback InjectedFunction = void(); - - // The origin for a style change. - // See style origins - // for more info. - enum StyleOrigin { - AUTHOR, - USER - }; - - // The JavaScript world for a script to execute within. - enum ExecutionWorld { - // Specifies the isolated world, which is the execution environment unique - // to this extension. - ISOLATED, - // Specifies the main world of the DOM, which is the execution environment - // shared with the host page's JavaScript. - MAIN - }; - - dictionary InjectionTarget { - // The ID of the tab into which to inject. - long tabId; - - // The IDs - // of specific frames to inject into. - long[]? frameIds; - - // The IDs - // of specific documentIds to inject into. This must not be set if - // frameIds is set. - DOMString[]? documentIds; - - // Whether the script should inject into all frames within the tab. Defaults - // to false. - // This must not be true if frameIds is specified. - boolean? allFrames; - }; - - dictionary ScriptInjection { - // A JavaScript function to inject. This function will be serialized, and - // then deserialized for injection. This means that any bound parameters - // and execution context will be lost. - // Exactly one of files or func must be - // specified. - [serializableFunction]InjectedFunction? func; - - // The arguments to pass to the provided function. This is only valid if - // the func parameter is specified. These arguments must be - // JSON-serializable. - any[]? args; - - // We used to call the injected function `function`, but this is - // incompatible with JavaScript's object declaration shorthand (see - // https://crbug.com/1166438). We leave this silently in for backwards - // compatibility. - // TODO(devlin): Remove this in M95. - [nodoc, serializableFunction]InjectedFunction? function; - - // The path of the JS or CSS files to inject, relative to the extension's - // root directory. - // Exactly one of files or func must be - // specified. - DOMString[]? files; - - // Details specifying the target into which to inject the script. - InjectionTarget target; - - // The JavaScript "world" to run the script in. Defaults to - // ISOLATED. - ExecutionWorld? world; - - // Whether the injection should be triggered in the target as soon as - // possible. Note that this is not a guarantee that injection will occur - // prior to page load, as the page may have already loaded by the time the - // script reaches the target. - boolean? injectImmediately; - }; - - dictionary CSSInjection { - // Details specifying the target into which to insert the CSS. - InjectionTarget target; - - // A string containing the CSS to inject. - // Exactly one of files and css must be - // specified. - DOMString? css; - - // The path of the CSS files to inject, relative to the extension's root - // directory. - // Exactly one of files and css must be - // specified. - DOMString[]? files; - - // The style origin for the injection. Defaults to 'AUTHOR'. - StyleOrigin? origin; - }; - - dictionary InjectionResult { - // The result of the script execution. - any? result; - - // The frame associated with the injection. - long frameId; - - // The document associated with the injection. - DOMString documentId; - }; - - // Describes a content script to be injected into a web page registered - // through this API. - dictionary RegisteredContentScript { - // The id of the content script, specified in the API call. Must not start - // with a '_' as it's reserved as a prefix for generated script IDs. - DOMString id; - // Specifies which pages this content script will be injected into. See - // Match Patterns for more - // details on the syntax of these strings. Must be specified for - // $(ref:registerContentScripts). - DOMString[]? matches; - // Excludes pages that this content script would otherwise be injected into. - // See Match Patterns for - // more details on the syntax of these strings. - DOMString[]? excludeMatches; - // The list of CSS files to be injected into matching pages. These are - // injected in the order they appear in this array, before any DOM is - // constructed or displayed for the page. - DOMString[]? css; - // The list of JavaScript files to be injected into matching pages. These - // are injected in the order they appear in this array. - DOMString[]? js; - // If specified true, it will inject into all frames, even if the frame is - // not the top-most frame in the tab. Each frame is checked independently - // for URL requirements; it will not inject into child frames if the URL - // requirements are not met. Defaults to false, meaning that only the top - // frame is matched. - boolean? allFrames; - // Indicates whether the script can be injected into frames where the URL - // contains an unsupported scheme; specifically: about:, data:, blob:, or - // filesystem:. In these cases, the URL's origin is checked to determine if - // the script should be injected. If the origin is `null` (as is the case - // for data: URLs) then the used origin is either the frame that created - // the current frame or the frame that initiated the navigation to this - // frame. Note that this may not be the parent frame. - boolean? matchOriginAsFallback; - // Specifies when JavaScript files are injected into the web page. The - // preferred and default value is document_idle. - extensionTypes.RunAt? runAt; - // Specifies if this content script will persist into future sessions. The - // default is true. - boolean? persistAcrossSessions; - // The JavaScript "world" to run the script in. Defaults to - // ISOLATED. - ExecutionWorld? world; - }; - - // An object used to filter content scripts for - // ${ref:getRegisteredContentScripts}. - dictionary ContentScriptFilter { - // If specified, $(ref:getRegisteredContentScripts) will only return scripts - // with an id specified in this list. - DOMString[]? ids; - }; - - callback ScriptInjectionCallback = void(InjectionResult[] results); - - callback CSSInjectionCallback = void(); - - callback RegisterContentScriptsCallback = void(); - - callback GetRegisteredContentScriptsCallback = void( - RegisteredContentScript[] scripts); - - callback UnregisterContentScriptsCallback = void(); - - callback UpdateContentScriptsCallback = void(); - - interface Properties { - // An object available for content scripts running in isolated worlds to use - // and modify as a JS object. One instance exists per frame and is shared - // between all content scripts for a given extension. This object is - // initialized when the frame is created, before document_start. - // TODO(crbug.com/40119604): Enable this once implementation is complete. - [nodoc, nocompile] static long globalParams(); - }; - - interface Functions { - // Injects a script into a target context. By default, the script will be run - // at document_idle, or immediately if the page has already - // loaded. If the injectImmediately property is set, the script - // will inject without waiting, even if the page has not finished loading. If - // the script evaluates to a promise, the browser will wait for the promise to - // settle and return the resulting value. - // |injection|: The details of the script which to inject. - // |callback|: Invoked upon completion of the injection. The resulting - // array contains the result of execution for each frame where the - // injection succeeded. - static void executeScript( - ScriptInjection injection, - optional ScriptInjectionCallback callback); - - // Inserts a CSS stylesheet into a target context. - // If multiple frames are specified, unsuccessful injections are ignored. - // |injection|: The details of the styles to insert. - // |callback|: Invoked upon completion of the insertion. - static void insertCSS( - CSSInjection injection, - optional CSSInjectionCallback callback); - - // Removes a CSS stylesheet that was previously inserted by this extension - // from a target context. - // |injection|: The details of the styles to remove. Note that the - // css, files, and origin properties - // must exactly match the stylesheet inserted through $(ref:insertCSS). - // Attempting to remove a non-existent stylesheet is a no-op. - // |callback|: A callback to be invoked upon the completion of the removal. - static void removeCSS( - CSSInjection injection, - optional CSSInjectionCallback callback); - - // Registers one or more content scripts for this extension. - // |scripts|: Contains a list of scripts to be registered. If there are - // errors during script parsing/file validation, or if the IDs specified - // already exist, then no scripts are registered. - // |callback|: A callback to be invoked once scripts have been fully - // registered or if an error has occurred. - static void registerContentScripts( - RegisteredContentScript[] scripts, - optional RegisterContentScriptsCallback callback); - - // Returns all dynamically registered content scripts for this extension - // that match the given filter. - // |filter|: An object to filter the extension's dynamically registered - // scripts. - static void getRegisteredContentScripts( - optional ContentScriptFilter filter, - GetRegisteredContentScriptsCallback callback); - - // Unregisters content scripts for this extension. - // |filter|: If specified, only unregisters dynamic content scripts which - // match the filter. Otherwise, all of the extension's dynamic content - // scripts are unregistered. - // |callback|: A callback to be invoked once scripts have been unregistered - // or if an error has occurred. - static void unregisterContentScripts( - optional ContentScriptFilter filter, - optional UnregisterContentScriptsCallback callback); - - // Updates one or more content scripts for this extension. - // |scripts|: Contains a list of scripts to be updated. A property is only - // updated for the existing script if it is specified in this object. If - // there are errors during script parsing/file validation, or if the IDs - // specified do not correspond to a fully registered script, then no scripts - // are updated. - // |callback|: A callback to be invoked once scripts have been updated or - // if an error has occurred. - static void updateContentScripts( - RegisteredContentScript[] scripts, - optional RegisterContentScriptsCallback callback); - }; -};