view background.js @ 8:688074a7d58e default tip

Added tag version-3 for changeset 369e61618ff0
author Guido Berhoerster <guido+cws@berhoerster.name>
date Sun, 10 Oct 2021 20:56:41 +0200
parents d5f5d016facd
children
line wrap: on
line source

/*
 * 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();
    // create a new tab with the same contextual identity as the current tab
    let newTab;
    try {
        newTab = await browser.tabs.create({
            active: info.button === 1 ? false : true,
            cookieStoreId: tab.cookieStoreId,
            openerTabId: tab.id
        });
    } catch (e) {
        console.log(`Failed to create new tab: ${e.message}`);
        return;
    }
    try {
        await browser.search.search({
            query: query,
            engine: info.menuItemId,
            tabId: newTab.id
        });
    } catch (e) {
        console.log(`Failed to search for "${query}": ${e.message}`);
    }
});