« Firefox webextensions » : différence entre les versions

De Banane Atomic
Aller à la navigationAller à la recherche
 
(39 versions intermédiaires par le même utilisateur non affichées)
Ligne 4 : Ligne 4 :
* [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch fetch]
* [https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch fetch]
* [https://discourse.mozilla-community.org/t/accessing-filesystem-in-webextensions-addon/4034/2 Pas d'accès au système de fichiers]
* [https://discourse.mozilla-community.org/t/accessing-filesystem-in-webextensions-addon/4034/2 Pas d'accès au système de fichiers]
* [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Anatomy_of_a_WebExtension Anatomy of an extension]
* [https://github.com/mdn/webextensions-examples webextensions-examples]


= Hierarchie des fichiers =
= Hierarchie des fichiers =
Ligne 14 : Ligne 16 :
* content_scripts
* content_scripts


= manifest.json =
= [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json manifest.json] =
<filebox fn=manifest.json lang=javascript>
<filebox fn=manifest.json lang=javascript>
{
{
  // mandatory keys
   "manifest_version": 2,
   "manifest_version": 2,
   "name": "Nom de l'extension",
   "name": "Nom de l'extension",
Ligne 25 : Ligne 28 :
   "icons": {
   "icons": {
     "48": "icons/icon-48.png"
     "48": "icons/icon-48.png"
   },
   }
}
</filebox>


  "permissions": [ "tabs" ]
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/manifest.json/permissions permissions] ==
<filebox fn=manifest.json lang=javascript>
{
    "permissions": [
        // permissions d'hôte (fetch, tabs.executeScript, webRequest, cookies)
        "<all_url>", // no restriction, allow access to all urls
        "*://www.domain.net/*",
        "*://localhost/*",
        "tabs",
        "notifications"
    ]
}
}
</filebox>
</filebox>
[https://developer.mozilla.org/fr/Add-ons/WebExtensions/manifest.json/permissions permissions]


== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/getManifest Accès au manifest] ==
== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/getManifest Accès au manifest] ==
Ligne 43 : Ligne 57 :
</filebox>
</filebox>


= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts Content script] =
= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Background_scripts Background script] =
{{info | Tourne dans le contexte de la page et peut accéder au contenu de la page}}
Background scripts do not get direct access to web pages.<br>
{{warn | {{boxx|console.log}} ne fonctionne pas ici, utiliser plutôt {{boxx|alert}}}}
However, they can load content scripts into web pages and can communicate with these content scripts using a message-passing API.
{{info | Log only in Browser Console}}


= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Background_scripts Background script] =
<filebox fn=manifest.json>
<filebox fn=manifest.json>
"background": {
"background": {
Ligne 58 : Ligne 72 :
</filebox>
</filebox>


= Injecter du JS / CSS dans la page =
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/API/tabs/insertCSS Insert CSS] ==
== content_scripts manifest.json key ==
<filebox fn='background.js'>
<filebox fn=manifest.json>
var css = "body { border: 20px dotted pink; }";
// charge le script si le pattern correspond
 
browser.browserAction.onClicked.addListener(() => {
 
  function onError(error) {
    console.log(`Error: ${error}`);
  }
 
  var insertingCSS = browser.tabs.insertCSS({code: css});
  var insertingCSS = browser.tabs.insertCSS({file: "content-style.css"});
  insertingCSS.then(null, onError);
});
</filebox>
 
= [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts Content script] =
{{info | Tourne dans le contexte de la page et peut accéder au contenu de la page}}
 
Content scripts can read and modify the content of their pages using the standard DOM APIs.<br>
Content scripts can only access a small subset of the WebExtension APIs, but they can communicate with background scripts using a messaging system, and thereby indirectly access the WebExtension APIs.
 
== manifest content_scripts ==
<filebox fn='manifest.json'>
// charge le script uniquement si le pattern correspond
"content_scripts": [
"content_scripts": [
    {
  {
        "matches": ["*://*.mozilla.org/*"],
    "matches": ["*://*.domain.net/*"],
        "js": ["script.js"]
    "js": ["content.js"],
     }
     "css": ["content.css"]
  }
]
]
</filebox>
</filebox>


<filebox fn=script.js>
<filebox fn=content.js>
document.body.style.border = "5px solid red";
document.body.style.border = "20px dotted pink";
</filebox>
 
<filebox fn=content.css>
body { border: 20px dotted pink; }
</filebox>
</filebox>


== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contentScripts contentScripts API] ==
== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contentScripts contentScripts API] ==


== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/executeScript tabs.executeScript] ==
== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/executeScript tabs.executeScript API] ==
<filebox fn='background.js'>
<filebox fn='background.js'>
browser.tabs.executeScript({
browser.tabs.executeScript({
Ligne 102 : Ligne 142 :
<filebox fn='manifest.json'>
<filebox fn='manifest.json'>
"permissions": [ "<all_urls>", "*://developer.mozilla.org/*" ]
"permissions": [ "<all_urls>", "*://developer.mozilla.org/*" ]
</filebox>
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/API/tabs/insertCSS insertCSS] ==
<filebox fn='background.js'>
browser.tabs.insertCSS({
    code: "#id { font-size: 64px; }"
});
browser.tabs.insertCSS({
    file: "style.css"
});
</filebox>
</filebox>


== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/manifest.json/web_accessible_resources web_accessible_resources] ==
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/manifest.json/web_accessible_resources web_accessible_resources] ==
<pre>
<pre>
Security Error: Content at https://www.domain.fr.com may not load or link to moz-extension://<guid>/icons/icon.png.
Security Error: Content at https://www.domain.net may not load or link to moz-extension://<guid>/icons/icon.png.
</pre>
</pre>


Ligne 126 : Ligne 155 :
<filebox fn='content.js'>
<filebox fn='content.js'>
var icon = document.createElement('img');
var icon = document.createElement('img');
var iconUrl = browser.extension.getURL("icons/icon.png");
var iconUrl = browser.runtime.getURL("icons/icon.png");
icon.src = iconUrl;
icon.src = iconUrl;
</filebox>
</filebox>


= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Communicating_with_background_scripts Communication] =
= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Content_scripts#Communicating_with_background_scripts Communication] =
== Content → Background ==
== [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage Content → Background] ==
<filebox fn='content.js'>
<filebox fn='content.js'>
browser.runtime.sendMessage({"key": value});
function handleResponse(message) {
    console.log(`Message from the background script:  ${message.response}`);
}
 
function handleError(error) {
    console.log(`Error: ${error}`);
}
 
browser.runtime
    .sendMessage({key: "value"})
    .then(handleResponse, handleError)
</filebox>
</filebox>


<filebox fn='background.js'>
<filebox fn='background.js'>
browser.runtime.onMessage.addListener((message) => {
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
     console.log("Message received " + message.key);
     console.log("Message received " + message.key);
    sendResponse({ response: "Response from background script" });
});
});
</filebox>
</filebox>


== Background → Content ==
== [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/sendMessage Background → Content] ==
<filebox fn='background.js'>
<filebox fn='background.js'>
browser.tabs.sendMessage(tabId, {"key": value});
function onError(error) {
  console.error(`Error: ${error}`);
}


function sendMessageToTabs(tabs) {
  for (let tab of tabs) {
    browser.tabs.sendMessage(
      tab.id,
      {key: "value"}
    ).then(response => {
      console.log("Message from the content script:");
      console.log(response.response);
    }).catch(onError);
  }
}
browser.tabs.query({
  currentWindow: true,
  active: true
}).then(sendMessageToTabs).catch(onError);
</filebox>
</filebox>


<filebox fn='content.js'>
<filebox fn='content.js'>
browser.runtime.onMessage.addListener((message) => {
browser.runtime.onMessage.addListener(message => {
     alert("Message received " + message.key);
     console.log("Message received " + message.key);
    return Promise.resolve({response: "Hi from content script"});
});
});
</filebox>
</filebox>
* [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/sendMessage tabs.sendMessage()]


= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action toolbar button] =
= [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action toolbar button] =
Ligne 411 : Ligne 469 :
<filebox fn='background.js'>
<filebox fn='background.js'>
// ouvrir une fenêtre popup contenant un html local
// ouvrir une fenêtre popup contenant un html local
var popupURL = browser.extension.getURL("popup.html");
var popupURL = browser.runtime.getURL("popup.html");
var creating = browser.windows.create({
var creating = browser.windows.create({
     url: popupURL,
     url: popupURL,
Ligne 427 : Ligne 485 :
});
});
</filebox>
</filebox>
= [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Context_menu_items Context menu items] =
<filebox fn='manifest.json'>
"permissions": ["contextMenus"]
</filebox>
<filebox fn='background.js'>
browser.contextMenus.create({
  id: "test",
  title: "test"
});
browser.contextMenus.onClicked.addListener(function(info, tab) {
  switch (info.menuItemId) {
    case "test":
      console.log("test menu item clicked !");
      break;
    ...
  }
})
</filebox>
* [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus menus]
= [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/notifications/create Notifications] =
<filebox fn='background.js'>
var notificationId = "my-notification"
// Create and display a basic notification
browser.notifications.create(notificationId, {
    "type": "basic",
    "iconUrl": browser.runtime.getURL("icons/icon-96.png"),
    "title": "Title!",
    "message": "Message"
});
</filebox>
= [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pageAction Page action] =
A page action is a clickable icon inside the browser's address bar.
<filebox fn='manifest.json'>
"page_action": {
  "browser_style": true,
  "default_icon": {
    "19": "icons/icon-19.png",
    "38": "icons/icon-38.png"
  },
  "default_title": "Tooltip!",
  "show_matches": [
    "https://www.domain.net/*"
  ]
}
</filebox>
<filebox fn='background.js'>
browser.pageAction.onClicked.addListener(function() {
  browser.tabs.create({
    url:"http://www.domain.net"
  });
});
</filebox>
* [https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/page_action page_action in manifest]


= Test =
= Test =
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/Getting_started_with_web-ext web-ext] ==
== [https://developer.mozilla.org/fr/Add-ons/WebExtensions/Getting_started_with_web-ext web-ext] ==
{{info | Par défaut, la commande {{boxx|run}} crée un profil Firefox temporaire.<br />
{{info | Par défaut, la commande {{boxx|[https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-run run]}} crée un profil Firefox temporaire.<br />
La commande {{boxx|run}} surveille vos fichiers sources et dit à Firefox de recharger l'extension quand vous éditez et enregistrez un fichier.}}
La commande {{boxx|run}} surveille vos fichiers sources et dit à Firefox de recharger l'extension quand vous éditez et enregistrez un fichier.}}
<kode lang='powershell'>
<kode lang='powershell'>
# depuis le dossier contenant les fichiers sources
# depuis le dossier contenant les fichiers sources
web-ext run
web-ext run
# forcer une version de firefox et un profile
 
web-ext run --firefox="C:\Program Files\Firefox Developer Edition\firefox.exe" --firefox-profile=MyProfile
# utiliser un profile existant
web-ext run --firefox-profile=MyProfile
web-ext run --firefox-profile=~/.mozilla/firefox/MyProfile
</kode>
</kode>


Ligne 462 : Ligne 582 :
web-ext build
web-ext build
# web-ext-artifacts/my_extension-1.0.zip
# web-ext-artifacts/my_extension-1.0.zip
# the tool automatically excludes files that are commonly unwanted in packages, such as .git files.
</kode>
{{info | The generated .zip file won't work on Firefox without signing.}}


cd /path/to/my-extension
zip -r -FS ../my-extension.zip * --exclude '*.git*'
</kode>
* [https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-build web-ext build]
* [https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#web-ext-build web-ext build]
* [https://extensionworkshop.com/documentation/publish/package-your-extension/ zip]
* [https://extensionworkshop.com/documentation/publish/package-your-extension/ zip]

Dernière version du 17 octobre 2021 à 21:36

Liens

Hierarchie des fichiers

  • manifest.json
  • main.js
  • main.html
  • icons
    • icon-48.png
    • icon-96.png
  • content_scripts

manifest.json

manifest.json
{
  // mandatory keys
  "manifest_version": 2,
  "name": "Nom de l'extension",
  "version": "1.0",

  "description": "description",
  // taille 48 ou 96
  "icons": {
    "48": "icons/icon-48.png"
  }
}

permissions

manifest.json
{
    "permissions": [
        // permissions d'hôte (fetch, tabs.executeScript, webRequest, cookies)
        "<all_url>", // no restriction, allow access to all urls
        "*://www.domain.net/*",
        "*://localhost/*",
        "tabs",
        "notifications"
    ]
}

Accès au manifest

manifest.json
{
    "mySetting": "value"
}
background.js
var mySetting = browser.runtime.getManifest().mySetting;

Background script

Background scripts do not get direct access to web pages.
However, they can load content scripts into web pages and can communicate with these content scripts using a message-passing API.

Log only in Browser Console
manifest.json
"background": {
    "scripts": ["background.js"]
}
background.js

Insert CSS

background.js
var css = "body { border: 20px dotted pink; }";

browser.browserAction.onClicked.addListener(() => {

  function onError(error) {
    console.log(`Error: ${error}`);
  }

  var insertingCSS = browser.tabs.insertCSS({code: css});
  var insertingCSS = browser.tabs.insertCSS({file: "content-style.css"});
  insertingCSS.then(null, onError);
});

Content script

Tourne dans le contexte de la page et peut accéder au contenu de la page

Content scripts can read and modify the content of their pages using the standard DOM APIs.
Content scripts can only access a small subset of the WebExtension APIs, but they can communicate with background scripts using a messaging system, and thereby indirectly access the WebExtension APIs.

manifest content_scripts

manifest.json
// charge le script uniquement si le pattern correspond
"content_scripts": [
  {
    "matches": ["*://*.domain.net/*"],
    "js": ["content.js"],
    "css": ["content.css"]
  }
]
content.js
document.body.style.border = "20px dotted pink";
content.css
body { border: 20px dotted pink; }

contentScripts API

tabs.executeScript API

background.js
browser.tabs.executeScript({
    code: "alert('xxx');"
});

var executing = browser.tabs.executeScript({
    file: "modifyPage.js"
});

executing.then((result) => {
    console.log(result);  // Array [ 0 ]
}, (error) => {
    console.log(error);
});
modifyPage.js
// to avoid the error result is non-structured-clonable data
// return a value as a result which is structured clonable.
0;

Nécessite des permissions d'hôte pour la page dans laquelle on veut injecter du js.

manifest.json
"permissions": [ "<all_urls>", "*://developer.mozilla.org/*" ]

web_accessible_resources

Security Error: Content at https://www.domain.net may not load or link to moz-extension://<guid>/icons/icon.png.
manifest.json
"web_accessible_resources": ["icons/icon.png"]
content.js
var icon = document.createElement('img');
var iconUrl = browser.runtime.getURL("icons/icon.png");
icon.src = iconUrl;

Communication

Content → Background

content.js
function handleResponse(message) {
    console.log(`Message from the background script:  ${message.response}`);
}

function handleError(error) {
    console.log(`Error: ${error}`);
}

browser.runtime
    .sendMessage({key: "value"})
    .then(handleResponse, handleError)
background.js
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log("Message received " + message.key);
    sendResponse({ response: "Response from background script" });
});

Background → Content

background.js
function onError(error) {
  console.error(`Error: ${error}`);
}

function sendMessageToTabs(tabs) {
  for (let tab of tabs) {
    browser.tabs.sendMessage(
      tab.id,
      {key: "value"}
    ).then(response => {
      console.log("Message from the content script:");
      console.log(response.response);
    }).catch(onError);
  }
}

browser.tabs.query({
  currentWindow: true,
  active: true
}).then(sendMessageToTabs).catch(onError);
content.js
browser.runtime.onMessage.addListener(message => {
    console.log("Message received " + message.key);
    return Promise.resolve({response: "Hi from content script"});
});

toolbar button

manifest.json
"browser_action": {
    "default_icon": "icons/icon-32.png",
    "theme_icons": [{
        "light": "icons/icon-32-light.png",
        "dark": "icons/icon-32.png",
        "size": 32
    }],
    "default_title": "Title"
}

Sans Popup

manifest.json
"background": {
    "scripts": ["background.js"]
}
background.js
function doSomething() { }

browser.browserAction.onClicked.addListener(doSomething);

Popup

manifest.json
"browser_action": {
    "default_popup": "popup/actions.html"
}
actions.html
<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="actions.css"/>
  </head>

  <body>
    <div>Choix 1</div>
    <div>Choix 2</div>

    <script src="actions.js"></script>
  </body>

</html>
actions.css
html, body {
    width: 200px;
    background-color: black;
    color: white;
}

div {
    border-bottom: solid 1px black;
    margin: 10px 0;
}
div:hover {
    border-color: red;
    cursor: pointer;
}
actions.js
document.addEventListener("click", function(e) {
    if (e.target.tagName != "DIV") {
        return;
    }
    
    alert(e.target.textContent);
});

button title and icon

Js.svg
function toggleTitle(title) {
  if (title == "Old Title") {
    browser.browserAction.setTitle({ title: "New Title" });
    browser.browserAction.setIcon({ path: "new-icon.png" });
  } else {
    browser.browserAction.setTitle({title: "Old Title"});
    browser.browserAction.setIcon({ path: "old-icon.png" });
  }
}

browser.browserAction.onClicked.addListener(() => {
  var gettingTitle = browser.browserAction.getTitle({});
  gettingTitle.then(toggleTitle);
});

Tab

background.js
// listen to tab URL changes
browser.tabs.onUpdated.addListener((tabId, changeInfo, tabInfo) => {
    if (changeInfo.url) {
        // changeInfo.url nécessite la permission tabs
        console.log("URL changed to " + changeInfo.url);
        console.log("Status " + changeInfo.status);  // loading ou complete
        // regex url
        if (/https:\/\/www.domain.fr\/path\/.*/.test(changeInfo.url)) { }
    }
});

// listen to tab switching
browser.tabs.onActivated.addListener(() => {
    console.log("tab switching");
});

// active tab
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
    if (tabs[0]) {
        currentTab = tabs[0];
        var tabId = currentTab.id;
        var tabUrl = currentTab.url;
    }
});
content.js
let currentUrl = window.location.href;

Download

background.js
var downloading = browser.downloads.download({
	url: myUrl,
	filename: "image.jpg",  // définit le chemin relatif depuis le dossier des téléchargements (créé les dossiers au besoin)
	saveAs: true,           // affiche la fenêtre de choix de l'emplacement
        incognito: true         // téléchargement dans une navigation privé donc pas affiché dans la liste des téléchargements
});
downloading.then(
(id) => { console.log("ok " + id); }, 
(error) => { console.log(error); });

Request / XMLHttpRequest

background.js
var request = new XMLHttpRequest();
request.withCredentials = true;

// async
request.addEventListener("readystatechange", function () {
  if (this.readyState === 4) {
    console.log(this.responseText);
  }
});
request.open("GET", "http://www.domain.fr/api/controller");

// sync
request.open('GET', 'http://www.domain.fr/api/controller', false);

request.setRequestHeader("Content-Type", "application/json");
request.send(null);
// request.status → 200
// request.response → { ... }

Request / fetch

GET

background.js
fetch(myRequest)
.then(response => {  // récupération de la réponse
    if (response.ok) {  // test du code de retour
        // parser les données
        return response.json();  // json
        return response.text();  // text
        return response.blob();  // blob (image)
    } else {
        throw new Error('Something went wrong on api server!');
    }
})
.then(blob => {  // données récupérées
    console.log(blob);  // Blob { size: 25248, type: "image/jpeg" }
    var objectURL = URL.createObjectURL(blob);  // blob:moz-extension://guid/guid
}).catch(error => {
    console.error(error);
});

async / await

Js.svg
async function myFunction() {
    let data = await (await (fetch(url).
        then(response => response.blob())));
    return data;
}

POST

background.js
// json
var data = JSON.stringify({ key: "value" });

// blob
var data = new FormData();
data.append("Content-Type", "multipart/form-data");
data.append("myKey", myBlob);


var myHeaders = new Headers();
myHeaders.append('Cache-Control', 'no-cache');
myHeaders.append('Content-Type', 'application/json');  // json

const myRequest = new Request('http://www.domain.fr/api/images', 
{
    method: 'POST',
    headers: myHeaders, 
    body: data
});
fetch(myRequest).then(response => {
    if (response.ok) {
        return response.json();
    } else {
        throw new Error('Something went wrong on api server!');
    }
})
.then(response => {
    console.debug(response);
}).catch(error => {
    console.error(error);
});

415 Unsupported Media Type response

Js.svg
// définir le bon Content-Type
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
var myRequest = new Request('http://www.domain.fr/api/items', {method: 'POST', headers: myHeaders, body: { "key": "value" }});

Cross-Origin Request Blocked

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:59383/api/listapi. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Ajouter les urls dans permissions

manifest.json
"permissions": [ "<all_urls>" ]

Fichiers

Window

background.js
// ouvrir une fenêtre popup contenant un html local
var popupURL = browser.runtime.getURL("popup.html");
var creating = browser.windows.create({
    url: popupURL,
    type: "popup",
    state: "maximized",  // maximized et fullscreen doivent être utilisés sans popup, height, width, top, left
    height: 600,
    width: 800,
    left: 100,
    top: 100
});
creating.then((windowInfo) => {
    console.log(windowInfo);
}, (error) => {
    console.log("Error: " + error);
});

Context menu items

manifest.json
"permissions": ["contextMenus"]
background.js
browser.contextMenus.create({
  id: "test",
  title: "test"
});

browser.contextMenus.onClicked.addListener(function(info, tab) {
  switch (info.menuItemId) {
    case "test":
      console.log("test menu item clicked !");
      break;
    ...
  }
})

Notifications

background.js
var notificationId = "my-notification"
// Create and display a basic notification
browser.notifications.create(notificationId, {
    "type": "basic",
    "iconUrl": browser.runtime.getURL("icons/icon-96.png"),
    "title": "Title!",
    "message": "Message"
});

Page action

A page action is a clickable icon inside the browser's address bar.

manifest.json
"page_action": {
  "browser_style": true,
  "default_icon": {
    "19": "icons/icon-19.png",
    "38": "icons/icon-38.png"
  },
  "default_title": "Tooltip!",
  "show_matches": [
    "https://www.domain.net/*"
  ]
}
background.js
browser.pageAction.onClicked.addListener(function() {
  browser.tabs.create({
    url:"http://www.domain.net"
  });
});

Test

web-ext

Par défaut, la commande run crée un profil Firefox temporaire.
La commande run surveille vos fichiers sources et dit à Firefox de recharger l'extension quand vous éditez et enregistrez un fichier.
Powershell.svg
# depuis le dossier contenant les fichiers sources
web-ext run

# utiliser un profile existant
web-ext run --firefox-profile=MyProfile
web-ext run --firefox-profile=~/.mozilla/firefox/MyProfile

Installation

Bash.svg
# pacman
sudo pacman -S web-ext

# npm
npm i -g web-ext

Load Temporary Add-on

about:debugging → Load Temporary Add-on → sélectionner n'importe quel fichier
Ceci installe l'add-on jusqu'au redémarrage de firefox

Debugging

  1. about:debugging
    1. Enable add-on debugging
    2. Load Temporary Add-on ou web-ext run
    3. Debug (ouvre une fenêtre Developer Tools)

Package your extension

Bash.svg
web-ext build
# web-ext-artifacts/my_extension-1.0.zip
# the tool automatically excludes files that are commonly unwanted in packages, such as .git files.
The generated .zip file won't work on Firefox without signing.

Sign your extension

Only signed extensions can be installed on Firefox.
  1. Developer Hub → Submit a New Add-on
  2. Once the extension has been validated, an email is sent with a link to the extension versions list
On peut signer un add-on sans le publier sur le site de mozilla.

Supprimer une extension publiée dans Developer Hub

  1. Manage My Submissions → My Extension
  2. View all → Delete Add-on