view background.js @ 3:f77dab12bb52

Add support for contextual identities Create new tab with the same contextual identity as the current tab and open the search results in newly created tab. Accessing the tab's cookieStoreId property requires the "cookies" permission.
author Guido Berhoerster <guido+cws@berhoerster.name>
date Thu, 31 Oct 2019 16:22:48 +0100
parents 2050741e9711
children d5f5d016facd
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: 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}`);
    }
});