Add API sample for chrome.scripting API. (#830)

* Add API sample for chrome.scripting API.

* Run formatter.

* Address feedback.

* Update licence headers.

* Invert tab colours.
This commit is contained in:
Oliver Dunk 2023-02-23 13:51:12 +00:00 коммит произвёл GitHub
Родитель dc2174377a
Коммит cf2df4f073
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 374 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,24 @@
# chrome.scripting API
This sample demonstrates how to use the [chrome.scripting](https://developer.chrome.com/docs/extensions/reference/scripting/) API to inject JavaScript into web pages.
## Overview
Once installed, clicking this extension's action icon will open an extension page.
<img src="screenshot.png" height=300 alt="Screenshot showing the chrome.scripting API demo running in Chrome.">
## Features
This sample allows you to experiment with the following injection mechanisms:
- [Dynamic Declarations](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#dynamic-declarative), where a content script is registered at runtime.
- [Programmatic Injection](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#programmatic), where a script is programatically executed in a tab which is already open.
Learn more at https://developer.chrome.com/docs/extensions/mv3/content_scripts/.
## Implementation Notes
Programmatic injection is handled in the service worker. A tab is opened to a specific URL (https://example.com/#inject-programmatic). When the page finishes loading, a script is then run using `chrome.scripting.executeScript`.
When registering a dynamic content script, a tab is automatically opened if using the default matches URL. Otherwise, no tab is opened and the correct URL needs to be manually navigated to.

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
alert('Hello World!');

Просмотреть файл

@ -0,0 +1,76 @@
/* Copyright 2023 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
form {
display: inline-block;
border: 1px solid #dadce0;
min-width: 500px;
}
h1 {
background: #f6f9fe;
margin: 0;
padding: 15px;
font-size: 20px;
text-align: center;
}
.tabs {
border-top: 1px solid #dadce0;
border-bottom: 1px solid #dadce0;
display: flex;
}
.tabs label:first-child {
border-right: 1px solid grey;
}
.tabs label.selected {
background: white;
font-weight: bold;
}
.tabs label {
display: block;
background: #f6f9fe;
text-align: center;
width: 100%;
padding: 7px 0;
margin: 0;
}
.tabs input {
display: none;
}
label {
margin: 20px;
display: flex;
justify-content: space-between;
}
.hint {
margin: 0 20px;
background: #f6f6f6;
padding: 7px 12px;
}
.buttons {
display: flex;
margin: 20px;
}
.buttons button {
margin-right: 15px;
}

Просмотреть файл

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scripting Demo</title>
<link rel="stylesheet" href="index.css">
<script defer src="index.js"></script>
</head>
<body>
<form>
<h1>Scripting Demo</h1>
<div class="tabs">
<label>
Dynamic
<input type="radio" name="type" value="dynamic" checked></input>
</label>
<label class="selected">
Programmatic
<input type="radio" name="type" value="programmatic"></input>
</label>
</div>
<label>
<span>Persist across sessions</span>
<select name="persist" value="no">
<option value="no">No</option>
<option value="yes">Yes</option>
</select>
</label>
<label>
<span>Run at</span>
<select name="run-at" value="document_idle">
<option value="document_start">document_start</option>
<option value="document_end">document_end</option>
<option value="document_idle">document_idle</option>
</select>
</label>
<label>
<span>World</span>
<select name="world" value="ISOLATED">
<option value="ISOLATED">ISOLATED</option>
<option value="MAIN">MAIN</option>
</select>
</label>
<label>
<span>All Frames</span>
<select name="all-frames" value="no">
<option value="no">No</option>
<option value="yes">Yes</option>
</select>
</label>
<label>
<span>Matches</span>
<input type="text" name="matches" value='https://example.com/*'>
</label>
<p class="hint">Note: Make sure you request the required <code>host_permissions</code> in the manifest.</p>
<div class="buttons programmatic-buttons">
<button type="button" id="inject-programmatic">Execute Script</button>
</div>
<div class="buttons dynamic-buttons">
<button type="button" id="register-dynamic" disabled>Register Script</button>
<button type="button" id="unregister-dynamic" disabled>Unregister Script</button>
</div>
</form>
</body>
</html>

Просмотреть файл

@ -0,0 +1,137 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const DYNAMIC_SCRIPT_ID = 'dynamic-script';
async function isDynamicContentScriptRegistered() {
const scripts = await chrome.scripting.getRegisteredContentScripts();
return scripts.some((s) => s.id === DYNAMIC_SCRIPT_ID);
}
document
.querySelector('#inject-programmatic')
.addEventListener('click', async () => {
// Unregister the dynamic content script to avoid multiple injections.
const dynamicContentScriptRegistered =
await isDynamicContentScriptRegistered();
if (dynamicContentScriptRegistered) {
await chrome.scripting.unregisterContentScripts({
ids: [DYNAMIC_SCRIPT_ID]
});
}
// Now, execute the script. We handle this in the service worker so we can
// wait for the tab to open and **then** inject our script.
const world = document.querySelector("[name='world']").value;
chrome.runtime.sendMessage({
name: 'inject-programmatic',
options: { world }
});
});
document
.querySelector('#register-dynamic')
.addEventListener('click', async () => {
const persistAcrossSessions =
document.querySelector("[name='persist']").value === 'yes';
const matches = document.querySelector("[name='matches']").value;
const runAt = document.querySelector("[name='run-at']").value;
const allFrames =
document.querySelector("[name='persist']").value === 'yes';
const world = document.querySelector("[name='world']").value;
await chrome.scripting.registerContentScripts([
{
id: DYNAMIC_SCRIPT_ID,
js: ['content-script.js'],
persistAcrossSessions,
matches: [matches],
runAt,
allFrames,
world
}
]);
// Only open the page by default if the `matches` field hasn't been changed.
if (matches === 'https://example.com/*') {
await chrome.tabs.create({ url: 'https://example.com' });
}
updateUI();
});
document
.querySelector('#unregister-dynamic')
.addEventListener('click', async () => {
await chrome.scripting.unregisterContentScripts({
ids: [DYNAMIC_SCRIPT_ID]
});
updateUI();
});
const PROGRAMMATIC_TAB_SELECTOR = "[name='type'][value='programmatic']";
const DYNAMIC_TAB_SELECTOR = "[name='type'][value='dynamic']";
function updateUI() {
const type = document.querySelector(PROGRAMMATIC_TAB_SELECTOR).checked
? 'programmatic'
: 'dynamic';
// Update selected tab.
document.querySelector(PROGRAMMATIC_TAB_SELECTOR).parentElement.className =
type === 'programmatic' ? 'selected' : '';
document.querySelector(DYNAMIC_TAB_SELECTOR).parentElement.className =
type === 'dynamic' ? 'selected' : '';
// Only show some fields for dynamic scripts.
document.querySelector("[name='run-at']").parentElement.style.display =
type === 'dynamic' ? '' : 'none';
document.querySelector("[name='persist']").parentElement.style.display =
type === 'dynamic' ? '' : 'none';
document.querySelector("[name='all-frames']").parentElement.style.display =
type === 'dynamic' ? '' : 'none';
document.querySelector("[name='matches']").parentElement.style.display =
type === 'dynamic' ? '' : 'none';
document.querySelector('.hint').style.display =
type === 'dynamic' ? '' : 'none';
// Update visible buttons.
document.querySelector('.programmatic-buttons').style.display =
type === 'programmatic' ? 'flex' : 'none';
document.querySelector('.dynamic-buttons').style.display =
type === 'dynamic' ? 'flex' : 'none';
// Decide if the register or unregister button is visible for dynamic scripts.
isDynamicContentScriptRegistered().then((dynamicContentScriptRegistered) => {
document
.querySelector('#register-dynamic')
.toggleAttribute('disabled', dynamicContentScriptRegistered);
document
.querySelector('#unregister-dynamic')
.toggleAttribute('disabled', !dynamicContentScriptRegistered);
});
}
updateUI();
document
.querySelector(PROGRAMMATIC_TAB_SELECTOR)
.addEventListener('change', (e) => {
e.target.checked && updateUI();
});
document.querySelector(DYNAMIC_TAB_SELECTOR).addEventListener('change', (e) => {
e.target.checked && updateUI();
});

Просмотреть файл

@ -0,0 +1,17 @@
{
"name": "Scripting API Demo",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "sw.js"
},
"permissions": [
"scripting",
"webNavigation",
"storage"
],
"host_permissions": [
"https://example.com/*"
],
"action": {}
}

Двоичные данные
api-samples/scripting/screenshot.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 63 KiB

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
chrome.action.onClicked.addListener(openDemoTab);
function openDemoTab() {
chrome.tabs.create({ url: 'index.html' });
}
chrome.webNavigation.onDOMContentLoaded.addListener(async ({ tabId, url }) => {
if (url !== 'https://example.com/#inject-programmatic') return;
const { options } = await chrome.storage.local.get('options');
chrome.scripting.executeScript({
target: { tabId },
files: ['content-script.js'],
...options
});
});
chrome.runtime.onMessage.addListener(async ({ name, options }) => {
if (name === 'inject-programmatic') {
await chrome.storage.local.set({ options });
await chrome.tabs.create({
url: 'https://example.com/#inject-programmatic'
});
}
});