diff --git a/src/background.ts b/src/background.ts index 53f276f6..2f55c40a 100644 --- a/src/background.ts +++ b/src/background.ts @@ -3,7 +3,6 @@ /* tslint:disable:import-spacing */ import * as proxy_background from "@src/lib/browser_proxy_background" - import * as controller from "@src/lib/controller" import * as perf from "@src/perf" import { listenForCounters } from "@src/perf" @@ -30,6 +29,7 @@ import * as commands from "@src/background/commands" import * as meta from "@src/background/meta" import * as Logging from "@src/lib/logging" import * as Proxy from "@src/lib/proxy" +import { tabsProxy } from "@src/lib/tabs" // Add various useful modules to the window for debugging ;(window as any).tri = Object.assign(Object.create(null), { @@ -52,6 +52,7 @@ import * as Proxy from "@src/lib/proxy" R, perf, meta, + tabs: tabsProxy, }) import { HintingCmds } from "@src/background/hinting" diff --git a/src/content.ts b/src/content.ts index 6ca529f6..a3a6385a 100644 --- a/src/content.ts +++ b/src/content.ts @@ -83,6 +83,7 @@ try { } const controller = await import("@src/lib/controller") +const { omniscient_controller } = await import("@src/lib/omniscient_controller") const excmds_content = await import("@src/.excmds_content.generated") const hinting_content = await import("@src/content/hinting") // Hook the keyboard up to the controller @@ -124,6 +125,7 @@ messaging.addListener( "controller_content", messaging.attributeCaller(controller), ) +messaging.addListener("omniscient_content", messaging.attributeCaller(omniscient_controller)) // eslint-disable-next-line @typescript-eslint/require-await messaging.addListener("alive", async () => true) diff --git a/src/lib/messaging.ts b/src/lib/messaging.ts index de2946a6..77d7a624 100644 --- a/src/lib/messaging.ts +++ b/src/lib/messaging.ts @@ -9,6 +9,7 @@ export type TabMessageType = | "controller_content" | "commandline_content" | "finding_content" + | "omniscient_content" | "commandline_cmd" | "commandline_frame" | "state" diff --git a/src/lib/omniscient_controller.ts b/src/lib/omniscient_controller.ts new file mode 100644 index 00000000..32d49652 --- /dev/null +++ b/src/lib/omniscient_controller.ts @@ -0,0 +1,38 @@ +export const omniscient_controller = { + set: ({ target, value }) => { + let result + try { + const the_last_one = target[target.length - 1] + const everything_except_the_last_one = target.slice( + 0, + target.length - 1, + ) + const second_to_last = everything_except_the_last_one.reduce( + (acc, prop) => acc[prop], + window, + ) + result = second_to_last[the_last_one] = value + } catch (e) { + console.error(e) + } + return result + }, + get: ({ target, _ }) => { + let result + try { + result = target.reduce((acc, prop) => acc[prop], window) + } catch (e) { + console.error(e) + } + return result + }, + apply: ({ target, value }) => { + let result + try { + result = target.reduce((acc, prop) => acc[prop], window)(...value) + } catch (e) { + console.error(e) + } + return result + }, +} diff --git a/src/lib/tabs.ts b/src/lib/tabs.ts new file mode 100644 index 00000000..fb38bac6 --- /dev/null +++ b/src/lib/tabs.ts @@ -0,0 +1,51 @@ +import * as Messaging from "@src/lib/messaging" + +const allTabs = -1; + +// tabProxy accumulates all accesses to a tab's properties and then, when the +// value of said properties are read, set or called, sends a request to the +// corresponding tab to either retrieve or set the value. +const tabProxy = (tabId, props) => new Proxy(()=>undefined, { + get(target, p, receiver) { + if (p === Symbol.toPrimitive) { + const prop = `tabs[${tabId}].${props.join(".")}`; + throw `${prop} cannot be used directly - use ${prop}.get() instead`; + } + return tabProxy(tabId, props.concat(p)); + }, + apply(target, thisArg, argArray) { + const last = props[props.length -1]; + switch (last) { + case "get": + case "set": + case "apply": + break; + default: + const call = `tabs[${tabId}].${props.join(".")}`; + const args = `(${argArray.join(", ")})`; + throw `${call}${args} cannot be called directly, use ${call}.apply${args} instead`; + }; + let msg = Messaging.messageAllTabs; + if (tabId !== allTabs) { + msg = (...args) => Messaging.messageTab(tabId, ...args); + } + return msg("omniscient_content", last, [{target: props.slice(0, props.length - 1), value: argArray}]); + }, +}) + +export const tabsProxy = new Proxy(Object.create(null), { + get(target, p, receiver) { + let id = Number(p); + if (typeof id === "number" && isFinite(id)) { + // We're accessing tabs[i] - meaning that we should return a proxy + // for a single tab + return tabProxy(id, []) + } + throw "Foreground tabs proxy needs to be indexed by tab ID."; + // Ideally, if p is a string, we should construct a proxy for all + // existing tabs. This unfortunately does not seem to work: + // Messaging.messageAllTab seems to return an array of undefined when + // running e.g. `tabs.document.title`. + // return tabProxy(allTabs, []); + } +})