Files
2026-01-20 21:53:59 +03:30

103 lines
3.7 KiB
JavaScript

// Using contextMenus namespace for compatibility with Chrome
// It's not possible to create tools menu items (contexts: ["tools_menu"]) using the contextMenus namespace.
import {App} from './app.js';
import {Proxy} from './proxy.js';
import {OnRequest} from './on-request.js';
import {Flag} from './flag.js';
// ---------- Context Menu ---------------------------------
export class Menus {
static {
// contextMenus is not supported on Android
browser.contextMenus?.onClicked.addListener((...e) => this.process(...e));
this.data = [];
}
static init(pref) {
// not available on Android
if (!browser.contextMenus) { return; }
this.pref = pref;
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/extensions/api/context_menus.json
// chrome.contextMenus not promise yet -> Uncaught TypeError: Cannot read properties of undefined (reading 'then')
browser.contextMenus.removeAll(() => this.addMenus(pref.data));
}
static addMenus(data) {
// not for PAC, limit to 10
this.data = data.filter(i => i.active && i.type !== 'pac').slice(0, 10);
if (!this.data[0]) { return; }
// --- create contextMenus
// https://searchfox.org/mozilla-central/source/browser/components/extensions/parent/ext-menus.js#756
// https://searchfox.org/mozilla-central/source/browser/components/extensions/parent/ext-menus.js#625-636
// contexts defaults to ['page'], 'all' is also added in Firefox but not in Chrome
// https://github.com/w3c/webextensions/issues/774
// Inconsistency: contextMenus/Menus
// child menu inherits parent's contexts but chrome has a problem with inheriting in "action" contextMenus
const {basic, firefox} = App;
const allowedPattern = !basic && !this.pref.managed;
const documentUrlPatterns = ['http://*/*', 'https://*/*'];
// menus.create requires an id for non-persistent background scripts.
this.contextMenus = [
...(allowedPattern ? [{id: 'includeHost', documentUrlPatterns}] : []),
...(allowedPattern ? [{id: 'excludeHost', documentUrlPatterns}] : []),
...(allowedPattern && firefox ? [{id: 'sep', type: 'separator', documentUrlPatterns}] : []),
...(firefox ? [{id: 'tabProxy'}] : []),
...(firefox ? [{parentId: 'tabProxy', id: 'tabProxy' + this.data.length, title: '\u00A0'}] : []),
...(firefox ? [{id: 'openLinkTabProxy', contexts: ['link']}] : []),
];
allowedPattern && this.addProxies('includeHost');
allowedPattern && this.addProxies('excludeHost');
firefox && this.addProxies('tabProxy');
firefox && this.addProxies('openLinkTabProxy');
this.contextMenus.forEach(i => {
// always use the same ID for i18n
i.type !== 'separator' && (i.title ||= browser.i18n.getMessage(i.id));
// add contexts
// !i.parentId && (i.contexts ||= ['all']);
i.contexts ||= ['all'];
browser.contextMenus.create(i);
});
}
static addProxies(parentId) {
this.data.forEach((i, index) =>
this.contextMenus.push({
parentId,
id: parentId + index,
title: Flag.get(i.cc) + ' ' + (i.title || `${i.hostname}:${i.port}`)
})
);
}
static async process(info, tab) {
const pref = this.pref;
const id = info.parentMenuItemId;
const index = info.menuItemId.substring(id.length);
const proxy = this.data[index];
switch (id) {
case 'includeHost':
case 'excludeHost':
Proxy.includeHost(pref, proxy, tab, id);
break;
// --- firefox only
case 'setTabProxy':
OnRequest.setTabProxy(tab, proxy);
break;
case 'openLinkTabProxy':
tab = await browser.tabs.create({});
OnRequest.setTabProxy(tab, proxy);
browser.tabs.update(tab.id, {url: info.linkUrl});
break;
}
}
}