1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375 |
- // 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 <algorithm>
- #include "base/check.h"
- #include "base/containers/contains.h"
- #include "base/json/json_writer.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/types/optional_util.h"
- #include "chrome/common/extensions/api/scripting.h"
- #include "content/public/browser/browser_task_traits.h"
- #include "content/public/browser/navigation_controller.h"
- #include "content/public/browser/navigation_entry.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/scripting_utils.h"
- #include "extensions/browser/user_script_manager.h"
- #include "extensions/common/api/extension_types.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/run_location.mojom-shared.h"
- #include "extensions/common/permissions/api_permission.h"
- #include "extensions/common/permissions/permissions_data.h"
- #include "extensions/common/script_constants.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"
- namespace extensions {
- namespace {
- constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
- constexpr char kDuplicateFileSpecifiedError[] =
- "Duplicate file specified: '*'.";
- constexpr char kExactlyOneOfCssAndFilesError[] =
- "Exactly one of 'css' and 'files' must be specified.";
- constexpr char kNonExistentScriptIdError[] = "Nonexistent script ID '*'";
- // 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());
- }
- // Constructs an array of file sources from the read file `data`.
- std::vector<InjectedFileSource> ConstructFileSources(
- std::vector<std::unique_ptr<std::string>> data,
- std::vector<std::string> file_names) {
- // Note: CHECK (and not DCHECK) because if it fails, we have an out-of-bounds
- // access.
- CHECK_EQ(data.size(), file_names.size());
- const size_t num_sources = data.size();
- std::vector<InjectedFileSource> sources;
- sources.reserve(num_sources);
- for (size_t i = 0; i < num_sources; ++i)
- sources.emplace_back(std::move(file_names[i]), std::move(data[i]));
- return sources;
- }
- std::vector<mojom::JSSourcePtr> FileSourcesToJSSources(
- const Extension& extension,
- std::vector<InjectedFileSource> file_sources) {
- std::vector<mojom::JSSourcePtr> 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.GetResourceURL(file_source.file_name)));
- }
- return js_sources;
- }
- std::vector<mojom::CSSSourcePtr> FileSourcesToCSSSources(
- const Extension& extension,
- std::vector<InjectedFileSource> file_sources) {
- mojom::HostID host_id(mojom::HostID::HostType::kExtensions, extension.id());
- std::vector<mojom::CSSSourcePtr> 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.GetResourceURL(file_source.file_name))));
- }
- return css_sources;
- }
- // Checks `files` and populates `resources_out` with the appropriate extension
- // resource. Returns true on success; on failure, populates `error_out`.
- bool GetFileResources(const std::vector<std::string>& files,
- const Extension& extension,
- std::vector<ExtensionResource>* resources_out,
- std::string* error_out) {
- if (files.empty()) {
- static constexpr char kAtLeastOneFileError[] =
- "At least one file must be specified.";
- *error_out = kAtLeastOneFileError;
- return false;
- }
- std::vector<ExtensionResource> resources;
- for (const auto& file : files) {
- ExtensionResource resource = extension.GetResource(file);
- if (resource.extension_root().empty() || resource.relative_path().empty()) {
- *error_out = ErrorUtils::FormatErrorMessage(kCouldNotLoadFileError, file);
- return false;
- }
- // ExtensionResource doesn't implement an operator==.
- if (base::Contains(resources, resource.relative_path(),
- &ExtensionResource::relative_path)) {
- // Disallow duplicates. Note that we could allow this, if we wanted (and
- // there *might* be reason to with JS injection, to perform an operation
- // twice?). However, this matches content script behavior, and injecting
- // twice can be done by chaining calls to executeScript() / insertCSS().
- // This isn't a robust check, and could probably be circumvented by
- // passing two paths that look different but are the same - but in that
- // case, we just try to load and inject the script twice, which is
- // inefficient, but safe.
- *error_out =
- ErrorUtils::FormatErrorMessage(kDuplicateFileSpecifiedError, file);
- return false;
- }
- resources.push_back(std::move(resource));
- }
- resources_out->swap(resources);
- return true;
- }
- using ResourcesLoadedCallback =
- base::OnceCallback<void(std::vector<InjectedFileSource>,
- std::optional<std::string>)>;
- // Checks the loaded content of extension resources. Invokes `callback` with
- // the constructed file sources on success or with an error on failure.
- void CheckLoadedResources(std::vector<std::string> file_names,
- ResourcesLoadedCallback callback,
- std::vector<std::unique_ptr<std::string>> file_data,
- std::optional<std::string> load_error) {
- if (load_error) {
- std::move(callback).Run({}, std::move(load_error));
- return;
- }
- std::vector<InjectedFileSource> file_sources =
- ConstructFileSources(std::move(file_data), std::move(file_names));
- for (const auto& source : file_sources) {
- DCHECK(source.data);
- // TODO(devlin): What necessitates this encoding requirement? Is it needed
- // for blink injection?
- if (!base::IsStringUTF8(*source.data)) {
- static constexpr char kBadFileEncodingError[] =
- "Could not load file '*'. It isn't UTF-8 encoded.";
- std::string error = ErrorUtils::FormatErrorMessage(kBadFileEncodingError,
- source.file_name);
- std::move(callback).Run({}, std::move(error));
- return;
- }
- }
- std::move(callback).Run(std::move(file_sources), std::nullopt);
- }
- // Checks the specified `files` for validity, and attempts to load and localize
- // them, invoking `callback` with the result. Returns true on success; on
- // failure, populates `error`.
- bool CheckAndLoadFiles(std::vector<std::string> files,
- const Extension& extension,
- bool requires_localization,
- ResourcesLoadedCallback callback,
- std::string* error) {
- std::vector<ExtensionResource> resources;
- if (!GetFileResources(files, extension, &resources, error))
- return false;
- LoadAndLocalizeResources(
- extension, resources, requires_localization,
- script_parsing::GetMaxScriptLength(),
- base::BindOnce(&CheckLoadedResources, std::move(files),
- std::move(callback)));
- return true;
- }
- // 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<int>& frame_ids,
- std::set<content::RenderFrameHost*>& 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 = base::StringPrintf("Invalid document id %s", id.c_str());
- 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 =
- base::StringPrintf("No document with id %s in tab with id %d",
- id.c_str(), 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 = base::StringPrintf("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<int>* frame_ids_out,
- std::string* error_out) {
- auto* contents = electron::api::WebContents::FromID(target.tab_id);
- if (!contents) {
- *error_out = base::StringPrintf("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<int> frame_ids;
- std::set<content::RenderFrameHost*> 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<UserScript> ParseUserScript(
- content::BrowserContext* browser_context,
- const Extension& extension,
- api::scripting::RegisteredContentScript content_script,
- std::u16string* error) {
- api::scripts_internal::SerializedUserScript serialized_script =
- ConvertRegisteredContentScriptToSerializedUserScript(
- std::move(content_script));
- std::unique_ptr<UserScript> user_script =
- script_serialization::ParseSerializedUserScript(serialized_script,
- extension, error);
- if (!user_script) {
- return nullptr; // Parsing failed.
- }
- // Post conversion validation and values.
- // TODO(https://crbug.com/1494155): See which of these can be moved into
- // script_serialization::ParseSerializedUserScript().
- if (!script_parsing::ValidateMatchOriginAsFallback(
- user_script->match_origin_as_fallback(), user_script->url_patterns(),
- error)) {
- return nullptr;
- }
- user_script->set_incognito_enabled(
- util::IsIncognitoEnabled(extension.id(), browser_context));
- return user_script;
- }
- // 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<api::scripts_internal::ScriptSource> sources) {
- std::vector<std::string> converted;
- converted.reserve(sources.size());
- for (auto& source : sources) {
- CHECK(source.file)
- << "Content scripts don't allow arbtirary 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_NORETURN()
- << "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_NORETURN()
- << "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
- InjectedFileSource::InjectedFileSource(std::string file_name,
- std::unique_ptr<std::string> data)
- : file_name(std::move(file_name)), data(std::move(data)) {}
- InjectedFileSource::InjectedFileSource(InjectedFileSource&&) = default;
- InjectedFileSource::~InjectedFileSource() = default;
- ScriptingExecuteScriptFunction::ScriptingExecuteScriptFunction() = default;
- ScriptingExecuteScriptFunction::~ScriptingExecuteScriptFunction() = default;
- ExtensionFunction::ResponseAction ScriptingExecuteScriptFunction::Run() {
- std::optional<api::scripting::ExecuteScript::Params> 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), *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<std::string> string_args;
- string_args.reserve(injection_.args->size());
- for (const auto& arg : *injection_.args) {
- if (auto json = base::WriteJson(arg)) {
- string_args.push_back(std::move(*json));
- } else {
- return RespondNow(Error("Unserializable argument passed."));
- }
- }
- args_expression = base::JoinString(string_args, ",");
- }
- std::string code_to_execute = base::StringPrintf(
- "(%s)(%s)", injection_.func->c_str(), args_expression.c_str());
- std::vector<mojom::JSSourcePtr> 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<InjectedFileSource> file_sources,
- std::optional<std::string> 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<mojom::JSSourcePtr> sources,
- std::string* error) {
- ScriptExecutor* script_executor = nullptr;
- ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
- std::set<int> 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);
- // Extensions can specify that the script should be injected "immediately".
- // In this case, we specify kDocumentStart as the injection time. Due to
- // inherent raciness between tab creation and load and this function
- // execution, there is no guarantee that it will actually happen at
- // document start, but the renderer will appropriately inject it
- // immediately if document start has already passed.
- mojom::RunLocation run_location =
- injection_.inject_immediately && *injection_.inject_immediately
- ? mojom::RunLocation::kDocumentStart
- : mojom::RunLocation::kDocumentIdle;
- script_executor->ExecuteScript(
- mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()),
- mojom::CodeInjection::NewJs(mojom::JSInjection::New(
- std::move(sources), execution_world, /*world_id=*/std::nullopt,
- blink::mojom::WantResultOption::kWantResult,
- user_gesture() ? blink::mojom::UserActivationOption::kActivate
- : blink::mojom::UserActivationOption::kDoNotActivate,
- blink::mojom::PromiseResultOption::kAwait)),
- frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK, run_location,
- ScriptExecutor::DEFAULT_PROCESS,
- /* webview_src */ GURL(),
- base::BindOnce(&ScriptingExecuteScriptFunction::OnScriptExecuted, this));
- return true;
- }
- void ScriptingExecuteScriptFunction::OnScriptExecuted(
- std::vector<ScriptExecutor::FrameResult> 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<api::scripting::InjectionResult> 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<api::scripting::InsertCSS::Params> 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), *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<mojom::CSSSourcePtr> 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<InjectedFileSource> file_sources,
- std::optional<std::string> load_error) {
- if (load_error) {
- Respond(Error(std::move(*load_error)));
- return;
- }
- DCHECK(!file_sources.empty());
- std::vector<mojom::CSSSourcePtr> 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<mojom::CSSSourcePtr> sources,
- std::string* error) {
- ScriptExecutor* script_executor = nullptr;
- ScriptExecutor::FrameScope frame_scope = ScriptExecutor::SPECIFIED_FRAMES;
- std::set<int> 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, ScriptExecutor::MATCH_ABOUT_BLANK,
- kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS,
- /* webview_src */ GURL(),
- base::BindOnce(&ScriptingInsertCSSFunction::OnCSSInserted, this));
- return true;
- }
- void ScriptingInsertCSSFunction::OnCSSInserted(
- std::vector<ScriptExecutor::FrameResult> 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<api::scripting::RemoveCSS::Params> 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<int> 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<mojom::CSSSourcePtr> sources;
- if (injection.files) {
- std::vector<ExtensionResource> resources;
- if (!GetFileResources(*injection.files, *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()->GetResourceURL(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, ScriptExecutor::MATCH_ABOUT_BLANK,
- kCSSRunLocation, ScriptExecutor::DEFAULT_PROCESS,
- /* webview_src */ GURL(),
- base::BindOnce(&ScriptingRemoveCSSFunction::OnCSSRemoved, this));
- return RespondLater();
- }
- void ScriptingRemoveCSSFunction::OnCSSRemoved(
- std::vector<ScriptExecutor::FrameResult> 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 ScriptingUpdateContentScriptsFunction::Run() {
- std::optional<api::scripting::UpdateContentScripts::Params> params =
- api::scripting::UpdateContentScripts::Params::Create(args());
- EXTENSION_FUNCTION_VALIDATE(params);
- std::vector<api::scripting::RegisteredContentScript>& scripts =
- params->scripts;
- std::string error;
- // Add the prefix for dynamic content scripts onto the IDs of all scripts in
- // `scripts` before continuing.
- std::set<std::string> ids_to_update = scripting::CreateDynamicScriptIds(
- scripts, UserScript::Source::kDynamicContentScript,
- std::set<std::string>(), &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::map<std::string, api::scripting::RegisteredContentScript>
- loaded_scripts_metadata;
- const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts();
- for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
- if (script->GetSource() == UserScript::Source::kDynamicContentScript) {
- loaded_scripts_metadata.emplace(
- script->id(), CreateRegisteredContentScriptInfo(*script));
- }
- }
- for (const auto& script : scripts) {
- std::string error_script_id = UserScript::TrimPrefixFromScriptID(script.id);
- if (loaded_scripts_metadata.find(script.id) ==
- loaded_scripts_metadata.end()) {
- return RespondNow(
- Error(base::StringPrintf("Script with ID '%s' does not exist "
- "or is not fully registered",
- error_script_id.c_str())));
- }
- }
- std::u16string parse_error;
- UserScriptList parsed_scripts;
- std::set<std::string> updated_script_ids_to_persist;
- std::set<std::string> persistent_script_ids =
- loader->GetPersistentDynamicScriptIDs();
- parsed_scripts.reserve(scripts.size());
- for (size_t i = 0; i < scripts.size(); ++i) {
- api::scripting::RegisteredContentScript& update_delta = scripts[i];
- DCHECK(base::Contains(loaded_scripts_metadata, update_delta.id));
- api::scripting::RegisteredContentScript& updated_script =
- loaded_scripts_metadata[update_delta.id];
- if (update_delta.matches)
- updated_script.matches = std::move(update_delta.matches);
- if (update_delta.exclude_matches)
- updated_script.exclude_matches = std::move(update_delta.exclude_matches);
- if (update_delta.js)
- updated_script.js = std::move(update_delta.js);
- if (update_delta.css)
- updated_script.css = std::move(update_delta.css);
- if (update_delta.all_frames)
- *updated_script.all_frames = *update_delta.all_frames;
- if (update_delta.match_origin_as_fallback) {
- *updated_script.match_origin_as_fallback =
- *update_delta.match_origin_as_fallback;
- }
- if (update_delta.run_at != api::extension_types::RunAt::kNone) {
- updated_script.run_at = update_delta.run_at;
- }
- // Parse/Create user script.
- std::unique_ptr<UserScript> user_script =
- ParseUserScript(browser_context(), *extension(),
- std::move(updated_script), &parse_error);
- if (!user_script)
- return RespondNow(Error(base::UTF16ToASCII(parse_error)));
- // 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 ((update_delta.persist_across_sessions &&
- *update_delta.persist_across_sessions) ||
- (!update_delta.persist_across_sessions &&
- base::Contains(persistent_script_ids, update_delta.id))) {
- updated_script_ids_to_persist.insert(update_delta.id);
- }
- parsed_scripts.push_back(std::move(user_script));
- }
- // 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();
- }
- void ScriptingRegisterContentScriptsFunction::OnContentScriptFilesValidated(
- std::set<std::string> 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<std::string> 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<std::string>& 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<api::scripting::GetRegisteredContentScripts::Params> params =
- api::scripting::GetRegisteredContentScripts::Params::Create(args());
- EXTENSION_FUNCTION_VALIDATE(params);
- const std::optional<api::scripting::ContentScriptFilter>& filter =
- params->filter;
- std::set<std::string> 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<api::scripting::RegisteredContentScript> script_infos;
- std::set<std::string> persistent_script_ids =
- loader->GetPersistentDynamicScriptIDs();
- for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
- if (script->GetSource() != UserScript::Source::kDynamicContentScript) {
- continue;
- }
- if (!id_filter.empty() && !base::Contains(id_filter, script->id())) {
- continue;
- }
- auto registered_script = CreateRegisteredContentScriptInfo(*script);
- registered_script.persist_across_sessions =
- base::Contains(persistent_script_ids, 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<api::scripting::ContentScriptFilter>& filter = params->filter;
- ExtensionUserScriptLoader* loader =
- ExtensionSystem::Get(browser_context())
- ->user_script_manager()
- ->GetUserScriptLoaderForExtension(extension()->id());
- // TODO(crbug.com/1300657): Only clear all scripts if `filter` did not specify
- // the list of scripts ids to remove.
- if (!filter || !filter->ids || filter->ids->empty()) {
- loader->ClearDynamicScripts(
- UserScript::Source::kDynamicContentScript,
- base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
- OnContentScriptsUnregistered,
- this));
- return RespondLater();
- }
- std::set<std::string> ids_to_remove;
- std::set<std::string> existing_script_ids =
- loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
- std::string error;
- for (const auto& provided_id : *filter->ids) {
- if (!scripting::IsScriptIdValid(provided_id, &error)) {
- return RespondNow(Error(std::move(error)));
- }
- // Add the dynamic content script prefix to `provided_id` before checking
- // against `existing_script_ids`.
- std::string id_with_prefix = scripting::AddPrefixToDynamicScriptId(
- provided_id, UserScript::Source::kDynamicContentScript);
- if (!base::Contains(existing_script_ids, id_with_prefix)) {
- return RespondNow(Error(ErrorUtils::FormatErrorMessage(
- kNonExistentScriptIdError, provided_id.c_str())));
- }
- ids_to_remove.insert(id_with_prefix);
- }
- loader->RemoveDynamicScripts(
- std::move(ids_to_remove),
- base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
- OnContentScriptsUnregistered,
- this));
- return RespondLater();
- }
- void ScriptingUnregisterContentScriptsFunction::OnContentScriptsUnregistered(
- const std::optional<std::string>& error) {
- if (error.has_value())
- Respond(Error(std::move(*error)));
- else
- Respond(NoArguments());
- }
- ScriptingUpdateContentScriptsFunction::ScriptingUpdateContentScriptsFunction() =
- default;
- ScriptingUpdateContentScriptsFunction::
- ~ScriptingUpdateContentScriptsFunction() = default;
- ExtensionFunction::ResponseAction
- ScriptingRegisterContentScriptsFunction::Run() {
- std::optional<api::scripting::RegisterContentScripts::Params> params =
- api::scripting::RegisterContentScripts::Params::Create(args());
- EXTENSION_FUNCTION_VALIDATE(params);
- std::vector<api::scripting::RegisteredContentScript>& 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<std::string> existing_script_ids =
- loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
- std::set<std::string> 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<std::string> persistent_script_ids;
- parsed_scripts.reserve(scripts.size());
- for (auto& script : scripts) {
- if (!script.matches) {
- std::string error_script_id =
- UserScript::TrimPrefixFromScriptID(script.id);
- return RespondNow(
- Error(base::StringPrintf("Script with ID '%s' must specify 'matches'",
- error_script_id.c_str())));
- }
- // Scripts will persist across sessions by default.
- bool persist_across_sessions =
- script.persist_across_sessions.value_or(true);
- std::unique_ptr<UserScript> user_script = ParseUserScript(
- browser_context(), *extension(), std::move(script), &parse_error);
- if (!user_script)
- return RespondNow(Error(base::UTF16ToASCII(parse_error)));
- // Scripts will persist across sessions by default.
- 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 ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
- std::set<std::string> 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());
- std::set<std::string> script_ids;
- for (const auto& script : scripts)
- script_ids.insert(script->id());
- if (error.has_value()) {
- loader->RemovePendingDynamicScriptIDs(script_ids);
- Respond(Error(std::move(*error)));
- Release(); // Matches the `AddRef()` in `Run()`.
- return;
- }
- // To guarantee that scripts are updated, they need to be removed then added
- // again. It should be guaranteed that the new scripts are added after the old
- // ones are removed.
- loader->RemoveDynamicScripts(script_ids, /*callback=*/base::DoNothing());
- // Since RemoveDynamicScripts will remove pending script IDs, but
- // AddDynamicScripts will only add scripts that are marked as pending, we must
- // mark `script_ids` as pending again here.
- loader->AddPendingDynamicScriptIDs(std::move(script_ids));
- loader->AddDynamicScripts(
- std::move(scripts), std::move(persistent_script_ids),
- base::BindOnce(
- &ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated,
- this));
- }
- void ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated(
- const std::optional<std::string>& error) {
- if (error.has_value())
- Respond(Error(std::move(*error)));
- else
- Respond(NoArguments());
- Release(); // Matches the `AddRef()` in `Run()`.
- }
- } // namespace extensions
|