diff background.js @ 0:2050741e9711

Initial revision
author Guido Berhoerster <guido+cws@berhoerster.name>
date Tue, 18 Sep 2018 22:57:18 +0200
parents
children f77dab12bb52
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/background.js	Tue Sep 18 22:57:18 2018 +0200
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+cws@berhoerster.name>
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+'use strict';
+
+function encodeXML(str) {
+    return str.replace(/[<>&'"]/g, c => {
+        switch (c) {
+            case '<': return '&lt;';
+            case '>': return '&gt;';
+            case '&': return '&amp;';
+            case '\'': return '&apos;';
+            case '"': return '&quot;';
+        }
+    });
+}
+
+function createFavIcon(name) {
+    let firstLetter = name.trim().charAt(0);
+    if (firstLetter === '') {
+        firstLetter = '?';
+    }
+    let svg = `<svg viewBox="0 0 16 16" ` +
+            `xmlns="http://www.w3.org/2000/svg">` +
+            `<defs>` +
+            `<filter id="ds">` +
+            `<feDropShadow dx="0" dy="0" stdDeviation=".5"/>` +
+            `</filter>` +
+            `</defs>` +
+            `<rect width="16" height="16" rx="3" ry="3" fill="#ed00b5"/>` +
+            `<text x="8" y="12" alignment-baseline="middle" fill="#fff" ` +
+            `filter="url(#ds)" font-family="sans-serif" font-size="11px" ` +
+            `font-weight="bold" text-anchor="middle">` +
+            `${encodeXML(firstLetter)}</text>` +
+            `</svg>`;
+    return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
+}
+
+async function createMenu(isSelection) {
+    let searchEngines = await browser.search.get();
+    let menuItems = new Set();
+    for (let engine of searchEngines) {
+        let favIcon = typeof engine.favIconUrl !== 'undefined' ?
+                engine.favIconUrl : createFavIcon(engine.name);
+        let menuItemId = browser.menus.create({
+            id: engine.name,
+            title: browser.i18n.getMessage(isSelection ?
+                    'searchSelectionWithEngine' : 'searchLinkWithEngine',
+                    engine.name.trim()),
+            icons: {16: favIcon},
+            contexts: ['link', 'selection']
+        }, async () => {
+            if (browser.runtime.lastError) {
+                console.log(`Failed to create menu item:` +
+                        `${browser.runtime.lastError}`);
+            }
+
+            // browser.menus.create does not return a promise that can be
+            // awaited, thus collect the returned menu item IDs in a set and
+            // remove them from this callback which is called when the
+            // asynchronous creation function has finished; when the set is
+            // empty all creation functions have finished and the menu can be
+            // refreshed
+            menuItems.delete(menuItemId);
+            if (menuItems.size === 0) {
+                try {
+                    await browser.menus.refresh();
+                } catch (e) {
+                    console.log(`Failed to refresh menu: ${e.message}`);
+                }
+            }
+        });
+        menuItems.add(menuItemId);
+    }
+}
+
+browser.menus.onShown.addListener(async (info, tab) => {
+    try {
+        await createMenu(info.contexts.includes('selection'));
+    } catch (e) {
+        console.log(e);
+    }
+});
+
+browser.menus.onHidden.addListener(async () => {
+    try {
+        await browser.menus.removeAll();
+    } catch (e) {
+        console.log(`Failed to remove menu items: ${e.message}`);
+    }
+    try {
+        await browser.menus.refresh();
+    } catch (e) {
+        console.log(`Failed to refresh menu: ${e.message}`);
+    }
+});
+
+browser.menus.onClicked.addListener(async (info, tab) => {
+    let query = typeof info.selectionText !== 'undefined' ?
+            info.selectionText.trim() : info.linkText.trim();
+    try {
+        await browser.search.search({
+            query: query,
+            engine: info.menuItemId
+        });
+    } catch (e) {
+        console.log(`Failed to search for "${query}": ${e.message}`);
+    }
+});