« Firefox webextensions » : différence entre les versions
(12 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 28 : | Ligne 28 : | ||
"icons": { | "icons": { | ||
"48": "icons/icon-48.png" | "48": "icons/icon-48.png" | ||
} | } | ||
} | |||
</filebox> | |||
== [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/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 149 : | Ligne 160 : | ||
= [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({ | 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'> | ||
function onError(error) { | function onError(error) { | ||
Ligne 171 : | Ligne 193 : | ||
tab.id, | tab.id, | ||
{key: "value"} | {key: "value"} | ||
).catch(onError); | ).then(response => { | ||
console.log("Message from the content script:"); | |||
console.log(response.response); | |||
}).catch(onError); | |||
} | } | ||
} | } | ||
Ligne 182 : | Ligne 207 : | ||
<filebox fn='content.js'> | <filebox fn='content.js'> | ||
browser.runtime.onMessage.addListener | browser.runtime.onMessage.addListener(message => { | ||
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/user_interface/Browser_action toolbar button] = | = [https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action toolbar button] = | ||
Ligne 444 : | 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. | var popupURL = browser.runtime.getURL("popup.html"); | ||
var creating = browser.windows.create({ | var creating = browser.windows.create({ | ||
url: popupURL, | url: popupURL, | ||
Ligne 482 : | Ligne 507 : | ||
</filebox> | </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/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 = | ||
Ligne 519 : | 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> | </kode> | ||
{{info | The generated .zip file won't work on Firefox without signing.}} | {{info | The generated .zip file won't work on Firefox without signing.}} |
Dernière version du 17 octobre 2021 à 21:36
Liens
- https://developer.mozilla.org/en-US/Add-ons/WebExtensions
- fetch
- Pas d'accès au système de fichiers
- Anatomy of an extension
- webextensions-examples
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
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
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
// 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); }); |
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. |
# 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
# 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
- about:debugging
- Enable add-on debugging
- Load Temporary Add-on ou web-ext run
- Debug (ouvre une fenêtre Developer Tools)
Package your extension
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. |
- Developer Hub → Submit a New Add-on
- 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
- Manage My Submissions → My Extension
- View all → Delete Add-on