feat: support chrome.scripting
extension APIs (#39395)
feat: support chrome.scripting extension APIs
This commit is contained in:
parent
5078cae861
commit
f0ad357af2
16 changed files with 2087 additions and 15 deletions
|
@ -61,14 +61,20 @@ See [Manifest file format](https://developer.chrome.com/docs/extensions/mv3/mani
|
||||||
|
|
||||||
All features of this API are supported.
|
All features of this API are supported.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow) for more information.
|
||||||
|
|
||||||
### `chrome.devtools.network`
|
### `chrome.devtools.network`
|
||||||
|
|
||||||
All features of this API are supported.
|
All features of this API are supported.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_network) for more information.
|
||||||
|
|
||||||
### `chrome.devtools.panels`
|
### `chrome.devtools.panels`
|
||||||
|
|
||||||
All features of this API are supported.
|
All features of this API are supported.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/devtools_panels) for more information.
|
||||||
|
|
||||||
### `chrome.extension`
|
### `chrome.extension`
|
||||||
|
|
||||||
The following properties of `chrome.extension` are supported:
|
The following properties of `chrome.extension` are supported:
|
||||||
|
@ -80,6 +86,25 @@ The following methods of `chrome.extension` are supported:
|
||||||
- `chrome.extension.getURL`
|
- `chrome.extension.getURL`
|
||||||
- `chrome.extension.getBackgroundPage`
|
- `chrome.extension.getBackgroundPage`
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/extension) for more information.
|
||||||
|
|
||||||
|
### `chrome.management`
|
||||||
|
|
||||||
|
The following methods of `chrome.management` are supported:
|
||||||
|
|
||||||
|
- `chrome.management.getAll`
|
||||||
|
- `chrome.management.get`
|
||||||
|
- `chrome.management.getSelf`
|
||||||
|
- `chrome.management.getPermissionWarningsById`
|
||||||
|
- `chrome.management.getPermissionWarningsByManifest`
|
||||||
|
|
||||||
|
The following events of `chrome.management` are supported:
|
||||||
|
|
||||||
|
- `chrome.management.onEnabled`
|
||||||
|
- `chrome.management.onDisabled`
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/management) for more information.
|
||||||
|
|
||||||
### `chrome.runtime`
|
### `chrome.runtime`
|
||||||
|
|
||||||
The following properties of `chrome.runtime` are supported:
|
The following properties of `chrome.runtime` are supported:
|
||||||
|
@ -106,12 +131,24 @@ The following events of `chrome.runtime` are supported:
|
||||||
- `chrome.runtime.onConnect`
|
- `chrome.runtime.onConnect`
|
||||||
- `chrome.runtime.onMessage`
|
- `chrome.runtime.onMessage`
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/runtime) for more information.
|
||||||
|
|
||||||
|
### `chrome.scripting`
|
||||||
|
|
||||||
|
All features of this API are supported.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/scripting) for more information.
|
||||||
|
|
||||||
### `chrome.storage`
|
### `chrome.storage`
|
||||||
|
|
||||||
The following methods of `chrome.storage` are supported:
|
The following methods of `chrome.storage` are supported:
|
||||||
|
|
||||||
- `chrome.storage.local`
|
- `chrome.storage.local`
|
||||||
|
|
||||||
|
`chrome.storage.sync` and `chrome.storage.managed` are **not** supported.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/storage) for more information.
|
||||||
|
|
||||||
### `chrome.tabs`
|
### `chrome.tabs`
|
||||||
|
|
||||||
The following methods of `chrome.tabs` are supported:
|
The following methods of `chrome.tabs` are supported:
|
||||||
|
@ -128,23 +165,12 @@ The following methods of `chrome.tabs` are supported:
|
||||||
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
|
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
|
||||||
> supported and will raise an error.
|
> supported and will raise an error.
|
||||||
|
|
||||||
### `chrome.management`
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/tabs) for more information.
|
||||||
|
|
||||||
The following methods of `chrome.management` are supported:
|
|
||||||
|
|
||||||
- `chrome.management.getAll`
|
|
||||||
- `chrome.management.get`
|
|
||||||
- `chrome.management.getSelf`
|
|
||||||
- `chrome.management.getPermissionWarningsById`
|
|
||||||
- `chrome.management.getPermissionWarningsByManifest`
|
|
||||||
|
|
||||||
The following events of `chrome.management` are supported:
|
|
||||||
|
|
||||||
- `chrome.management.onEnabled`
|
|
||||||
- `chrome.management.onDisabled`
|
|
||||||
|
|
||||||
### `chrome.webRequest`
|
### `chrome.webRequest`
|
||||||
|
|
||||||
All features of this API are supported.
|
All features of this API are supported.
|
||||||
|
|
||||||
> **NOTE:** Electron's [`webRequest`](web-request.md) module takes precedence over `chrome.webRequest` if there are conflicting handlers.
|
> **NOTE:** Electron's [`webRequest`](web-request.md) module takes precedence over `chrome.webRequest` if there are conflicting handlers.
|
||||||
|
|
||||||
|
See [official documentation](https://developer.chrome.com/docs/extensions/reference/webRequest) for more information.
|
||||||
|
|
|
@ -714,6 +714,8 @@ filenames = {
|
||||||
"shell/browser/extensions/api/resources_private/resources_private_api.h",
|
"shell/browser/extensions/api/resources_private/resources_private_api.h",
|
||||||
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
|
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
|
||||||
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
|
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
|
||||||
|
"shell/browser/extensions/api/scripting/scripting_api.cc",
|
||||||
|
"shell/browser/extensions/api/scripting/scripting_api.h",
|
||||||
"shell/browser/extensions/api/streams_private/streams_private_api.cc",
|
"shell/browser/extensions/api/streams_private/streams_private_api.cc",
|
||||||
"shell/browser/extensions/api/streams_private/streams_private_api.h",
|
"shell/browser/extensions/api/streams_private/streams_private_api.h",
|
||||||
"shell/browser/extensions/api/tabs/tabs_api.cc",
|
"shell/browser/extensions/api/tabs/tabs_api.cc",
|
||||||
|
|
|
@ -13,6 +13,7 @@ function_registration("api_registration") {
|
||||||
sources = [
|
sources = [
|
||||||
"//electron/shell/common/extensions/api/extension.json",
|
"//electron/shell/common/extensions/api/extension.json",
|
||||||
"//electron/shell/common/extensions/api/resources_private.idl",
|
"//electron/shell/common/extensions/api/resources_private.idl",
|
||||||
|
"//electron/shell/common/extensions/api/scripting.idl",
|
||||||
"//electron/shell/common/extensions/api/tabs.json",
|
"//electron/shell/common/extensions/api/tabs.json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
1390
shell/browser/extensions/api/scripting/scripting_api.cc
Normal file
1390
shell/browser/extensions/api/scripting/scripting_api.cc
Normal file
File diff suppressed because it is too large
Load diff
207
shell/browser/extensions/api/scripting/scripting_api.h
Normal file
207
shell/browser/extensions/api/scripting/scripting_api.h
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
// Copyright 2023 Microsoft, GmbH
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
|
||||||
|
#define ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "chrome/common/extensions/api/scripting.h"
|
||||||
|
#include "extensions/browser/extension_function.h"
|
||||||
|
#include "extensions/browser/script_executor.h"
|
||||||
|
#include "extensions/common/mojom/code_injection.mojom.h"
|
||||||
|
#include "extensions/common/user_script.h"
|
||||||
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||||
|
|
||||||
|
namespace extensions {
|
||||||
|
|
||||||
|
// A simple helper struct to represent a read file (either CSS or JS) to be
|
||||||
|
// injected.
|
||||||
|
struct InjectedFileSource {
|
||||||
|
InjectedFileSource(std::string file_name, std::unique_ptr<std::string> data);
|
||||||
|
InjectedFileSource(InjectedFileSource&&);
|
||||||
|
~InjectedFileSource();
|
||||||
|
|
||||||
|
std::string file_name;
|
||||||
|
std::unique_ptr<std::string> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingExecuteScriptFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.executeScript", SCRIPTING_EXECUTESCRIPT)
|
||||||
|
|
||||||
|
ScriptingExecuteScriptFunction();
|
||||||
|
ScriptingExecuteScriptFunction(const ScriptingExecuteScriptFunction&) =
|
||||||
|
delete;
|
||||||
|
ScriptingExecuteScriptFunction& operator=(
|
||||||
|
const ScriptingExecuteScriptFunction&) = delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingExecuteScriptFunction() override;
|
||||||
|
|
||||||
|
// Called when the resource files to be injected has been loaded.
|
||||||
|
void DidLoadResources(std::vector<InjectedFileSource> file_sources,
|
||||||
|
absl::optional<std::string> load_error);
|
||||||
|
|
||||||
|
// Triggers the execution of `sources` in the appropriate context.
|
||||||
|
// Returns true on success; on failure, populates `error`.
|
||||||
|
bool Execute(std::vector<mojom::JSSourcePtr> sources, std::string* error);
|
||||||
|
|
||||||
|
// Invoked when script execution is complete.
|
||||||
|
void OnScriptExecuted(std::vector<ScriptExecutor::FrameResult> frame_results);
|
||||||
|
|
||||||
|
api::scripting::ScriptInjection injection_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingInsertCSSFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.insertCSS", SCRIPTING_INSERTCSS)
|
||||||
|
|
||||||
|
ScriptingInsertCSSFunction();
|
||||||
|
ScriptingInsertCSSFunction(const ScriptingInsertCSSFunction&) = delete;
|
||||||
|
ScriptingInsertCSSFunction& operator=(const ScriptingInsertCSSFunction&) =
|
||||||
|
delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingInsertCSSFunction() override;
|
||||||
|
|
||||||
|
// Called when the resource files to be injected has been loaded.
|
||||||
|
void DidLoadResources(std::vector<InjectedFileSource> file_sources,
|
||||||
|
absl::optional<std::string> load_error);
|
||||||
|
|
||||||
|
// Triggers the execution of `sources` in the appropriate context.
|
||||||
|
// Returns true on success; on failure, populates `error`.
|
||||||
|
bool Execute(std::vector<mojom::CSSSourcePtr> sources, std::string* error);
|
||||||
|
|
||||||
|
// Called when the CSS insertion is complete.
|
||||||
|
void OnCSSInserted(std::vector<ScriptExecutor::FrameResult> results);
|
||||||
|
|
||||||
|
api::scripting::CSSInjection injection_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingRemoveCSSFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.removeCSS", SCRIPTING_REMOVECSS)
|
||||||
|
|
||||||
|
ScriptingRemoveCSSFunction();
|
||||||
|
ScriptingRemoveCSSFunction(const ScriptingRemoveCSSFunction&) = delete;
|
||||||
|
ScriptingRemoveCSSFunction& operator=(const ScriptingRemoveCSSFunction&) =
|
||||||
|
delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingRemoveCSSFunction() override;
|
||||||
|
|
||||||
|
// Called when the CSS removal is complete.
|
||||||
|
void OnCSSRemoved(std::vector<ScriptExecutor::FrameResult> results);
|
||||||
|
};
|
||||||
|
|
||||||
|
using ValidateContentScriptsResult =
|
||||||
|
std::pair<std::unique_ptr<UserScriptList>, absl::optional<std::string>>;
|
||||||
|
|
||||||
|
class ScriptingRegisterContentScriptsFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.registerContentScripts",
|
||||||
|
SCRIPTING_REGISTERCONTENTSCRIPTS)
|
||||||
|
|
||||||
|
ScriptingRegisterContentScriptsFunction();
|
||||||
|
ScriptingRegisterContentScriptsFunction(
|
||||||
|
const ScriptingRegisterContentScriptsFunction&) = delete;
|
||||||
|
ScriptingRegisterContentScriptsFunction& operator=(
|
||||||
|
const ScriptingRegisterContentScriptsFunction&) = delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingRegisterContentScriptsFunction() override;
|
||||||
|
|
||||||
|
// Called when script files have been checked.
|
||||||
|
void OnContentScriptFilesValidated(
|
||||||
|
std::set<std::string> persistent_script_ids,
|
||||||
|
ValidateContentScriptsResult result);
|
||||||
|
|
||||||
|
// Called when content scripts have been registered.
|
||||||
|
void OnContentScriptsRegistered(const absl::optional<std::string>& error);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingGetRegisteredContentScriptsFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.getRegisteredContentScripts",
|
||||||
|
SCRIPTING_GETREGISTEREDCONTENTSCRIPTS)
|
||||||
|
|
||||||
|
ScriptingGetRegisteredContentScriptsFunction();
|
||||||
|
ScriptingGetRegisteredContentScriptsFunction(
|
||||||
|
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
|
||||||
|
ScriptingGetRegisteredContentScriptsFunction& operator=(
|
||||||
|
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingGetRegisteredContentScriptsFunction() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingUnregisterContentScriptsFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.unregisterContentScripts",
|
||||||
|
SCRIPTING_UNREGISTERCONTENTSCRIPTS)
|
||||||
|
|
||||||
|
ScriptingUnregisterContentScriptsFunction();
|
||||||
|
ScriptingUnregisterContentScriptsFunction(
|
||||||
|
const ScriptingUnregisterContentScriptsFunction&) = delete;
|
||||||
|
ScriptingUnregisterContentScriptsFunction& operator=(
|
||||||
|
const ScriptingUnregisterContentScriptsFunction&) = delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingUnregisterContentScriptsFunction() override;
|
||||||
|
|
||||||
|
// Called when content scripts have been unregistered.
|
||||||
|
void OnContentScriptsUnregistered(const absl::optional<std::string>& error);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptingUpdateContentScriptsFunction : public ExtensionFunction {
|
||||||
|
public:
|
||||||
|
DECLARE_EXTENSION_FUNCTION("scripting.updateContentScripts",
|
||||||
|
SCRIPTING_UPDATECONTENTSCRIPTS)
|
||||||
|
|
||||||
|
ScriptingUpdateContentScriptsFunction();
|
||||||
|
ScriptingUpdateContentScriptsFunction(
|
||||||
|
const ScriptingUpdateContentScriptsFunction&) = delete;
|
||||||
|
ScriptingUpdateContentScriptsFunction& operator=(
|
||||||
|
const ScriptingUpdateContentScriptsFunction&) = delete;
|
||||||
|
|
||||||
|
// ExtensionFunction:
|
||||||
|
ResponseAction Run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
~ScriptingUpdateContentScriptsFunction() override;
|
||||||
|
|
||||||
|
// Called when script files have been checked.
|
||||||
|
void OnContentScriptFilesValidated(
|
||||||
|
std::set<std::string> persistent_script_ids,
|
||||||
|
ValidateContentScriptsResult result);
|
||||||
|
|
||||||
|
// Called when content scripts have been updated.
|
||||||
|
void OnContentScriptsUpdated(const absl::optional<std::string>& error);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extensions
|
||||||
|
|
||||||
|
#endif // ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
|
|
@ -7,6 +7,7 @@
|
||||||
#include "extensions/browser/api/i18n/i18n_api.h"
|
#include "extensions/browser/api/i18n/i18n_api.h"
|
||||||
#include "extensions/browser/extension_function_registry.h"
|
#include "extensions/browser/extension_function_registry.h"
|
||||||
#include "shell/browser/extensions/api/generated_api_registration.h"
|
#include "shell/browser/extensions/api/generated_api_registration.h"
|
||||||
|
#include "shell/browser/extensions/api/scripting/scripting_api.h"
|
||||||
#include "shell/browser/extensions/api/tabs/tabs_api.h"
|
#include "shell/browser/extensions/api/tabs/tabs_api.h"
|
||||||
|
|
||||||
namespace extensions {
|
namespace extensions {
|
||||||
|
|
|
@ -39,6 +39,7 @@ generated_json_strings("generated_api_json_strings") {
|
||||||
sources = [
|
sources = [
|
||||||
"extension.json",
|
"extension.json",
|
||||||
"resources_private.idl",
|
"resources_private.idl",
|
||||||
|
"scripting.idl",
|
||||||
"tabs.json",
|
"tabs.json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ generated_json_strings("generated_api_json_strings") {
|
||||||
generated_types("generated_api_types") {
|
generated_types("generated_api_types") {
|
||||||
sources = [
|
sources = [
|
||||||
"resources_private.idl",
|
"resources_private.idl",
|
||||||
|
"scripting.idl",
|
||||||
"tabs.json",
|
"tabs.json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -51,5 +51,9 @@
|
||||||
"matches": [
|
"matches": [
|
||||||
"chrome://print/*"
|
"chrome://print/*"
|
||||||
]
|
]
|
||||||
}]
|
}],
|
||||||
|
"scripting": {
|
||||||
|
"dependencies": ["permission:scripting"],
|
||||||
|
"contexts": ["blessed_extension"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,5 +26,12 @@
|
||||||
"extension_types": [
|
"extension_types": [
|
||||||
"extension"
|
"extension"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"scripting": {
|
||||||
|
"channel": "stable",
|
||||||
|
"extension_types": [
|
||||||
|
"extension"
|
||||||
|
],
|
||||||
|
"min_manifest_version": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
262
shell/common/extensions/api/scripting.idl
Normal file
262
shell/common/extensions/api/scripting.idl
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
// Copyright 2020 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Use the <code>chrome.scripting</code> API to execute script in different
|
||||||
|
// contexts.
|
||||||
|
namespace scripting {
|
||||||
|
callback InjectedFunction = void();
|
||||||
|
|
||||||
|
// The origin for a style change.
|
||||||
|
// See <a href="https://developer.mozilla.org/en-US/docs/Glossary/Style_origin">style origins</a>
|
||||||
|
// for more info.
|
||||||
|
enum StyleOrigin {
|
||||||
|
AUTHOR,
|
||||||
|
USER
|
||||||
|
};
|
||||||
|
|
||||||
|
// The JavaScript world for a script to execute within.
|
||||||
|
enum ExecutionWorld {
|
||||||
|
// Specifies the isolated world, which is the execution environment unique
|
||||||
|
// to this extension.
|
||||||
|
ISOLATED,
|
||||||
|
// Specifies the main world of the DOM, which is the execution environment
|
||||||
|
// shared with the host page's JavaScript.
|
||||||
|
MAIN
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary InjectionTarget {
|
||||||
|
// The ID of the tab into which to inject.
|
||||||
|
long tabId;
|
||||||
|
|
||||||
|
// The <a href="https://developer.chrome.com/extensions/webNavigation#frame_ids">IDs</a>
|
||||||
|
// of specific frames to inject into.
|
||||||
|
long[]? frameIds;
|
||||||
|
|
||||||
|
// The <a href="https://developer.chrome.com/extensions/webNavigation#document_ids">IDs</a>
|
||||||
|
// of specific documentIds to inject into. This must not be set if
|
||||||
|
// <code>frameIds</code> is set.
|
||||||
|
DOMString[]? documentIds;
|
||||||
|
|
||||||
|
// Whether the script should inject into all frames within the tab. Defaults
|
||||||
|
// to false.
|
||||||
|
// This must not be true if <code>frameIds</code> is specified.
|
||||||
|
boolean? allFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary ScriptInjection {
|
||||||
|
// A JavaScript function to inject. This function will be serialized, and
|
||||||
|
// then deserialized for injection. This means that any bound parameters
|
||||||
|
// and execution context will be lost.
|
||||||
|
// Exactly one of <code>files</code> and <code>func</code> must be
|
||||||
|
// specified.
|
||||||
|
[serializableFunction]InjectedFunction? func;
|
||||||
|
|
||||||
|
// The arguments to curry into a provided function. This is only valid if
|
||||||
|
// the <code>func</code> parameter is specified. These arguments must be
|
||||||
|
// JSON-serializable.
|
||||||
|
any[]? args;
|
||||||
|
|
||||||
|
// We used to call the injected function `function`, but this is
|
||||||
|
// incompatible with JavaScript's object declaration shorthand (see
|
||||||
|
// https://crbug.com/1166438). We leave this silently in for backwards
|
||||||
|
// compatibility.
|
||||||
|
// TODO(devlin): Remove this in M95.
|
||||||
|
[nodoc, serializableFunction]InjectedFunction? function;
|
||||||
|
|
||||||
|
// The path of the JS or CSS files to inject, relative to the extension's
|
||||||
|
// root directory.
|
||||||
|
// Exactly one of <code>files</code> and <code>func</code> must be
|
||||||
|
// specified.
|
||||||
|
DOMString[]? files;
|
||||||
|
|
||||||
|
// Details specifying the target into which to inject the script.
|
||||||
|
InjectionTarget target;
|
||||||
|
|
||||||
|
// The JavaScript "world" to run the script in. Defaults to
|
||||||
|
// <code>ISOLATED</code>.
|
||||||
|
ExecutionWorld? world;
|
||||||
|
|
||||||
|
// Whether the injection should be triggered in the target as soon as
|
||||||
|
// possible. Note that this is not a guarantee that injection will occur
|
||||||
|
// prior to page load, as the page may have already loaded by the time the
|
||||||
|
// script reaches the target.
|
||||||
|
boolean? injectImmediately;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary CSSInjection {
|
||||||
|
// Details specifying the target into which to insert the CSS.
|
||||||
|
InjectionTarget target;
|
||||||
|
|
||||||
|
// A string containing the CSS to inject.
|
||||||
|
// Exactly one of <code>files</code> and <code>css</code> must be
|
||||||
|
// specified.
|
||||||
|
DOMString? css;
|
||||||
|
|
||||||
|
// The path of the CSS files to inject, relative to the extension's root
|
||||||
|
// directory.
|
||||||
|
// Exactly one of <code>files</code> and <code>css</code> must be
|
||||||
|
// specified.
|
||||||
|
DOMString[]? files;
|
||||||
|
|
||||||
|
// The style origin for the injection. Defaults to <code>'AUTHOR'</code>.
|
||||||
|
StyleOrigin? origin;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary InjectionResult {
|
||||||
|
// The result of the script execution.
|
||||||
|
any? result;
|
||||||
|
|
||||||
|
// The frame associated with the injection.
|
||||||
|
long frameId;
|
||||||
|
|
||||||
|
// The document associated with the injection.
|
||||||
|
DOMString documentId;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Describes a content script to be injected into a web page registered
|
||||||
|
// through this API.
|
||||||
|
dictionary RegisteredContentScript {
|
||||||
|
// The id of the content script, specified in the API call. Must not start
|
||||||
|
// with a '_' as it's reserved as a prefix for generated script IDs.
|
||||||
|
DOMString id;
|
||||||
|
// Specifies which pages this content script will be injected into. See
|
||||||
|
// <a href="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.
|
||||||
|
DOMString[]? excludeMatches;
|
||||||
|
// The list of CSS files to be injected into matching pages. These are
|
||||||
|
// injected in the order they appear in this array, before any DOM is
|
||||||
|
// constructed or displayed for the page.
|
||||||
|
DOMString[]? css;
|
||||||
|
// The list of JavaScript files to be injected into matching pages. These
|
||||||
|
// are injected in the order they appear in this array.
|
||||||
|
DOMString[]? js;
|
||||||
|
// If specified true, it will inject into all frames, even if the frame is
|
||||||
|
// not the top-most frame in the tab. Each frame is checked independently
|
||||||
|
// for URL requirements; it will not inject into child frames if the URL
|
||||||
|
// requirements are not met. Defaults to false, meaning that only the top
|
||||||
|
// frame is matched.
|
||||||
|
boolean? allFrames;
|
||||||
|
// TODO(devlin): Add documentation once the implementation is complete. See
|
||||||
|
// crbug.com/55084.
|
||||||
|
[nodoc]
|
||||||
|
boolean? matchOriginAsFallback;
|
||||||
|
// Specifies when JavaScript files are injected into the web page. The
|
||||||
|
// preferred and default value is <code>document_idle</code>.
|
||||||
|
extensionTypes.RunAt? runAt;
|
||||||
|
// Specifies if this content script will persist into future sessions. The
|
||||||
|
// default is true.
|
||||||
|
boolean? persistAcrossSessions;
|
||||||
|
// The JavaScript "world" to run the script in. Defaults to
|
||||||
|
// <code>ISOLATED</code>.
|
||||||
|
ExecutionWorld? world;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An object used to filter content scripts for
|
||||||
|
// ${ref:getRegisteredContentScripts}.
|
||||||
|
dictionary ContentScriptFilter {
|
||||||
|
// If specified, $(ref:getRegisteredContentScripts) will only return scripts
|
||||||
|
// with an id specified in this list.
|
||||||
|
DOMString[]? ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
callback ScriptInjectionCallback = void(InjectionResult[] results);
|
||||||
|
|
||||||
|
callback CSSInjectionCallback = void();
|
||||||
|
|
||||||
|
callback RegisterContentScriptsCallback = void();
|
||||||
|
|
||||||
|
callback GetRegisteredContentScriptsCallback = void(
|
||||||
|
RegisteredContentScript[] scripts);
|
||||||
|
|
||||||
|
callback UnregisterContentScriptsCallback = void();
|
||||||
|
|
||||||
|
callback UpdateContentScriptsCallback = void();
|
||||||
|
|
||||||
|
interface Properties {
|
||||||
|
// An object available for content scripts running in isolated worlds to use
|
||||||
|
// and modify as a JS object. One instance exists per frame and is shared
|
||||||
|
// between all content scripts for a given extension. This object is
|
||||||
|
// initialized when the frame is created, before document_start.
|
||||||
|
// TODO(crbug.com/1054624): 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.
|
||||||
|
// |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(
|
||||||
|
ScriptInjection injection,
|
||||||
|
optional ScriptInjectionCallback callback);
|
||||||
|
|
||||||
|
// Inserts a CSS stylesheet into a target context.
|
||||||
|
// If multiple frames are specified, unsuccessful injections are ignored.
|
||||||
|
// |injection|: The details of the styles to insert.
|
||||||
|
// |callback|: Invoked upon completion of the insertion.
|
||||||
|
[supportsPromises] static void insertCSS(
|
||||||
|
CSSInjection injection,
|
||||||
|
optional CSSInjectionCallback callback);
|
||||||
|
|
||||||
|
// Removes a CSS stylesheet that was previously inserted by this extension
|
||||||
|
// from a target context.
|
||||||
|
// |injection|: The details of the styles to remove. Note that the
|
||||||
|
// <code>css</code>, <code>files</code>, and <code>origin</code> properties
|
||||||
|
// must exactly match the stylesheet inserted through $(ref:insertCSS).
|
||||||
|
// Attempting to remove a non-existent stylesheet is a no-op.
|
||||||
|
// |callback|: A callback to be invoked upon the completion of the removal.
|
||||||
|
[supportsPromises] static void removeCSS(
|
||||||
|
CSSInjection injection,
|
||||||
|
optional CSSInjectionCallback callback);
|
||||||
|
|
||||||
|
// Registers one or more content scripts for this extension.
|
||||||
|
// |scripts|: Contains a list of scripts to be registered. If there are
|
||||||
|
// errors during script parsing/file validation, or if the IDs specified
|
||||||
|
// already exist, then no scripts are registered.
|
||||||
|
// |callback|: A callback to be invoked once scripts have been fully
|
||||||
|
// registered or if an error has occurred.
|
||||||
|
[supportsPromises] static void registerContentScripts(
|
||||||
|
RegisteredContentScript[] scripts,
|
||||||
|
optional RegisterContentScriptsCallback callback);
|
||||||
|
|
||||||
|
// Returns all dynamically registered content scripts for this extension
|
||||||
|
// that match the given filter.
|
||||||
|
// |filter|: An object to filter the extension's dynamically registered
|
||||||
|
// scripts.
|
||||||
|
[supportsPromises] static void getRegisteredContentScripts(
|
||||||
|
optional ContentScriptFilter filter,
|
||||||
|
GetRegisteredContentScriptsCallback callback);
|
||||||
|
|
||||||
|
// Unregisters content scripts for this extension.
|
||||||
|
// |filter|: If specified, only unregisters dynamic content scripts which
|
||||||
|
// match the filter. Otherwise, all of the extension's dynamic content
|
||||||
|
// scripts are unregistered.
|
||||||
|
// |callback|: A callback to be invoked once scripts have been unregistered
|
||||||
|
// or if an error has occurred.
|
||||||
|
[supportsPromises] static void unregisterContentScripts(
|
||||||
|
optional ContentScriptFilter filter,
|
||||||
|
optional UnregisterContentScriptsCallback callback);
|
||||||
|
|
||||||
|
// Updates one or more content scripts for this extension.
|
||||||
|
// |scripts|: Contains a list of scripts to be updated. A property is only
|
||||||
|
// updated for the existing script if it is specified in this object. If
|
||||||
|
// there are errors during script parsing/file validation, or if the IDs
|
||||||
|
// specified do not correspond to a fully registered script, then no scripts
|
||||||
|
// are updated.
|
||||||
|
// |callback|: A callback to be invoked once scripts have been updated or
|
||||||
|
// if an error has occurred.
|
||||||
|
[supportsPromises] static void updateContentScripts(
|
||||||
|
RegisteredContentScript[] scripts,
|
||||||
|
optional RegisterContentScriptsCallback callback);
|
||||||
|
};
|
||||||
|
};
|
|
@ -41,6 +41,8 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = {
|
||||||
{mojom::APIPermissionID::kManagement, "management"},
|
{mojom::APIPermissionID::kManagement, "management"},
|
||||||
{mojom::APIPermissionID::kTab, "tabs",
|
{mojom::APIPermissionID::kTab, "tabs",
|
||||||
APIPermissionInfo::kFlagRequiresManagementUIWarning},
|
APIPermissionInfo::kFlagRequiresManagementUIWarning},
|
||||||
|
{mojom::APIPermissionID::kScripting, "scripting",
|
||||||
|
APIPermissionInfo::kFlagRequiresManagementUIWarning},
|
||||||
};
|
};
|
||||||
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
|
base::span<const APIPermissionInfo::InitInfo> GetPermissionInfos() {
|
||||||
return base::make_span(permissions_to_register);
|
return base::make_span(permissions_to_register);
|
||||||
|
|
|
@ -1129,5 +1129,75 @@ describe('chrome extensions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('chrome.scripting', () => {
|
||||||
|
let customSession: Session;
|
||||||
|
let w = null as unknown as BrowserWindow;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
customSession = session.fromPartition(`persist:${uuid.v4()}`);
|
||||||
|
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-scripting'));
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
w = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
session: customSession,
|
||||||
|
nodeIntegration: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
|
it('executeScript', async () => {
|
||||||
|
await w.loadURL(url);
|
||||||
|
|
||||||
|
const message = { method: 'executeScript' };
|
||||||
|
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||||
|
|
||||||
|
const updated = await once(w.webContents, 'page-title-updated');
|
||||||
|
expect(updated[1]).to.equal('HEY HEY HEY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('registerContentScripts', async () => {
|
||||||
|
await w.loadURL(url);
|
||||||
|
|
||||||
|
const message = { method: 'registerContentScripts' };
|
||||||
|
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||||
|
|
||||||
|
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||||
|
const response = JSON.parse(responseString);
|
||||||
|
expect(response).to.be.an('array').with.lengthOf(1);
|
||||||
|
expect(response[0]).to.deep.equal({
|
||||||
|
allFrames: false,
|
||||||
|
id: 'session-script',
|
||||||
|
js: ['content.js'],
|
||||||
|
matchOriginAsFallback: false,
|
||||||
|
matches: ['<all_urls>'],
|
||||||
|
persistAcrossSessions: false,
|
||||||
|
runAt: 'document_start',
|
||||||
|
world: 'ISOLATED'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('insertCSS', async () => {
|
||||||
|
await w.loadURL(url);
|
||||||
|
|
||||||
|
const bgBefore = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).backgroundColor');
|
||||||
|
expect(bgBefore).to.equal('rgba(0, 0, 0, 0)');
|
||||||
|
|
||||||
|
const message = { method: 'insertCSS' };
|
||||||
|
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||||
|
|
||||||
|
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||||
|
const response = JSON.parse(responseString);
|
||||||
|
expect(response.success).to.be.true();
|
||||||
|
|
||||||
|
const bgAfter = await w.webContents.executeJavaScript('window.getComputedStyle(document.body).backgroundColor');
|
||||||
|
expect(bgAfter).to.equal('rgb(255, 0, 0)');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
51
spec/fixtures/extensions/chrome-scripting/background.js
vendored
Normal file
51
spec/fixtures/extensions/chrome-scripting/background.js
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* global chrome */
|
||||||
|
|
||||||
|
const handleRequest = async (request, sender, sendResponse) => {
|
||||||
|
const { method } = request;
|
||||||
|
const tabId = sender.tab.id;
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'executeScript': {
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
function: () => {
|
||||||
|
document.title = 'HEY HEY HEY';
|
||||||
|
return document.title;
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
console.log('success');
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log('error', err);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'registerContentScripts': {
|
||||||
|
await chrome.scripting.registerContentScripts([{
|
||||||
|
id: 'session-script',
|
||||||
|
js: ['content.js'],
|
||||||
|
persistAcrossSessions: false,
|
||||||
|
matches: ['<all_urls>'],
|
||||||
|
runAt: 'document_start'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
chrome.scripting.getRegisteredContentScripts().then(sendResponse);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'insertCSS': {
|
||||||
|
chrome.scripting.insertCSS({
|
||||||
|
target: { tabId },
|
||||||
|
css: 'body { background-color: red; }'
|
||||||
|
}).then(() => {
|
||||||
|
sendResponse({ success: true });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
handleRequest(request, sender, sendResponse);
|
||||||
|
return true;
|
||||||
|
});
|
0
spec/fixtures/extensions/chrome-scripting/content.js
vendored
Normal file
0
spec/fixtures/extensions/chrome-scripting/content.js
vendored
Normal file
30
spec/fixtures/extensions/chrome-scripting/main.js
vendored
Normal file
30
spec/fixtures/extensions/chrome-scripting/main.js
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/* global chrome */
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
sendResponse(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = {
|
||||||
|
executeScript () {
|
||||||
|
chrome.runtime.sendMessage({ method: 'executeScript' }, response => {
|
||||||
|
console.log(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
registerContentScripts () {
|
||||||
|
chrome.runtime.sendMessage({ method: 'registerContentScripts' }, response => {
|
||||||
|
console.log(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
insertCSS () {
|
||||||
|
chrome.runtime.sendMessage({ method: 'insertCSS' }, response => {
|
||||||
|
console.log(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dispatchTest = (event) => {
|
||||||
|
const { method, args = [] } = JSON.parse(event.data);
|
||||||
|
map[method](...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', dispatchTest, false);
|
17
spec/fixtures/extensions/chrome-scripting/manifest.json
vendored
Normal file
17
spec/fixtures/extensions/chrome-scripting/manifest.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "execute-script",
|
||||||
|
"version": "1.0",
|
||||||
|
"permissions": [
|
||||||
|
"scripting"
|
||||||
|
],
|
||||||
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"content_scripts": [{
|
||||||
|
"matches": [ "<all_urls>"],
|
||||||
|
"js": ["main.js"],
|
||||||
|
"run_at": "document_start"
|
||||||
|
}],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"manifest_version": 3
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue