diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..a8e3912ef3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +_archive +third-party +node_modules diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..c89f8d860a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +/* eslint-env node */ +module.exports = { + extends: ['prettier', 'eslint:recommended'], + plugins: ['prettier'], + rules: { + 'prettier/prettier': ['error'], + 'no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_' + } + ] + }, + env: { + browser: true, + webextensions: true, + es2021: true, + jquery: true, + worker: true + }, + overrides: [], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + } +}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6777e87e6f..ab7875c22a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- ⚠️ If you have general Chrome Extensions questions, consider posting to the [Chromium Extensions Group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-extensions) or [Stack Overflow](https://stackoverflow.com/questions/tagged/google-chrome-extension). @@ -14,6 +13,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior, or file the issue is found in: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' diff --git a/.gitignore b/.gitignore index 61b4cee870..d85f45326e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *~ *.DS_store - +node_modules # Temporary directory for debugging extension samples _debug diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..a16d8b1d55 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..a8e3912ef3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +_archive +third-party +node_modules diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..6b7043e964 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "bracketSpacing": true, + "arrowParens": "always" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a653458f3..385c575bfc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,48 @@ -# Contributing to this Repository +# How to Contribute -Thank you for your interest in contributing! +We'd love to accept your patches and contributions to this project. -Send us your patches early and often and in whatever shape or form. +## Before you begin -## Legal +### Sign our Contributor License Agreement -Unfortunately there are some legal hurdles. Sorry about that. +Contributions to this project must be accompanied by a +[Contributor License Agreement](https://cla.developers.google.com/about) (CLA). +You (or your employer) retain the copyright to your contribution; this simply +gives us permission to use and redistribute your contributions as part of the +project. -This repository is a Google open source project, and so we require contributors to sign Google's open source Contributor License Agreement. -It's easy to do, just click here to sign as an [individual](https://developers.google.com/open-source/cla/individual) or [corporation](https://developers.google.com/open-source/cla/corporate). -Individuals can sign electronically in seconds (see the bottom of the page); corporations will need to email a PDF, or mail. +If you or your current employer have already signed the Google CLA (even if it +was for a different project), you probably don't need to do it again. -We cannot accept PRs or patches larger than fixing typos and the like without a signed CLA. +Visit to see your current agreements or to +sign a new one. -If your Github account doesn't show the name you used to sign, please mention your name in your PR. +### Review our Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). + +## Contribution process + +### Code Reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +### Setting up your Environment + +If you want to contribute to this repository, you need to first [create your own fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo). +After forking chrome-extensions-samples to your own Github account, run the following steps to get started: + +```sh +# clone your fork to your local machine +git clone https://github.com/your-fork/chrome-extensions-samples.git + +cd chrome-extensions-samples + +# install dependencies +npm install +``` diff --git a/README.md b/README.md index 5e641c0480..44a9626d13 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,16 @@ Note that Chrome Apps are deprecated. Learn more [on the Chromium blog](https:// For more information on extensions, see [Chrome Developers](https://developer.chrome.com). -**Note: Samples for Manifest V3 are still being prepared. In the mean time, consider referring to [_archive/mv2/](_archive/mv2/).** +**Note: Samples for Manifest V3 are still being prepared. In the mean time, consider referring to [\_archive/mv2/](_archive/mv2/).** ## Samples The directory structure is as follows: -* [api-samples/](api-samples/) - extensions focused on a single API package -* [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages -* [_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below) -* [_archive/mv2/](_archive/mv2/) - resources for manifest version 2 +- [api-samples/](api-samples/) - extensions focused on a single API package +- [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages +- [\_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below) +- [\_archive/mv2/](_archive/mv2/) - resources for manifest version 2 To experiment with these samples, please clone this repo and use 'Load Unpacked Extension'. Read more on [Development Basics](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked). @@ -119,3 +119,11 @@ Read more on [Development Basics](https://developer.chrome.com/docs/extensions/m + +## Contributing + +Please see [the CONTRIBUTING file](/CONTRIBUTING.md) for information on contributing to the `chrome-extensions-samples` project. + +## License + +`chrome-extensions-samples` are authored by Google and are licensed under the [Apache License, Version 2.0](/LICENSE). diff --git a/api-samples/action/demo/index.css b/api-samples/action/demo/index.css index 1617b14b38..212bfec5ea 100644 --- a/api-samples/action/demo/index.css +++ b/api-samples/action/demo/index.css @@ -4,13 +4,13 @@ p { .flex { display: flex; - gap: .25em; - margin: .5em 0; + gap: 0.25em; + margin: 0.5em 0; align-items: flex-end; } .spaced { - margin: .5em 0; + margin: 0.5em 0; } .full-width { diff --git a/api-samples/action/demo/index.html b/api-samples/action/demo/index.html index 7e4c06e17b..534c2f7d70 100644 --- a/api-samples/action/demo/index.html +++ b/api-samples/action/demo/index.html @@ -1,167 +1,213 @@ - - - - - Document - - - - - -
-
-

Action API Demo

-

Before experimenting with these APIs, we recommend you pin the extension's action button to your - toolbar in order to make it easier to see the changes.

- -
- -
-

enable / disable

- -

Clicking the below toggle enabled state button will enable or disable the extensions' - action button in Chrome's toolbar and extensions menu.

- -

When disabled, clicking the action will not open a popup or trigger action.onClicked - events.

- - - -
-
- -
Action enabled
-
-
- -
Action disabled
-
-
-
- - - - - -
-

Badge Text

- -

The action's badge text is a text overlay with a solid background color. This provides a - passive UI surface to share information with the user. It is most commonly used to show a - notification count or number of actions taken on the current page.

- -
- -
- -
- - -
- -
- -
+ + + + + Document + + + + + +
+
+

Action API Demo

+

+ Before experimenting with these APIs, we recommend you pin the + extension's action button to your toolbar in order to make it easier + to see the changes. +

+ +
+ +
+

enable / disable

+ +

+ Clicking the below toggle enabled state button will enable or + disable the extensions' action button in Chrome's toolbar and + extensions menu. +

+ +

+ When disabled, clicking the action will not open a popup or trigger + action.onClicked + events. +

+ + + +
+
+ +
Action enabled
+
+
+ +
Action disabled
+
+
+
+ + - - - -
-

Icon

- -

The action.setIcon - method allows you to change the action button's icon by either providing the path of an image - or the raw ImageData.

- - - -
- - - -
-

Hover Text

- -

The action's title is visible when mousing over the extension's action button.

- -

This value can be read and changed at runtime using the action.getTitle - and action.setTitle - methods, respectively.

- -
-
-
- - -
- -
-
- -
Default appearance
-
-
- -
Title appears on hover
-
-
-
- +
+ +
+ +

+ Register a handler to change how the action button behaves. Once + changed, clicking the action will open your new favorite website. +

+ + +
+ + + +
+

Badge Text

+ +

+ The action's badge text is a text overlay with a solid background + color. This provides a passive UI surface to share information with + the user. It is most commonly used to show a notification count or + number of actions taken on the current page. +

+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +
+
+ + + +
+

Icon

+ +

+ The + action.setIcon + method allows you to change the action button's icon by either + providing the path of an image or the raw + ImageData. +

+ + + +
+ + + +
+

Hover Text

+ +

+ The action's title is visible when mousing over the extension's action + button. +

+ +

+ This value can be read and changed at runtime using the + action.getTitle + and + action.setTitle + methods, respectively. +

+ +
+ +
+ +
+ + +
+ +
+
+ +
Default appearance
+
+
+ +
Title appears on hover
+
+
+
+
+ diff --git a/api-samples/action/demo/index.js b/api-samples/action/demo/index.js index 5257af12cf..a8c401d991 100644 --- a/api-samples/action/demo/index.js +++ b/api-samples/action/demo/index.js @@ -25,39 +25,43 @@ function debounce(timeout, callback) { // have to track it ourselves. // Relevant feature request: https://bugs.chromium.org/p/chromium/issues/detail?id=1189295 let actionEnabled = true; -let showToggleState = document.getElementById('show-toggle-state'); -document.getElementById('toggle-state-button').addEventListener('click', (_event) => { - if (actionEnabled) { - chrome.action.disable(); - } else { - chrome.action.enable(); - } - actionEnabled = !actionEnabled; -}); - -document.getElementById('popup-options').addEventListener('change', async (event) => { - let popup = event.target.value; - await chrome.action.setPopup({ popup }); - - // Show the updated popup path - await getCurrentPopup(); -}); +const showToggleState = document.getElementById('show-toggle-state'); +document + .getElementById('toggle-state-button') + .addEventListener('click', (_event) => { + if (actionEnabled) { + chrome.action.disable(); + } else { + chrome.action.enable(); + } + actionEnabled = !actionEnabled; + }); + +document + .getElementById('popup-options') + .addEventListener('change', async (event) => { + const popup = event.target.value; + await chrome.action.setPopup({ popup }); + + // Show the updated popup path + await getCurrentPopup(); + }); async function getCurrentPopup() { - let popup = await chrome.action.getPopup({}); + const popup = await chrome.action.getPopup({}); document.getElementById('current-popup-value').value = popup; return popup; -}; +} async function showCurrentPage() { - let popup = await getCurrentPopup(); + const popup = await getCurrentPopup(); let pathname = ''; if (popup) { pathname = new URL(popup).pathname; } - let options = document.getElementById('popup-options'); - let option = options.querySelector(`option[value="${pathname}"]`); + const options = document.getElementById('popup-options'); + const option = options.querySelector(`option[value="${pathname}"]`); option.selected = true; } @@ -75,129 +79,139 @@ chrome.action.onClicked.addListener((tab) => { chrome.tabs.create({ url: 'https://html5zombo.com/' }); }); -document.getElementById('onclicked-button').addEventListener('click', async () => { - // Our listener will only receive the action's click event after clear out the popup URL - await chrome.action.setPopup({ popup: '' }); - await showCurrentPage(); -}); - -document.getElementById('onclicked-reset-button').addEventListener('click', async () => { - await chrome.action.setPopup({ popup: 'popups/popup.html' }); - await showCurrentPage(); -}); +document + .getElementById('onclicked-button') + .addEventListener('click', async () => { + // Our listener will only receive the action's click event after clear out the popup URL + await chrome.action.setPopup({ popup: '' }); + await showCurrentPage(); + }); + +document + .getElementById('onclicked-reset-button') + .addEventListener('click', async () => { + await chrome.action.setPopup({ popup: 'popups/popup.html' }); + await showCurrentPage(); + }); // ---------- // badge text // ---------- async function showBadgeText() { - let text = await chrome.action.getBadgeText({}); + const text = await chrome.action.getBadgeText({}); document.getElementById('current-badge-text').value = text; } // Populate badge text inputs on on page load showBadgeText(); -document.getElementById('badge-text-input').addEventListener('input', async (event) => { - let text = event.target.value; - await chrome.action.setBadgeText({ text }); +document + .getElementById('badge-text-input') + .addEventListener('input', async (event) => { + const text = event.target.value; + await chrome.action.setBadgeText({ text }); - showBadgeText(); -}); + showBadgeText(); + }); -document.getElementById('clear-badge-button').addEventListener('click', async () => { - await chrome.action.setBadgeText({ text: '' }); +document + .getElementById('clear-badge-button') + .addEventListener('click', async () => { + await chrome.action.setBadgeText({ text: '' }); - showBadgeText(); -}); + showBadgeText(); + }); // ---------------------- // badge background color // ---------------------- async function showBadgeColor() { - let color = await chrome.action.getBadgeBackgroundColor({}); - document.getElementById('current-badge-bg-color').value = JSON.stringify(color, null, 0); + const color = await chrome.action.getBadgeBackgroundColor({}); + document.getElementById('current-badge-bg-color').value = JSON.stringify( + color, + null, + 0 + ); } // Populate badge background color inputs on on page load showBadgeColor(); -document.getElementById('set-badge-background-color-button').addEventListener('click', async () => { - // To show off this method, we must first make sure the badge has text - let currentText = await chrome.action.getBadgeText({}); - if (!currentText) { - chrome.action.setBadgeText({ text: 'hi :)' }); - showBadgeText(); - } - - // Next, generate a random RGBA color - let color = [0, 0, 0].map(() => Math.floor(Math.random() * 255)); - - // Use the default background color ~10% of the time. - // - // NOTE: Alpha color cannot be set due to crbug.com/1184905. At the time of writing (Chrome 89), - // an alpha value of 0 sets the default color while a value of 1-255 will make the RGB color - // fully opaque. - if (Math.random() < 0.1) { - color.push(0); - } else { - color.push(255); - } - - chrome.action.setBadgeBackgroundColor({ color }); - showBadgeColor(); -}); - -document.getElementById('reset-badge-background-color-button').addEventListener('click', async () => { - chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] }); - showBadgeColor(); -}); +document + .getElementById('set-badge-background-color-button') + .addEventListener('click', async () => { + // To show off this method, we must first make sure the badge has text + let currentText = await chrome.action.getBadgeText({}); + if (!currentText) { + chrome.action.setBadgeText({ text: 'hi :)' }); + showBadgeText(); + } + + // Next, generate a random RGBA color + const color = [0, 0, 0].map(() => Math.floor(Math.random() * 255)); + + // Use the default background color ~10% of the time. + // + // NOTE: Alpha color cannot be set due to crbug.com/1184905. At the time of writing (Chrome 89), + // an alpha value of 0 sets the default color while a value of 1-255 will make the RGB color + // fully opaque. + if (Math.random() < 0.1) { + color.push(0); + } else { + color.push(255); + } + + chrome.action.setBadgeBackgroundColor({ color }); + showBadgeColor(); + }); + +document + .getElementById('reset-badge-background-color-button') + .addEventListener('click', async () => { + chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] }); + showBadgeColor(); + }); // ----------- // action icon // ----------- -const EMOJI = [ - 'confetti', - 'suit', - 'bow', - 'dog', - 'skull', - 'yoyo', - 'cat', -]; +const EMOJI = ['confetti', 'suit', 'bow', 'dog', 'skull', 'yoyo', 'cat']; let lastIconIndex = 0; -document.getElementById('set-icon-button').addEventListener('click', async () => { - // Clear out the badge text in order to make the icon change easier to see - chrome.action.setBadgeText({ text: '' }); - - // Randomly pick a new icon - let index = lastIconIndex; - index = Math.floor(Math.random() * (EMOJI.length)); - if (index === lastIconIndex) { - // Dupe detected! Increment the index & modulo to make sure we don't go out of bounds - index = (index + 1) % EMOJI.length; - } - let emojiFile = `images/emoji-${EMOJI[index]}.png`; - lastIconIndex = index; - - // There are easier ways for a page to extract an image's imageData, but the approach used here - // works in both extension pages and service workers. - let response = await fetch(chrome.runtime.getURL(emojiFile)); - let blob = await response.blob(); - let imageBitmap = await createImageBitmap(blob); - let osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height); - let ctx = osc.getContext('2d'); - ctx.drawImage(imageBitmap, 0, 0); - let imageData = ctx.getImageData(0, 0, osc.width, osc.height); - - chrome.action.setIcon({ imageData }); -}); +document + .getElementById('set-icon-button') + .addEventListener('click', async () => { + // Clear out the badge text in order to make the icon change easier to see + chrome.action.setBadgeText({ text: '' }); + + // Randomly pick a new icon + let index = lastIconIndex; + index = Math.floor(Math.random() * EMOJI.length); + if (index === lastIconIndex) { + // Dupe detected! Increment the index & modulo to make sure we don't go out of bounds + index = (index + 1) % EMOJI.length; + } + const emojiFile = `images/emoji-${EMOJI[index]}.png`; + lastIconIndex = index; + + // There are easier ways for a page to extract an image's imageData, but the approach used here + // works in both extension pages and service workers. + const response = await fetch(chrome.runtime.getURL(emojiFile)); + const blob = await response.blob(); + const imageBitmap = await createImageBitmap(blob); + const osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height); + let ctx = osc.getContext('2d'); + ctx.drawImage(imageBitmap, 0, 0); + const imageData = ctx.getImageData(0, 0, osc.width, osc.height); + + chrome.action.setIcon({ imageData }); + }); document.getElementById('reset-icon-button').addEventListener('click', () => { - let manifest = chrome.runtime.getManifest(); + const manifest = chrome.runtime.getManifest(); chrome.action.setIcon({ path: manifest.action.default_icon }); }); @@ -205,23 +219,28 @@ document.getElementById('reset-icon-button').addEventListener('click', () => { // get/set title // ------------- -let titleInput = document.getElementById('title-input'); -let titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100); -titleInput.addEventListener('input', debounce(200, async (event) => { - let title = event.target.value; - chrome.action.setTitle({ title }); +const titleInput = document.getElementById('title-input'); +const titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100); +titleInput.addEventListener( + 'input', + debounce(200, async (event) => { + const title = event.target.value; + chrome.action.setTitle({ title }); - showActionTitle(); -})); + showActionTitle(); + }) +); -document.getElementById('reset-title-button').addEventListener('click', async (event) => { - let manifest = chrome.runtime.getManifest(); - let title = manifest.action.default_title; +document + .getElementById('reset-title-button') + .addEventListener('click', async (event) => { + const manifest = chrome.runtime.getManifest(); + let title = manifest.action.default_title; - chrome.action.setTitle({ title }); + chrome.action.setTitle({ title }); - showActionTitle(); -}); + showActionTitle(); + }); async function showActionTitle() { let title = await chrome.action.getTitle({}); @@ -229,7 +248,7 @@ async function showActionTitle() { // If empty, the title falls back to the name of the extension if (title === '') { // … which we can get from the extension's manifest - let manifest = chrome.runtime.getManifest(); + const manifest = chrome.runtime.getManifest(); title = manifest.name; } diff --git a/api-samples/action/popups/a.html b/api-samples/action/popups/a.html index cbd03b756e..805ea507ed 100644 --- a/api-samples/action/popups/a.html +++ b/api-samples/action/popups/a.html @@ -1,32 +1,32 @@ - - - - - Document - - - -

Action API Demo

-
- A -
- + + + + + Document + + + +

Action API Demo

+
+ A +
+ diff --git a/api-samples/action/popups/b.html b/api-samples/action/popups/b.html index eda5b65ff3..e7a880f281 100644 --- a/api-samples/action/popups/b.html +++ b/api-samples/action/popups/b.html @@ -1,32 +1,32 @@ - - - - - Document - - - -

Action API Demo

-
- B -
- + + + + + Document + + + +

Action API Demo

+
+ B +
+ diff --git a/api-samples/action/popups/popup.html b/api-samples/action/popups/popup.html index 79727541fa..308a0dd9e1 100644 --- a/api-samples/action/popups/popup.html +++ b/api-samples/action/popups/popup.html @@ -1,32 +1,32 @@ - - - - - Document - - - -

Action API Demo

-
- Hello, world! -
- + + + + + Document + + + +

Action API Demo

+
+ Hello, world! +
+ diff --git a/api-samples/alarms/background.js b/api-samples/alarms/background.js index 83664ee95b..f74bce4823 100644 --- a/api-samples/alarms/background.js +++ b/api-samples/alarms/background.js @@ -6,14 +6,16 @@ // Initialize the demo on install chrome.runtime.onInstalled.addListener((reason) => { - if (reason !== chrome.runtime.OnInstalledReason.INSTALL) { return } + if (reason !== chrome.runtime.OnInstalledReason.INSTALL) { + return; + } openDemoTab(); // Create an alarm so we have something to look at in the demo chrome.alarms.create('demo-default-alarm', { delayInMinutes: 1, - periodInMinutes: 1, + periodInMinutes: 1 }); }); diff --git a/api-samples/alarms/index.css b/api-samples/alarms/index.css index e59c1db540..0c94fa773f 100644 --- a/api-samples/alarms/index.css +++ b/api-samples/alarms/index.css @@ -43,7 +43,7 @@ body { .alarm-display, .alarm-log { min-height: 2em; - padding: .5em; + padding: 0.5em; background-color: hsl(0, 0%, 95%); border: 1px solid hsl(0, 0%, 80%); border-radius: 4px; @@ -55,7 +55,7 @@ body { } .alarm-log > * { - padding: .5em; + padding: 0.5em; } .alarm-log > *:not(:first-child) { @@ -67,7 +67,7 @@ body { } .alarm-row { - padding: .5em; + padding: 0.5em; position: relative; border-top: 1px solid transparent; border-bottom: 1px solid transparent; @@ -75,7 +75,7 @@ body { .alarm-row:hover, .alarm-log > *:hover { - background: hsl(190, 20%, 90%) + background: hsl(190, 20%, 90%); } .alarm-row:not(:first-child) { @@ -92,6 +92,6 @@ body { .alarm-row__cancel-button { position: absolute; - top: .5em; - right: .5em; + top: 0.5em; + right: 0.5em; } diff --git a/api-samples/alarms/index.html b/api-samples/alarms/index.html index e72e7aac60..99a0fd045c 100644 --- a/api-samples/alarms/index.html +++ b/api-samples/alarms/index.html @@ -1,66 +1,75 @@ - - - - - Document - - - - -
-

Create Alarm

-
-
Name
-
- -
- -
Initial delay *
-
- - - -
- -
Repetition period *
-
- minutes -
Non-zero values create a repeating alarm that repeats every period. -
+ + + + + Document + + + + +
+

Create Alarm

+ +
Name
+
+ +
-
- * -
-
- Can be set to < 1 min in an unpacked extension, but not in a distributed CRX file. -
+
Initial delay *
+
+ - - -
+ + +
Repetition period *
+
+ + minutes
Non-zero values create a repeating alarm that repeats every + period. +
-
-
-

Current Alarms -
- - +
*
+
+ Can be set to < 1 min in an unpacked extension, but not in a + distributed CRX file.
-

-

+        
+      
     
-
-

Alarm log

-

+    
+
+

+ Current Alarms +
+ + +
+

+ +

+      
+ +
+

Alarm log

+

+      
-
- + diff --git a/api-samples/alarms/index.js b/api-samples/alarms/index.js index 9a2576c965..91afb869ad 100644 --- a/api-samples/alarms/index.js +++ b/api-samples/alarms/index.js @@ -13,26 +13,26 @@ const pad = (val, len = 2) => val.toString().padStart(len, '0'); // DOM event bindings -//// Alarm display buttons +// Alarm display buttons clearButton.addEventListener('click', () => manager.cancelAllAlarms()); refreshButton.addEventListener('click', () => manager.refreshDisplay()); -//// New alarm form +// New alarm form form.addEventListener('submit', (event) => { event.preventDefault(); - let formData = new FormData(form); - let data = Object.fromEntries(formData); + const formData = new FormData(form); + const data = Object.fromEntries(formData); // Extract form values - let name = data['alarm-name']; - let delay = Number.parseFloat(data['time-value']); - let delayFormat = data['time-format']; - let period = Number.parseFloat(data['period']); + const name = data['alarm-name']; + const delay = Number.parseFloat(data['time-value']); + const delayFormat = data['time-format']; + const period = Number.parseFloat(data['period']); // Prepare alarm info for creation call - let alarmInfo = {}; + const alarmInfo = {}; if (delayFormat === 'ms') { // Specified in milliseconds, use `when` property @@ -62,15 +62,15 @@ class AlarmManager { } logMessage(message) { - let date = new Date(); - let pad = (val, len = 2) => val.toString().padStart(len, '0'); - let h = pad(date.getHours()); - let m = pad(date.getMinutes()); - let s = pad(date.getSeconds()); - let ms = pad(date.getMilliseconds(), 3); - let time = `${h}:${m}:${s}.${ms}`; - - let logLine = document.createElement('div'); + const date = new Date(); + const pad = (val, len = 2) => val.toString().padStart(len, '0'); + const h = pad(date.getHours()); + const m = pad(date.getMinutes()); + const s = pad(date.getSeconds()); + const ms = pad(date.getMilliseconds(), 3); + const time = `${h}:${m}:${s}.${ms}`; + + const logLine = document.createElement('div'); logLine.textContent = `[${time}] ${message}`; // Log events in reverse chronological order @@ -78,20 +78,20 @@ class AlarmManager { } handleAlarm = async (alarm) => { - let json = JSON.stringify(alarm); + const json = JSON.stringify(alarm); this.logMessage(`Alarm "${alarm.name}" fired\n${json}}`); await this.refreshDisplay(); - } + }; handleCancelAlarm = async (event) => { if (!event.target.classList.contains('alarm-row__cancel-button')) { return; } - let name = event.target.parentElement.dataset.name; + const name = event.target.parentElement.dataset.name; await this.cancelAlarm(name); await this.refreshDisplay(); - } + }; async cancelAlarm(name) { // TODO: Remove custom promise wrapper once the Alarms API supports promises @@ -111,18 +111,18 @@ class AlarmManager { // Thin wrapper around alarms.create to log creation event createAlarm(name, alarmInfo) { chrome.alarms.create(name, alarmInfo); - let json = JSON.stringify(alarmInfo, null, 2).replace(/\s+/g, ' '); + const json = JSON.stringify(alarmInfo, null, 2).replace(/\s+/g, ' '); this.logMessage(`Created "${name}"\n${json}`); this.refreshDisplay(); } renderAlarm(alarm, isLast) { - let alarmEl = document.createElement('div'); + const alarmEl = document.createElement('div'); alarmEl.classList.add('alarm-row'); alarmEl.dataset.name = alarm.name; alarmEl.textContent = JSON.stringify(alarm, 0, 2) + (isLast ? '' : ','); - let cancelButton = document.createElement('button'); + const cancelButton = document.createElement('button'); cancelButton.classList.add('alarm-row__cancel-button'); cancelButton.textContent = 'cancel'; alarmEl.appendChild(cancelButton); @@ -142,15 +142,15 @@ class AlarmManager { resolve(wasCleared); }); - }) + }); } async populateDisplay() { // TODO: Remove custom promise wrapper once the Alarms API supports promises return new Promise((resolve) => { chrome.alarms.getAll((alarms) => { - for (let [index, alarm] of alarms.entries()) { - let isLast = index === alarms.length - 1; + for (const [index, alarm] of alarms.entries()) { + const isLast = index === alarms.length - 1; this.renderAlarm(alarm, isLast); } resolve(); @@ -163,16 +163,15 @@ class AlarmManager { #refreshing = false; async refreshDisplay() { - if (this.#refreshing) { return } // refresh in progress, bail + if (this.#refreshing) { + return; + } // refresh in progress, bail - this.#refreshing = true; // acquire lock + this.#refreshing = true; // acquire lock try { - await Promise.all([ - this.clearDisplay(), - this.populateDisplay(), - ]); + await Promise.all([this.clearDisplay(), this.populateDisplay()]); } finally { - this.#refreshing = false; // release lock + this.#refreshing = false; // release lock } } @@ -181,5 +180,5 @@ class AlarmManager { } } -let manager = new AlarmManager(display, log); +const manager = new AlarmManager(display, log); manager.refreshDisplay(); diff --git a/api-samples/alarms/manifest.json b/api-samples/alarms/manifest.json index 54fd69cb55..9b3f5f3eeb 100644 --- a/api-samples/alarms/manifest.json +++ b/api-samples/alarms/manifest.json @@ -5,8 +5,6 @@ "background": { "service_worker": "bg-wrapper.js" }, - "permissions": [ - "alarms" - ], + "permissions": ["alarms"], "action": {} } diff --git a/api-samples/contextMenus/global_context_search/background.js b/api-samples/contextMenus/global_context_search/background.js index 4095e07800..f504b6ad43 100644 --- a/api-samples/contextMenus/global_context_search/background.js +++ b/api-samples/contextMenus/global_context_search/background.js @@ -3,56 +3,54 @@ // found in the LICENSE file. // When you specify "type": "module" in the manifest background, -// you can include the service worker as an ES Module, -import { tldLocales } from './locales.js' +// you can include the service worker as an ES Module, +import { tldLocales } from './locales.js'; // Add a listener to create the initial context menu items, // context menu items only need to be created at runtime.onInstalled chrome.runtime.onInstalled.addListener(async () => { - for (let [tld, locale] of Object.entries(tldLocales)) { + for (const [tld, locale] of Object.entries(tldLocales)) { chrome.contextMenus.create({ id: tld, title: locale, type: 'normal', - contexts: ['selection'], + contexts: ['selection'] }); } }); // Open a new search tab when the user clicks a context menu chrome.contextMenus.onClicked.addListener((item, tab) => { - const tld = item.menuItemId - let url = new URL(`https://google.${tld}/search`) - url.searchParams.set('q', item.selectionText) + const tld = item.menuItemId; + const url = new URL(`https://google.${tld}/search`); + url.searchParams.set('q', item.selectionText); chrome.tabs.create({ url: url.href, index: tab.index + 1 }); }); // Add or removes the locale from context menu // when the user checks or unchecks the locale in the popup chrome.storage.onChanged.addListener(({ enabledTlds }) => { - if (typeof enabledTlds === 'undefined') return + if (typeof enabledTlds === 'undefined') return; - let allTlds = Object.keys(tldLocales) - let currentTlds = new Set(enabledTlds.newValue); - let oldTlds = new Set(enabledTlds.oldValue ?? allTlds); - let changes = allTlds.map((tld) => ({ + const allTlds = Object.keys(tldLocales); + const currentTlds = new Set(enabledTlds.newValue); + const oldTlds = new Set(enabledTlds.oldValue ?? allTlds); + const changes = allTlds.map((tld) => ({ tld, added: currentTlds.has(tld) && !oldTlds.has(tld), removed: !currentTlds.has(tld) && oldTlds.has(tld) - })) + })); - for (let { tld, added, removed } of changes) { + for (const { tld, added, removed } of changes) { if (added) { chrome.contextMenus.create({ id: tld, title: tldLocales[tld], type: 'normal', - contexts: ['selection'], + contexts: ['selection'] }); - } - else if (removed) { + } else if (removed) { chrome.contextMenus.remove(tld); } } - }); diff --git a/api-samples/contextMenus/global_context_search/locales.js b/api-samples/contextMenus/global_context_search/locales.js index 559a836b23..1573ef6285 100644 --- a/api-samples/contextMenus/global_context_search/locales.js +++ b/api-samples/contextMenus/global_context_search/locales.js @@ -6,14 +6,14 @@ export const tldLocales = { 'com.au': 'Australia', 'com.br': 'Brazil', - 'ca': 'Canada', - 'cn': 'China', - 'fr': 'France', - 'it': 'Italy', + ca: 'Canada', + cn: 'China', + fr: 'France', + it: 'Italy', 'co.in': 'India', 'co.jp': 'Japan', 'com.ms': 'Mexico', - 'ru': 'Russia', + ru: 'Russia', 'co.za': 'South Africa', 'co.uk': 'United Kingdom' }; diff --git a/api-samples/contextMenus/global_context_search/manifest.json b/api-samples/contextMenus/global_context_search/manifest.json index 5088ca21df..69b64c558a 100644 --- a/api-samples/contextMenus/global_context_search/manifest.json +++ b/api-samples/contextMenus/global_context_search/manifest.json @@ -12,8 +12,8 @@ "default_popup": "popup.html" }, "icons": { - "16": "globalGoogle16.png", - "48": "globalGoogle48.png", - "128": "globalGoogle128.png" - } + "16": "globalGoogle16.png", + "48": "globalGoogle48.png", + "128": "globalGoogle128.png" + } } diff --git a/api-samples/contextMenus/global_context_search/popup.html b/api-samples/contextMenus/global_context_search/popup.html index d4fd62c169..333ed625f9 100644 --- a/api-samples/contextMenus/global_context_search/popup.html +++ b/api-samples/contextMenus/global_context_search/popup.html @@ -1,26 +1,24 @@ + + Global Context Search + + - input { - margin: 5px; - outline: none; - } - - - - -

Global Google Search

-

Countries

-
- - - - \ No newline at end of file + +

Global Google Search

+

Countries

+
+ + + diff --git a/api-samples/contextMenus/global_context_search/popup.js b/api-samples/contextMenus/global_context_search/popup.js index 244921f9ab..5ca1aa84f5 100644 --- a/api-samples/contextMenus/global_context_search/popup.js +++ b/api-samples/contextMenus/global_context_search/popup.js @@ -3,48 +3,47 @@ // found in the LICENSE file. // TLD: top level domain; the "com" in "google.com" -import { tldLocales } from './locales.js' +import { tldLocales } from './locales.js'; createForm().catch(console.error); async function createForm() { - let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds'); - let checked = new Set(enabledTlds) + const { enabledTlds = Object.keys(tldLocales) } = + await chrome.storage.sync.get('enabledTlds'); + const checked = new Set(enabledTlds); - let form = document.getElementById('form'); - for (let [tld, locale] of Object.entries(tldLocales)) { - let checkbox = document.createElement('input'); + const form = document.getElementById('form'); + for (const [tld, locale] of Object.entries(tldLocales)) { + const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = checked.has(tld); checkbox.name = tld; checkbox.addEventListener('click', (event) => { - handleCheckboxClick(event).catch(console.error) - }) - - let span = document.createElement('span'); + handleCheckboxClick(event).catch(console.error); + }); + + const span = document.createElement('span'); span.textContent = locale; - - let div = document.createElement('div'); + + const div = document.createElement('div'); div.appendChild(checkbox); div.appendChild(span); - + form.appendChild(div); } } async function handleCheckboxClick(event) { - let checkbox = event.target - let tld = checkbox.name - let enabled = checkbox.checked - - let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds'); - let tldSet = new Set(enabledTlds) - - if (enabled) tldSet.add(tld) - else tldSet.delete(tld) - - await chrome.storage.sync.set({ enabledTlds: [...tldSet] }) -} + const checkbox = event.target; + const tld = checkbox.name; + const enabled = checkbox.checked; + const { enabledTlds = Object.keys(tldLocales) } = + await chrome.storage.sync.get('enabledTlds'); + const tldSet = new Set(enabledTlds); + if (enabled) tldSet.add(tld); + else tldSet.delete(tld); + await chrome.storage.sync.set({ enabledTlds: [...tldSet] }); +} diff --git a/api-samples/cookies/cookie-clearer/manifest.json b/api-samples/cookies/cookie-clearer/manifest.json index 6a26d120fc..25a07d4f36 100644 --- a/api-samples/cookies/cookie-clearer/manifest.json +++ b/api-samples/cookies/cookie-clearer/manifest.json @@ -7,4 +7,4 @@ "action": { "default_popup": "popup.html" } -} \ No newline at end of file +} diff --git a/api-samples/cookies/cookie-clearer/popup.html b/api-samples/cookies/cookie-clearer/popup.html index 000b227348..dda5f37332 100644 --- a/api-samples/cookies/cookie-clearer/popup.html +++ b/api-samples/cookies/cookie-clearer/popup.html @@ -1,15 +1,15 @@ - + - -
- - -
- -
- - + +
+ + +
+ +
+ + diff --git a/api-samples/cookies/cookie-clearer/popup.js b/api-samples/cookies/cookie-clearer/popup.js index f73edccd9f..a3b578f2c4 100644 --- a/api-samples/cookies/cookie-clearer/popup.js +++ b/api-samples/cookies/cookie-clearer/popup.js @@ -1,7 +1,7 @@ -const form = document.getElementById("control-row"); -const go = document.getElementById("go"); -const input = document.getElementById("input"); -const message = document.getElementById("message"); +const form = document.getElementById('control-row'); +const go = document.getElementById('go'); +const input = document.getElementById('input'); +const message = document.getElementById('message'); // The async IIFE is necessary because Chrome <89 does not support top level await. (async function initPopupWindow() { @@ -11,13 +11,15 @@ const message = document.getElementById("message"); try { let url = new URL(tab.url); input.value = url.hostname; - } catch {} + } catch { + // ignore + } } input.focus(); })(); -form.addEventListener("submit", handleFormSubmit); +form.addEventListener('submit', handleFormSubmit); async function handleFormSubmit(event) { event.preventDefault(); @@ -26,7 +28,7 @@ async function handleFormSubmit(event) { let url = stringToUrl(input.value); if (!url) { - setMessage("Invalid URL"); + setMessage('Invalid URL'); return; } @@ -38,11 +40,15 @@ function stringToUrl(input) { // Start with treating the provided value as a URL try { return new URL(input); - } catch {} + } catch { + // ignore + } // If that fails, try assuming the provided input is an HTTP host try { - return new URL("http://" + input); - } catch {} + return new URL('http://' + input); + } catch { + // ignore + } // If that fails ¯\_(ツ)_/¯ return null; } @@ -53,7 +59,7 @@ async function deleteDomainCookies(domain) { const cookies = await chrome.cookies.getAll({ domain }); if (cookies.length === 0) { - return "No cookies found"; + return 'No cookies found'; } let pending = cookies.map(deleteCookie); @@ -76,7 +82,7 @@ function deleteCookie(cookie) { // To remove cookies set with a Secure attribute, we must provide the correct protocol in the // details object's `url` property. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure - const protocol = cookie.secure ? "https:" : "http:"; + const protocol = cookie.secure ? 'https:' : 'http:'; // Note that the final URL may not be valid. The domain value for a standard cookie is prefixed // with a period (invalid) while cookies that are set to `cookie.hostOnly == true` do not have @@ -87,7 +93,7 @@ function deleteCookie(cookie) { return chrome.cookies.remove({ url: cookieUrl, name: cookie.name, - storeId: cookie.storeId, + storeId: cookie.storeId }); } @@ -98,5 +104,5 @@ function setMessage(str) { function clearMessage() { message.hidden = true; - message.textContent = ""; + message.textContent = ''; } diff --git a/api-samples/default_command_override/background.js b/api-samples/default_command_override/background.js index 430a844677..ecf9de2325 100644 --- a/api-samples/default_command_override/background.js +++ b/api-samples/default_command_override/background.js @@ -4,13 +4,18 @@ chrome.commands.onCommand.addListener(async (command) => { const tabs = await chrome.tabs.query({ currentWindow: true }); // Sort tabs according to their index in the window. - tabs.sort((a, b) => { return a.index < b.index; }); - let activeIndex = tabs.findIndex((tab) => { return tab.active; }); - let lastTab = tabs.length - 1; + tabs.sort((a, b) => { + return a.index < b.index; + }); + const activeIndex = tabs.findIndex((tab) => { + return tab.active; + }); + const lastTab = tabs.length - 1; let newIndex = -1; - if (command === 'flip-tabs-forward') + if (command === 'flip-tabs-forward') { newIndex = activeIndex === 0 ? lastTab : activeIndex - 1; - else // 'flip-tabs-backwards' - newIndex = activeIndex === lastTab ? 0 : activeIndex + 1; + } + // 'flip-tabs-backwards' + else newIndex = activeIndex === lastTab ? 0 : activeIndex + 1; chrome.tabs.update(tabs[newIndex].id, { active: true, highlighted: true }); }); diff --git a/api-samples/default_command_override/manifest.json b/api-samples/default_command_override/manifest.json index 7e216b288c..58e13798b2 100644 --- a/api-samples/default_command_override/manifest.json +++ b/api-samples/default_command_override/manifest.json @@ -32,4 +32,4 @@ "48": "images/tabFlipper48.png", "128": "images/tabFlipper128.png" } -} \ No newline at end of file +} diff --git a/api-samples/favicon/popup.html b/api-samples/favicon/popup.html index 391d10faa4..30fb591e04 100644 --- a/api-samples/favicon/popup.html +++ b/api-samples/favicon/popup.html @@ -1,6 +1,6 @@ - + - + diff --git a/api-samples/favicon/popup.js b/api-samples/favicon/popup.js index ad1964ecab..6f6a136f6a 100644 --- a/api-samples/favicon/popup.js +++ b/api-samples/favicon/popup.js @@ -1,11 +1,11 @@ function faviconURL(u) { - const url = new URL(chrome.runtime.getURL("/_favicon/")); - url.searchParams.set("pageUrl", u); // this encodes the URL as well - url.searchParams.set("size", "32"); + const url = new URL(chrome.runtime.getURL('/_favicon/')); + url.searchParams.set('pageUrl', u); // this encodes the URL as well + url.searchParams.set('size', '32'); return url.toString(); } const img = document.createElement('img'); // chrome-extension://EXTENSION_ID/_favicon/?pageUrl=https%3A%2F%2Fwww.google.com&size=32 -img.src = faviconURL("https://www.google.com") +img.src = faviconURL('https://www.google.com'); document.body.appendChild(img); diff --git a/api-samples/omnibox/new-tab-search/background.js b/api-samples/omnibox/new-tab-search/background.js index 7cf7a4f4ad..4b0ca3c6f5 100644 --- a/api-samples/omnibox/new-tab-search/background.js +++ b/api-samples/omnibox/new-tab-search/background.js @@ -5,6 +5,6 @@ // This event is fired with the user accepts the input in the omnibox. chrome.omnibox.onInputEntered.addListener((text) => { // Encode user input for special characters , / ? : @ & = + $ # - var newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text); + const newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text); chrome.tabs.create({ url: newURL }); }); diff --git a/api-samples/omnibox/new-tab-search/manifest.json b/api-samples/omnibox/new-tab-search/manifest.json index 9eec01db91..b88b51d3ff 100644 --- a/api-samples/omnibox/new-tab-search/manifest.json +++ b/api-samples/omnibox/new-tab-search/manifest.json @@ -6,12 +6,12 @@ "background": { "service_worker": "background.js" }, - "omnibox": { "keyword" : "nt" }, + "omnibox": { "keyword": "nt" }, "action": { "default_icon": { "16": "newtab_search16.png", "32": "newtab_search32.png" - } + } }, "icons": { "16": "newtab_search16.png", diff --git a/api-samples/printing/printers.html b/api-samples/printing/printers.html index 3654aff246..ec15dfe7b4 100644 --- a/api-samples/printing/printers.html +++ b/api-samples/printing/printers.html @@ -3,11 +3,11 @@ * source code is governed by a BSD-style license that can be found in the * LICENSE file. --> - + Printers - + diff --git a/api-samples/printing/printers.js b/api-samples/printing/printers.js index 2462665797..7704aa9e9a 100644 --- a/api-samples/printing/printers.js +++ b/api-samples/printing/printers.js @@ -6,42 +6,46 @@ function onPrintButtonClicked(printerId, dpi) { var ticket = { version: '1.0', print: { - color: {type: 'STANDARD_MONOCHROME'}, - duplex: {type: 'NO_DUPLEX'}, - page_orientation: {type: 'LANDSCAPE'}, - copies: {copies: 1}, - dpi: {horizontal_dpi: dpi.horizontal_dpi, vertical_dpi: dpi.vertical_dpi}, + color: { type: 'STANDARD_MONOCHROME' }, + duplex: { type: 'NO_DUPLEX' }, + page_orientation: { type: 'LANDSCAPE' }, + copies: { copies: 1 }, + dpi: { + horizontal_dpi: dpi.horizontal_dpi, + vertical_dpi: dpi.vertical_dpi + }, media_size: { width_microns: 210000, height_microns: 297000, vendor_id: 'iso_a4_210x297mm' }, - collate: {collate: false} + collate: { collate: false } } }; fetch('test.pdf') - .then(response => response.arrayBuffer()) - .then(arrayBuffer => { - const request = { - job: { - printerId: printerId, - title: 'test job', - ticket: ticket, - contentType: 'application/pdf', - document: new Blob( - [new Uint8Array(arrayBuffer)], {type: 'application/pdf'}) - } - }; - chrome.printing.submitJob(request, (response) => { - if (response !== undefined) { - console.log(response.status); - } - if (chrome.runtime.lastError !== undefined) { - console.log(chrome.runtime.lastError.message); - } - }); + .then((response) => response.arrayBuffer()) + .then((arrayBuffer) => { + const request = { + job: { + printerId: printerId, + title: 'test job', + ticket: ticket, + contentType: 'application/pdf', + document: new Blob([new Uint8Array(arrayBuffer)], { + type: 'application/pdf' + }) + } + }; + chrome.printing.submitJob(request, (response) => { + if (response !== undefined) { + console.log(response.status); + } + if (chrome.runtime.lastError !== undefined) { + console.log(chrome.runtime.lastError.message); + } }); + }); } function createPrintButton(onClicked) { @@ -52,12 +56,12 @@ function createPrintButton(onClicked) { } function createPrintersTable() { - chrome.printing.getPrinters(function(printers) { + chrome.printing.getPrinters(function (printers) { const tbody = document.createElement('tbody'); for (let i = 0; i < printers.length; ++i) { const printer = printers[i]; - chrome.printing.getPrinterInfo(printer.id, function(response) { + chrome.printing.getPrinterInfo(printer.id, function (response) { const columnValues = [ printer.id, printer.name, @@ -67,7 +71,7 @@ function createPrintersTable() { printer.isDefault, printer.recentlyUsedRank, JSON.stringify(response.capabilities), - response.status, + response.status ]; let tr = document.createElement('tr'); @@ -79,10 +83,14 @@ function createPrintersTable() { } const printTd = document.createElement('td'); - printTd.appendChild(createPrintButton(function() { - onPrintButtonClicked( - printer.id, response.capabilities.printer.dpi.option[0]); - })); + printTd.appendChild( + createPrintButton(function () { + onPrintButtonClicked( + printer.id, + response.capabilities.printer.dpi.option[0] + ); + }) + ); tr.appendChild(printTd); tbody.appendChild(tr); @@ -94,6 +102,6 @@ function createPrintersTable() { }); } -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { createPrintersTable(); }); diff --git a/api-samples/web-accessible-resources/background.js b/api-samples/web-accessible-resources/background.js index 4e8fd42a11..ba04bec9d1 100644 --- a/api-samples/web-accessible-resources/background.js +++ b/api-samples/web-accessible-resources/background.js @@ -9,6 +9,6 @@ chrome.action.onClicked.addListener((tab) => { }); function showReadme(info, tab) { - let url = chrome.runtime.getURL("readme.html"); + const url = chrome.runtime.getURL('readme.html'); chrome.tabs.create({ url }); } diff --git a/api-samples/web-accessible-resources/content-script.js b/api-samples/web-accessible-resources/content-script.js index 3020990213..0d4baf323f 100644 --- a/api-samples/web-accessible-resources/content-script.js +++ b/api-samples/web-accessible-resources/content-script.js @@ -1,14 +1,14 @@ -let imageIds = ["test2", "test4"]; +const imageIds = ['test2', 'test4']; -let loadButton = document.createElement('button'); +const loadButton = document.createElement('button'); loadButton.innerText = 'Load images'; loadButton.addEventListener('click', handleLoadRequest); document.querySelector('body').append(loadButton); function handleLoadRequest() { - for (let id of imageIds) { - let element = document.getElementById(id); + for (const id of imageIds) { + const element = document.getElementById(id); element.src = chrome.runtime.getURL(`${id}.png`); } } diff --git a/api-samples/web-accessible-resources/manifest.json b/api-samples/web-accessible-resources/manifest.json index 60ff3221ee..904dd88b88 100644 --- a/api-samples/web-accessible-resources/manifest.json +++ b/api-samples/web-accessible-resources/manifest.json @@ -18,13 +18,14 @@ ], "web_accessible_resources": [ { - "resources": [ "test1.png", "test2.png" ], - "matches": [ "https://web-accessible-resources-1.glitch.me/*" ] - }, { - "resources": [ "test3.png", "test4.png" ], - "matches": [ "https://web-accessible-resources-2.glitch.me/*" ], + "resources": ["test1.png", "test2.png"], + "matches": ["https://web-accessible-resources-1.glitch.me/*"] + }, + { + "resources": ["test3.png", "test4.png"], + "matches": ["https://web-accessible-resources-2.glitch.me/*"], "use_dynamic_url": true } ], "key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCTnUK8jgYTxnQLdtE6QzkZgn3rZv0U1naCx4csdSDqYEBXgW2pR2m/uUIAU1HzAUfkDckqTezyIG1bPw8l5X8FyWfgMQANFgTPXGRNXTmDSqHcqvS7zvuEr0xF12oGLBKa7cdEsaQzdfDWsm5BlwFIPfPXUokaHEGvxPBjrXHQmx+Z4xAyhzNh+v5bFr63lsL0ysS8z4KVKc1G1lcUZnp7Oz9n0pZP9QW0Oei2KCumDqGpqVd249232a0E9TUeQ+lqAxiN4ybzBgUT5al7Yh1nIhGHxPyRnihtHmx+hxupCuhzXeaoKjWiADp+FEK/aPAzvP5ynLDQHelez/eGdF" -} \ No newline at end of file +} diff --git a/api-samples/web-accessible-resources/readme.css b/api-samples/web-accessible-resources/readme.css index ed11813c4e..3675e64525 100644 --- a/api-samples/web-accessible-resources/readme.css +++ b/api-samples/web-accessible-resources/readme.css @@ -3,46 +3,127 @@ License: none (public domain) */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; } body { - line-height: 1; + line-height: 1; } -ol, ul { - list-style: none; +ol, +ul { + list-style: none; } -blockquote, q { - quotes: none; +blockquote, +q { + quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; } table { - border-collapse: collapse; - border-spacing: 0; + border-collapse: collapse; + border-spacing: 0; } diff --git a/api-samples/web-accessible-resources/readme.html b/api-samples/web-accessible-resources/readme.html index b31a3aa339..e3771a1160 100644 --- a/api-samples/web-accessible-resources/readme.html +++ b/api-samples/web-accessible-resources/readme.html @@ -1,91 +1,123 @@ - - - - - Web Accessible Resources - Readme - + + +

Web Accessible Resources Demo

+

This demo shows off the core features of web accessible resources.

+

+ In this demo we have 4 images (test1.png, etc.) that we want to expose on + 2 different websites. Each website should only be able to load two + specific images, but both websites will attempt to access all 4 images. To + do this, we define a set of + "web_accessable_resources" + in our manifest.json. This object specifies + what assets should be accessible to which external resources. +

- } - th { - background: hsl(0,0%,90%); - padding: .25em .5em; - text-align: left; - } - td { - padding: .25em .5em; - border-top: 1px solid hsl(0,0%,50%); - } - - - -

Web Accessible Resources Demo

-

This demo shows off the core features of web accessible resources.

-

In this demo we have 4 images (test1.png, etc.) that we want to expose on 2 different websites. - Each website should only be able to load two specific images, but both websites will attempt to - access all 4 images. To do this, we define a set of "web_accessable_resources" - in our manifest.json. This object specifies what assets should be - accessible to which external resources.

+

+ The first image on each site is statically referenced by the site using a + URL in the following format: + chrome-extension://<extension-id>/<image-path>. The + second image on each site will only be injected into the page when you + click the "Load images" button for that page. This injection is performed + by using + chrome.runtime.getURL() + to build the image's URL at runtime. +

-

The first image on each site is statically referenced by the site using a URL in the following - format: chrome-extension://<extension-id>/<image-path>. The second image on - each site will only be injected into the page when you click the "Load images" button for that - page. This injection is performed by using chrome.runtime.getURL() - to build the image's URL at runtime.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileTarget domainInjection method
+ test1.png + web-accessible-resources-1.glitch.meStatically referenced
+ test2.png + web-accessible-resources-1.glitch.meDynamically injected
+ test3.png + web-accessible-resources-2.glitch.meStatically referenced
+ test4.png + web-accessible-resources-2.glitch.meDynamically injected
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileTarget domainInjection method
test1.pngweb-accessible-resources-1.glitch.meStatically referenced
test2.pngweb-accessible-resources-1.glitch.meDynamically injected
test3.pngweb-accessible-resources-2.glitch.meStatically referenced
test4.pngweb-accessible-resources-2.glitch.meDynamically injected
- -
-
https://web-accessible-resources-1.glitch.me/ can access images - test1.png and test2.png
- -
-
-
https://web-accessible-resources-2.glitch.me/ can access images - test3.png and test4.png
- -
- +
+
+ https://web-accessible-resources-1.glitch.me/ can access images + test1.png and + test2.png +
+ +
+
+
+ https://web-accessible-resources-2.glitch.me/ can access images + test3.png and + test4.png +
+ +
+ diff --git a/functional-samples/cookbook.offscreen-clipboard-write/background.js b/functional-samples/cookbook.offscreen-clipboard-write/background.js index c6a87f56d5..38d5f2cfa1 100644 --- a/functional-samples/cookbook.offscreen-clipboard-write/background.js +++ b/functional-samples/cookbook.offscreen-clipboard-write/background.js @@ -29,7 +29,7 @@ async function addToClipboard(value) { await chrome.offscreen.createDocument({ url: 'offscreen.html', reasons: [chrome.offscreen.Reason.CLIPBOARD], - justification: 'Write text to the clipboard.', + justification: 'Write text to the clipboard.' }); // Now that we have an offscreen document, we can dispatch the @@ -37,11 +37,11 @@ async function addToClipboard(value) { chrome.runtime.sendMessage({ type: 'copy-data-to-clipboard', target: 'offscreen-doc', - data: value, + data: value }); } -// Solution 2 – Once extension service workers can use the Clipboard API, +// Solution 2 – Once extension service workers can use the Clipboard API, // replace the offscreen document based implementation with something like this. async function addToClipboardV2(value) { navigator.clipboard.writeText(value); diff --git a/functional-samples/cookbook.offscreen-clipboard-write/manifest.json b/functional-samples/cookbook.offscreen-clipboard-write/manifest.json index 60673db0fd..4b56a5945a 100644 --- a/functional-samples/cookbook.offscreen-clipboard-write/manifest.json +++ b/functional-samples/cookbook.offscreen-clipboard-write/manifest.json @@ -6,8 +6,5 @@ "service_worker": "background.js" }, "action": {}, - "permissions": [ - "offscreen", - "clipboardWrite" - ] + "permissions": ["offscreen", "clipboardWrite"] } diff --git a/functional-samples/cookbook.offscreen-clipboard-write/offscreen.html b/functional-samples/cookbook.offscreen-clipboard-write/offscreen.html index 92e097bee1..5160646740 100644 --- a/functional-samples/cookbook.offscreen-clipboard-write/offscreen.html +++ b/functional-samples/cookbook.offscreen-clipboard-write/offscreen.html @@ -1,3 +1,3 @@ - + diff --git a/functional-samples/cookbook.offscreen-clipboard-write/offscreen.js b/functional-samples/cookbook.offscreen-clipboard-write/offscreen.js index 674a494b35..8fb52b5f63 100644 --- a/functional-samples/cookbook.offscreen-clipboard-write/offscreen.js +++ b/functional-samples/cookbook.offscreen-clipboard-write/offscreen.js @@ -41,11 +41,10 @@ async function handleMessages(message) { } } - // We use a