refactor: update chrome.scripting extensions api impls (#43290)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
997afe62f2
commit
bd70c3a740
3 changed files with 200 additions and 229 deletions
|
@ -39,9 +39,9 @@
|
||||||
#include "extensions/common/mojom/execution_world.mojom-shared.h"
|
#include "extensions/common/mojom/execution_world.mojom-shared.h"
|
||||||
#include "extensions/common/mojom/host_id.mojom.h"
|
#include "extensions/common/mojom/host_id.mojom.h"
|
||||||
#include "extensions/common/mojom/run_location.mojom-shared.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/permissions/permissions_data.h"
|
||||||
#include "extensions/common/script_constants.h"
|
#include "extensions/common/script_constants.h"
|
||||||
|
#include "extensions/common/user_script.h"
|
||||||
#include "extensions/common/utils/content_script_utils.h"
|
#include "extensions/common/utils/content_script_utils.h"
|
||||||
#include "extensions/common/utils/extension_types_utils.h"
|
#include "extensions/common/utils/extension_types_utils.h"
|
||||||
#include "shell/browser/api/electron_api_web_contents.h"
|
#include "shell/browser/api/electron_api_web_contents.h"
|
||||||
|
@ -53,9 +53,10 @@ namespace {
|
||||||
constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
|
constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
|
||||||
constexpr char kDuplicateFileSpecifiedError[] =
|
constexpr char kDuplicateFileSpecifiedError[] =
|
||||||
"Duplicate file specified: '*'.";
|
"Duplicate file specified: '*'.";
|
||||||
|
constexpr char kEmptyMatchesError[] =
|
||||||
|
"Script with ID '*' must specify 'matches'.";
|
||||||
constexpr char kExactlyOneOfCssAndFilesError[] =
|
constexpr char kExactlyOneOfCssAndFilesError[] =
|
||||||
"Exactly one of 'css' and 'files' must be specified.";
|
"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
|
// Note: CSS always injects as soon as possible, so we default to
|
||||||
// document_start. Because of tab loading, there's no guarantee this will
|
// document_start. Because of tab loading, there's no guarantee this will
|
||||||
|
@ -484,6 +485,7 @@ ConvertRegisteredContentScriptToSerializedUserScript(
|
||||||
std::unique_ptr<UserScript> ParseUserScript(
|
std::unique_ptr<UserScript> ParseUserScript(
|
||||||
content::BrowserContext* browser_context,
|
content::BrowserContext* browser_context,
|
||||||
const Extension& extension,
|
const Extension& extension,
|
||||||
|
bool allowed_in_incognito,
|
||||||
api::scripting::RegisteredContentScript content_script,
|
api::scripting::RegisteredContentScript content_script,
|
||||||
std::u16string* error) {
|
std::u16string* error) {
|
||||||
api::scripts_internal::SerializedUserScript serialized_script =
|
api::scripts_internal::SerializedUserScript serialized_script =
|
||||||
|
@ -491,23 +493,12 @@ std::unique_ptr<UserScript> ParseUserScript(
|
||||||
std::move(content_script));
|
std::move(content_script));
|
||||||
|
|
||||||
std::unique_ptr<UserScript> user_script =
|
std::unique_ptr<UserScript> user_script =
|
||||||
script_serialization::ParseSerializedUserScript(serialized_script,
|
script_serialization::ParseSerializedUserScript(
|
||||||
extension, error);
|
serialized_script, extension, allowed_in_incognito, error);
|
||||||
if (!user_script) {
|
if (!user_script) {
|
||||||
return nullptr; // Parsing failed.
|
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;
|
return user_script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,6 +507,7 @@ std::unique_ptr<UserScript> ParseUserScript(
|
||||||
api::scripting::RegisteredContentScript CreateRegisteredContentScriptInfo(
|
api::scripting::RegisteredContentScript CreateRegisteredContentScriptInfo(
|
||||||
const UserScript& script) {
|
const UserScript& script) {
|
||||||
CHECK_EQ(UserScript::Source::kDynamicContentScript, script.GetSource());
|
CHECK_EQ(UserScript::Source::kDynamicContentScript, script.GetSource());
|
||||||
|
|
||||||
// To convert a `UserScript`, we first go through our script_internal
|
// To convert a `UserScript`, we first go through our script_internal
|
||||||
// serialization; this allows us to do simple conversions and avoid any
|
// serialization; this allows us to do simple conversions and avoid any
|
||||||
// complex logic.
|
// complex logic.
|
||||||
|
@ -632,11 +624,10 @@ ExtensionFunction::ResponseAction ScriptingExecuteScriptFunction::Run() {
|
||||||
std::vector<std::string> string_args;
|
std::vector<std::string> string_args;
|
||||||
string_args.reserve(injection_.args->size());
|
string_args.reserve(injection_.args->size());
|
||||||
for (const auto& arg : *injection_.args) {
|
for (const auto& arg : *injection_.args) {
|
||||||
if (auto json = base::WriteJson(arg)) {
|
std::string json;
|
||||||
string_args.push_back(std::move(*json));
|
if (!base::JSONWriter::Write(arg, &json))
|
||||||
} else {
|
|
||||||
return RespondNow(Error("Unserializable argument passed."));
|
return RespondNow(Error("Unserializable argument passed."));
|
||||||
}
|
string_args.push_back(std::move(json));
|
||||||
}
|
}
|
||||||
args_expression = base::JoinString(string_args, ",");
|
args_expression = base::JoinString(string_args, ",");
|
||||||
}
|
}
|
||||||
|
@ -930,121 +921,78 @@ ScriptingRegisterContentScriptsFunction::
|
||||||
ScriptingRegisterContentScriptsFunction::
|
ScriptingRegisterContentScriptsFunction::
|
||||||
~ScriptingRegisterContentScriptsFunction() = default;
|
~ScriptingRegisterContentScriptsFunction() = default;
|
||||||
|
|
||||||
ExtensionFunction::ResponseAction ScriptingUpdateContentScriptsFunction::Run() {
|
ExtensionFunction::ResponseAction
|
||||||
std::optional<api::scripting::UpdateContentScripts::Params> params =
|
ScriptingRegisterContentScriptsFunction::Run() {
|
||||||
api::scripting::UpdateContentScripts::Params::Create(args());
|
std::optional<api::scripting::RegisterContentScripts::Params> params =
|
||||||
|
api::scripting::RegisterContentScripts::Params::Create(args());
|
||||||
EXTENSION_FUNCTION_VALIDATE(params);
|
EXTENSION_FUNCTION_VALIDATE(params);
|
||||||
|
|
||||||
std::vector<api::scripting::RegisteredContentScript>& scripts =
|
std::vector<api::scripting::RegisteredContentScript>& scripts =
|
||||||
params->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 =
|
ExtensionUserScriptLoader* loader =
|
||||||
ExtensionSystem::Get(browser_context())
|
ExtensionSystem::Get(browser_context())
|
||||||
->user_script_manager()
|
->user_script_manager()
|
||||||
->GetUserScriptLoaderForExtension(extension()->id());
|
->GetUserScriptLoaderForExtension(extension()->id());
|
||||||
|
|
||||||
std::map<std::string, api::scripting::RegisteredContentScript>
|
// Create script ids for dynamic content scripts.
|
||||||
loaded_scripts_metadata;
|
std::string error;
|
||||||
const UserScriptList& dynamic_scripts = loader->GetLoadedDynamicScripts();
|
std::set<std::string> existing_script_ids =
|
||||||
for (const std::unique_ptr<UserScript>& script : dynamic_scripts) {
|
loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
|
||||||
if (script->GetSource() == UserScript::Source::kDynamicContentScript) {
|
std::set<std::string> new_script_ids = scripting::CreateDynamicScriptIds(
|
||||||
loaded_scripts_metadata.emplace(
|
scripts, UserScript::Source::kDynamicContentScript, existing_script_ids,
|
||||||
script->id(), CreateRegisteredContentScriptInfo(*script));
|
&error);
|
||||||
}
|
|
||||||
}
|
if (!error.empty()) {
|
||||||
|
CHECK(new_script_ids.empty());
|
||||||
for (const auto& script : scripts) {
|
return RespondNow(Error(std::move(error)));
|
||||||
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())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse content scripts.
|
||||||
std::u16string parse_error;
|
std::u16string parse_error;
|
||||||
UserScriptList parsed_scripts;
|
UserScriptList parsed_scripts;
|
||||||
std::set<std::string> updated_script_ids_to_persist;
|
std::set<std::string> persistent_script_ids;
|
||||||
std::set<std::string> persistent_script_ids =
|
|
||||||
loader->GetPersistentDynamicScriptIDs();
|
bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
|
||||||
|
extension()->id(), browser_context());
|
||||||
|
|
||||||
parsed_scripts.reserve(scripts.size());
|
parsed_scripts.reserve(scripts.size());
|
||||||
for (size_t i = 0; i < scripts.size(); ++i) {
|
for (auto& script : scripts) {
|
||||||
api::scripting::RegisteredContentScript& update_delta = scripts[i];
|
if (!script.matches) {
|
||||||
DCHECK(base::Contains(loaded_scripts_metadata, update_delta.id));
|
return RespondNow(Error(ErrorUtils::FormatErrorMessage(
|
||||||
|
kEmptyMatchesError, UserScript::TrimPrefixFromScriptID(script.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) {
|
// Scripts will persist across sessions by default.
|
||||||
updated_script.run_at = update_delta.run_at;
|
bool persist_across_sessions =
|
||||||
}
|
script.persist_across_sessions.value_or(true);
|
||||||
|
|
||||||
// Parse/Create user script.
|
|
||||||
std::unique_ptr<UserScript> user_script =
|
std::unique_ptr<UserScript> user_script =
|
||||||
ParseUserScript(browser_context(), *extension(),
|
ParseUserScript(browser_context(), *extension(), allowed_in_incognito,
|
||||||
std::move(updated_script), &parse_error);
|
std::move(script), &parse_error);
|
||||||
if (!user_script)
|
if (!user_script) {
|
||||||
return RespondNow(Error(base::UTF16ToASCII(parse_error)));
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (persist_across_sessions) {
|
||||||
|
persistent_script_ids.insert(user_script->id());
|
||||||
|
}
|
||||||
parsed_scripts.push_back(std::move(user_script));
|
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
|
// Add new script IDs now in case another call with the same script IDs is
|
||||||
// made immediately following this one.
|
// made immediately following this one.
|
||||||
loader->AddPendingDynamicScriptIDs(std::move(ids_to_update));
|
loader->AddPendingDynamicScriptIDs(std::move(new_script_ids));
|
||||||
|
|
||||||
GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
|
GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
|
||||||
FROM_HERE,
|
FROM_HERE,
|
||||||
base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
|
base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
|
||||||
script_parsing::GetSymlinkPolicy(extension()),
|
script_parsing::GetSymlinkPolicy(extension()),
|
||||||
std::move(parsed_scripts)),
|
std::move(parsed_scripts)),
|
||||||
base::BindOnce(
|
base::BindOnce(&ScriptingRegisterContentScriptsFunction::
|
||||||
&ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated,
|
OnContentScriptFilesValidated,
|
||||||
this, std::move(updated_script_ids_to_persist)));
|
this, std::move(persistent_script_ids)));
|
||||||
|
|
||||||
// Balanced in `OnContentScriptFilesValidated()` or
|
// Balanced in `OnContentScriptFilesValidated()` or
|
||||||
// `OnContentScriptsRegistered()`.
|
// `OnContentScriptsRegistered()`.
|
||||||
|
@ -1142,12 +1090,15 @@ ScriptingGetRegisteredContentScriptsFunction::Run() {
|
||||||
if (script->GetSource() != UserScript::Source::kDynamicContentScript) {
|
if (script->GetSource() != UserScript::Source::kDynamicContentScript) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!id_filter.empty() && !base::Contains(id_filter, script->id())) {
|
if (!id_filter.empty() && !base::Contains(id_filter, script->id())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto registered_script = CreateRegisteredContentScriptInfo(*script);
|
auto registered_script = CreateRegisteredContentScriptInfo(*script);
|
||||||
registered_script.persist_across_sessions =
|
registered_script.persist_across_sessions =
|
||||||
base::Contains(persistent_script_ids, script->id());
|
base::Contains(persistent_script_ids, script->id());
|
||||||
|
|
||||||
// Remove the internally used prefix from the `script`'s ID before
|
// Remove the internally used prefix from the `script`'s ID before
|
||||||
// returning.
|
// returning.
|
||||||
registered_script.id = script->GetIDWithoutPrefix();
|
registered_script.id = script->GetIDWithoutPrefix();
|
||||||
|
@ -1171,49 +1122,27 @@ ScriptingUnregisterContentScriptsFunction::Run() {
|
||||||
EXTENSION_FUNCTION_VALIDATE(params);
|
EXTENSION_FUNCTION_VALIDATE(params);
|
||||||
|
|
||||||
std::optional<api::scripting::ContentScriptFilter>& filter = params->filter;
|
std::optional<api::scripting::ContentScriptFilter>& filter = params->filter;
|
||||||
ExtensionUserScriptLoader* loader =
|
std::optional<std::vector<std::string>> ids = std::nullopt;
|
||||||
ExtensionSystem::Get(browser_context())
|
// TODO(crbug.com/40216362): `ids` should have an empty list when filter ids
|
||||||
->user_script_manager()
|
// is empty, instead of a nullopt. Otherwise, we are incorrectly removing all
|
||||||
->GetUserScriptLoaderForExtension(extension()->id());
|
// content scripts when ids is empty.
|
||||||
|
if (filter && filter->ids && !filter->ids->empty()) {
|
||||||
// TODO(crbug.com/1300657): Only clear all scripts if `filter` did not specify
|
ids = std::move(filter->ids);
|
||||||
// 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;
|
std::string error;
|
||||||
for (const auto& provided_id : *filter->ids) {
|
bool removal_triggered = scripting::RemoveScripts(
|
||||||
if (!scripting::IsScriptIdValid(provided_id, &error)) {
|
ids, UserScript::Source::kDynamicContentScript, browser_context(),
|
||||||
return RespondNow(Error(std::move(error)));
|
extension()->id(),
|
||||||
}
|
|
||||||
|
|
||||||
// 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::
|
base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
|
||||||
OnContentScriptsUnregistered,
|
OnContentScriptsUnregistered,
|
||||||
this));
|
this),
|
||||||
|
&error);
|
||||||
|
|
||||||
|
if (!removal_triggered) {
|
||||||
|
CHECK(!error.empty());
|
||||||
|
return RespondNow(Error(std::move(error)));
|
||||||
|
}
|
||||||
|
|
||||||
return RespondLater();
|
return RespondLater();
|
||||||
}
|
}
|
||||||
|
@ -1231,76 +1160,56 @@ ScriptingUpdateContentScriptsFunction::ScriptingUpdateContentScriptsFunction() =
|
||||||
ScriptingUpdateContentScriptsFunction::
|
ScriptingUpdateContentScriptsFunction::
|
||||||
~ScriptingUpdateContentScriptsFunction() = default;
|
~ScriptingUpdateContentScriptsFunction() = default;
|
||||||
|
|
||||||
ExtensionFunction::ResponseAction
|
ExtensionFunction::ResponseAction ScriptingUpdateContentScriptsFunction::Run() {
|
||||||
ScriptingRegisterContentScriptsFunction::Run() {
|
std::optional<api::scripting::UpdateContentScripts::Params> params =
|
||||||
std::optional<api::scripting::RegisterContentScripts::Params> params =
|
api::scripting::UpdateContentScripts::Params::Create(args());
|
||||||
api::scripting::RegisterContentScripts::Params::Create(args());
|
|
||||||
EXTENSION_FUNCTION_VALIDATE(params);
|
EXTENSION_FUNCTION_VALIDATE(params);
|
||||||
|
|
||||||
std::vector<api::scripting::RegisteredContentScript>& scripts =
|
std::vector<api::scripting::RegisteredContentScript>& scripts_to_update =
|
||||||
params->scripts;
|
params->scripts;
|
||||||
|
std::string error;
|
||||||
|
|
||||||
|
// Add the prefix for dynamic content scripts onto the IDs of all
|
||||||
|
// `scripts_to_update` before continuing.
|
||||||
|
std::set<std::string> ids_to_update = scripting::CreateDynamicScriptIds(
|
||||||
|
scripts_to_update, UserScript::Source::kDynamicContentScript,
|
||||||
|
std::set<std::string>(), &error);
|
||||||
|
|
||||||
|
if (!error.empty()) {
|
||||||
|
CHECK(ids_to_update.empty());
|
||||||
|
return RespondNow(Error(std::move(error)));
|
||||||
|
}
|
||||||
|
|
||||||
ExtensionUserScriptLoader* loader =
|
ExtensionUserScriptLoader* loader =
|
||||||
ExtensionSystem::Get(browser_context())
|
ExtensionSystem::Get(browser_context())
|
||||||
->user_script_manager()
|
->user_script_manager()
|
||||||
->GetUserScriptLoaderForExtension(extension()->id());
|
->GetUserScriptLoaderForExtension(extension()->id());
|
||||||
|
|
||||||
// Create script ids for dynamic content scripts.
|
std::set<std::string> updated_script_ids_to_persist;
|
||||||
std::string error;
|
UserScriptList parsed_scripts = scripting::UpdateScripts(
|
||||||
std::set<std::string> existing_script_ids =
|
scripts_to_update, UserScript::Source::kDynamicContentScript, *loader,
|
||||||
loader->GetDynamicScriptIDs(UserScript::Source::kDynamicContentScript);
|
base::BindRepeating(&CreateRegisteredContentScriptInfo),
|
||||||
std::set<std::string> new_script_ids = scripting::CreateDynamicScriptIds(
|
base::BindRepeating(&ScriptingUpdateContentScriptsFunction::ApplyUpdate,
|
||||||
scripts, UserScript::Source::kDynamicContentScript, existing_script_ids,
|
this, &updated_script_ids_to_persist),
|
||||||
&error);
|
&error);
|
||||||
|
|
||||||
if (!error.empty()) {
|
if (!error.empty()) {
|
||||||
CHECK(new_script_ids.empty());
|
CHECK(parsed_scripts.empty());
|
||||||
return RespondNow(Error(std::move(error)));
|
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
|
// Add new script IDs now in case another call with the same script IDs is
|
||||||
// made immediately following this one.
|
// made immediately following this one.
|
||||||
loader->AddPendingDynamicScriptIDs(std::move(new_script_ids));
|
loader->AddPendingDynamicScriptIDs(std::move(ids_to_update));
|
||||||
|
|
||||||
GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
|
GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
|
||||||
FROM_HERE,
|
FROM_HERE,
|
||||||
base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
|
base::BindOnce(&scripting::ValidateParsedScriptsOnFileThread,
|
||||||
script_parsing::GetSymlinkPolicy(extension()),
|
script_parsing::GetSymlinkPolicy(extension()),
|
||||||
std::move(parsed_scripts)),
|
std::move(parsed_scripts)),
|
||||||
base::BindOnce(&ScriptingRegisterContentScriptsFunction::
|
base::BindOnce(
|
||||||
OnContentScriptFilesValidated,
|
&ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated,
|
||||||
this, std::move(persistent_script_ids)));
|
this, std::move(updated_script_ids_to_persist)));
|
||||||
|
|
||||||
// Balanced in `OnContentScriptFilesValidated()` or
|
// Balanced in `OnContentScriptFilesValidated()` or
|
||||||
// `OnContentScriptsRegistered()`.
|
// `OnContentScriptsRegistered()`.
|
||||||
|
@ -1308,6 +1217,63 @@ ScriptingRegisterContentScriptsFunction::Run() {
|
||||||
return RespondLater();
|
return RespondLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<UserScript> ScriptingUpdateContentScriptsFunction::ApplyUpdate(
|
||||||
|
std::set<std::string>* 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<UserScript> 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 &&
|
||||||
|
base::Contains(*script_ids_to_persist, new_script.id))) {
|
||||||
|
script_ids_to_persist->insert(new_script.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed_script;
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
|
void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
|
||||||
std::set<std::string> persistent_script_ids,
|
std::set<std::string> persistent_script_ids,
|
||||||
scripting::ValidateScriptsResult result) {
|
scripting::ValidateScriptsResult result) {
|
||||||
|
@ -1336,10 +1302,16 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
|
||||||
->user_script_manager()
|
->user_script_manager()
|
||||||
->GetUserScriptLoaderForExtension(extension()->id());
|
->GetUserScriptLoaderForExtension(extension()->id());
|
||||||
|
|
||||||
|
bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
|
||||||
|
extension()->id(), browser_context());
|
||||||
|
|
||||||
std::set<std::string> script_ids;
|
std::set<std::string> script_ids;
|
||||||
for (const auto& script : scripts)
|
for (const auto& script : scripts) {
|
||||||
script_ids.insert(script->id());
|
script_ids.insert(script->id());
|
||||||
|
|
||||||
|
script->set_incognito_enabled(allowed_in_incognito);
|
||||||
|
}
|
||||||
|
|
||||||
if (error.has_value()) {
|
if (error.has_value()) {
|
||||||
loader->RemovePendingDynamicScriptIDs(script_ids);
|
loader->RemovePendingDynamicScriptIDs(script_ids);
|
||||||
Respond(Error(std::move(*error)));
|
Respond(Error(std::move(*error)));
|
||||||
|
@ -1347,18 +1319,9 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To guarantee that scripts are updated, they need to be removed then added
|
loader->UpdateDynamicScripts(
|
||||||
// again. It should be guaranteed that the new scripts are added after the old
|
std::move(scripts), std::move(script_ids),
|
||||||
// ones are removed.
|
std::move(persistent_script_ids),
|
||||||
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(
|
base::BindOnce(
|
||||||
&ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated,
|
&ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated,
|
||||||
this));
|
this));
|
||||||
|
|
|
@ -191,6 +191,15 @@ class ScriptingUpdateContentScriptsFunction : public ExtensionFunction {
|
||||||
private:
|
private:
|
||||||
~ScriptingUpdateContentScriptsFunction() override;
|
~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<UserScript> ApplyUpdate(
|
||||||
|
std::set<std::string>* 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.
|
// Called when script files have been checked.
|
||||||
void OnContentScriptFilesValidated(
|
void OnContentScriptFilesValidated(
|
||||||
std::set<std::string> persistent_script_ids,
|
std::set<std::string> persistent_script_ids,
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
// Use the <code>chrome.scripting</code> API to execute script in different
|
// Use the <code>chrome.scripting</code> API to execute script in different
|
||||||
// contexts.
|
// contexts.
|
||||||
[modernised_enums]
|
|
||||||
namespace scripting {
|
namespace scripting {
|
||||||
callback InjectedFunction = void();
|
callback InjectedFunction = void();
|
||||||
|
|
||||||
|
@ -49,11 +48,11 @@ namespace scripting {
|
||||||
// A JavaScript function to inject. This function will be serialized, and
|
// A JavaScript function to inject. This function will be serialized, and
|
||||||
// then deserialized for injection. This means that any bound parameters
|
// then deserialized for injection. This means that any bound parameters
|
||||||
// and execution context will be lost.
|
// and execution context will be lost.
|
||||||
// Exactly one of <code>files</code> and <code>func</code> must be
|
// Exactly one of <code>files</code> or <code>func</code> must be
|
||||||
// specified.
|
// specified.
|
||||||
[serializableFunction]InjectedFunction? func;
|
[serializableFunction]InjectedFunction? func;
|
||||||
|
|
||||||
// The arguments to curry into a provided function. This is only valid if
|
// The arguments to pass to the provided function. This is only valid if
|
||||||
// the <code>func</code> parameter is specified. These arguments must be
|
// the <code>func</code> parameter is specified. These arguments must be
|
||||||
// JSON-serializable.
|
// JSON-serializable.
|
||||||
any[]? args;
|
any[]? args;
|
||||||
|
@ -67,7 +66,7 @@ namespace scripting {
|
||||||
|
|
||||||
// The path of the JS or CSS files to inject, relative to the extension's
|
// The path of the JS or CSS files to inject, relative to the extension's
|
||||||
// root directory.
|
// root directory.
|
||||||
// Exactly one of <code>files</code> and <code>func</code> must be
|
// Exactly one of <code>files</code> or <code>func</code> must be
|
||||||
// specified.
|
// specified.
|
||||||
DOMString[]? files;
|
DOMString[]? files;
|
||||||
|
|
||||||
|
@ -122,13 +121,13 @@ namespace scripting {
|
||||||
// with a '_' as it's reserved as a prefix for generated script IDs.
|
// with a '_' as it's reserved as a prefix for generated script IDs.
|
||||||
DOMString id;
|
DOMString id;
|
||||||
// Specifies which pages this content script will be injected into. See
|
// Specifies which pages this content script will be injected into. See
|
||||||
// <a href="match_patterns">Match Patterns</a> for more details on the
|
// <a href="develop/concepts/match-patterns">Match Patterns</a> for more
|
||||||
// syntax of these strings. Must be specified for
|
// details on the syntax of these strings. Must be specified for
|
||||||
// $(ref:registerContentScripts).
|
// $(ref:registerContentScripts).
|
||||||
DOMString[]? matches;
|
DOMString[]? matches;
|
||||||
// Excludes pages that this content script would otherwise be injected into.
|
// Excludes pages that this content script would otherwise be injected into.
|
||||||
// See <a href="match_patterns">Match Patterns</a> for more details on the
|
// See <a href="develop/concepts/match-patterns">Match Patterns</a> for
|
||||||
// syntax of these strings.
|
// more details on the syntax of these strings.
|
||||||
DOMString[]? excludeMatches;
|
DOMString[]? excludeMatches;
|
||||||
// The list of CSS files to be injected into matching pages. These are
|
// 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
|
// injected in the order they appear in this array, before any DOM is
|
||||||
|
@ -143,15 +142,13 @@ namespace scripting {
|
||||||
// requirements are not met. Defaults to false, meaning that only the top
|
// requirements are not met. Defaults to false, meaning that only the top
|
||||||
// frame is matched.
|
// frame is matched.
|
||||||
boolean? allFrames;
|
boolean? allFrames;
|
||||||
// Whether the script should inject into any frames where the URL belongs to
|
// Indicates whether the script can be injected into frames where the URL
|
||||||
// a scheme that would never match a specified Match Pattern, including
|
// contains an unsupported scheme; specifically: about:, data:, blob:, or
|
||||||
// about:, data:, blob:, and filesystem: schemes. In these cases, in order
|
// filesystem:. In these cases, the URL's origin is checked to determine if
|
||||||
// to determine if the script should inject, the origin of the URL is
|
// the script should be injected. If the origin is `null` (as is the case
|
||||||
// checked. If the origin is `null` (as is the case for data: URLs), then
|
// for data: URLs) then the used origin is either the frame that created
|
||||||
// the "initiator" or "creator" origin is used (i.e., the origin of the
|
// the current frame or the frame that initiated the navigation to this
|
||||||
// frame that created or navigated this frame). Note that this may not
|
// frame. Note that this may not be the parent frame.
|
||||||
// be the parent frame, if the frame was navigated by another frame in the
|
|
||||||
// document hierarchy.
|
|
||||||
boolean? matchOriginAsFallback;
|
boolean? matchOriginAsFallback;
|
||||||
// Specifies when JavaScript files are injected into the web page. The
|
// Specifies when JavaScript files are injected into the web page. The
|
||||||
// preferred and default value is <code>document_idle</code>.
|
// preferred and default value is <code>document_idle</code>.
|
||||||
|
@ -190,20 +187,22 @@ namespace scripting {
|
||||||
// and modify as a JS object. One instance exists per frame and is shared
|
// 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
|
// between all content scripts for a given extension. This object is
|
||||||
// initialized when the frame is created, before document_start.
|
// initialized when the frame is created, before document_start.
|
||||||
// TODO(crbug.com/1054624): Enable this once implementation is complete.
|
// TODO(crbug.com/40119604): Enable this once implementation is complete.
|
||||||
[nodoc, nocompile] static long globalParams();
|
[nodoc, nocompile] static long globalParams();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Functions {
|
interface Functions {
|
||||||
// Injects a script into a target context. The script will be run at
|
// Injects a script into a target context. By default, the script will be run
|
||||||
// <code>document_idle</code>. If the script evaluates to a promise,
|
// at <code>document_idle</code>, or immediately if the page has already
|
||||||
// the browser will wait for the promise to settle and return the
|
// loaded. If the <code>injectImmediately</code> property is set, the script
|
||||||
// resulting value.
|
// 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.
|
// |injection|: The details of the script which to inject.
|
||||||
// |callback|: Invoked upon completion of the injection. The resulting
|
// |callback|: Invoked upon completion of the injection. The resulting
|
||||||
// array contains the result of execution for each frame where the
|
// array contains the result of execution for each frame where the
|
||||||
// injection succeeded.
|
// injection succeeded.
|
||||||
[supportsPromises] static void executeScript(
|
static void executeScript(
|
||||||
ScriptInjection injection,
|
ScriptInjection injection,
|
||||||
optional ScriptInjectionCallback callback);
|
optional ScriptInjectionCallback callback);
|
||||||
|
|
||||||
|
@ -211,7 +210,7 @@ namespace scripting {
|
||||||
// If multiple frames are specified, unsuccessful injections are ignored.
|
// If multiple frames are specified, unsuccessful injections are ignored.
|
||||||
// |injection|: The details of the styles to insert.
|
// |injection|: The details of the styles to insert.
|
||||||
// |callback|: Invoked upon completion of the insertion.
|
// |callback|: Invoked upon completion of the insertion.
|
||||||
[supportsPromises] static void insertCSS(
|
static void insertCSS(
|
||||||
CSSInjection injection,
|
CSSInjection injection,
|
||||||
optional CSSInjectionCallback callback);
|
optional CSSInjectionCallback callback);
|
||||||
|
|
||||||
|
@ -222,7 +221,7 @@ namespace scripting {
|
||||||
// must exactly match the stylesheet inserted through $(ref:insertCSS).
|
// must exactly match the stylesheet inserted through $(ref:insertCSS).
|
||||||
// Attempting to remove a non-existent stylesheet is a no-op.
|
// Attempting to remove a non-existent stylesheet is a no-op.
|
||||||
// |callback|: A callback to be invoked upon the completion of the removal.
|
// |callback|: A callback to be invoked upon the completion of the removal.
|
||||||
[supportsPromises] static void removeCSS(
|
static void removeCSS(
|
||||||
CSSInjection injection,
|
CSSInjection injection,
|
||||||
optional CSSInjectionCallback callback);
|
optional CSSInjectionCallback callback);
|
||||||
|
|
||||||
|
@ -232,7 +231,7 @@ namespace scripting {
|
||||||
// already exist, then no scripts are registered.
|
// already exist, then no scripts are registered.
|
||||||
// |callback|: A callback to be invoked once scripts have been fully
|
// |callback|: A callback to be invoked once scripts have been fully
|
||||||
// registered or if an error has occurred.
|
// registered or if an error has occurred.
|
||||||
[supportsPromises] static void registerContentScripts(
|
static void registerContentScripts(
|
||||||
RegisteredContentScript[] scripts,
|
RegisteredContentScript[] scripts,
|
||||||
optional RegisterContentScriptsCallback callback);
|
optional RegisterContentScriptsCallback callback);
|
||||||
|
|
||||||
|
@ -240,7 +239,7 @@ namespace scripting {
|
||||||
// that match the given filter.
|
// that match the given filter.
|
||||||
// |filter|: An object to filter the extension's dynamically registered
|
// |filter|: An object to filter the extension's dynamically registered
|
||||||
// scripts.
|
// scripts.
|
||||||
[supportsPromises] static void getRegisteredContentScripts(
|
static void getRegisteredContentScripts(
|
||||||
optional ContentScriptFilter filter,
|
optional ContentScriptFilter filter,
|
||||||
GetRegisteredContentScriptsCallback callback);
|
GetRegisteredContentScriptsCallback callback);
|
||||||
|
|
||||||
|
@ -250,7 +249,7 @@ namespace scripting {
|
||||||
// scripts are unregistered.
|
// scripts are unregistered.
|
||||||
// |callback|: A callback to be invoked once scripts have been unregistered
|
// |callback|: A callback to be invoked once scripts have been unregistered
|
||||||
// or if an error has occurred.
|
// or if an error has occurred.
|
||||||
[supportsPromises] static void unregisterContentScripts(
|
static void unregisterContentScripts(
|
||||||
optional ContentScriptFilter filter,
|
optional ContentScriptFilter filter,
|
||||||
optional UnregisterContentScriptsCallback callback);
|
optional UnregisterContentScriptsCallback callback);
|
||||||
|
|
||||||
|
@ -262,7 +261,7 @@ namespace scripting {
|
||||||
// are updated.
|
// are updated.
|
||||||
// |callback|: A callback to be invoked once scripts have been updated or
|
// |callback|: A callback to be invoked once scripts have been updated or
|
||||||
// if an error has occurred.
|
// if an error has occurred.
|
||||||
[supportsPromises] static void updateContentScripts(
|
static void updateContentScripts(
|
||||||
RegisteredContentScript[] scripts,
|
RegisteredContentScript[] scripts,
|
||||||
optional RegisterContentScriptsCallback callback);
|
optional RegisterContentScriptsCallback callback);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue