What is webextension-polyfill?
The webextension-polyfill npm package provides a standardized API for developing browser extensions that work across different browsers. It offers a promise-based interface to the WebExtension APIs, making it easier to write consistent and maintainable code.
What are webextension-polyfill's main functionalities?
Browser Tabs
This feature allows you to interact with browser tabs. The code sample demonstrates how to get the current active tab in the current window.
const browser = require('webextension-polyfill');
async function getCurrentTab() {
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
return tabs[0];
}
getCurrentTab().then(tab => console.log(tab));
Storage
This feature allows you to store and retrieve data using the browser's storage API. The code sample demonstrates how to save a key-value pair to local storage and then retrieve it.
const browser = require('webextension-polyfill');
async function saveToStorage(key, value) {
await browser.storage.local.set({ [key]: value });
}
async function getFromStorage(key) {
let result = await browser.storage.local.get(key);
return result[key];
}
saveToStorage('exampleKey', 'exampleValue');
getFromStorage('exampleKey').then(value => console.log(value));
Messaging
This feature allows you to send messages between different parts of your extension, such as between a background script and a content script. The code sample demonstrates how to set up a message listener in the background script and send a message from the content script.
const browser = require('webextension-polyfill');
// Background script
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.greeting === 'hello') {
sendResponse({ response: 'hi' });
}
});
// Content script
browser.runtime.sendMessage({ greeting: 'hello' }).then(response => {
console.log(response.response);
});
Other packages similar to webextension-polyfill
chrome-extension-async
The chrome-extension-async package provides a promise-based interface for Chrome's extension APIs. It is similar to webextension-polyfill but is specific to Chrome, whereas webextension-polyfill aims to be cross-browser.
WebExtension browser
API Polyfill
This library allows extensions written for the Promise-based
WebExtension/BrowserExt API being standardized by the W3 Browser
Extensions group to be used without modification in Google
Chrome.
Building
To build, assuming you're already installed node >= 6 and
npm, simply run:
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.
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
:
{
"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:
<!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
:
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:
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:
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:
browser.tabs.sendMessage("get-ids").then(results => {
processResults(results);
});
And like this from the content script:
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:
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);
}
});
Or vice versa.