Browse Source

refactor: update chrome.scripting extensions api impls (#43205)

Shelley Vohr 8 months ago
parent
commit
6d13c503c5

+ 162 - 199
shell/browser/extensions/api/scripting/scripting_api.cc

@@ -38,9 +38,9 @@
 #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/user_script.h"
 #include "extensions/common/utils/content_script_utils.h"
 #include "extensions/common/utils/extension_types_utils.h"
 #include "shell/browser/api/electron_api_web_contents.h"
@@ -52,9 +52,10 @@ namespace {
 constexpr char kCouldNotLoadFileError[] = "Could not load file: '*'.";
 constexpr char kDuplicateFileSpecifiedError[] =
     "Duplicate file specified: '*'.";
+constexpr char kEmptyMatchesError[] =
+    "Script with ID '*' must specify 'matches'.";
 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
@@ -483,6 +484,7 @@ ConvertRegisteredContentScriptToSerializedUserScript(
 std::unique_ptr<UserScript> ParseUserScript(
     content::BrowserContext* browser_context,
     const Extension& extension,
+    bool allowed_in_incognito,
     api::scripting::RegisteredContentScript content_script,
     std::u16string* error) {
   api::scripts_internal::SerializedUserScript serialized_script =
@@ -490,23 +492,12 @@ std::unique_ptr<UserScript> ParseUserScript(
           std::move(content_script));
 
   std::unique_ptr<UserScript> user_script =
-      script_serialization::ParseSerializedUserScript(serialized_script,
-                                                      extension, error);
+      script_serialization::ParseSerializedUserScript(
+          serialized_script, extension, allowed_in_incognito, 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;
 }
 
@@ -515,6 +506,7 @@ std::unique_ptr<UserScript> ParseUserScript(
 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.
@@ -631,11 +623,10 @@ ExtensionFunction::ResponseAction ScriptingExecuteScriptFunction::Run() {
     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 {
+      std::string json;
+      if (!base::JSONWriter::Write(arg, &json))
         return RespondNow(Error("Unserializable argument passed."));
-      }
+      string_args.push_back(std::move(json));
     }
     args_expression = base::JoinString(string_args, ",");
   }
@@ -929,121 +920,78 @@ ScriptingRegisterContentScriptsFunction::
 ScriptingRegisterContentScriptsFunction::
     ~ScriptingRegisterContentScriptsFunction() = default;
 
-ExtensionFunction::ResponseAction ScriptingUpdateContentScriptsFunction::Run() {
-  std::optional<api::scripting::UpdateContentScripts::Params> params =
-      api::scripting::UpdateContentScripts::Params::Create(args());
+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;
-  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));
-    }
-  }
+  // 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);
 
-  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())));
-    }
+  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> 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);
+  std::set<std::string> persistent_script_ids;
 
-    if (update_delta.all_frames)
-      *updated_script.all_frames = *update_delta.all_frames;
+  bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
+      extension()->id(), browser_context());
 
-    if (update_delta.match_origin_as_fallback) {
-      *updated_script.match_origin_as_fallback =
-          *update_delta.match_origin_as_fallback;
+  parsed_scripts.reserve(scripts.size());
+  for (auto& script : scripts) {
+    if (!script.matches) {
+      return RespondNow(Error(ErrorUtils::FormatErrorMessage(
+          kEmptyMatchesError, UserScript::TrimPrefixFromScriptID(script.id))));
     }
 
-    if (update_delta.run_at != api::extension_types::RunAt::kNone) {
-      updated_script.run_at = update_delta.run_at;
-    }
+    // Scripts will persist across sessions by default.
+    bool persist_across_sessions =
+        script.persist_across_sessions.value_or(true);
 
-    // Parse/Create user script.
     std::unique_ptr<UserScript> user_script =
-        ParseUserScript(browser_context(), *extension(),
-                        std::move(updated_script), &parse_error);
-    if (!user_script)
+        ParseUserScript(browser_context(), *extension(), allowed_in_incognito,
+                        std::move(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);
     }
 
+    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(ids_to_update));
+  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(
-          &ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated,
-          this, std::move(updated_script_ids_to_persist)));
+      base::BindOnce(&ScriptingRegisterContentScriptsFunction::
+                         OnContentScriptFilesValidated,
+                     this, std::move(persistent_script_ids)));
 
   // Balanced in `OnContentScriptFilesValidated()` or
   // `OnContentScriptsRegistered()`.
@@ -1141,12 +1089,15 @@ ScriptingGetRegisteredContentScriptsFunction::Run() {
     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();
@@ -1170,49 +1121,27 @@ ScriptingUnregisterContentScriptsFunction::Run() {
   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::optional<std::vector<std::string>> ids = std::nullopt;
+  // TODO(crbug.com/40216362): `ids` should have an empty list when filter ids
+  // is empty, instead of a nullopt. Otherwise, we are incorrectly removing all
+  // content scripts when ids is empty.
+  if (filter && filter->ids && !filter->ids->empty()) {
+    ids = std::move(filter->ids);
   }
 
-  std::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),
+  bool removal_triggered = scripting::RemoveScripts(
+      ids, UserScript::Source::kDynamicContentScript, browser_context(),
+      extension()->id(),
       base::BindOnce(&ScriptingUnregisterContentScriptsFunction::
                          OnContentScriptsUnregistered,
-                     this));
+                     this),
+      &error);
+
+  if (!removal_triggered) {
+    CHECK(!error.empty());
+    return RespondNow(Error(std::move(error)));
+  }
 
   return RespondLater();
 }
@@ -1230,76 +1159,56 @@ ScriptingUpdateContentScriptsFunction::ScriptingUpdateContentScriptsFunction() =
 ScriptingUpdateContentScriptsFunction::
     ~ScriptingUpdateContentScriptsFunction() = default;
 
-ExtensionFunction::ResponseAction
-ScriptingRegisterContentScriptsFunction::Run() {
-  std::optional<api::scripting::RegisterContentScripts::Params> params =
-      api::scripting::RegisterContentScripts::Params::Create(args());
+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 =
+  std::vector<api::scripting::RegisteredContentScript>& scripts_to_update =
       params->scripts;
+  std::string error;
+
+  // Add the prefix for dynamic content scripts onto the IDs of all
+  // `scripts_to_update` before continuing.
+  std::set<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 =
       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,
+  std::set<std::string> updated_script_ids_to_persist;
+  UserScriptList parsed_scripts = scripting::UpdateScripts(
+      scripts_to_update, UserScript::Source::kDynamicContentScript, *loader,
+      base::BindRepeating(&CreateRegisteredContentScriptInfo),
+      base::BindRepeating(&ScriptingUpdateContentScriptsFunction::ApplyUpdate,
+                          this, &updated_script_ids_to_persist),
       &error);
 
   if (!error.empty()) {
-    CHECK(new_script_ids.empty());
+    CHECK(parsed_scripts.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));
+  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(&ScriptingRegisterContentScriptsFunction::
-                         OnContentScriptFilesValidated,
-                     this, std::move(persistent_script_ids)));
+      base::BindOnce(
+          &ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated,
+          this, std::move(updated_script_ids_to_persist)));
 
   // Balanced in `OnContentScriptFilesValidated()` or
   // `OnContentScriptsRegistered()`.
@@ -1307,6 +1216,63 @@ ScriptingRegisterContentScriptsFunction::Run() {
   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(
     std::set<std::string> persistent_script_ids,
     scripting::ValidateScriptsResult result) {
@@ -1335,10 +1301,16 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
           ->user_script_manager()
           ->GetUserScriptLoaderForExtension(extension()->id());
 
+  bool allowed_in_incognito = scripting::ScriptsShouldBeAllowedInIncognito(
+      extension()->id(), browser_context());
+
   std::set<std::string> script_ids;
-  for (const auto& script : scripts)
+  for (const auto& script : scripts) {
     script_ids.insert(script->id());
 
+    script->set_incognito_enabled(allowed_in_incognito);
+  }
+
   if (error.has_value()) {
     loader->RemovePendingDynamicScriptIDs(script_ids);
     Respond(Error(std::move(*error)));
@@ -1346,18 +1318,9 @@ void ScriptingUpdateContentScriptsFunction::OnContentScriptFilesValidated(
     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),
+  loader->UpdateDynamicScripts(
+      std::move(scripts), std::move(script_ids),
+      std::move(persistent_script_ids),
       base::BindOnce(
           &ScriptingUpdateContentScriptsFunction::OnContentScriptsUpdated,
           this));

+ 9 - 0
shell/browser/extensions/api/scripting/scripting_api.h

@@ -191,6 +191,15 @@ class ScriptingUpdateContentScriptsFunction : public ExtensionFunction {
  private:
   ~ScriptingUpdateContentScriptsFunction() override;
 
+  // Returns a UserScript object by updating the `original_script` with the
+  // `new_script` given delta. If the updated script cannot be parsed, populates
+  // `parse_error` and returns nullptr.
+  std::unique_ptr<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.
   void OnContentScriptFilesValidated(
       std::set<std::string> persistent_script_ids,

+ 28 - 29
shell/common/extensions/api/scripting.idl

@@ -4,7 +4,6 @@
 
 // Use the <code>chrome.scripting</code> API to execute script in different
 // contexts.
-[modernised_enums]
 namespace scripting {
   callback InjectedFunction = void();
 
@@ -49,11 +48,11 @@ namespace scripting {
     // A JavaScript function to inject. This function will be serialized, and
     // then deserialized for injection. This means that any bound parameters
     // and execution context will be lost.
-    // Exactly one of <code>files</code> and <code>func</code> must be
+    // Exactly one of <code>files</code> or <code>func</code> must be
     // specified.
     [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
     // JSON-serializable.
     any[]? args;
@@ -67,7 +66,7 @@ namespace scripting {
 
     // The path of the JS or CSS files to inject, relative to the extension's
     // 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.
     DOMString[]? files;
 
@@ -122,13 +121,13 @@ namespace scripting {
     // with a '_' as it's reserved as a prefix for generated script IDs.
     DOMString id;
     // Specifies which pages this content script will be injected into. See
-    // <a href="match_patterns">Match Patterns</a> for more details on the
-    // syntax of these strings. Must be specified for
+    // <a href="develop/concepts/match-patterns">Match Patterns</a> for more
+    // details on the syntax of these strings. Must be specified for
     // $(ref:registerContentScripts).
     DOMString[]? matches;
     // Excludes pages that this content script would otherwise be injected into.
-    // See <a href="match_patterns">Match Patterns</a> for more details on the
-    // syntax of these strings.
+    // See <a href="develop/concepts/match-patterns">Match Patterns</a> for
+    // more details on the syntax of these strings.
     DOMString[]? excludeMatches;
     // The list of CSS files to be injected into matching pages. These are
     // injected in the order they appear in this array, before any DOM is
@@ -143,15 +142,13 @@ namespace scripting {
     // requirements are not met. Defaults to false, meaning that only the top
     // frame is matched.
     boolean? allFrames;
-    // Whether the script should inject into any frames where the URL belongs to
-    // a scheme that would never match a specified Match Pattern, including
-    // about:, data:, blob:, and filesystem: schemes. In these cases, in order
-    // to determine if the script should inject, the origin of the URL is
-    // checked. If the origin is `null` (as is the case for data: URLs), then
-    // the "initiator" or "creator" origin is used (i.e., the origin of the
-    // frame that created or navigated this frame). Note that this may not
-    // be the parent frame, if the frame was navigated by another frame in the
-    // document hierarchy.
+    // Indicates whether the script can be injected into frames where the URL
+    // contains an unsupported scheme; specifically: about:, data:, blob:, or
+    // filesystem:. In these cases, the URL's origin is checked to determine if
+    // the script should be injected. If the origin is `null` (as is the case
+    // for data: URLs) then the used origin is either the frame that created
+    // the current frame or the frame that initiated the navigation to this
+    // frame. Note that this may not be the parent frame.
     boolean? matchOriginAsFallback;
     // Specifies when JavaScript files are injected into the web page. The
     // preferred and default value is <code>document_idle</code>.
@@ -190,20 +187,22 @@ namespace scripting {
     // and modify as a JS object. One instance exists per frame and is shared
     // between all content scripts for a given extension. This object is
     // initialized when the frame is created, before document_start.
-    // TODO(crbug.com/1054624): Enable this once implementation is complete.
+    // TODO(crbug.com/40119604): Enable this once implementation is complete.
     [nodoc, nocompile] static long globalParams();
   };
 
   interface Functions {
-    // Injects a script into a target context. The script will be run at
-    // <code>document_idle</code>. If the script evaluates to a promise,
-    // the browser will wait for the promise to settle and return the
-    // resulting value.
+    // Injects a script into a target context. By default, the script will be run
+    // at <code>document_idle</code>, or immediately if the page has already
+    // loaded. If the <code>injectImmediately</code> property is set, the script
+    // will inject without waiting, even if the page has not finished loading. If
+    // the script evaluates to a promise, the browser will wait for the promise to
+    // settle and return the resulting value.
     // |injection|: The details of the script which to inject.
     // |callback|: Invoked upon completion of the injection. The resulting
     // array contains the result of execution for each frame where the
     // injection succeeded.
-    [supportsPromises] static void executeScript(
+    static void executeScript(
         ScriptInjection injection,
         optional ScriptInjectionCallback callback);
 
@@ -211,7 +210,7 @@ namespace scripting {
     // If multiple frames are specified, unsuccessful injections are ignored.
     // |injection|: The details of the styles to insert.
     // |callback|: Invoked upon completion of the insertion.
-    [supportsPromises] static void insertCSS(
+    static void insertCSS(
         CSSInjection injection,
         optional CSSInjectionCallback callback);
 
@@ -222,7 +221,7 @@ namespace scripting {
     // must exactly match the stylesheet inserted through $(ref:insertCSS).
     // Attempting to remove a non-existent stylesheet is a no-op.
     // |callback|: A callback to be invoked upon the completion of the removal.
-    [supportsPromises] static void removeCSS(
+    static void removeCSS(
         CSSInjection injection,
         optional CSSInjectionCallback callback);
 
@@ -232,7 +231,7 @@ namespace scripting {
     // already exist, then no scripts are registered.
     // |callback|: A callback to be invoked once scripts have been fully
     // registered or if an error has occurred.
-    [supportsPromises] static void registerContentScripts(
+    static void registerContentScripts(
         RegisteredContentScript[] scripts,
         optional RegisterContentScriptsCallback callback);
 
@@ -240,7 +239,7 @@ namespace scripting {
     // that match the given filter.
     // |filter|: An object to filter the extension's dynamically registered
     // scripts.
-    [supportsPromises] static void getRegisteredContentScripts(
+    static void getRegisteredContentScripts(
         optional ContentScriptFilter filter,
         GetRegisteredContentScriptsCallback callback);
 
@@ -250,7 +249,7 @@ namespace scripting {
     // scripts are unregistered.
     // |callback|: A callback to be invoked once scripts have been unregistered
     // or if an error has occurred.
-    [supportsPromises] static void unregisterContentScripts(
+    static void unregisterContentScripts(
         optional ContentScriptFilter filter,
         optional UnregisterContentScriptsCallback callback);
 
@@ -262,7 +261,7 @@ namespace scripting {
     // are updated.
     // |callback|: A callback to be invoked once scripts have been updated or
     // if an error has occurred.
-    [supportsPromises] static void updateContentScripts(
+    static void updateContentScripts(
         RegisteredContentScript[] scripts,
         optional RegisterContentScriptsCallback callback);
   };