"use strict"; // License: MIT import { ALLOWED_SCHEMES, TRANSFERABLE_PROPERTIES } from "./constants"; import { API } from "./api"; import { Finisher, makeUniqueItems } from "./item"; import { Prefs } from "./prefs"; import { _, locale } from "./i18n"; import { openPrefs, openManager } from "./windowutils"; import { filters } from "./filters"; import { getManager } from "./manager/man"; import { browserAction as action, menus as _menus, contextMenus as _cmenus, tabs, webNavigation as nav, // eslint-disable-next-line no-unused-vars Tab, // eslint-disable-next-line no-unused-vars MenuClickInfo, CHROME, } from "./browser"; import { Bus } from "./bus"; import { filterInSitu } from "./util"; const menus = typeof (_menus) !== "undefined" && _menus || _cmenus; const GATHER = "/bundles/content-gather.js"; const CHROME_CONTEXTS = Object.freeze(new Set([ "all", "audio", "browser_action", "editable", "frame", "image", "launcher", "link", "page", "page_action", "selection", "video", ])); async function runContentJob(tab: Tab, file: string, msg: any) { try { const res = await tabs.executeScript(tab.id, { file, allFrames: true, runAt: "document_start" }); if (!msg) { return res; } const promises = []; const results: any[] = []; for (const frame of await nav.getAllFrames({ tabId: tab.id })) { promises.push(tabs.sendMessage(tab.id, msg, { frameId: frame.frameId} ).then(function(res: any) { results.push(res); }).catch(console.error)); } await Promise.all(promises); return results; } catch (ex) { console.error("Failed to execute content script", file, ex.message || ex.toString(), ex); return []; } } type SelectionOptions = { selectionOnly: boolean; allTabs: boolean; turbo: boolean; tab: Tab; }; class Handler { async processResults(turbo = false, results: any[]) { const links = this.makeUnique(results, "links"); const media = this.makeUnique(results, "media"); await API[turbo ? "turbo" : "regular"](links, media); } makeUnique(results: any[], what: string) { return makeUniqueItems( results.filter(e => e[what]).map(e => { const finisher = new Finisher(e); return filterInSitu(e[what]. map((item: any) => finisher.finish(item)), e => !!e); })); } async performSelection(options: SelectionOptions) { try { const toptions: any = { currentWindow: true, discarded: false, }; if (!CHROME) { toptions.hidden = true; } const selectedTabs = options.allTabs ? await tabs.query(toptions) as any[] : [options.tab]; const textLinks = await Prefs.get("text-links", true); const goptions = { type: "DTA:gather", selectionOnly: options.selectionOnly, textLinks, schemes: Array.from(ALLOWED_SCHEMES.values()), transferable: TRANSFERABLE_PROPERTIES, }; const results = await Promise.all(selectedTabs. map((tab: any) => runContentJob(tab, GATHER, goptions))); await this.processResults(options.turbo, results.flat()); } catch (ex) { console.error(ex.toString(), ex.stack, ex); } } } locale.then(() => { new class Action extends Handler { constructor() { super(); this.onClicked = this.onClicked.bind(this); action.onClicked.addListener(this.onClicked); } async onClicked(tab: {id: number}) { if (!tab.id) { return; } try { await this.processResults( true, await runContentJob( tab, "/bundles/content-gather.js", { type: "DTA:gather", selectionOnly: false, textLinks: await Prefs.get("text-links", true), schemes: Array.from(ALLOWED_SCHEMES.values()), transferable: TRANSFERABLE_PROPERTIES, })); } catch (ex) { console.error(ex); } } }(); const menuHandler = new class Menus extends Handler { constructor() { super(); this.onClicked = this.onClicked.bind(this); const alls = new Map(); const mcreate = (options: any) => { if (CHROME) { delete options.icons; options.contexts = options.contexts. filter((e: string) => CHROME_CONTEXTS.has(e)); if (!options.contexts.length) { return; } } if (options.contexts.includes("all")) { alls.set(options.id, options.contexts); } menus.create(options); }; mcreate({ id: "DTARegularLink", contexts: ["link"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta.regular.link"), }); mcreate({ id: "DTATurboLink", contexts: ["link"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta.turbo.link"), }); mcreate({ id: "DTARegularImage", contexts: ["image"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta.regular.image"), }); mcreate({ id: "DTATurboImage", contexts: ["image"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta.turbo.image"), }); mcreate({ id: "DTARegularMedia", contexts: ["video", "audio"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta.regular.media"), }); mcreate({ id: "DTATurboMedia", contexts: ["video", "audio"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta.turbo.media"), }); mcreate({ id: "DTARegularSelection", contexts: ["selection"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta.regular.selection"), }); mcreate({ id: "DTATurboSelection", contexts: ["selection"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta.turbo.selection"), }); mcreate({ id: "DTARegular", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta.regular"), }); mcreate({ id: "DTATurbo", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta.turbo"), }); mcreate({ id: "sep-1", contexts: ["all", "browser_action", "tools_menu"], type: "separator" }); mcreate({ id: "DTARegularAll", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/button-regular.png", 32: "/style/button-regular@2x.png", }, title: _("dta-regular-all"), }); mcreate({ id: "DTATurboAll", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", }, title: _("dta-turbo-all"), }); const sep2ctx = menus.ACTION_MENU_TOP_LEVEL_LIMIT === 6 ? ["all", "tools_menu"] : ["all", "browser_action", "tools_menu"]; mcreate({ id: "sep-2", contexts: sep2ctx, type: "separator" }); mcreate({ id: "DTAAdd", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/add.svg", 32: "/style/add.svg", 64: "/style/add.svg", 128: "/style/add.svg", }, title: _("add-download"), }); mcreate({ id: "sep-3", contexts: ["all", "browser_action", "tools_menu"], type: "separator" }); mcreate({ id: "DTAManager", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/button-manager.png", 32: "/style/button-manager@2x.png", }, title: _("manager.short"), }); mcreate({ id: "DTAPrefs", contexts: ["all", "browser_action", "tools_menu"], icons: { 16: "/style/settings.svg", 32: "/style/settings.svg", 64: "/style/settings.svg", 128: "/style/settings.svg", }, title: _("prefs.short"), }); Object.freeze(alls); const adjustMenus = (v: boolean) => { for (const [id, contexts] of alls.entries()) { const adjusted = v ? contexts.filter(e => e !== "all") : contexts; menus.update(id, { contexts: adjusted }); } }; Prefs.get("hide-context", false).then((v: boolean) => { // This is the initial load, so no need to adjust when visible already if (!v) { return; } adjustMenus(v); }); Prefs.on("hide-context", (prefs, key, value: boolean) => { adjustMenus(value); }); menus.onClicked.addListener(this.onClicked); } *makeSingleItemList(url: string, results: any[]) { for (const result of results) { const finisher = new Finisher(result); for (const list of [result.links, result.media]) { for (const e of list) { if (e.url !== url) { continue; } const finished = finisher.finish(e); if (!finished) { continue; } yield finished; } } } } async findSingleItem(tab: Tab, url: string, turbo = false) { if (!url) { return; } const results = await runContentJob( tab, "/bundles/content-gather.js", { type: "DTA:gather", selectionOnly: false, schemes: Array.from(ALLOWED_SCHEMES.values()), transferable: TRANSFERABLE_PROPERTIES, }); const found = Array.from(this.makeSingleItemList(url, results)); const unique = makeUniqueItems([found]); if (!unique.length) { return; } const [item] = unique; API[turbo ? "singleTurbo" : "singleRegular"](item); } onClicked(info: MenuClickInfo, tab: Tab) { if (!tab.id) { return; } const {menuItemId} = info; const {[`onClicked${menuItemId}`]: handler}: any = this; if (!handler) { console.error("Invalid Handler for", menuItemId); return; } const rv: Promise | void = handler.call(this, info, tab); if (rv && rv.catch) { rv.catch(console.error); } } async enumulate(action: string) { const tab = await tabs.query({ active: true, currentWindow: true, }); if (!tab || !tab.length) { return; } this.onClicked({ menuItemId: action }, tab[0]); } async onClickedDTARegular(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: false, turbo: false, tab, }); } async onClickedDTARegularAll(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: true, turbo: false, tab, }); } async onClickedDTARegularSelection(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: true, allTabs: false, turbo: false, tab, }); } async onClickedDTATurbo(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: false, turbo: true, tab, }); } async onClickedDTATurboAll(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: false, allTabs: true, turbo: true, tab, }); } async onClickedDTATurboSelection(info: MenuClickInfo, tab: Tab) { return await this.performSelection({ selectionOnly: true, allTabs: false, turbo: true, tab, }); } async onClickedDTARegularLink(info: MenuClickInfo, tab: Tab) { if (!info.linkUrl) { return; } await this.findSingleItem(tab, info.linkUrl, false); } async onClickedDTATurboLink(info: MenuClickInfo, tab: Tab) { if (!info.linkUrl) { return; } await this.findSingleItem(tab, info.linkUrl, true); } async onClickedDTARegularImage(info: MenuClickInfo, tab: Tab) { if (!info.srcUrl) { return; } await this.findSingleItem(tab, info.srcUrl, false); } async onClickedDTATurboImage(info: MenuClickInfo, tab: Tab) { if (!info.srcUrl) { return; } await this.findSingleItem(tab, info.srcUrl, true); } async onClickedDTARegularMedia(info: MenuClickInfo, tab: Tab) { if (!info.srcUrl) { return; } await this.findSingleItem(tab, info.srcUrl, false); } async onClickedDTATurboMedia(info: MenuClickInfo, tab: Tab) { if (!info.srcUrl) { return; } await this.findSingleItem(tab, info.srcUrl, true); } onClickedDTAAdd() { API.singleRegular(null); } async onClickedDTAManager() { await openManager(); } async onClickedDTAPrefs() { await openPrefs(); } }(); Bus.on("do-regular", () => menuHandler.enumulate("DTARegular")); Bus.on("do-regular-all", () => menuHandler.enumulate("DTARegularAll")); Bus.on("do-turbo", () => menuHandler.enumulate("DTATurbo")); Bus.on("do-turbo-all", () => menuHandler.enumulate("DTATurboAll")); Bus.on("do-single", () => API.singleRegular(null)); Bus.on("open-manager", () => openManager(true)); Bus.on("open-prefs", () => openPrefs()); function adjustAction(globalTurbo: boolean) { action.setPopup({ popup: globalTurbo ? "" : "/windows/popup.html" }); action.setIcon({ path: globalTurbo ? { 16: "/style/button-turbo.png", 32: "/style/button-turbo@2x.png", } : { 16: "/style/icon16.png", 32: "/style/icon32.png", 48: "/style/icon48.png", 64: "/style/icon64.png", 96: "/style/icon96.png", 128: "/style/icon128.png", 256: "/style/icon256.png" } }); } (async function init() { await Prefs.set("last-run", new Date()); Prefs.get("global-turbo", false).then(v => adjustAction(v)); Prefs.on("global-turbo", (prefs, key, value) => { adjustAction(value); }); await filters(); await getManager(); })().catch(ex => { console.error("Failed to init components", ex.toString(), ex.stack, ex); }); });