tridactyl/src/content/commandline_content.ts
2021-07-20 13:00:46 +02:00

143 lines
4.6 KiB
TypeScript

/** Inject an input element into unsuspecting webpages and provide an API for interaction with tridactyl */
import Logger from "@src/lib/logging"
import * as config from "@src/lib/config"
import { theme } from "@src/content/styling"
const logger = new Logger("messaging")
const cmdline_logger = new Logger("cmdline")
/* TODO:
CSS
Friendliest-to-webpage way of injecting commandline bar?
Security: how to prevent other people's JS from seeing or accessing the bar or its output?
- Method here is isolation via iframe
- Web content can replace the iframe, but can't view or edit its content.
- see doc/escalating-privilege.md for other approaches.
*/
// inject the commandline iframe into a content page
const cmdline_iframe = window.document.createElementNS(
"http://www.w3.org/1999/xhtml",
"iframe",
) as HTMLIFrameElement
cmdline_iframe.className = "cleanslate"
cmdline_iframe.setAttribute(
"src",
browser.runtime.getURL("static/commandline.html"),
)
cmdline_iframe.setAttribute("id", "cmdline_iframe")
cmdline_iframe.setAttribute("loading", "lazy")
let enabled = false
/** Initialise the cmdline_iframe element unless the window location is included in a value of config/noiframe */
async function init() {
const noiframe = await config.getAsync("noiframe")
const notridactyl = await config.getAsync("superignore")
if (noiframe === "false" && notridactyl !== "true" && !enabled) {
hide()
document.documentElement.appendChild(cmdline_iframe)
enabled = true
// first theming of page root
await theme(window.document.querySelector(":root"))
}
}
// Load the iframe immediately if we can (happens if tridactyl is reloaded or on ImageDocument)
// Else load lazily to avoid upsetting page JS that hates foreign iframes.
init().catch(() => {
// Surrender event loop with setTimeout() to page JS in case it's still doing stuff.
document.addEventListener("DOMContentLoaded", () =>
setTimeout(() => {
init().catch(e =>
logger.error("Couldn't initialise cmdline_iframe!", e),
)
}, 0),
)
})
export function show(hidehover = false) {
try {
/* Hide "hoverlink" pop-up which obscures command line
*
* Inspired by VVimpulation: https://github.com/amedama41/vvimpulation/commit/53065d015d1e9a892496619b51be83771f57b3d5
*/
if (hidehover) {
const a = window.document.createElement("A")
;(a as any).href = ""
document.body.appendChild(a)
a.focus({ preventScroll: true })
document.body.removeChild(a)
}
cmdline_iframe.classList.remove("hidden")
const height =
cmdline_iframe.contentWindow.document.body.offsetHeight + "px"
cmdline_iframe.setAttribute("style", `height: ${height} !important;`)
} catch (e) {
// Note: We can't use cmdline_logger.error because it will try to log
// the error in the commandline, which we can't show!
// cmdline_logger.error(e)
console.error(e)
}
}
export function hide() {
try {
cmdline_iframe.classList.add("hidden")
cmdline_iframe.setAttribute("style", "height: 0px !important;")
} catch (e) {
// Using cmdline_logger here is OK because cmdline_logger won't try to
// call hide(), thus we avoid the recursion that happens for show() and
// focus()
cmdline_logger.error(e)
}
}
export function focus() {
try {
cmdline_iframe.focus()
} catch (e) {
// Note: We can't use cmdline_logger.error because it will try to log
// the error in the commandline, which will need to focus() it again,
// which will throw again...
// cmdline_logger.error(e)
console.error(e)
}
}
export function blur() {
try {
cmdline_iframe.blur()
} catch (e) {
// Same as with hide(), it's ok to use cmdline_logger here
cmdline_logger.error(e)
}
}
export function hide_and_blur() {
hide()
blur()
}
export function executeWithoutCommandLine(fn) {
let parent
if (cmdline_iframe) {
parent = cmdline_iframe.parentNode
parent.removeChild(cmdline_iframe)
}
let result
try {
result = fn()
} catch (e) {
cmdline_logger.error(e)
}
if (cmdline_iframe) parent.appendChild(cmdline_iframe)
return result
}
import * as Messaging from "@src/lib/messaging"
import * as SELF from "@src/content/commandline_content"
Messaging.addListener("commandline_content", Messaging.attributeCaller(SELF))