webextension-polyfill/README.md

189 lines
5.0 KiB
Markdown
Raw Permalink Normal View History

2016-10-07 01:20:37 +00:00
# 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/
## Building
To build, assuming you're already installed [node >= 6](https://nodejs.org) and
[npm](https://www.npmjs.com/), simply run:
```sh
npm install
npm run build
npm run test
```
This will install all the npm dependencies and build both non-minified and minified versions
of the final library, and output them to `dist/browser-polyfill.js` and `dist/browser-polyfill.min.js`,
respectively, and finally executes the unit tests on the generated dist files.
2016-10-07 01:20:37 +00:00
## 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:
```javascript
browser.runtime.onMessage.addListener(async function(msg) {
if (msg == "get-ids") {
let {idPattern} = await browser.storage.get("idPattern");
return Array.from(document.querySelectorAll(idPattern),
elem => elem.textContent);
}
});
```
2016-10-07 01:20:37 +00:00
Or vice versa.