Developing our first browser extension
NB: you need a browser with
JavaScript enabled
to watch this presentation!
tripu
name: cover #
Developing our first browser extension .footnote[[MadridJS](https://www.meetup.com/madridjs/) / [Spotahome](https://www.spotahome.com/)
Copyright © 2019 [tripu](https://tripu.info/)
.nb[(Press `→` or swipe left)]] --- name: agenda .footer[*Developing our first browser extension*] ## Agenda * [`$ whoami`](#me) * [Solving a personal need with a simple extension](#need) * [Anatomy of an extension](#anatomy) * [`manifest.json`](#manifest) * [How to make it cross-browser](#cross) * [Debugging](#debugging) * [Publishing our extension](#publishing) * [Tips & tricks](#tips) * [Resources](#resources) * [Questions & comments](#qa) --- name: me .footer[*Developing our first browser extension*] ## $ whoami .twoCols[ .column[ .center[ [.portrait[![Portrait](tripu-640.jpeg)]](https://tripu.info/) **tripu** [`t@tripu.info`](mailto:t@tripu.info) [`https://tripu.info`](https://tripu.info/) ] ] .column[ .center[ 4 years at the [.logow[![w3c-logo.png](w3c-logo.png)]](https://www.w3.org/)
*Software engineer* at [.logod[![Logo](devo-logo.png)]](https://www.devo.com/careers/openings/) (we are hiring!) ] ] ] --- name: need .footer[*Developing our first browser extension*] ## Solving a personal need with a simple extension .center[ .chunk[ Her request -> "room surface >= 9x6 m" -- quite large! ] ≠ .chunk[ Her request → “room surface ≥ 9×4 m” — quite large! ]
My (bad) solution until now: [`https://gist.github.com/tripu/8964164`](https://gist.github.com/tripu/8964164) ] --- .footer[*Developing our first browser extension*] ## Introducing Glypher (I) .center[ [![Glypher logo](glypher-logo.png)](https://github.com/tripu/glypher) [`https://github.com/tripu/glypher`](https://github.com/tripu/glypher)
![Icon](icon.png) ] --- .footer[*Developing our first browser extension*] ## Introducing Glypher (II) .center[.logot[![Pop-up](popup.png)]] --- name: anatomy .footer[*Developing our first browser extension*] ## Anatomy of an extension (I) .center[.screenshot[![d](webextension-anatomy.png)]] --- .footer[*Developing our first browser extension*] ## Anatomy of an extension (II) ```txt Glypher ├── LICENSE.md ├── .github ├── manifest.json │ └── README.md ├── pages ├── build.sh │ ├── background.html ├── dist │ ├── glyphs.html │ ├── chrome │ └── options.html │ └── firefox └── screenshots ├── img ├── icon.png │ ├── logo-256.png └── popup.png │ ├── logo-48.png │ ├── logo-96.png │ └── logo.svg ├── js │ ├── api-wrapper.js │ ├── background.js │ └── glyphs.js ``` --- .footer[*Developing our first browser extension*] ## Installing our own extension (*Temporary* extension) * **FF:** 1. `about:debugging#/runtime/this-firefox` 1. *Load Temporary Add-on…* 1. Select the *manifest* (`glypher/dist/firefox/manifest.json`) * **Chrom*:** 1. `chrome://extensions/` 1. *Developer mode* 1. *Load unpacked* 1. Select the *directory* (`glypher/dist/chrome/`) --- name: manifest .footer[*Developing our first browser extension*] ## manifest.json (I) ```json { "manifest_version": 2, "name": "Glypher", "description": "Quick copy'n'paste of miscellaneous characters and symbols", "version": "0.1.0", "homepage_url": "https://github.com/tripu/glypher", "applications": { "gecko": { "id": "glypher@tripu.info" } }, "icons": { "48": "img/logo-48.png", "96": "img/logo-96.png", "256": "img/logo-256.png" }, ⋮ ``` --- .footer[*Developing our first browser extension*] ## manifest.json (II) ```json ⋮ "browser_action": { "default_title": "Glypher [Ctrl+↑ / ⌘+↑]", "default_icon": { "48": "img/logo-48.png", "96": "img/logo-96.png", "256": "img/logo-256.png" } }, "commands": { "show": { "description": "Glypher", "suggested_key": { "default": "Ctrl+Up" } } }, ⋮ ``` --- .footer[*Developing our first browser extension*] ## manifest.json (III) ```json ⋮ "options_ui": { "browser_style": true, "chrome_style": true, "open_in_tab": false, "page": "pages/options.html" }, "background": { "page": "pages/background.html" } } ``` --- .footer[*Developing our first browser extension*] ## Options page * **FF:** 1. `about:addons` 1. *Preferences* * **Chrom*** (two ways): - `chrome://extensions/` → *Details* → *Extension options* - right-click on extension icon --- name: cross .footer[*Developing our first browser extension*] ## How to make it cross-browser MDN: * [*“Building a cross-browser extension”*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension) * [*“Differences between API implementations”*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Differences_between_API_implementations) * [*“Chrome incompatibilities”*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities) [Extension Workshop: *“Porting a Google Chrome extension”*](https://extensionworkshop.com/documentation/develop/porting-a-google-chrome-extension/) [`manifest.json` compatibility table](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json) --- name: debugging .footer[*Developing our first browser extension*] ## Debugging * **[FF](https://extensionworkshop.com/documentation/develop/debugging/):** - Background page/script: `about:debugging#/runtime/this-firefox` → *Inspect* - Pop-ups and the option page: usual developer tools * **Chrom*:** 1. `chrome://extensions/` 1. *Details* 1. Under *Inspect views*, click on the page you want to debug --- name: publishing .footer[*Developing our first browser extension*] ## Publishing our extension (FF) * [AMO (`addons.mozilla.org`)](https://addons.mozilla.org/en-GB/firefox/extensions/) * [Documentation](https://extensionworkshop.com/documentation/publish/signing-and-distribution-overview/) * No registration fee * No 2FA required * Format: ZIP (expected extension `.xpi`); *signed* * Can target [Firefox for Android](https://extensionworkshop.com/documentation/develop/developing-extensions-for-firefox-for-android/)! * Facilitated by the CLI tool [`web-ext`](https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/) * Only other ways outside AMO: - `about:debugging#/runtime/this-firefox` (*temporary*) - `about:config` → `xpinstall.signatures.required=true` --- .footer[*Developing our first browser extension*] ## Publishing our extension (Chrom*) * [Chrome Web Store](https://chrome.google.com/webstore/category/extensions) * [Documentation](https://developer.chrome.com/extensions/hosting) * Registration fee: $5 * 2FA required * Format: ZIP (extension `.crx`) * Only other ways outside Chrome Web Store: - Unpacked *temporary* extensions, under developer mode - Enterprise policy - Linux! --- name: tips .footer[*Developing our first browser extension*] ## Tips & tricks (I): an API wrapper ```js let _found_api; if ('undefined' !== typeof browser && browser.windows) _found_api = browser; else if ('undefined' !== typeof chrome && chrome.windows) _found_api = chrome; else throw new Error(`glypher/api-wrapper: ERROR: cannot detect API!`); export default _found_api; ``` ```js import api from './api-wrapper.js'; api.windows.create({url: 'pages/list.html'}); ⋮ api.browserAction.onClicked.addListener(⋯); ``` --- .footer[*Developing our first browser extension*] ## Tips & tricks (II): use pages, not scripts To use ES6 modules, [you have to specify *pages* instead of *scripts*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Specifying_background_scripts) ```json "background": { "page": "pages/background.html" } ``` ```html ``` ```js import api from './api-wrapper.js'; ⋮ ``` --- .footer[*Developing our first browser extension*] ## Tips & tricks (III): keep a single manifest.json ```sh #!/bin/sh # Common stuff: for BROWSER in firefox chrome; do rm -r dist/$BROWSER/* cp -a img/ js/ pages/ dist/$BROWSER/ done # Firefox: cat manifest.json | jq 'del(.options_ui.chrome_style)' > dist/firefox/manifest.json cd dist/firefox && zip -r -FS ../glypher-firefox-unsigned.xpi * && cd - # Chrome: cat manifest.json | jq 'del(.applications) | del(.options_ui.browser_style)' > dist/chrome/manifest.json ``` --- .footer[*Developing our first browser extension*] ## Tips & tricks (IV): check & refresh * Don't forget to `./build.sh` * Don't forget to *reload* the extension on the browser * Don't forget to clear all previous errors * Remember that in Chrom\*, `console` output is considered an *error* * If things fail, check *permissions* in `manifest.json` --- .footer[*Developing our first browser extension*] ## Worth noting: user scripts are a simpler alternative! ✓ Simpler to develop, debug, and tinker with ✓ Unbounded by security restrictions (do whatever you want) ✗ Harder to adopt for regular users ✗ Worse distribution and discoverability ✗ Associated to specific domains only ✗ Can't modify the chrome of the browser The best repository: [Greasy Fork](https://greasyfork.org/en) FF add-on: [Greasemonkey](https://addons.mozilla.org/en-GB/firefox/addon/greasemonkey/) Chrom* extension: [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) --- name: resources .footer[*Developing our first browser extension*] ## Resources * Best reference: [MDN *“Browser extensions”*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) (for FF) * [Google Chrome *“What are extensions?”*](https://developer.chrome.com/extensions) (for Chrom*) * [`caniuse.com`](https://caniuse.com/) * [MDN *“Browser support for JavaScript APIs”*](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs) * [Extension Workshop *“Security best practices”*](https://extensionworkshop.com/documentation/develop/build-a-secure-extension/) --- name: credits .footer[*Developing our first browser extension*] ## Credits * [MDN](https://developer.mozilla.org/en-US/) --- name: qa class: middle .footer[*Developing our first browser extension*] ## Questions,
comments,
debate --- name: hiring class: middle .footer[*Developing our first browser extension*] .center[.logod2[![Logo](devo-logo.png)]] ##
[We are hiring!](https://www.devo.com/careers/openings/)
[`t@tripu.info`](mailto:t@tripu.info)