mirror of
https://github.com/vale981/tridactyl
synced 2025-03-04 09:01:39 -05:00
Merge pull request #4891 from tridactyl/ez-bg-acS-2-fg
Enable easy access to content process from background script
This commit is contained in:
commit
4164f7d8d7
6 changed files with 163 additions and 1 deletions
|
@ -3,8 +3,8 @@
|
|||
/* tslint:disable:import-spacing */
|
||||
|
||||
import * as proxy_background from "@src/lib/browser_proxy_background"
|
||||
|
||||
import * as controller from "@src/lib/controller"
|
||||
import { omniscient_controller } from "@src/lib/omniscient_controller"
|
||||
import * as perf from "@src/perf"
|
||||
import { listenForCounters } from "@src/perf"
|
||||
import * as messaging from "@src/lib/messaging"
|
||||
|
@ -30,6 +30,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 +53,7 @@ import * as Proxy from "@src/lib/proxy"
|
|||
R,
|
||||
perf,
|
||||
meta,
|
||||
tabs: tabsProxy,
|
||||
})
|
||||
|
||||
import { HintingCmds } from "@src/background/hinting"
|
||||
|
@ -258,6 +260,7 @@ const messages = {
|
|||
downloadUrlAs: download_background.downloadUrlAs,
|
||||
},
|
||||
browser_proxy_background: { shim: proxy_background.shim },
|
||||
omniscient_background: omniscient_controller,
|
||||
}
|
||||
export type Messages = typeof messages
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -95,6 +96,7 @@ const excmds = await import("@src/.excmds_content.generated")
|
|||
const finding_content = await import("@src/content/finding")
|
||||
const itertools = await import("@src/lib/itertools")
|
||||
const messaging = await import("@src/lib/messaging")
|
||||
const backgroundProxy = await import("@src/lib/tabs")
|
||||
const State = await import("@src/state")
|
||||
const webext = await import("@src/lib/webext")
|
||||
const perf = await import("@src/perf")
|
||||
|
@ -124,6 +126,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)
|
||||
|
@ -206,6 +209,7 @@ config.getAsync("preventautofocusjackhammer").then(allowautofocus => {
|
|||
})
|
||||
;(window as any).tri = Object.assign(Object.create(null), {
|
||||
browserBg: webext.browserBg,
|
||||
bg: backgroundProxy.backgroundProxy,
|
||||
commandline_content,
|
||||
convert,
|
||||
config,
|
||||
|
|
|
@ -5853,6 +5853,7 @@ async function js_helper(str: string[]) {
|
|||
* - -r load the js source from a Javascript file relative to your RC file. (NB: will throw an error if no RC file exists)
|
||||
*
|
||||
* Some of Tridactyl's functions are accessible here via the `tri` object. Just do `console.log(tri)` in the web console on the new tab page to see what's available.
|
||||
* `tri.bg` is an object enabling access to the background script's context. It works similarly to the `tri.tabs` objects documented in the [[jsb]] documentation.
|
||||
*
|
||||
* If you want to pipe an argument to `js`, you need to use the "-p" flag or "-d" flag with an argument and then use the JS_ARG global variable, e.g:
|
||||
*
|
||||
|
@ -5884,6 +5885,29 @@ export async function js(...str: string[]) {
|
|||
|
||||
/**
|
||||
* Lets you execute JavaScript in the background context. All the help from [[js]] applies. Gives you a different `tri` object which has access to more excmds and web-extension APIs.
|
||||
*
|
||||
* In `:jsb`, the `tri` object has a special `tabs` property that can be used to access the window object of the corresponding tab by indexing it with the tab ID. Here are a few examples:
|
||||
*
|
||||
* - Get the URL of the tab whose id 3:
|
||||
* `:jsb tri.tabs[3].location.href.then(console.log)`
|
||||
* - Set the title of the tab whose id is 6:
|
||||
* `:jsb tri.tabs[6].document.title = "New title!"`
|
||||
* - Run `alert()` in a tab whose id is 9:
|
||||
* `:jsb tri.tabs[9].alert()`
|
||||
*
|
||||
* You can also directly access the corresonding property in all tabs by using
|
||||
* the "tabs" object itself, e.g.
|
||||
*
|
||||
* - Build a string containing the id of the active element of each tab:
|
||||
* `:jsb tri.tabs.document.activeElement.id.then(ids => ids.reduce(s, id => s + " " + id))`
|
||||
* - Scroll all tabs to the tenth pixel:
|
||||
* `:jsb tri.tabs.document.documentElement.scrollTop = 10`
|
||||
* - Use tridactyl's JS ex command to perform a complex computation:
|
||||
* `:jsb tri.tabs.tri.excmds.js("let x = 1; let y = 2; x + y").then(console.log)`
|
||||
*
|
||||
* When fetching a value or running a function in a tab through the `tabs` property, the returned value is a Promise and must be awaited.
|
||||
* Setting values through the `tab` property is asynchronous too and there is no way to await this operation.
|
||||
* If you need to ensure that the value has been set before performing another action, use tri.tabs[tab.id].tri.excmds.js to set the value instead and await the result.
|
||||
*/
|
||||
/* tslint:disable:no-identical-functions */
|
||||
//#background
|
||||
|
|
|
@ -9,6 +9,7 @@ export type TabMessageType =
|
|||
| "controller_content"
|
||||
| "commandline_content"
|
||||
| "finding_content"
|
||||
| "omniscient_content"
|
||||
| "commandline_cmd"
|
||||
| "commandline_frame"
|
||||
| "state"
|
||||
|
|
38
src/lib/omniscient_controller.ts
Normal file
38
src/lib/omniscient_controller.ts
Normal file
|
@ -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
|
||||
},
|
||||
}
|
92
src/lib/tabs.ts
Normal file
92
src/lib/tabs.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import * as Messaging from "@src/lib/messaging"
|
||||
|
||||
const allTabs = -1
|
||||
const background = -2
|
||||
|
||||
const msg = (tabId, ...args) => {
|
||||
if (tabId === allTabs) {
|
||||
return Messaging.messageAllTabs("omniscient_content", ...args)
|
||||
} else if (tabId === background) {
|
||||
return (Messaging.message as any)("omniscient_background", args[0], args[1][0])
|
||||
} else {
|
||||
return Messaging.messageTab(tabId, "omniscient_content", ...args)
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used to generate proxies. We use a function rather than an
|
||||
// object created through Object.create(null) in order to make our proxy
|
||||
// callable. As all proxy calls should be handled through the proxy's "apply"
|
||||
// function, we make ItWouldBeAMistakeToCallThis throw an error, to make bugs
|
||||
// as obvious as possible.
|
||||
const ItWouldBeAMistakeToCallThis = () => {
|
||||
throw Error("Error, base function ItWouldBeAMistakeToCallThis was called!")
|
||||
}
|
||||
|
||||
// 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(ItWouldBeAMistakeToCallThis, {
|
||||
get(target, p) {
|
||||
if (p === Symbol.toPrimitive) {
|
||||
// Symbol.toPrimitive will be accessed when the user attempts
|
||||
// to use a content process value without awaiting it first,
|
||||
// e.g.:
|
||||
//
|
||||
// tri.tabs[3].document.title + "!"
|
||||
//
|
||||
// It is a mistake to do this, so we throw an error - see the
|
||||
// if condition checking for "then" for more details.
|
||||
throw Error(
|
||||
`TypeError: tabs[${tabId}].${props.join(".")} is a Promise, you need to await its value.`,
|
||||
)
|
||||
}
|
||||
if (p === "then") {
|
||||
// Because we can only access content process values by
|
||||
// messaging a tab, we can only get values as a promise. We
|
||||
// take advantage of this fact to wait until we get an access
|
||||
// to a "then" property before fetching anything - this enables
|
||||
// rapid traversal of objects instead of having to perform slow
|
||||
// back and forths for every property.
|
||||
//
|
||||
// This works with the "await" keyword too as awaits are turned
|
||||
// into calls to "then" by the javascript engine.
|
||||
// One drawback of this approach is that properties named
|
||||
//
|
||||
// "then" in the content process cannot be directly accessed.
|
||||
// We consider this an okay trade-off, as there exists an
|
||||
// alternative: using tri.tabs[3].eval("x.then") instead.
|
||||
const promise = msg(tabId, "get", [
|
||||
{ target: props, value: undefined },
|
||||
])
|
||||
return promise.then.bind(promise)
|
||||
}
|
||||
return tabProxy(tabId, props.concat(p))
|
||||
},
|
||||
set(target, p, value) {
|
||||
msg(tabId, "set", [{ target: props.concat(p), value }])
|
||||
return true
|
||||
},
|
||||
apply(target, thisArg, argArray) {
|
||||
return msg(tabId, "apply", [{ target: props, value: argArray }])
|
||||
},
|
||||
})
|
||||
|
||||
export const tabsProxy = new Proxy(Object.create(null), {
|
||||
get(target, p) {
|
||||
const 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, [])
|
||||
}
|
||||
if (typeof p === "string") {
|
||||
// If `p` is a string, then we return a proxy with a sentinel value
|
||||
// indicating that the request should be sent to all tabs instead.
|
||||
return tabProxy(allTabs, [])[p]
|
||||
}
|
||||
throw new Error(`'tabs' object can only be accessed by a number (e.g. tabs[3]) or a string (e.g. tabs.document or tabs['document']). Type of accessor: "${typeof p}"`)
|
||||
},
|
||||
})
|
||||
|
||||
export const backgroundProxy = tabProxy(background, [])
|
Loading…
Add table
Reference in a new issue