Merge remote-tracking branch 'origin/master' into export-helpers

* origin/master: (149 commits)
  Add some icons (#285)
  add in simple http-response example (#281)
  convert chrome. to browser. Issue #165 #166 (#262)
  Update examples.json for contextMenus->menus change (#282)
  Update context-menu-demo (#272)
  change shortcut to Ctrl+Shift+U for commands example (#264)
  Update `proxy-blocker` extension to be compatible with firefox 56+ (#260)
  Update eslint and .travis.yml (#259)
  add in titlePreface (#256)
  Hellosct1 webext (#237)
  Remove 'highlight', as Firefox does not support it (#252)
  Reflect bookmark state in icon title to make it accessible to screen readers (#255)
  Adding listing/descriptions for the imagify and themes examples. (#253)
  Selfify example (#251)
  New theme examples (#248)
  Example: embedded webextension overlay (#249)
  Add an indexedDB file storage example: image-reference-collector (#224)
  webextension -> extension (#250)
  more fields into package.json
  Add travis-ci build status badge
  ...
This commit is contained in:
Will Bamberg
2017-10-10 12:20:26 -07:00
453 changed files with 8327 additions and 1118 deletions

4
.eslintignore Normal file
View File

@@ -0,0 +1,4 @@
**/node_modules/**
react-es6-popup/**/dist
mocha-client-tests
store-collected-images/webextension-plain/deps

22
.eslintrc.json Normal file
View File

@@ -0,0 +1,22 @@
{
"root": true,
"parserOptions": {
"ecmaVersion": 6
},
"env": {
"browser": true,
"es6": true,
"webextensions": true
},
"extends": [
"eslint:recommended"
],
"rules": {
"no-console": 0,
"no-unused-vars": ["warn", { "vars": "all", "args": "all" } ],
"no-undef": ["warn"],
"no-proto": ["error"],
"prefer-arrow-callback": ["warn"],
"prefer-spread": ["warn"]
}
}

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

3
.travis.yml Normal file
View File

@@ -0,0 +1,3 @@
language: node_js
node_js: stable
sudo: false

4
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,4 @@
Code of conduct
===============
This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details please see the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/) and [Developer Etiquette Guidelines](https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).

View File

@@ -12,15 +12,13 @@ to help contributors write useful examples.
There are many ways you can help improve this repository! For example:
* **write a brand-new example:** for example, you might notice that there are no
examples highlighting the [cookies API](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/cookies).
examples highlighting a particular [API](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API).
* **extend an existing example:** for example,
you might notice that the [tabs-tabs-tabs](https://github.com/mdn/webextensions-examples/tree/master/tabs-tabs-tabs) example
uses a lot of tab manipulation APIs, but does not cover
[tabs.setZoom()](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/setZoom).
you might notice that an existing example could usefully be extended to demonstrate some extra APIs or techniques.
* **fix a bug:** we have a list of [issues](https://github.com/mdn/webextensions-examples/issues),
or maybe you found your own.
* **contribute a translation:** find an example with a `_locales` directory in
it, and create a translation of the example's localizable strings into a new language.
it, and create a translation of the example's localizable strings into a new language.
## Guidelines for examples
@@ -35,9 +33,17 @@ complexity
* [`description`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/description)
* [`icons`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/icons)
* `homepage_url`
* [`strict_min_version` in the `applications` key](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications)
* omit the [`applications` key](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications), unless either of the following apply:
* the example uses an API or other feature that's not yet available in the current released version of Firefox. In this case, include the `applications` key and set `strict_min_version` to the minimum required version of Firefox.
* the example needs an explicitly specified ID (for example, [native messaging](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging) requires an explicitly specified ID). In this case, include the `applications` key and set `id` appropriately.
Finally, note that the examples are all made available under the
## Code style
If you're editing an existing file, code style should be consistent with the rest of the code in the file. Otherwise, code style should follow the style for WebExtensions code itself: [https://wiki.mozilla.org/WebExtensions/Hacking#Code_Style](https://wiki.mozilla.org/WebExtensions/Hacking#Code_Style).
## Licensing
Please note that the examples are all made available under the
[Mozilla Public License 2.0](https://github.com/mdn/webextensions-examples/blob/master/LICENSE),
so any contributions must be
[compatible with that license](https://www.mozilla.org/en-US/MPL/license-policy/).

190
README.md
View File

@@ -1,4 +1,4 @@
# webextensions-examples
# webextensions-examples [![Build Status](https://travis-ci.org/mdn/webextensions-examples.svg?branch=master)](https://travis-ci.org/mdn/webextensions-examples)
[https://github.com/mdn/webextensions-examples](https://github.com/mdn/webextensions-examples)
@@ -22,7 +22,7 @@ The examples are made available under the
To use the repository, first clone it.
Each example is in its own top-level directory. Install an example in your
favourite web browser (see [installation instructions for Firefox](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Packaging_and_installation)),
favourite web browser ([installation instructions](#installing-an-example) are below),
and see how it works. Each example has its own short README explaining what
it does.
@@ -30,11 +30,186 @@ To find your way around a WebExtension's internal structure, have a look at the
[Anatomy of a WebExtension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Anatomy_of_a_WebExtension)
page on MDN.
To use these examples in Firefox, you need at least Firefox 45. Some examples
rely on APIs that were added in more recent versions of Firefox.
To check the minimum version of Firefox needed for a given example,
see the `strict_min_version` part of the [applications key](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications)
in the example's manifest.json file.
To use these examples in Firefox, you should use the most recent release
of Firefox. Some examples work with earlier releases.
A few examples rely on APIs that are currently only available in pre-release
versions of Firefox. Where this is the case, the example should declare
the minimum version that it needs in the `strict_min_version` part of the
[applications key](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications)
in its manifest.json file.
## Installing an example
There are a couple ways to try out the example extensions in this repository.
1. Open Firefox and load `about:debugging` in the URL bar. Click the
[Load Temporary Add-on](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox)
button and select the `manifest.json` file within the
directory of an example extension you'd like to install.
Here is a [video](https://www.youtube.com/watch?v=cer9EUKegG4)
that demonstrates how to do this.
2. Install the
[web-ext](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext)
tool, change into the directory of the example extension
you'd like to install, and type `web-ext run`. This will launch Firefox and
install the extension automatically. This tool gives you some
additional development features such as
[automatic reloading](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext#Automatic_extension_reloading).
## Index of examples
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/annotate-page">annotate-page</a></dt>
<dd>Sidebar demo.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/apply-css">apply-css</a></dt>
<dd>Inserts CSS into a web page.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/beastify">beastify</a></dt>
<dd>Adds a browser action with a popup and injects a script into a web page.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/bookmark-it">bookmark-it</a></dt>
<dd>Adds and removes bookmarks.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/borderify">borderify</a></dt>
<dd>Injects scripts into pages that match a URL pattern.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/chill-out">chill-out</a></dt>
<dd>Page action demo, plus some features of <code>alarms</code> and <code>tabs</code>.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/commands">commands</a></dt>
<dd>Defines keyboard shortcuts.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/context-menu-copy-link-with-types">context-menu-copy-link-with-types</a></dt>
<dd>Advanced clipboard interaction, including copy to clipboard from a background page.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/context-menu-demo">context-menu-demo</a></dt>
<dd>Adds items to the context menu.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/contextual-identities">contextual-identities</a></dt>
<dd>Contextual identities (containers) demo.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/cookie-bg-picker">cookie-bg-picker</a></dt>
<dd>Demo using cookies, browser actions with popups, and content scripts.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/embedded-webextension-bootstrapped">embedded-webextension-bootstrapped</a></dt>
<dd>An embedded WebExtension in a bootstrapped Firefox add-on.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/embedded-webextension-sdk">embedded-webextension-sdk</a></dt>
<dd>An embedded WebExtension in an Add-on SDK Firefox add-on.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/emoji-substitution">emoji-substitution</a></dt>
<dd>Injects content scripts into web pages matching a given URL pattern.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/eslint-example">eslint-example</a></dt>
<dd>How to configure a WebExtension with eslint.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/favourite-colour">favourite-colour</a></dt>
<dd>Demo of storage and options(settings) pages.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/firefox-code-search">firefox-code-search</a></dt>
<dd>Customizes the behavior of the browser's address bar.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/forget-it">forget-it</a></dt>
<dd>Clears stored browsing data.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/google-userinfo">google-userinfo</a></dt>
<dd>Authenticates the user with Google.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/history-deleter">history-deleter</a></dt>
<dd>Clears browsing history entries by domain.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/latest-download">latest-download</a></dt>
<dd>Displays the most recently downloaded item.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/list-cookies">list-cookies</a></dt>
<dd>Lists all cookies in the active tab.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/mocha-client-tests">mocha-client-tests</a></dt>
<dd>Tests the add-on using Mocha.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/native-messaging">native-messaging</a></dt>
<dd>Exchanges messages between the add-on and a Python program installed on the user's computer.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/navigation-stats">navigation-stats</a></dt>
<dd>Collects and displays statistics for sites the user navigates to.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n">notify-link-clicks-i18n</a></dt>
<dd>Displays localized notifications when the user clicks on links.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/open-my-page-button">open-my-page-button</a></dt>
<dd>Opens a page bundled with the add-on, when the user clicks a toolbar button.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/page-to-extension-messaging">page-to-extension-messaging</a></dt>
<dd>Exchanges messages between a web page and the add-on.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/permissions">permissions</a></dt>
<dd>Demo showing how to ask the user for additional permissions at runtime.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/quicknote">quicknote</a></dt>
<dd>Note-taking add-on: shows how to store data, and how to use browser actions and popups.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/react-es6-popup">react-es6-popup</a></dt>
<dd>How to use React and ES6 in an add-on.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/selection-to-clipboard">selection-to-clipboard</a></dt>
<dd>Basic clipboard example: copying to the clipboard in a content script.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/stored-credentials">stored-credentials</a></dt>
<dd>Performs HTTP basic authentication using a stored username and password.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/tabs-tabs-tabs">tabs-tabs-tabs</a></dt>
<dd>Demo of various <code>tabs</code> functions: open, close, move, etc.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/top-sites">top-sites</a></dt>
<dd>Replaces the "new tab" page with links to pages that the user often visits.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/user-agent-rewriter">user-agent-rewriter</a></dt>
<dd>Intercepts and modifies HTTP requests.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/webpack-modules">webpack-modules</a></dt>
<dd>Uses webpack to package npm modules for use in an add-on.</dd>
</dl>
<dl><dt><a href="https://github.com/mdn/webextensions-examples/tree/master/window-manipulator">window-manipulator</a></dt>
<dd>Demo of various <code>windows</code> functions: create, close, resize, etc.</dd>
</dl>
## Learn more
@@ -53,4 +228,3 @@ If you need help, email the [dev-addons mailing list](https://mail.mozilla.org/l
We welcome contributions, whether they are whole new examples, new features,
bug fixes, or translations of localizable strings into new languages. Please
see the [CONTRIBUTING.md](https://github.com/mdn/webextensions-examples/blob/master/CONTRIBUTING.md) file for more details.

11
annotate-page/README.md Normal file
View File

@@ -0,0 +1,11 @@
# annotate-page
## What it does
This example adds a sidebar that lets you take notes on the current web page. The notes are saved to local storage, and the notes for each page are shown again when you open that page again.
The example also uses the `commands` manifest key to add a keyboard shortcut that opens the sidebar.
## What it shows
How to create a sidebar for an add-on. How to associate the sidebar with the currently active tab in that sidebar's window. How to store and restore sidebar content.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,29 @@
{
"manifest_version": 2,
"name": "Web page annotator",
"description": "Displays a sidebar that lets you take notes on web pages.",
"version": "1.0",
"applications": {
"gecko": {
"strict_min_version": "54.0a1"
}
},
"sidebar_action": {
"default_icon": "icons/star.png",
"default_title" : "Annotator",
"default_panel": "sidebar/panel.html"
},
"permissions": ["storage", "tabs"],
"commands": {
"_execute_sidebar_action": {
"suggested_key": {
"default": "Ctrl+Shift+Y"
}
}
}
}

View File

@@ -0,0 +1,32 @@
html, body {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
}
body {
height: 90%;
font: caption;
background-color: #F4F7F8;
}
p {
margin: 1em 2em;
}
#content {
height: 90%;
margin: 2em 2em 0 2em;
border: .5em solid #DDE4E9;
transition: background-color .2s ease-out;
}
#content[contenteditable=true] {
background-color: white;
transition: background-color .2s ease-in;
}
[contenteditable]:focus {
outline: 0px solid transparent;
}

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css"/>
</head>
<body>
<div id = "content"></div>
<p>Click inside the box to start taking notes on this page.</p>
<script src="panel.js"></script>
</body>
</html>

View File

@@ -0,0 +1,57 @@
var myWindowId;
const contentBox = document.querySelector("#content");
/*
Make the content box editable as soon as the user mouses over the sidebar.
*/
window.addEventListener("mouseover", () => {
contentBox.setAttribute("contenteditable", true);
});
/*
When the user mouses out, save the current contents of the box.
*/
window.addEventListener("mouseout", () => {
contentBox.setAttribute("contenteditable", false);
browser.tabs.query({windowId: myWindowId, active: true}).then((tabs) => {
let contentToStore = {};
contentToStore[tabs[0].url] = contentBox.textContent;
browser.storage.local.set(contentToStore);
});
});
/*
Update the sidebar's content.
1) Get the active tab in this sidebar's window.
2) Get its stored content.
3) Put it in the content box.
*/
function updateContent() {
browser.tabs.query({windowId: myWindowId, active: true})
.then((tabs) => {
return browser.storage.local.get(tabs[0].url);
})
.then((storedInfo) => {
contentBox.textContent = storedInfo[Object.keys(storedInfo)[0]];
});
}
/*
Update content when a new tab becomes active.
*/
browser.tabs.onActivated.addListener(updateContent);
/*
Update content when a new page is loaded into a tab.
*/
browser.tabs.onUpdated.addListener(updateContent);
/*
When the sidebar loads, get the ID of its window,
and update its content.
*/
browser.windows.getCurrent({populate: true}).then((windowInfo) => {
myWindowId = windowInfo.id;
updateContent();
});

21
apply-css/README.md Normal file
View File

@@ -0,0 +1,21 @@
# apply-css
**This add-on injects CSS into web pages. The `addons.mozilla.org` domain disallows this operation, so this add-on will not work properly when it's run on pages in the `addons.mozilla.org` domain.**
## What it does
This extension includes:
* a background script, "background.js"
* a page action
It adds the [page action](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/pageAction)
to every tab the user opens. Clicking the page action
for a tab applies some CSS using [tabs.insertCSS](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/tabs/insertCSS).
Clicking again removes the CSS using [tabs.removeCSS](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/tabs/removeCSS).
## What it shows
* some basic page action functions
* how to apply and remove CSS.

69
apply-css/background.js Normal file
View File

@@ -0,0 +1,69 @@
const CSS = "body { border: 20px solid red; }";
const TITLE_APPLY = "Apply CSS";
const TITLE_REMOVE = "Remove CSS";
const APPLICABLE_PROTOCOLS = ["http:", "https:"];
/*
Toggle CSS: based on the current title, insert or remove the CSS.
Update the page action's title and icon to reflect its state.
*/
function toggleCSS(tab) {
function gotTitle(title) {
if (title === TITLE_APPLY) {
browser.pageAction.setIcon({tabId: tab.id, path: "icons/on.svg"});
browser.pageAction.setTitle({tabId: tab.id, title: TITLE_REMOVE});
browser.tabs.insertCSS({code: CSS});
} else {
browser.pageAction.setIcon({tabId: tab.id, path: "icons/off.svg"});
browser.pageAction.setTitle({tabId: tab.id, title: TITLE_APPLY});
browser.tabs.removeCSS({code: CSS});
}
}
var gettingTitle = browser.pageAction.getTitle({tabId: tab.id});
gettingTitle.then(gotTitle);
}
/*
Returns true only if the URL's protocol is in APPLICABLE_PROTOCOLS.
*/
function protocolIsApplicable(url) {
var anchor = document.createElement('a');
anchor.href = url;
return APPLICABLE_PROTOCOLS.includes(anchor.protocol);
}
/*
Initialize the page action: set icon and title, then show.
Only operates on tabs whose URL's protocol is applicable.
*/
function initializePageAction(tab) {
if (protocolIsApplicable(tab.url)) {
browser.pageAction.setIcon({tabId: tab.id, path: "icons/off.svg"});
browser.pageAction.setTitle({tabId: tab.id, title: TITLE_APPLY});
browser.pageAction.show(tab.id);
}
}
/*
When first loaded, initialize the page action for all tabs.
*/
var gettingAllTabs = browser.tabs.query({});
gettingAllTabs.then((tabs) => {
for (let tab of tabs) {
initializePageAction(tab);
}
});
/*
Each time a tab is updated, reset the page action for that tab.
*/
browser.tabs.onUpdated.addListener((id, changeInfo, tab) => {
initializePageAction(tab);
});
/*
Toggle CSS when the page action is clicked.
*/
browser.pageAction.onClicked.addListener(toggleCSS);

2
apply-css/icons/LICENSE Normal file
View File

@@ -0,0 +1,2 @@
These icons are taken from the "Material Design" iconset designed by Google:
https://google.github.io/material-design-icons/.

1
apply-css/icons/off.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="20px" version="1.1" viewBox="0 0 20 20" width="20px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#FF0000" id="Core" transform="translate(-170.000000, -86.000000)"><g id="check-circle-outline-blank" transform="translate(170.000000, 86.000000)"><path d="M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" id="Shape"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 711 B

1
apply-css/icons/on.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="20px" version="1.1" viewBox="0 0 20 20" width="20px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#FF0000" id="Core" transform="translate(-338.000000, -338.000000)"><g id="radio-button-on" transform="translate(338.000000, 338.000000)"><path d="M10,5 C7.2,5 5,7.2 5,10 C5,12.8 7.2,15 10,15 C12.8,15 15,12.8 15,10 C15,7.2 12.8,5 10,5 L10,5 Z M10,0 C4.5,0 0,4.5 0,10 C0,15.5 4.5,20 10,20 C15.5,20 20,15.5 20,10 C20,4.5 15.5,0 10,0 L10,0 Z M10,18 C5.6,18 2,14.4 2,10 C2,5.6 5.6,2 10,2 C14.4,2 18,5.6 18,10 C18,14.4 14.4,18 10,18 L10,18 Z" id="Shape"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 798 B

23
apply-css/manifest.json Normal file
View File

@@ -0,0 +1,23 @@
{
"description": "Adds a page action to toggle applying CSS to pages.",
"manifest_version": 2,
"name": "apply-css",
"version": "1.0",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/apply-css",
"background": {
"scripts": ["background.js"]
},
"page_action": {
"default_icon": "icons/off.svg",
"browser_style": true
},
"permissions": [
"activeTab",
"tabs"
]
}

View File

@@ -1,5 +1,7 @@
# beastify
**This add-on injects JavaScript into web pages. The `addons.mozilla.org` domain disallows this operation, so this add-on will not work properly when it's run on pages in the `addons.mozilla.org` domain.**
## What it does ##
The extension includes:
@@ -18,6 +20,8 @@ the name of the chosen beast.
When the content script receives this message, it replaces the current page
content with an image of the chosen beast.
When the user clicks the reset button, the page reloads, and reverts to its original form.
## What it shows ##
* write a browser action with a popup
@@ -25,3 +29,4 @@ content with an image of the chosen beast.
* inject a content script programmatically using `tabs.executeScript()`
* send a message from the main extension to a content script
* use web accessible resources to enable web pages to load packaged content
* reload web pages

View File

@@ -7,7 +7,7 @@ beastify():
function beastify(request, sender, sendResponse) {
removeEverything();
insertBeast(request.beastURL);
chrome.runtime.onMessage.removeListener(beastify);
browser.runtime.onMessage.removeListener(beastify);
}
/*
@@ -34,4 +34,4 @@ function insertBeast(beastURL) {
/*
Assign beastify() as a listener for messages from the extension.
*/
chrome.runtime.onMessage.addListener(beastify);
browser.runtime.onMessage.addListener(beastify);

View File

@@ -9,13 +9,6 @@
"48": "icons/beasts-48.png"
},
"applications": {
"gecko": {
"id": "beastify@mozilla.org",
"strict_min_version": "45.0"
}
},
"permissions": [
"activeTab"
],

View File

@@ -2,15 +2,26 @@ html, body {
width: 100px;
}
.beast {
.button {
margin: 3% auto;
padding: 4px;
text-align: center;
font-size: 1.5em;
background-color: #E5F2F2;
cursor: pointer;
}
.beast:hover {
background-color: #CFF2F2;
}
.beast {
background-color: #E5F2F2;
}
.clear {
background-color: #FBFBC9;
}
.clear:hover {
background-color: #EAEA9D;
}

View File

@@ -7,9 +7,10 @@
</head>
<body>
<div class="beast">Frog</div>
<div class="beast">Turtle</div>
<div class="beast">Snake</div>
<div class="button beast">Frog</div>
<div class="button beast">Turtle</div>
<div class="button beast">Snake</div>
<div class="button clear">Reset</div>
<script src="choose_beast.js"></script>
</body>

View File

@@ -4,41 +4,45 @@ Given the name of a beast, get the URL to the corresponding image.
function beastNameToURL(beastName) {
switch (beastName) {
case "Frog":
return chrome.extension.getURL("beasts/frog.jpg");
return browser.extension.getURL("beasts/frog.jpg");
case "Snake":
return chrome.extension.getURL("beasts/snake.jpg");
return browser.extension.getURL("beasts/snake.jpg");
case "Turtle":
return chrome.extension.getURL("beasts/turtle.jpg");
return browser.extension.getURL("beasts/turtle.jpg");
}
}
/*
Listen for clicks in the popup.
If the click is not on one of the beasts, return early.
If the click is on one of the beasts:
Inject the "beastify.js" content script in the active tab.
Otherwise, the text content of the node is the name of the beast we want.
Then get the active tab and send "beastify.js" a message
containing the URL to the chosen beast's image.
Inject the "beastify.js" content script in the active tab.
Then get the active tab and send "beastify.js" a message
containing the URL to the chosen beast's image.
If it's on a button wich contains class "clear":
Reload the page.
Close the popup. This is needed, as the content script malfunctions after page reloads.
*/
document.addEventListener("click", function(e) {
if (!e.target.classList.contains("beast")) {
document.addEventListener("click", (e) => {
if (e.target.classList.contains("beast")) {
var chosenBeast = e.target.textContent;
var chosenBeastURL = beastNameToURL(chosenBeast);
browser.tabs.executeScript(null, {
file: "/content_scripts/beastify.js"
});
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
browser.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL});
});
}
else if (e.target.classList.contains("clear")) {
browser.tabs.reload();
window.close();
return;
}
var chosenBeast = e.target.textContent;
var chosenBeastURL = beastNameToURL(chosenBeast);
chrome.tabs.executeScript(null, {
file: "/content_scripts/beastify.js"
});
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL});
});
});

View File

@@ -1,7 +1,5 @@
# bookmark-it
> This example uses APIs that are available from Firefox 47 onwards.
## What it does
Displays a simple button in the menu bar that toggles a bookmark for the currently active tab.

View File

@@ -6,7 +6,7 @@ var currentBookmark;
* is already bookmarked.
*/
function updateIcon() {
chrome.browserAction.setIcon({
browser.browserAction.setIcon({
path: currentBookmark ? {
19: "icons/star-filled-19.png",
38: "icons/star-filled-38.png"
@@ -16,6 +16,11 @@ function updateIcon() {
},
tabId: currentTab.id
});
browser.browserAction.setTitle({
// Screen readers can see the title
title: currentBookmark ? 'Unbookmark it!' : 'Bookmark it!',
tabId: currentTab.id
});
}
/*
@@ -23,42 +28,59 @@ function updateIcon() {
*/
function toggleBookmark() {
if (currentBookmark) {
chrome.bookmarks.remove(currentBookmark.id);
currentBookmark = null;
updateIcon();
browser.bookmarks.remove(currentBookmark.id);
} else {
chrome.bookmarks.create({title: currentTab.title, url: currentTab.url}, function(bookmark) {
currentBookmark = bookmark;
updateIcon();
});
browser.bookmarks.create({title: currentTab.title, url: currentTab.url});
}
}
chrome.browserAction.onClicked.addListener(toggleBookmark);
browser.browserAction.onClicked.addListener(toggleBookmark);
/*
* Switches currentTab and currentBookmark to reflect the currently active tab
*/
function updateTab() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
function updateActiveTab(tabs) {
function isSupportedProtocol(urlString) {
var supportedProtocols = ["https:", "http:", "ftp:", "file:"];
var url = document.createElement('a');
url.href = urlString;
return supportedProtocols.indexOf(url.protocol) != -1;
}
function updateTab(tabs) {
if (tabs[0]) {
currentTab = tabs[0];
chrome.bookmarks.search({url: currentTab.url}, (bookmarks) => {
currentBookmark = bookmarks[0];
updateIcon();
});
if (isSupportedProtocol(currentTab.url)) {
var searching = browser.bookmarks.search({url: currentTab.url});
searching.then((bookmarks) => {
currentBookmark = bookmarks[0];
updateIcon();
});
} else {
console.log(`Bookmark it! does not support the '${currentTab.url}' URL.`)
}
}
});
}
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then(updateTab);
}
// TODO listen for bookmarks.onCreated and bookmarks.onRemoved once Bug 1221764 lands
// listen for bookmarks being created
browser.bookmarks.onCreated.addListener(updateActiveTab);
// listen for bookmarks being removed
browser.bookmarks.onRemoved.addListener(updateActiveTab);
// listen to tab URL changes
chrome.tabs.onUpdated.addListener(updateTab);
browser.tabs.onUpdated.addListener(updateActiveTab);
// listen to tab switching
chrome.tabs.onActivated.addListener(updateTab);
browser.tabs.onActivated.addListener(updateActiveTab);
// listen for window switching
browser.windows.onFocusChanged.addListener(updateActiveTab);
// update when the extension loads initially
updateTab();
updateActiveTab();

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Bookmark it!",
"version": "1.0",
"version": "1.1",
"description": "A simple bookmark button",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/bookmark-it",
"icons": {
@@ -9,13 +9,6 @@
"96": "icons/bookmark-it@2x.png"
},
"applications": {
"gecko": {
"id": "bookmark-it-extension@mozilla.org",
"strict_min_version": "47.0a1"
}
},
"permissions": [
"bookmarks",
"tabs"

View File

@@ -1,5 +1,7 @@
# borderify
**This add-on injects JavaScript into web pages. The `addons.mozilla.org` domain disallows this operation, so this add-on will not work properly when it's run on pages in the `addons.mozilla.org` domain.**
## What it does
This extension just includes:

View File

@@ -9,13 +9,6 @@
"48": "icons/border-48.png"
},
"applications": {
"gecko": {
"id": "borderify@mozilla.org",
"strict_min_version": "45.0"
}
},
"content_scripts": [
{
"matches": ["*://*.mozilla.org/*"],

13
build/README.md Normal file
View File

@@ -0,0 +1,13 @@
This is a built version of most of the extensions in this repository. It excludes:
* webpack-modules: because this one is all about building the module using webpack
Because the extensions mostly have no application id, they can be signed by anyone on addons.mozilla.org. We haven't checked in or stored those ids so you can rebuild them if you'd like. We don't expect end users to be expecting these examples to update automatically (which is what the id is for).
A couple do have ids. You can still sign them yourself by changing the application id. If you'd like to sign them officially and add them to this repo, contact @andymckay or @wbamberg and they can do it for you.
If an extension changes and you'd like to rebuild it, then you should probably update the version number before building it.
To build a new version of the extension use the web-ext sign command, for example:
pushd ../commands/ && web-ext sign --artifacts-dir ../build && popd

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,8 +2,10 @@
## What it does
After N seconds of inactivity (defined as, the user not having navigated
or switched the active tab) display show a page action for that tab.
After N seconds of inactivity (defined as the user not having navigated
or switched away from the active tab) display a
[page action](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/pageAction)
for that tab.
When the user clicks the page action,
navigate to http://chilloutandwatchsomecatgifs.com/.

View File

@@ -14,18 +14,20 @@ var CATGIFS = "http://chilloutandwatchsomecatgifs.com/";
/*
Restart alarm for the currently active tab, whenever background.js is run.
*/
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
restartAlarm(tabs[0].id);
});
/*
Restart alarm for the currently active tab, whenever the user navigates.
*/
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!changeInfo.url) {
return;
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
if (tabId == tabs[0].id) {
restartAlarm(tabId);
}
@@ -35,7 +37,7 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
/*
Restart alarm for the currently active tab, whenever a new tab becomes active.
*/
chrome.tabs.onActivated.addListener(function (activeInfo) {
browser.tabs.onActivated.addListener((activeInfo) => {
restartAlarm(activeInfo.tabId);
});
@@ -44,11 +46,12 @@ restartAlarm: clear all alarms,
then set a new alarm for the given tab.
*/
function restartAlarm(tabId) {
chrome.pageAction.hide(tabId);
chrome.alarms.clearAll();
chrome.tabs.get(tabId, function(tab) {
browser.pageAction.hide(tabId);
browser.alarms.clearAll();
var gettingTab = browser.tabs.get(tabId);
gettingTab.then((tab) => {
if (tab.url != CATGIFS) {
chrome.alarms.create("", {delayInMinutes: DELAY});
browser.alarms.create("", {delayInMinutes: DELAY});
}
});
}
@@ -56,15 +59,16 @@ function restartAlarm(tabId) {
/*
On alarm, show the page action.
*/
chrome.alarms.onAlarm.addListener(function(alarm) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.pageAction.show(tabs[0].id);
browser.alarms.onAlarm.addListener((alarm) => {
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
browser.pageAction.show(tabs[0].id);
});
});
/*
On page action click, navigate the corresponding tab to the cat gifs.
*/
chrome.pageAction.onClicked.addListener(function () {
chrome.tabs.update({url: CATGIFS});
browser.pageAction.onClicked.addListener(() => {
browser.tabs.update({url: CATGIFS});
});

View File

@@ -8,12 +8,6 @@
"48": "icons/chillout-48.png"
},
"applications": {
"gecko": {
"id": "chillout-page-action@mozilla.org"
}
},
"permissions": [
"alarms",
"tabs"

View File

@@ -10,4 +10,4 @@ All it does is:
It shows:
* how to use chrome.commands to register keyboard shortcuts for your extension.
* how to use browser.commands to register keyboard shortcuts for your extension.

View File

@@ -10,10 +10,11 @@
* shortcut: "Ctrl+Shift+U"
* }]
*/
chrome.commands.getAll(function(commands) {
commands.forEach(function(command) {
var gettingAllCommands = browser.commands.getAll();
gettingAllCommands.then((commands) => {
for (let command of commands) {
console.log(command);
});
}
});
/**
@@ -22,6 +23,6 @@ chrome.commands.getAll(function(commands) {
* In this sample extension, there is only one registered command: "Ctrl+Shift+U".
* On Mac, this command will automatically be converted to "Command+Shift+U".
*/
chrome.commands.onCommand.addListener(function(command) {
browser.commands.onCommand.addListener((command) => {
console.log("onCommand event received for message: ", command);
});

View File

@@ -7,12 +7,7 @@
"background": {
"scripts": ["background.js"]
},
"applications": {
"gecko": {
"id": "commands@mozilla.org",
"strict_min_version": "48.0a1"
}
},
"commands": {
"toggle-feature": {
"suggested_key": { "default": "Ctrl+Shift+U" },

View File

@@ -0,0 +1,35 @@
# Context menu: Copy link with types
This example adds a context menu item to every link that copies the URL to the
clipboard, as plain text and as rich HTML.
## What it does
This extension includes:
* a background script that:
- Registers a context menu item for every link.
- Upon click, it invokes the function to copy text and HTML to the clipboard.
* a helper script, "clipboard-helper.js" that provides the copy-to-clipboard functionality.
In the example, this script is run as a content script, but the actual functionality can also
be used in visible extension pages such as extension button popups or extension tabs.
* a page, "preview.html" for testing the effect of copying to the clipboard.
This page does not need to be part of the extension, and can directly be opened in the browser.
To test the extension, right-click on any link to open a context menu, and choose the
"Copy link to clipboard" option. Then open preview.html and paste the clipboard content
in the two displayed boxes. The first box will display "This is text: ..." and the second
box will display "This is HTML: ...".
Note: since the add-on relies on a content script for copying the text, the copy operation
will only succeed if the add-on is allowed to run scripts in the current page.
If you wish to successfully copy the text even if the current page cannot be scripted, then
you can open an (extension) page in a new tab as a fallback.
## What it shows
* how to put data on the [clipboard](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard)
with custom types ("text/plain" and "text/html" in the example).
* how to safely construct HTML from given text.
* how to safely create JavaScript code to run as a dynamic content script.
* how to dynamically run a static content script only once.

View File

@@ -0,0 +1,54 @@
browser.contextMenus.create({
id: "copy-link-to-clipboard",
title: "Copy link to clipboard",
contexts: ["link"],
});
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "copy-link-to-clipboard") {
// Examples: text and HTML to be copied.
const text = "This is text: " + info.linkUrl;
// Always HTML-escape external input to avoid XSS.
const safeUrl = escapeHTML(info.linkUrl);
const html = `This is HTML: <a href="${safeUrl}">${safeUrl}</a>`;
// The example will show how data can be copied, but since background
// pages cannot directly write to the clipboard, we will run a content
// script that copies the actual content.
// clipboard-helper.js defines function copyToClipboard.
const code = "copyToClipboard(" +
JSON.stringify(text) + "," +
JSON.stringify(html) + ");";
browser.tabs.executeScript({
code: "typeof copyToClipboard === 'function';",
}).then((results) => {
// The content script's last expression will be true if the function
// has been defined. If this is not the case, then we need to run
// clipboard-helper.js to define function copyToClipboard.
if (!results || results[0] !== true) {
return browser.tabs.executeScript(tab.id, {
file: "clipboard-helper.js",
});
}
}).then(() => {
return browser.tabs.executeScript(tab.id, {
code,
});
}).catch((error) => {
// This could happen if the extension is not allowed to run code in
// the page, for example if the tab is a privileged page.
console.error("Failed to copy text: " + error);
});
}
});
// https://gist.github.com/Rob--W/ec23b9d6db9e56b7e4563f1544e0d546
function escapeHTML(str) {
// Note: string cast using String; may throw if `str` is non-serializable, e.g. a Symbol.
// Most often this is not the case though.
return String(str)
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;").replace(/'/g, "&#39;")
.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

View File

@@ -0,0 +1,18 @@
// This function must be called in a visible page, such as a browserAction popup
// or a content script. Calling it in a background page has no effect!
function copyToClipboard(text, html) {
function oncopy(event) {
document.removeEventListener("copy", oncopy, true);
// Hide the event from the page to prevent tampering.
event.stopImmediatePropagation();
// Overwrite the clipboard content.
event.preventDefault();
event.clipboardData.setData("text/plain", text);
event.clipboardData.setData("text/html", html);
}
document.addEventListener("copy", oncopy, true);
// Requires the clipboardWrite permission, or a user gesture:
document.execCommand("copy");
}

View File

@@ -0,0 +1,19 @@
{
"manifest_version": 2,
"name": "Context menu: Copy link with types",
"description": "Add a context menu option to links to copy the link to the clipboard, as plain text and as a link in rich HTML.",
"version": "1.0",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/context-menu-copy-link-with-types",
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"activeTab",
"contextMenus",
"clipboardWrite"
]
}

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<style>
textarea, div[contentEditable] {
display: block;
border: 1px solid;
height: 5em;
width: 100%;
}
</style>
</head>
<body>
Use Ctrl+V (or Cmd+V) to paste plain text here:
<textarea></textarea>
Use Ctrl+V (or Cmd+V) to paste rich text (HTML) here:
<div contentEditable></div>
</body>
</html>

View File

@@ -1,29 +0,0 @@
# context-menu-demo
A demo of the [contextMenus API](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextMenus/).
## What it does
This add-on adds several items to the browser's context menu:
* one shown when there is a selection in the page, that logs the selected text
to the browser console when clicked.
* one shown in all contexts, that is removed when clicked.
* two "radio" items that are shown in all contexts.
These items are grouped using a separator item on each side.
One radio item adds a blue border to the page, the other adds a green border.
Note that these buttons only work on normal web pages, not special pages
like about:debugging.
* one "checkbox" item, shown in all contexts, whose title is updated when the
item is clicked.
## What it shows
* How to create various types of context menu item:
* normal
* radio
* separator
* checkbox
* How to use contexts to control when an item appears.
* How to update an item's properties.
* How to remove an item.

View File

@@ -0,0 +1,13 @@
# Contextual Identities
## What it does
Lists existing identities, lets you create new tabs with an identity and remove all tabs from an identity. For more information on contextual identities: https://wiki.mozilla.org/Security/Contextual_Identity_Project/Containers
## What it shows
How to use the contextualIdentities API. Please note: you must have contextualIdentities enabled. You can do that by going to about:config and setting the `privacy.userContext.enabled` preference to true. If you are using web-ext you can do this by running:
web-ext run --pref privacy.userContext.enabled=true
Icon from: https://www.iconfinder.com/icons/290119/card_id_identification_identity_profile_icon#size=128, License: "Free for commercial use".

View File

View File

@@ -0,0 +1,18 @@
html, body {
width: 350px;
}
a {
margin: 10px;
display: inline-block;
}
.panel {
margin: 5px;
}
span.identity {
width: 100px;
display: inline-block;
margin-left: 1em;
}

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="context.css"/>
</head>
<body>
<div class="panel">
<div id="identity-list"></div>
</div>
<script src="context.js"></script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
function eventHandler(event) {
if (event.target.dataset.action == 'create') {
browser.tabs.create({
url: 'about:blank',
cookieStoreId: event.target.dataset.identity
});
}
if (event.target.dataset.action == 'close-all') {
browser.tabs.query({
cookieStoreId: event.target.dataset.identity
}).then((tabs) => {
browser.tabs.remove(tabs.map((i) => i.id));
});
}
event.preventDefault();
}
function createOptions(node, identity) {
for (let option of ['Create', 'Close All']) {
let a = document.createElement('a');
a.href = '#';
a.innerText = option;
a.dataset.action = option.toLowerCase().replace(' ', '-');
a.dataset.identity = identity.cookieStoreId;
a.addEventListener('click', eventHandler);
node.appendChild(a);
}
}
var div = document.getElementById('identity-list');
if (browser.contextualIdentities === undefined) {
div.innerText = 'browser.contextualIdentities not available. Check that the privacy.userContext.enabled pref is set to true, and reload the add-on.';
} else {
browser.contextualIdentities.query({})
.then((identities) => {
if (!identities.length) {
div.innerText = 'No identities returned from the API.';
return;
}
for (let identity of identities) {
let row = document.createElement('div');
let span = document.createElement('span');
span.className = 'identity';
span.innerText = identity.name;
span.style = `color: ${identity.color}`;
console.log(identity);
row.appendChild(span);
createOptions(row, identity);
div.appendChild(row);
}
});
}

View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 48 48" height="48px" id="Layer_1" version="1.1" viewBox="0 0 48 48" width="48px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M43,42H5c-2.209,0-4-1.791-4-4V17c0-2.209,1.791-4,4-4h15V9c0-2.209,1.791-4,4-4 s4,1.791,4,4v4h15c2.209,0,4,1.791,4,4v21C47,40.209,45.209,42,43,42z M17.014,34.488c0,0.003,0,0.004,0,0.004 c-0.004,0-0.018-0.781-0.018-0.781s1.166-0.601,2.031-1.378c0.507-0.417,0.741-1.362,0.741-1.362 c0.137-0.828,0.238-2.877,0.238-3.703c0-2.062-1.033-4.28-4.007-4.28V22.98v0.007c-2.974,0-4.007,2.219-4.007,4.28 c0,0.826,0.102,2.875,0.238,3.703c0,0,0.234,0.945,0.741,1.362c0.865,0.777,2.031,1.378,2.031,1.378s-0.014,0.781-0.018,0.781 c0,0,0-0.001,0-0.004c0,0,0.029,1.146,0.029,1.487c0,1.362-1.365,2.018-2.223,2.018c-0.002,0-0.002,0-0.003,0 c-2.593,0.113-3.205,0.976-3.21,0.984C9.419,39.23,9.199,39.482,8.998,40h14.004c-0.201-0.518-0.421-0.77-0.582-1.022 c-0.005-0.009-0.617-0.871-3.21-0.984c-0.001,0-0.001,0-0.003,0c-0.857,0-2.223-0.655-2.223-2.018 C16.984,35.634,17.014,34.488,17.014,34.488z M26,9c0-1.104-0.896-2-2-2s-2,0.896-2,2v6c0,1.104,0.896,2,2,2s2-0.896,2-2V9z M45,17 c0-1.104-0.896-2-2-2H28c0,2.209-1.791,4-4,4s-4-1.791-4-4H5c-1.104,0-2,0.896-2,2v21c0,1.104,0.896,2,2,2h1.797 c0.231-0.589,0.656-1.549,1.16-2.24c0.025-0.014,0.848-1.739,4.998-1.79c0.006-0.021,0.01-1.042,0.022-1.027 c-0.32-0.202-0.737-0.516-1.051-0.816c-0.255-0.156-1.161-1.029-1.452-2.583c-0.087-0.542-0.488-3.099-0.488-4.166 c0-3.171,1.265-6.381,5.953-6.381c0.021,0,0.1,0,0.121,0c4.688,0,5.953,3.21,5.953,6.381c0,1.067-0.401,3.624-0.488,4.166 c-0.291,1.554-1.197,2.427-1.452,2.583c-0.313,0.301-0.73,0.614-1.051,0.816c0.013-0.015,0.017,1.007,0.022,1.027 c4.151,0.051,4.974,1.776,4.998,1.79c0.504,0.691,0.929,1.651,1.16,2.24H43c1.104,0,2-0.896,2-2V17z M40,26H28c-0.553,0-1-0.447-1-1 s0.447-1,1-1h12c0.553,0,1,0.447,1,1S40.553,26,40,26z M28,30h2c0.553,0,1,0.447,1,1s-0.447,1-1,1h-2c-0.553,0-1-0.447-1-1 S27.447,30,28,30z M28,34h6c0.553,0,1,0.447,1,1s-0.447,1-1,1h-6c-0.553,0-1-0.447-1-1S27.447,34,28,34z M32,31c0-0.553,0.447-1,1-1 h4c0.553,0,1,0.447,1,1s-0.447,1-1,1h-4C32.447,32,32,31.553,32,31z M23,9h2v2h-2V9z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,21 @@
{
"browser_action": {
"browser_style": true,
"default_title": "Contextual Identities",
"default_popup": "context.html",
"default_icon": {
"128": "identity.svg"
}
},
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/contextual-identities",
"manifest_version": 2,
"name": "Contextual Identities",
"version": "1.0",
"permissions": [
"contextualIdentities",
"cookies"
],
"icons": {
"128": "identity.svg"
}
}

View File

@@ -5,6 +5,8 @@ The WebExtension also uses cookies to save preferences for each site you customi
Works in Firefox 47+, and will also work as a Chrome extension, out of the box.
**This add-on injects JavaScript into web pages. The `addons.mozilla.org` domain disallows this operation, so this add-on will not work properly when it's run on pages in the `addons.mozilla.org` domain.**
## What it does
This extension includes:
@@ -33,4 +35,4 @@ Cookie BG Picker uses the WebExtension:
## Acknowledgements
* WebExtension icon courtesy of [icons8.com](http://icons8.com).
* Transparent background images taken from [Transparent Textures](https://www.transparenttextures.com/).
* Transparent background images taken from [Transparent Textures](https://www.transparenttextures.com/).

View File

@@ -1,26 +1,27 @@
/* Retrieve any previously set cookie and send to content script */
chrome.tabs.onUpdated.addListener(cookieUpdate);
function cookieUpdate(tabId, changeInfo, tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
/* inject content script into current tab */
chrome.tabs.executeScript(null, {
file: "/content_scripts/updatebg.js"
});
function getActiveTab() {
return browser.tabs.query({active: true, currentWindow: true});
}
function cookieUpdate() {
getActiveTab().then((tabs) => {
// get any previously set cookie for the current tab
chrome.cookies.get({
var gettingCookies = browser.cookies.get({
url: tabs[0].url,
name: "bgpicker"
}, function(cookie) {
if(cookie) {
});
gettingCookies.then((cookie) => {
if (cookie) {
var cookieVal = JSON.parse(cookie.value);
chrome.tabs.sendMessage(tabs[0].id, {image: cookieVal.image});
chrome.tabs.sendMessage(tabs[0].id, {color: cookieVal.color});
browser.tabs.sendMessage(tabs[0].id, {image: cookieVal.image});
browser.tabs.sendMessage(tabs[0].id, {color: cookieVal.color});
}
});
});
}
}
// update when the tab is updated
browser.tabs.onUpdated.addListener(cookieUpdate);
// update when the tab is activated
browser.tabs.onActivated.addListener(cookieUpdate);

View File

@@ -1,13 +1,12 @@
var html = document.querySelector('html');
var body = document.querySelector('body');
chrome.runtime.onMessage.addListener(updateBg);
browser.runtime.onMessage.addListener(updateBg);
function updateBg(request, sender, sendResponse) {
if(request.image) {
var html = document.querySelector('html');
var body = document.querySelector('body');
if (request.image) {
html.style.backgroundImage = 'url(' + request.image + ')';
body.style.backgroundImage = 'url(' + request.image + ')';
} else if(request.color) {
} else if (request.color) {
html.style.backgroundColor = request.color;
body.style.backgroundColor = request.color;
} else if (request.reset) {
@@ -16,4 +15,4 @@ function updateBg(request, sender, sendResponse) {
body.style.backgroundImage = '';
body.style.backgroundColor = '';
}
}
}

View File

@@ -9,13 +9,6 @@
"48": "icons/bgpicker-48.png"
},
"applications": {
"gecko": {
"id": "quicknote@mozilla.org",
"strict_min_version": "45.0"
}
},
"permissions": [
"tabs",
"cookies",
@@ -43,5 +36,12 @@
"background": {
"scripts": ["background_scripts/background.js"]
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_scripts/updatebg.js"]
}
]
}

View File

@@ -6,6 +6,10 @@ var reset = document.querySelector('.color-reset button');
var cookieVal = { image : '',
color : '' };
function getActiveTab() {
return browser.tabs.query({active: true, currentWindow: true});
}
/* apply backgrounds to buttons */
/* add listener so that when clicked, button applies background to page HTML */
@@ -15,13 +19,13 @@ for(var i = 0; i < bgBtns.length; i++) {
bgBtns[i].style.backgroundImage = bgImg;
bgBtns[i].onclick = function(e) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
getActiveTab().then((tabs) => {
var imgName = e.target.getAttribute('class');
var fullURL = chrome.extension.getURL('popup/images/'+ imgName + '.png');
chrome.tabs.sendMessage(tabs[0].id, {image: fullURL});
var fullURL = browser.extension.getURL('popup/images/'+ imgName + '.png');
browser.tabs.sendMessage(tabs[0].id, {image: fullURL});
cookieVal.image = fullURL;
chrome.cookies.set({
browser.cookies.set({
url: tabs[0].url,
name: "bgpicker",
value: JSON.stringify(cookieVal)
@@ -33,12 +37,12 @@ for(var i = 0; i < bgBtns.length; i++) {
/* apply chosen color to HTML background */
colorPick.onchange = function(e) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
getActiveTab().then((tabs) => {
var currColor = e.target.value;
chrome.tabs.sendMessage(tabs[0].id, {color: currColor});
browser.tabs.sendMessage(tabs[0].id, {color: currColor});
cookieVal.color = currColor;
chrome.cookies.set({
browser.cookies.set({
url: tabs[0].url,
name: "bgpicker",
value: JSON.stringify(cookieVal)
@@ -49,12 +53,12 @@ colorPick.onchange = function(e) {
/* reset background */
reset.onclick = function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {reset: true});
getActiveTab().then((tabs) => {
browser.tabs.sendMessage(tabs[0].id, {reset: true});
cookieVal = { image : '',
color : '' };
chrome.cookies.remove({
browser.cookies.remove({
url: tabs[0].url,
name: "bgpicker"
})
@@ -63,6 +67,9 @@ reset.onclick = function() {
/* Report cookie changes to the console */
chrome.cookies.onChanged.addListener(function(changeInfo) {
console.log('Cookie changed:\n* Cookie: ' + JSON.stringify(changeInfo.cookie) + '\n* Cause: ' + changeInfo.cause + '\n* Removed: ' + changeInfo.removed);
})
browser.cookies.onChanged.addListener((changeInfo) => {
console.log(`Cookie changed:\n
* Cookie: ${JSON.stringify(changeInfo.cookie)}\n
* Cause: ${changeInfo.cause}\n
* Removed: ${changeInfo.removed}`);
});

29
devtools-panels/README.md Normal file
View File

@@ -0,0 +1,29 @@
# devtools-panels
**Adds a new panel to the developer tools. The panel contains buttons that demonstrate various basic features of the devtools API.**
## What it does ##
This extension adds a new panel to the developer tools. The panel contains four buttons:
* **Inspect H1**: this injects a script into the active page. The script uses the [`inspect()` helper function](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.inspectedWindow/eval#Helpers) to select the first &lt;h1&gt; element in the page in the devtools inspector.
* **Reddinate inspected element**: this injects a script into the active page. The script uses the [`$0` helper](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.inspectedWindow/eval#Helpers) to get the element that's currently selected in the devtools Inspector, and gives it a red background.
* **Check for jQuery**: this injects a script into the active page. The script checks whether `jQuery` is defined in the page, and logs a string to the add-on debugging console (note: *not* the web console) recording the result.
* **Inject content script**: this sends a message to the extension's background script, asking it to inject a given content script in the active page.
To learn more about the devtools APIs, see [Extending the developer tools](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Extending_the_developer_tools).
## What it shows ##
* How to add a new panel to the devtools.
* How to inject a script into the active page using [`inspectedWindow.eval()`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.inspectedWindow/eval).
* How to use [helpers](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.inspectedWindow/eval#Helpers) to interact with the devtools.
* That unlike content scripts, scripts injected with `eval()` can see objects, like `jQuery`, that were added by page scripts.
* How to send messages to the background script.

View File

@@ -0,0 +1,23 @@
/**
When we receive the message, execute the given script in the given
tab.
*/
function handleMessage(request, sender, sendResponse) {
if (sender.url != browser.runtime.getURL("/devtools/panel/panel.html")) {
return;
}
browser.tabs.executeScript(
request.tabId,
{
code: request.script
});
}
/**
Listen for messages from our devtools panel.
*/
browser.runtime.onMessage.addListener(handleMessage);

View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="devtools.js"></script>
</body>
</html>

View File

@@ -0,0 +1,24 @@
/**
This script is run whenever the devtools are open.
In here, we can create our panel.
*/
function handleShown() {
console.log("panel is being shown");
}
function handleHidden() {
console.log("panel is being hidden");
}
/**
Create a panel, and add listeners for panel show/hide events.
*/
browser.devtools.panels.create(
"My Panel",
"icons/star.png",
"devtools/panel/panel.html"
).then((newPanel) => {
newPanel.onShown.addListener(handleShown);
newPanel.onHidden.addListener(handleHidden);
});

View File

@@ -0,0 +1,72 @@
/**
Handle errors from the injected script.
Errors may come from evaluating the JavaScript itself
or from the devtools framework.
See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/devtools.inspectedWindow/eval#Return_value
*/
function handleError(error) {
if (error.isError) {
console.log(`Devtools error: ${error.code}`);
} else {
console.log(`JavaScript error: ${error.value}`);
}
}
/**
Handle the result of evaluating the script.
If there was an error, call handleError.
*/
function handleResult(result) {
if (result[1]) {
handleError(result[1]);
}
}
/**
Handle the result of evaluating the jQuery test script.
Log the result of the test, or
if there was an error, call handleError.
*/
function handlejQueryResult(result) {
if (result[0] !== undefined) {
console.log(`jQuery: ${result[0]}`);
} else if (result[1]) {
handleError(result[1]);
}
}
/**
When the user clicks the 'jquery' button,
evaluate the jQuery script.
*/
const checkjQuery = "typeof jQuery != 'undefined'";
document.getElementById("button_jquery").addEventListener("click", () => {
browser.devtools.inspectedWindow.eval(checkjQuery)
.then(handlejQueryResult);
});
/**
When the user clicks each of the first three buttons,
evaluate the corresponding script.
*/
const evalString = "$0.style.backgroundColor = 'red'";
document.getElementById("button_background").addEventListener("click", () => {
browser.devtools.inspectedWindow.eval(evalString)
.then(handleResult);
});
const inspectString = "inspect(document.querySelector('h1'))";
document.getElementById("button_h1").addEventListener("click", () => {
browser.devtools.inspectedWindow.eval(inspectString)
.then(handleResult);
});
/**
When the user clicks the 'message' button,
send a message to the background script.
*/
const scriptToAttach = "document.body.innerHTML = 'Hi from the devtools';";
document.getElementById("button_message").addEventListener("click", () => {
browser.runtime.sendMessage({
tabId: browser.devtools.inspectedWindow.tabId,
script: scriptToAttach
});
});

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<button id="button_h1">Inspect h1</button>
<button id="button_background">Reddinate inspected element</button>
<button id="button_jquery">Check for jquery</button>
<button id="button_message">Inject content script</button>
<script src="devtools-panel.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,22 @@
{
"description": "Adds a new panel to the developer tools. The panel contains buttons that demonstrate various basic features of the devtools API.",
"manifest_version": 2,
"name": "devtools-panels",
"version": "1.0",
"author": "Christophe Villeneuve",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/devtools-panels",
"icons": {
"48": "icons/star.png"
},
"background": {
"scripts": ["background_scripts/background.js"]
},
"permissions": [
"<all_urls>"
],
"devtools_page": "devtools/devtools-page.html"
}

11
discogs-search/README.md Normal file
View File

@@ -0,0 +1,11 @@
# discogs-search
## What it does
This add-on adds a search engine to the browser, that sends the search term to the [discogs.com](https://discogs.com) website.
It also adds a keyword "disc", so you can type "disc Model 500" and get the discogs search engine without having to select it.
## What it shows
How to use the [`chrome_settings_overrides`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/chrome_settings_overrides) manifest key to define a new search engine.

View File

@@ -0,0 +1,23 @@
{
"manifest_version": 2,
"name": "Discogs search engine",
"description": "Adds a search engine that searches discogs.com",
"version": "1.0",
"applications": {
"gecko": {
"strict_min_version": "55"
}
},
"chrome_settings_overrides": {
"search_provider": {
"name": "Discogs",
"search_url": "https://www.discogs.com/search/?q={searchTerms}",
"keyword": "disc",
"favicon_url": "https://www.discogs.com/favicon.ico",
"is_default": false,
"encoding": "UTF-8"
}
}
}

17
dynamic-theme/README.md Normal file
View File

@@ -0,0 +1,17 @@
# Dynamic theme
## What it does
Checks the users time and then flips between two different themes. A "moon theme" for after 8pm and before 8am and a "sun theme" the rest of the time.
To flip themes you can change the system time.
## What it shows
How to use the dynamic theme API and the alarm API.
Images are under creative commons 2.0: https://creativecommons.org/licenses/by/2.0/ and are:
https://www.flickr.com/photos/smemon/21939278025/in/photolist-zqGx1e-RRJjP9-ifAAEu-5Bb3R8-RmUdfQ-do9maE-RXiUHq-avRBYY-UrLynC-NWHX1-fND353-7Hgf3N-peAT8G-ahQje-3j9m1U-7W72M7-oNgLaF-faUP9M-SCJVpm-m3zsiB-SLjW4X-6sXzow-nzmiNW-GpiC9-4yVL8D-6sYiVd-kbJFNA-dWePSs-pGoTdJ-UCKjMC-UJtDMx-UCKjf5-9iW4rf-dnhFMq-QJAu9A-VtfjzN-RLSSGN-9dWKeu-cpmnXL-4g21qm-a4t24c-edkrrt-5cQ21P-71A3Qb-qNKJoE-oQ5qc5-2f2YeN-6RGZyS-s8g3Nc-pkjybQ
https://www.flickr.com/photos/carrenho/2443850489/in/photolist-4HXnBg-5Pg5tX-538XR-4kWUM-Vqb73V-GVSn1-nzSMqa-6TZwDQ-8NjEAS-ejqFwz-9Mra6z-6QDu8y-89eHW7-6fMEWp-fdabE-8Jv15V-8Jy9QG-6KcAsE-HPMGM-fcuMuE-V6vMKB-gg4VC-9h8GQa-7d1VS2-8PkWv7-8PhofV-evJBcR-MzZKt-pWWi6w-qJuTvo-dha7vm-Bnxcee-pfu11h-cUVXx1-r6BnTV-eXnhNo-h5k9E-fcT9jW-jAtbkT-5MSiEB-sfEYVj-9h8H3K-kWqQuL-8PkthY-dGFHDa-8PkWqh-o2fFjk-TWgQHJ-71rpnG-jJK6gy

View File

@@ -0,0 +1,49 @@
var currentTheme = '';
const themes = {
'day': {
images: {
headerURL: 'sun.jpg',
},
colors: {
accentcolor: '#CF723F',
textcolor: '#111',
}
},
'night': {
images: {
headerURL: 'moon.jpg',
},
colors: {
accentcolor: '#000',
textcolor: '#fff',
}
}
};
function setTheme(theme) {
if (currentTheme === theme) {
// No point in changing the theme if it has already been set.
return;
}
currentTheme = theme;
browser.theme.update(themes[theme]);
}
function checkTime() {
let date = new Date();
let hours = date.getHours();
// Will set the sun theme between 8am and 8pm.
if ((hours > 8) && (hours < 20)) {
setTheme('day');
} else {
setTheme('night');
}
}
// On start up, check the time to see what theme to show.
checkTime();
// Set up an alarm to check this regularly.
browser.alarms.onAlarm.addListener(checkTime);
browser.alarms.create('checkTime', {periodInMinutes: 5});

View File

@@ -0,0 +1,17 @@
{
"description": "An example dynamic theme",
"homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/dynamic-theme",
"manifest_version": 2,
"name": "Dynamic theme example",
"permissions": [
"alarms",
"theme"
],
"background": {
"scripts": ["background.js"]
},
"version": "1.0",
"gecko": {
"strict_min_version": "55.0a2"
}
}

BIN
dynamic-theme/moon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
dynamic-theme/sun.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,8 @@
{
"env": {
"browser": true,
"es6": true,
"amd": true,
"webextensions": true
}
}

View File

@@ -0,0 +1,14 @@
This is an example of how to use [embedded WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to convert a legacy [Bootstrapped extension](https://developer.mozilla.org/en-US/Add-ons/Bootstrapped_extensions) to a [WebExtension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions) in stages, and migrate the legacy add-on's data so it's accessible by the WebExtension.
The legacy add-on contains:
- some user data stored in the Firefox preferences
- a button in the toolbar
When the button is pressed, the add-on displays a panel containing the stored data.
This directory contains three versions of the add-on.
- **step0-legacy-addon**: the initial add-on, written entirely using the bootstrapped extension method.
- **step1-hybrid-addon**: a hybrid consisting of a bootstrapped extension containing an embedded WebExtension. The bootstrapped extension reads the stored data and sends it to the embedded WebExtension. The embedded WebExtension stores the data using the [`storage`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage) API, and also implements the UI.
- **step2-pure-webextension**: the final version, written entirely using the WebExtensions method. This version can be deployed after the hybrid version has migrated the stored data to the `storage` API.

View File

@@ -0,0 +1,16 @@
"use strict";
function startup(data) {
Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonUI.jsm");
AddonPrefs.set("super-important-user-setting", "char", "addon preference content");
AddonUI.init(data);
}
function shutdown(data) {
AddonUI.shutdown(data);
Components.utils.unload("chrome://original-bootstrap-addon-id/content/AddonUI.jsm");
Components.utils.unload("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
}

View File

@@ -0,0 +1 @@
content original-bootstrap-addon-id chrome/

Some files were not shown because too many files have changed in this diff Show More