diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..aef6564 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +This repository contains example Firefox [WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions). + +We're really happy to accept contributions, either as new examples or as +improvements to the existing examples. This file lists some general guidelines +to help contributors write useful examples. + +The examples are intended to demonstrate how to use the WebExtensions technology, +particularly [APIs](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API), +but also [manifest.json keys](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json) +and more general techniques such as internationalization or message passing. + +More specifically, they're intended to be referenced from the MDN documentation +for WebExtensions. So, for example, we'll expect the reference page for +`tabs.executeScript()` to link to some examples that demonstrate how to use this +API. + +So examples should: + +* have a clear function, that's easy to explain and understand +* focus on demonstrating how to use the WebExtension technology, minimizing any +complex logic that's extraneous to the WebExtension technology itself +* demonstrate good-practice use of the technology, even at the expense of extra +complexity +* include useful optional manifest.json keys: + * [`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) + +Finally, 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/). diff --git a/beastify/README.md b/beastify/README.md index a37979d..26443f1 100644 --- a/beastify/README.md +++ b/beastify/README.md @@ -1 +1,27 @@ # beastify + +## What it does ## + +The extension includes: + +* a browser action with a popup including HTML, CSS, and JS +* a content script +* three images, each of a different beast, packaged as web accessible resources + +When the user clicks the browser action button, the popup is shown, enabling +the user to choose one of three beasts. + +When they choose a beast, the extension injects the content script into +the current page, and sends the content script a message containing +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. + +## What it shows ## + +* write a browser action with a popup +* give the popup style and behavior using CSS and JS +* 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 diff --git a/beastify/button/LICENSE b/beastify/button/LICENSE deleted file mode 100644 index c3727aa..0000000 --- a/beastify/button/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The icon "beasts.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/. diff --git a/beastify/content_scripts/beastify.js b/beastify/content_scripts/beastify.js index 9b6c5f5..43816bb 100644 --- a/beastify/content_scripts/beastify.js +++ b/beastify/content_scripts/beastify.js @@ -1,18 +1,28 @@ -// Assign beastify() as a listener for messages from the extension. -chrome.runtime.onMessage.addListener(beastify); - +/* +beastify(): +* removes every node in the document.body, +* then inserts the chosen beast +* then removes itself as a listener +*/ function beastify(request, sender, sendResponse) { removeEverything(); - insertBeast(beastNameToURL(request.beast)); + insertBeast(request.beastURL); chrome.runtime.onMessage.removeListener(beastify); } +/* +Remove every node under document.body +*/ function removeEverything() { while (document.body.firstChild) { document.body.firstChild.remove(); } } +/* +Given a URL to a beast image, create and style an IMG node pointing to +that image, then insert the node into the document. +*/ function insertBeast(beastURL) { var beastImage = document.createElement("img"); beastImage.setAttribute("src", beastURL); @@ -21,6 +31,15 @@ function insertBeast(beastURL) { document.body.appendChild(beastImage); } +/* +Assign beastify() as a listener for messages from the extension. +*/ +chrome.runtime.onMessage.addListener(beastify); + + +/* +Given the name of a beast, get the URL to the corresponding image. +*/ function beastNameToURL(beastName) { switch (beastName) { case "Frog": diff --git a/beastify/icons/LICENSE b/beastify/icons/LICENSE new file mode 100644 index 0000000..9a206f8 --- /dev/null +++ b/beastify/icons/LICENSE @@ -0,0 +1,4 @@ + +The icon "beasts-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/. + +The icon "beasts-48.png" is taken from Aha-Soft’s Free Retina iconset, and used under the terms of its license (http://www.aha-soft.com/free-icons/free-retina-icon-set/), with a link back to the website: http://www.aha-soft.com/. diff --git a/beastify/button/beasts.png b/beastify/icons/beasts-32.png similarity index 100% rename from beastify/button/beasts.png rename to beastify/icons/beasts-32.png diff --git a/beastify/icons/beasts-48.png b/beastify/icons/beasts-48.png new file mode 100644 index 0000000..c3f0924 Binary files /dev/null and b/beastify/icons/beasts-48.png differ diff --git a/beastify/manifest.json b/beastify/manifest.json index bce8efa..45d990e 100644 --- a/beastify/manifest.json +++ b/beastify/manifest.json @@ -4,20 +4,24 @@ "manifest_version": 2, "name": "Beastify", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/beastify", + "icons": { + "48": "icons/beasts-48.png" + }, "applications": { "gecko": { - "id": "beastify@mozilla.org" + "id": "beastify@mozilla.org", + "strict_min_version": "45.0.0" } }, "permissions": [ - "http://*/*", - "https://*/*" + "activeTab" ], "browser_action": { - "default_icon": "button/beasts.png", + "default_icon": "icons/beasts-32.png", "default_title": "Beastify", "default_popup": "popup/choose_beast.html" }, diff --git a/beastify/popup/choose_beast.css b/beastify/popup/choose_beast.css index f8d1e8d..e439cb0 100644 --- a/beastify/popup/choose_beast.css +++ b/beastify/popup/choose_beast.css @@ -1,14 +1,10 @@ html, body { - height: 100px; width: 100px; - margin: 0; } .beast { - height: 30%; - width: 90%; margin: 3% auto; - padding-top: 6%; + padding: 4px; text-align: center; font-size: 1.5em; background-color: #E5F2F2; diff --git a/beastify/popup/choose_beast.js b/beastify/popup/choose_beast.js index 497be28..e282e4f 100644 --- a/beastify/popup/choose_beast.js +++ b/beastify/popup/choose_beast.js @@ -1,3 +1,18 @@ +/* +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"); + case "Snake": + return chrome.extension.getURL("beasts/snake.jpg"); + case "Turtle": + return chrome.extension.getURL("beasts/turtle.jpg"); + } +} + + /* Listen for clicks in the popup. @@ -5,12 +20,10 @@ If the click is not on one of the beasts, return early. Otherwise, the text content of the node is the name of the beast we want. -Run the "beastify.js" content script in the active tab, calling setBeast() -once the content script has executed. +Inject the "beastify.js" content script in the active tab. -Inside setBeast(), get the active tab, then send it a message containing -the chosen beast's name. This message will be received by the content script -we just executed. +Then get the active tab and send "beastify.js" a message +containing the URL to the chosen beast's image. */ document.addEventListener("click", function(e) { if (!e.target.classList.contains("beast")) { @@ -18,13 +31,14 @@ document.addEventListener("click", function(e) { } var chosenBeast = e.target.textContent; - + var chosenBeastURL = beastNameToURL(chosenBeast); + chrome.tabs.executeScript(null, { - file: "content_scripts/beastify.js" + file: "/content_scripts/beastify.js" }); chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - chrome.tabs.sendMessage(tabs[0].id, {beast: chosenBeast}); + chrome.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL}); }); }); diff --git a/borderify/README.md b/borderify/README.md new file mode 100644 index 0000000..ea3b498 --- /dev/null +++ b/borderify/README.md @@ -0,0 +1,14 @@ +# borderify + +## What it does + +This extension just includes: + +* a content script, "borderify.js", that is injected into any pages +under "mozilla.org/" or any of its subdomains + +The content script draws a border around the document.body. + +## What it shows + +* how to inject content scripts declaratively using manifest.json diff --git a/borderify/borderify.js b/borderify/borderify.js index 9c3728b..fa08e19 100644 --- a/borderify/borderify.js +++ b/borderify/borderify.js @@ -1 +1,4 @@ +/* +Just draw a border round the document.body. +*/ document.body.style.border = "5px solid red"; diff --git a/borderify/icons/LICENSE b/borderify/icons/LICENSE new file mode 100644 index 0000000..9f3eac1 --- /dev/null +++ b/borderify/icons/LICENSE @@ -0,0 +1 @@ +The icon “border-48.png” is taken from the Google Material Design iconset, and is used under the terms of the Creative Commons Attribution-ShareAlike license: http://creativecommons.org/licenses/by-sa/3.0/. diff --git a/borderify/icons/border-48.png b/borderify/icons/border-48.png new file mode 100644 index 0000000..90687de Binary files /dev/null and b/borderify/icons/border-48.png differ diff --git a/borderify/manifest.json b/borderify/manifest.json index b855df9..44a188f 100644 --- a/borderify/manifest.json +++ b/borderify/manifest.json @@ -4,10 +4,15 @@ "manifest_version": 2, "name": "Borderify", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/borderify", + "icons": { + "48": "icons/border-48.png" + }, "applications": { "gecko": { - "id": "borderify@mozilla.org" + "id": "borderify@mozilla.org", + "strict_min_version": "45.0.0" } }, diff --git a/chill-out/README.md b/chill-out/README.md index 4005df3..93c09ad 100644 --- a/chill-out/README.md +++ b/chill-out/README.md @@ -1 +1,24 @@ # chill-out + +## 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. + +When the user clicks the page action, +navigate to http://chilloutandwatchsomecatgifs.com/. + +"N" is set to 6 seconds in this example. Such a short period is chosen to make +the extension's behavior more obvious, but this is not recommended in real life. +Note that in Chrome, alarms cannot be set for less than a minute. In Chrome: + +* if you install this extension "unpacked", you'll see a warning +in the console, but the alarm will still go off after 6 seconds +* if you package the extension and install it, then the alarm will go off after +a minute. + +## What it shows + +* how to use various `tabs` functions +* how to show/hide a page action +* how to set alarms and handle alarms going off diff --git a/chill-out/background.js b/chill-out/background.js index 4920409..696a76c 100644 --- a/chill-out/background.js +++ b/chill-out/background.js @@ -1,15 +1,25 @@ +/* +DELAY is set to 6 seconds in this example. Such a short period is chosen to make +the extension's behavior more obvious, but this is not recommended in real life. +Note that in Chrome, alarms cannot be set for less than a minute. In Chrome: + +* if you install this extension "unpacked", you'll see a warning +in the console, but the alarm will still go off after 6 seconds +* if you package the extension and install it, then the alarm will go off after +a minute. +*/ var DELAY = 0.1; var CATGIFS = "http://chilloutandwatchsomecatgifs.com/"; /* -Start-stop for the currently active tab, whenever this script is run. +Restart alarm for the currently active tab, whenever background.js is run. */ chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - startStopAlarm(tabs[0].id); + restartAlarm(tabs[0].id); }); /* -Start-stop for the currently active tab, whenever the user navigates. +Restart alarm for the currently active tab, whenever the user navigates. */ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (!changeInfo.url) { @@ -17,28 +27,28 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { } chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { if (tabId == tabs[0].id) { - startStopAlarm(tabId); + restartAlarm(tabId); } }); }); /* -Start-stop for the currently active tab, whenever a new tab becomes active. +Restart alarm for the currently active tab, whenever a new tab becomes active. */ chrome.tabs.onActivated.addListener(function (activeInfo) { - startStopAlarm(activeInfo.tabId); + restartAlarm(activeInfo.tabId); }); /* -Start-stop: clear all alarms, +restartAlarm: clear all alarms, then set a new alarm for the given tab. */ -function startStopAlarm(tabId) { +function restartAlarm(tabId) { chrome.pageAction.hide(tabId); chrome.alarms.clearAll(); chrome.tabs.get(tabId, function(tab) { if (tab.url != CATGIFS) { - chrome.alarms.create(tabId, {delayInMinutes: DELAY}); + chrome.alarms.create("", {delayInMinutes: DELAY}); } }); } @@ -48,7 +58,7 @@ On alarm, show the page action. */ chrome.alarms.onAlarm.addListener(function(alarm) { chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - chrome.pageAction.show(alarm.name); + chrome.pageAction.show(tabs[0].id); }); }); diff --git a/chill-out/button/LICENSE b/chill-out/button/LICENSE deleted file mode 100644 index 50b37ce..0000000 --- a/chill-out/button/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The icon "chillout.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/. diff --git a/chill-out/icons/LICENSE b/chill-out/icons/LICENSE new file mode 100644 index 0000000..2294057 --- /dev/null +++ b/chill-out/icons/LICENSE @@ -0,0 +1,3 @@ +The icon "chillout-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/. + +The icon "chillout-48.png" is taken from Aha-Soft’s Free Retina iconset, and used under the terms of its license (http://www.aha-soft.com/free-icons/free-retina-icon-set/), with a link back to the website: http://www.aha-soft.com/. diff --git a/chill-out/button/chillout.png b/chill-out/icons/chillout-32.png similarity index 100% rename from chill-out/button/chillout.png rename to chill-out/icons/chillout-32.png diff --git a/chill-out/icons/chillout-48.png b/chill-out/icons/chillout-48.png new file mode 100644 index 0000000..dc456e5 Binary files /dev/null and b/chill-out/icons/chillout-48.png differ diff --git a/chill-out/manifest.json b/chill-out/manifest.json index edc4c28..ff9cd30 100644 --- a/chill-out/manifest.json +++ b/chill-out/manifest.json @@ -1,4 +1,12 @@ { + "manifest_version": 2, + "name": "Chill out", + "version": "1.0", + "description": "Show a page action after a period of inactivity. Show cat gifs when the page action is clicked.", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/chill-out", + "icons": { + "48": "icons/chillout-48.png" + }, "manifest_version": 2, "name": "chillout-page-action", @@ -11,11 +19,12 @@ }, "permissions": [ + "alarms", "tabs" ], "page_action": { - "default_icon": "button/chillout.png", + "default_icon": "icons/chillout-32.png", "default_title": "Chill out" }, diff --git a/inpage-toolbar-ui/README.md b/inpage-toolbar-ui/README.md new file mode 100644 index 0000000..29d5b80 --- /dev/null +++ b/inpage-toolbar-ui/README.md @@ -0,0 +1,27 @@ +# inpage-toolbar-ui + +## What it does ## + +The extension includes: + +* a browser action which enables/disables the in-page toolbar +* a content script which creates/removes the in-page toolbar iframe +* the toolbar ui resources, packaged as web accessible resources + +When the user clicks the browser action button, a toolbar is shown/hidden +in the current web page. + +The toolbar UI is packaged in the add-on resources, exposed to the current +web page as a web accessible resource and injected into the page by the +content script by creating and injecting into the page an iframe which +points to the toolbar UI page. + +## What it shows ## + +How to expose an in-page toolbar UI by creating an iframe: + +* use web accessible resources to enable web pages to load packaged content +* use a content script to create and inject in a web page an iframe which points to the + packaged content +* use the same API enabled in content scripts (but from the add-on iframe) + to exchange messages directly with the add-on background page diff --git a/inpage-toolbar-ui/background.js b/inpage-toolbar-ui/background.js new file mode 100644 index 0000000..49aab61 --- /dev/null +++ b/inpage-toolbar-ui/background.js @@ -0,0 +1,17 @@ +// Send a message to the current tab's content script. +function toggleToolbar() { + chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { + chrome.tabs.sendMessage(tabs[0].id, "toggle-in-page-toolbar"); + }); +} + +// Handle the browser action button. +chrome.browserAction.onClicked.addListener(toggleToolbar); + +// Handle connections received from the add-on toolbar ui iframes. +chrome.runtime.onConnect.addListener(function (port) { + if (port.sender.url == chrome.runtime.getURL("toolbar/ui.html")) { + // Handle port messages received from the connected toolbar ui frames. + port.onMessage.addListener(toggleToolbar); + } +}); diff --git a/inpage-toolbar-ui/contentscript.js b/inpage-toolbar-ui/contentscript.js new file mode 100644 index 0000000..e99e84e --- /dev/null +++ b/inpage-toolbar-ui/contentscript.js @@ -0,0 +1,36 @@ +var toolbarUI; + +// Create the toolbar ui iframe and inject it in the current page +function initToolbar() { + var iframe = document.createElement("iframe"); + iframe.setAttribute("src", chrome.runtime.getURL("toolbar/ui.html")); + iframe.setAttribute("style", "position: fixed; top: 0; left: 0; z-index: 10000; width: 100%; height: 36px;"); + document.body.appendChild(iframe); + + return toolbarUI = { + iframe: iframe, visible: true + }; +} + +function toggleToolbar(toolbarUI) { + if (toolbarUI.visible) { + toolbarUI.visible = false; + toolbarUI.iframe.style["display"] = "none"; + } else { + toolbarUI.visible = true; + toolbarUI.iframe.style["display"] = "block"; + } +} + +// Handle messages from the add-on background page (only in top level iframes) +if (window.parent == window) { + chrome.runtime.onMessage.addListener(function(msg) { + if (msg == "toggle-in-page-toolbar") { + if (toolbarUI) { + toggleToolbar(toolbarUI); + } else { + toolbarUI = initToolbar(); + } + } + }); +} diff --git a/inpage-toolbar-ui/icons/32.png b/inpage-toolbar-ui/icons/32.png new file mode 100644 index 0000000..20345a7 Binary files /dev/null and b/inpage-toolbar-ui/icons/32.png differ diff --git a/inpage-toolbar-ui/icons/48.png b/inpage-toolbar-ui/icons/48.png new file mode 100644 index 0000000..e9896d2 Binary files /dev/null and b/inpage-toolbar-ui/icons/48.png differ diff --git a/inpage-toolbar-ui/manifest.json b/inpage-toolbar-ui/manifest.json new file mode 100644 index 0000000..b8994f8 --- /dev/null +++ b/inpage-toolbar-ui/manifest.json @@ -0,0 +1,40 @@ +{ + "description": "Adds a browser action icon to the toolbar. Click the button to inject an in-page toolbar UI into the current webpage. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#inpage-toolbar-ui", + "manifest_version": 2, + "name": "In Page Toolbar UI", + "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/inpage-toolbar-ui", + "icons": { + "48": "icons/48.png" + }, + + "permissions": [], + + "background": { + "scripts": ["background.js"] + }, + + "browser_action": { + "default_icon": "icons/32.png", + "default_title": "In Page Toolbar" + }, + + "content_scripts": [ + { + "js": ["contentscript.js"], + "run_at": "document_idle", + "matches": [""] + } + ], + + "web_accessible_resources": [ + "toolbar/ui.html" + ], + + "applications": { + "gecko": { + "id": "inpage-toolbar-ui@mozilla.org", + "strict_min_version": "46.0.0" + } + } +} diff --git a/inpage-toolbar-ui/toolbar/ui.css b/inpage-toolbar-ui/toolbar/ui.css new file mode 100644 index 0000000..9a81ae4 --- /dev/null +++ b/inpage-toolbar-ui/toolbar/ui.css @@ -0,0 +1,62 @@ +body { + overflow: hidden; + color: black; + background: rgba(255,255,255,0.9); +} + +#rainbow { + background: linear-gradient(0deg, rgba(217,26,18,0.70) 15%, rgba(225,51,0,0.70) 15%, rgba(255, 127, 20, 0.70) 16%, rgba(242, 171, 3, 0.70) 32%, rgba(235, 192, 0, 0.70) 32%, rgba(250, 222, 0, 0.70) 33%, rgba(239, 255, 3, 0.70) 48%, rgba(86, 252, 2, 0.70) 49%, rgba(82, 255, 1, 0.70) 66%, rgba(74, 222, 126, 0.70) 67%, rgba(59, 170, 242, 0.70) 67%, rgba(59, 170, 242, 0.70) 84%, rgba(115, 55, 247, 0.70) 84%, rgba(107, 64, 242, 0.70) 100%); + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 6px; +} + +#title { + font-weight: bold; + font-size: 1em; +} + +.whimsy { + width: 36px; + height: 36px; +} + +/* Toggle Button. */ + +#toggle { + top: 8px; + right: 4px; + position: absolute; + margin: 0 1em; + text-decoration: underline; + border-radius: 1em; + background: transparent linear-gradient(0deg, rgb(255, 162, 0), rgba(189, 122, 6, 0.66)); +} + +/* Annoying animation. */ + +@keyframes wobbling { + 50% { + transform: translateY(-13px); + } + + 100% { + transform: translateY(0px); + } +} + +.wobbling { + display: inline-block; + vertical-align: middle; + transform: translateZ(0); + box-shadow: 0 0 1px rgba(0, 0, 0, 0); + backface-visibility: hidden; + + animation-name: wobbling; + animation-duration: 1.25s; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-fill-mode: both; +} diff --git a/inpage-toolbar-ui/toolbar/ui.html b/inpage-toolbar-ui/toolbar/ui.html new file mode 100644 index 0000000..981bcf3 --- /dev/null +++ b/inpage-toolbar-ui/toolbar/ui.html @@ -0,0 +1,17 @@ + + + + + + + + +
+ + In-Page "Amazing and super-useful" Toolbar +
+ + + + diff --git a/inpage-toolbar-ui/toolbar/ui.js b/inpage-toolbar-ui/toolbar/ui.js new file mode 100644 index 0000000..4d67626 --- /dev/null +++ b/inpage-toolbar-ui/toolbar/ui.js @@ -0,0 +1,8 @@ +// Connect to the background page. +var port = chrome.runtime.connect(); + +// Handle click events on the toolbar button. +document.querySelector("#toggle").addEventListener("click", function() { + // Ask the background page to toggle the toolbar on the current tab + port.postMessage("toggle-in-page-toolbar"); +}); diff --git a/inpage-toolbar-ui/toolbar/whimsy.png b/inpage-toolbar-ui/toolbar/whimsy.png new file mode 100644 index 0000000..261ee19 Binary files /dev/null and b/inpage-toolbar-ui/toolbar/whimsy.png differ diff --git a/notify-link-clicks-i18n/LICENSE b/notify-link-clicks-i18n/LICENSE deleted file mode 100644 index 599b1c6..0000000 --- a/notify-link-clicks-i18n/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The "link.png" icon is taken from the Geomicons iconset, and is used here under the MIT license: http://opensource.org/licenses/MIT. diff --git a/notify-link-clicks-i18n/README.md b/notify-link-clicks-i18n/README.md new file mode 100644 index 0000000..752bf72 --- /dev/null +++ b/notify-link-clicks-i18n/README.md @@ -0,0 +1,25 @@ +# notify-link-clicks-i18n + +## What it does + +This extension includes: + +* a content script, "content-script.js", that is injected into all pages +* a background script, "background-script.js" + +The content script listens for clicks in the page it's attached to. +If a click is on a link, the content script sends the link's href +to the background script. + +The background script listens for this message. When the background script +receives the message, it displays a notification containing the href. + +The notification's content, as well as the extension's name and description, are +localized into German and Dutch, as well as en-US. + +# What it shows + +* how to inject content scripts declaratively using manifest.json +* how to send messages from a content script to a background script +* how to display system notifications using the notifications API +* how to use the internationalization (i18n) system diff --git a/notify-link-clicks-i18n/_locales/en/messages.json b/notify-link-clicks-i18n/_locales/en/messages.json index c7d1597..d0e720b 100644 --- a/notify-link-clicks-i18n/_locales/en/messages.json +++ b/notify-link-clicks-i18n/_locales/en/messages.json @@ -1,6 +1,6 @@ { "extensionName": { - "message": "Notify link clicks", + "message": "Notify link clicks i18n", "description": "Name of the extension." }, diff --git a/notify-link-clicks-i18n/_locales/ja/messages.json b/notify-link-clicks-i18n/_locales/ja/messages.json new file mode 100644 index 0000000..e9c1966 --- /dev/null +++ b/notify-link-clicks-i18n/_locales/ja/messages.json @@ -0,0 +1,21 @@ +{ + "extensionName": { + "message": "リンクを通知する", + "description": "拡張機能の名前です。" + }, + + "extensionDescription": { + "message": "ユーザーがリンクをクリックした時通知を表示します。", + "description": "拡張機能の説明です。" + }, + + "notificationTitle": { + "message": "クリック通知", + "description": "pushのタイトルです。" + }, + + "notificationContent": { + "message": "$1がクリックされました。", + "description": "リンクをクリックした時通知を表示します。:変数$1にはurlが代入されます。" + } +} diff --git a/notify-link-clicks-i18n/background-script.js b/notify-link-clicks-i18n/background-script.js index 24d5ccb..fad6458 100644 --- a/notify-link-clicks-i18n/background-script.js +++ b/notify-link-clicks-i18n/background-script.js @@ -1,13 +1,21 @@ -chrome.runtime.onMessage.addListener(notify); - +/* +Log that we received the message. +Then display a notification. The notification contains the URL, +which we read from the message. +*/ function notify(message) { console.log("background script received message"); var title = chrome.i18n.getMessage("notificationTitle"); var content = chrome.i18n.getMessage("notificationContent", message.url); chrome.notifications.create({ "type": "basic", - "iconUrl": chrome.extension.getURL("link.png"), + "iconUrl": chrome.extension.getURL("icons/link-48.png"), "title": title, "message": content }); } + +/* +Assign `notify()` as a listener to messages from the content script. +*/ +chrome.runtime.onMessage.addListener(notify); diff --git a/notify-link-clicks-i18n/content-script.js b/notify-link-clicks-i18n/content-script.js index 47899bb..ffe4610 100644 --- a/notify-link-clicks-i18n/content-script.js +++ b/notify-link-clicks-i18n/content-script.js @@ -1,9 +1,20 @@ -window.addEventListener("click", notifyExtension); - +/* +If the click was on a link, send a message to the background page. +The message contains the link's URL. +*/ function notifyExtension(e) { - console.log("content script sending message"); - if (e.target.tagName != "A") { - return; + var target = e.target; + while ((target.tagName != "A" || !target.href) && target.parentNode) { + target = target.parentNode; } - chrome.runtime.sendMessage({"url": e.target.href}); + if (target.tagName != "A") + return; + + console.log("content script sending message"); + chrome.runtime.sendMessage({"url": target.href}); } + +/* +Add notifyExtension() as a listener to click events. +*/ +window.addEventListener("click", notifyExtension); diff --git a/notify-link-clicks-i18n/icons/LICENSE b/notify-link-clicks-i18n/icons/LICENSE new file mode 100644 index 0000000..3bab068 --- /dev/null +++ b/notify-link-clicks-i18n/icons/LICENSE @@ -0,0 +1 @@ +The "link-48.png" icon is taken from the Geomicons iconset, and is used here under the MIT license: http://opensource.org/licenses/MIT. diff --git a/notify-link-clicks-i18n/link.png b/notify-link-clicks-i18n/icons/link-48.png similarity index 100% rename from notify-link-clicks-i18n/link.png rename to notify-link-clicks-i18n/icons/link-48.png diff --git a/notify-link-clicks-i18n/manifest.json b/notify-link-clicks-i18n/manifest.json index 7cc33c3..fbf5f00 100644 --- a/notify-link-clicks-i18n/manifest.json +++ b/notify-link-clicks-i18n/manifest.json @@ -4,9 +4,15 @@ "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/notify-link-clicks-i18n", + "icons": { + "48": "icons/link-48.png" + }, + "applications": { "gecko": { - "id": "notify-link-clicks@mozilla.org" + "id": "notify-link-clicks-i18n@mozilla.org", + "strict_min_version": "45.0.0" } }, diff --git a/notify-link-clicks/LICENSE b/notify-link-clicks/LICENSE deleted file mode 100644 index 599b1c6..0000000 --- a/notify-link-clicks/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The "link.png" icon is taken from the Geomicons iconset, and is used here under the MIT license: http://opensource.org/licenses/MIT. diff --git a/notify-link-clicks/background-script.js b/notify-link-clicks/background-script.js deleted file mode 100644 index d12caa9..0000000 --- a/notify-link-clicks/background-script.js +++ /dev/null @@ -1,11 +0,0 @@ -chrome.runtime.onMessage.addListener(notify); - -function notify(message) { - console.log("background script received message"); - chrome.notifications.create({ - "type": "basic", - "iconUrl": chrome.extension.getURL("link.png"), - "title": "You clicked a link!", - "message": message.url - }); -} diff --git a/notify-link-clicks/content-script.js b/notify-link-clicks/content-script.js deleted file mode 100644 index 47899bb..0000000 --- a/notify-link-clicks/content-script.js +++ /dev/null @@ -1,9 +0,0 @@ -window.addEventListener("click", notifyExtension); - -function notifyExtension(e) { - console.log("content script sending message"); - if (e.target.tagName != "A") { - return; - } - chrome.runtime.sendMessage({"url": e.target.href}); -} diff --git a/notify-link-clicks/link.png b/notify-link-clicks/link.png deleted file mode 100644 index 17d7da7..0000000 Binary files a/notify-link-clicks/link.png and /dev/null differ diff --git a/notify-link-clicks/manifest.json b/notify-link-clicks/manifest.json deleted file mode 100644 index b125e74..0000000 --- a/notify-link-clicks/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - - "description": "Displays notifications when the user clicks links. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#notify-link-clicks", - "manifest_version": 2, - "name": "Notify link clicks", - "version": "1.0", - "applications": { - "gecko": { - "id": "notify-link-clicks@mozilla.org" - } - }, - - "permissions": ["notifications"], - - "background": { - "scripts": ["background-script.js"] - }, - - "content_scripts": [ - { - "matches": [""], - "js": ["content-script.js"] - } - ] -} diff --git a/open-my-page-button/background.js b/open-my-page-button/background.js index d2afdff..d627e4a 100644 --- a/open-my-page-button/background.js +++ b/open-my-page-button/background.js @@ -1,5 +1,11 @@ + /* + Add openMyPage() as a listener to clicks on the browser action. + */ chrome.browserAction.onClicked.addListener(openMyPage); + /* + Open a new tab, and load "my-page.html" into it. + */ function openMyPage() { console.log("injecting"); chrome.tabs.create({ diff --git a/open-my-page-button/button/LICENSE b/open-my-page-button/button/LICENSE deleted file mode 100644 index c3727aa..0000000 --- a/open-my-page-button/button/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The icon "beasts.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/. diff --git a/open-my-page-button/button/beasts.png b/open-my-page-button/button/beasts.png deleted file mode 100644 index bcf6379..0000000 Binary files a/open-my-page-button/button/beasts.png and /dev/null differ diff --git a/open-my-page-button/icons/LICENSE b/open-my-page-button/icons/LICENSE new file mode 100644 index 0000000..20e821d --- /dev/null +++ b/open-my-page-button/icons/LICENSE @@ -0,0 +1,2 @@ + +The "page-32.png" and "page-48.png" icons are taken from the miu iconset created by Linh Pham Thi Dieu, and are used under the terms of its license: http://linhpham.me/miu/. diff --git a/open-my-page-button/icons/page-32.png b/open-my-page-button/icons/page-32.png new file mode 100644 index 0000000..dae663d Binary files /dev/null and b/open-my-page-button/icons/page-32.png differ diff --git a/open-my-page-button/icons/page-48.png b/open-my-page-button/icons/page-48.png new file mode 100644 index 0000000..ba042cd Binary files /dev/null and b/open-my-page-button/icons/page-48.png differ diff --git a/open-my-page-button/manifest.json b/open-my-page-button/manifest.json index a2f116a..0d8815f 100644 --- a/open-my-page-button/manifest.json +++ b/open-my-page-button/manifest.json @@ -4,23 +4,24 @@ "manifest_version": 2, "name": "open-my-page", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/open-my-page-button", + "icons": { + "48": "icons/page-48.png" + }, "applications": { "gecko": { - "id": "open-my-page-button@mozilla.org" + "id": "open-my-page-button@mozilla.org", + "strict_min_version": "45.0.0" } }, - "permissions": [ - "tabs" - ], - "background": { "scripts": ["background.js"] }, "browser_action": { - "default_icon": "button/beasts.png" + "default_icon": "icons/page-32.png" } } diff --git a/page-to-extension-messaging/content-script.js b/page-to-extension-messaging/content-script.js index b3ab008..1323af1 100644 --- a/page-to-extension-messaging/content-script.js +++ b/page-to-extension-messaging/content-script.js @@ -1,3 +1,7 @@ +/* +Listen for messages from the page. +If the message was from the page script, show an alert. +*/ window.addEventListener("message", function(event) { if (event.source == window && event.data.direction && @@ -6,13 +10,19 @@ window.addEventListener("message", function(event) { } }); +/* +Add messagePageScript() as a listener to click events on +the "from-content-script" element. +*/ var fromContentScript = document.getElementById("from-content-script"); - fromContentScript.addEventListener("click", messagePageScript); +/* +Send a message to the page script. +*/ function messagePageScript() { window.postMessage({ direction: "from-content-script", message: "Message from the content script" - }, "*"); + }, "https://mdn.github.io"); } diff --git a/page-to-extension-messaging/icons/LICENSE b/page-to-extension-messaging/icons/LICENSE new file mode 100644 index 0000000..a414ec0 --- /dev/null +++ b/page-to-extension-messaging/icons/LICENSE @@ -0,0 +1,2 @@ + +The "message-48.png" icon is taken from the miu iconset created by Linh Pham Thi Dieu, and is used under the terms of its license: http://linhpham.me/miu/. diff --git a/page-to-extension-messaging/icons/message-48.png b/page-to-extension-messaging/icons/message-48.png new file mode 100644 index 0000000..cf77c5a Binary files /dev/null and b/page-to-extension-messaging/icons/message-48.png differ diff --git a/page-to-extension-messaging/manifest.json b/page-to-extension-messaging/manifest.json index e195526..bbbd458 100644 --- a/page-to-extension-messaging/manifest.json +++ b/page-to-extension-messaging/manifest.json @@ -4,10 +4,15 @@ "name": "Page to extension messaging", "description": "Visit https://mdn.github.io/webextensions-examples/content-script-page-script-messaging.html for the demo. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#page-to-extension-messaging", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/page-to-extension-messaging", + "icons": { + "48": "icons/message-48.png" + }, "applications": { "gecko": { - "id": "page-to-extension-messaging@mozilla.org" + "id": "page-to-extension-messaging@mozilla.org", + "strict_min_version": "45.0.0" } }, diff --git a/user-agent-rewriter/background.js b/user-agent-rewriter/background.js index 545d35a..40255f6 100644 --- a/user-agent-rewriter/background.js +++ b/user-agent-rewriter/background.js @@ -1,19 +1,37 @@ "use strict"; +/* +This is the page for which we want to rewrite the User-Agent header. +*/ var targetPage = "http://useragentstring.com/*"; +/* +Map browser names to UA strings. +*/ var uaStrings = { "Firefox 41": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0", "Chrome 41": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", "IE 11": "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" } +/* +Initialize the UA to Firefox 41. +*/ var ua = uaStrings["Firefox 41"]; +/* +Add rewriteUserAgentHeader as a listener to onBeforeSendHeaders, +only for the target page. + +Make it "blocking" so we can modify the headers. +*/ chrome.webRequest.onBeforeSendHeaders.addListener(rewriteUserAgentHeader, {urls: [targetPage]}, ["blocking", "requestHeaders"]); +/* +Rewrite the User-Agent header to "ua". +*/ function rewriteUserAgentHeader(e) { for (var header of e.requestHeaders) { if (header.name == "User-Agent") { @@ -23,8 +41,9 @@ function rewriteUserAgentHeader(e) { return {requestHeaders: e.requestHeaders}; } -chrome.runtime.onMessage.addListener(setUaString); - -function setUaString(message) { - ua = uaStrings[message.uaString]; +/* +Update ua to a new value, mapped from the uaString parameter. +*/ +function setUaString(uaString) { + ua = uaStrings[uaString]; } diff --git a/user-agent-rewriter/button/LICENSE b/user-agent-rewriter/button/LICENSE deleted file mode 100644 index 5a4c0a5..0000000 --- a/user-agent-rewriter/button/LICENSE +++ /dev/null @@ -1 +0,0 @@ -The icon “choose_ua.png” is taken from Yummygum’s Iconsweets iconset, and is used under the terms of its license (http://yummygum.com/work/iconsweets). diff --git a/user-agent-rewriter/button/choose_ua.png b/user-agent-rewriter/button/choose_ua.png deleted file mode 100644 index 08b9108..0000000 Binary files a/user-agent-rewriter/button/choose_ua.png and /dev/null differ diff --git a/user-agent-rewriter/icons/LICENSE b/user-agent-rewriter/icons/LICENSE new file mode 100644 index 0000000..18fae49 --- /dev/null +++ b/user-agent-rewriter/icons/LICENSE @@ -0,0 +1 @@ +The "person-32.png" "person-48.png" icons are taken from the Ionicons iconset (http://ionicons.com/), and are used here under the MIT license: http://opensource.org/licenses/MIT. diff --git a/user-agent-rewriter/icons/person-32.png b/user-agent-rewriter/icons/person-32.png new file mode 100644 index 0000000..38a16bd Binary files /dev/null and b/user-agent-rewriter/icons/person-32.png differ diff --git a/user-agent-rewriter/icons/person-48.png b/user-agent-rewriter/icons/person-48.png new file mode 100644 index 0000000..0cb787b Binary files /dev/null and b/user-agent-rewriter/icons/person-48.png differ diff --git a/user-agent-rewriter/manifest.json b/user-agent-rewriter/manifest.json index 7a2ad4f..f8bec37 100644 --- a/user-agent-rewriter/manifest.json +++ b/user-agent-rewriter/manifest.json @@ -4,10 +4,15 @@ "manifest_version": 2, "name": "user-agent-rewriter", "version": "1.0", + "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/user-agent-rewriter", + "icons": { + "48": "icons/person-48.png" + }, "applications": { "gecko": { - "id": "user-agent-rewriter@mozilla.org" + "id": "user-agent-rewriter@mozilla.org", + "strict_min_version": "45.0.0" } }, @@ -18,9 +23,9 @@ "background": { "scripts": ["background.js"] }, - + "browser_action": { - "default_icon": "button/choose_ua.png", + "default_icon": "icons/person-32.png", "default_title": "Choose a user agent", "default_popup": "popup/choose_ua.html" } diff --git a/user-agent-rewriter/popup/choose_ua.css b/user-agent-rewriter/popup/choose_ua.css index 2815fd5..9e15677 100644 --- a/user-agent-rewriter/popup/choose_ua.css +++ b/user-agent-rewriter/popup/choose_ua.css @@ -1,14 +1,19 @@ -html, body { +html, body, .ua-choices { height: 100px; - width: 100px; + width: 120px; margin: 0; } +.ua-choices { + display: flex; + flex-direction: column; + justify-content: space-around; +} + .ua-choice { height: 20%; - width: 90%; - margin: 3% auto; - padding: 8% 6% 0 6%; + margin: 0.2em; + padding: 0.2em; background-color: #E5F2F2; cursor: pointer; } diff --git a/user-agent-rewriter/popup/choose_ua.html b/user-agent-rewriter/popup/choose_ua.html index 6f5cee3..837307d 100644 --- a/user-agent-rewriter/popup/choose_ua.html +++ b/user-agent-rewriter/popup/choose_ua.html @@ -8,9 +8,11 @@ -
Firefox 41
-
Chrome 41
-
IE 11
+
+
Firefox 41
+
Chrome 41
+
IE 11
+
diff --git a/user-agent-rewriter/popup/choose_ua.js b/user-agent-rewriter/popup/choose_ua.js index f7d4f88..0a1869b 100644 --- a/user-agent-rewriter/popup/choose_ua.js +++ b/user-agent-rewriter/popup/choose_ua.js @@ -1,12 +1,15 @@ + +/* +If the user clicks on an element which has the class "ua-choice": +* fetch the element's textContent: for example, "IE 11" +* pass it into the background page's setUaString() function +*/ document.addEventListener("click", function(e) { if (!e.target.classList.contains("ua-choice")) { return; } var chosenUa = e.target.textContent; - - chrome.runtime.sendMessage({ - "command": "set-user-agent", - "uaString": chosenUa - }); + var backgroundPage = chrome.extension.getBackgroundPage(); + backgroundPage.setUaString(chosenUa); });