162 lines
4.3 KiB
Markdown
162 lines
4.3 KiB
Markdown
|
# WebExtension `browser` API Polyfill
|
||
|
|
||
|
This library allows extensions written for the Promise-based
|
||
|
WebExtension/BrowserExt API being standardized by the [W3 Browser
|
||
|
Extensions][w3-browserext] group to be used without modification in Google
|
||
|
Chrome.
|
||
|
|
||
|
[w3-browserext]: https://www.w3.org/community/browserext/
|
||
|
|
||
|
## Basic Setup
|
||
|
|
||
|
In order to use the polyfill, it must be loaded into any context where
|
||
|
`browser` APIs are accessed. The most common cases are background and
|
||
|
content scripts, which can be specified in `manifest.json`:
|
||
|
|
||
|
```javascript
|
||
|
{
|
||
|
// ...
|
||
|
|
||
|
"background": {
|
||
|
"scripts": [
|
||
|
"browser-polyfill.js",
|
||
|
"background.js"
|
||
|
]
|
||
|
},
|
||
|
|
||
|
"content_scripts": [{
|
||
|
// ...
|
||
|
"js": [
|
||
|
"browser-polyfill.js",
|
||
|
"content.js"
|
||
|
]
|
||
|
}]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
For HTML documents, such as `browserAction` popups, or tab pages, it must be
|
||
|
included more explicitly:
|
||
|
|
||
|
```html
|
||
|
<!DOCTYPE html>
|
||
|
<html>
|
||
|
<head>
|
||
|
<script type="application/javascript" src="browser-polyfill.js"></script>
|
||
|
<script type="application/javascript" src="popup.js"></script>
|
||
|
</head>
|
||
|
<!-- ... -->
|
||
|
</html>
|
||
|
```
|
||
|
|
||
|
And for dynamically-injected content scripts loaded by `tabs.executeScript`,
|
||
|
it must be injected by a separate `executeScript` call, unless it has
|
||
|
already been loaded via a `content_scripts` declaration in
|
||
|
`manifest.json`:
|
||
|
|
||
|
```javascript
|
||
|
browser.tabs.executeScript({file: "browser-polyfill.js"});
|
||
|
browser.tabs.executeScript({file: "content.js"}).then(result => {
|
||
|
// ...
|
||
|
});
|
||
|
```
|
||
|
|
||
|
|
||
|
## Using the Promise-based APIs
|
||
|
|
||
|
The Promise-based APIs in the `browser` namespace work, for the most part,
|
||
|
very similarly to the callback-based APIs in Chrome's `chrome` namespace.
|
||
|
The major differences are:
|
||
|
|
||
|
* Rather than receiving a callback argument, every async function returns a
|
||
|
`Promise` object, which resolves or rejects when the operation completes.
|
||
|
|
||
|
* Rather than checking the `chrome.runtime.lastError` property from every
|
||
|
callback, code which needs to explicitly deal with errors registers a
|
||
|
separate Promise rejection handler.
|
||
|
|
||
|
* Rather than receiving a `sendResponse` callback to send a response,
|
||
|
`onMessage` listeners simply return a Promise whose resolution value is
|
||
|
used as a reply.
|
||
|
|
||
|
* Rather than nesting callbacks when a sequence of operations depend on each
|
||
|
other, Promise chaining is generally used instead.
|
||
|
|
||
|
* For users of an ES7 transpiler, such as Babel, the resulting Promises are
|
||
|
generally used with `async` and `await`, rather than dealt with
|
||
|
directly.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
The following code will retrieve a list of URLs patterns from the `storage`
|
||
|
API, retrieve a list of tabs which match any of them, reload each of those
|
||
|
tabs, and notify the user that is has been done:
|
||
|
|
||
|
```javascript
|
||
|
browser.storage.get("urls").then(({urls}) => {
|
||
|
return browser.tabs.query({url: urls});
|
||
|
}).then(tabs => {
|
||
|
return Promise.all(
|
||
|
Array.from(tabs, tab => browser.tabs.reload(tab.id)));
|
||
|
);
|
||
|
}).then(() => {
|
||
|
return browser.notifications.create({
|
||
|
type: "basic",
|
||
|
iconUrl: "icon.png",
|
||
|
title: "Tabs reloaded",
|
||
|
message: "Your tabs have been reloaded",
|
||
|
});
|
||
|
}).catch(error => {
|
||
|
console.error(`An error occurred while reloading tabs: ${error.message}`);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Or, using an async function:
|
||
|
|
||
|
```javascript
|
||
|
async function reloadTabs() {
|
||
|
try {
|
||
|
let {urls} = await browser.storage.get("urls");
|
||
|
|
||
|
let tabs = await browser.tabs.query({url: urls});
|
||
|
|
||
|
await Promise.all(
|
||
|
Array.from(tabs, tab => browser.tabs.reload(tab.id)));
|
||
|
);
|
||
|
|
||
|
await browser.notifications.create({
|
||
|
type: "basic",
|
||
|
iconUrl: "icon.png",
|
||
|
title: "Tabs reloaded",
|
||
|
message: "Your tabs have been reloaded",
|
||
|
});
|
||
|
} catch (error) {
|
||
|
console.error(`An error occurred while reloading tabs: ${error.message}`);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
It's also possible to use Promises effectively using two-way messaging.
|
||
|
Communication between a background page and a tab content script, for example,
|
||
|
looks something like this from the background page side:
|
||
|
|
||
|
```javascript
|
||
|
browser.tabs.sendMessage("get-ids").then(results => {
|
||
|
processResults(results);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
And like this from the content script:
|
||
|
|
||
|
```javascript
|
||
|
browser.runtime.onMessage.addListener(msg => {
|
||
|
if (msg == "get-ids") {
|
||
|
return browser.storage.get("idPattern").then(({idPattern}) => {
|
||
|
return Array.from(document.querySelectorAll(idPattern),
|
||
|
elem => elem.textContent);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Or vice versa.
|