From 19e6d5451c9117bbffd1c8480187fa23d116c561 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 4 Aug 2023 16:13:23 +1000 Subject: [PATCH] fix: only re-import dom modules in menu, don't use until post-re-import --- src/core/menu/menu.mjs | 16 ++++----------- src/load.mjs | 45 +++++++++++++++++++++++++++++++----------- src/shared/markup.js | 6 +++--- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/core/menu/menu.mjs b/src/core/menu/menu.mjs index f2d6a94..6abdc5c 100644 --- a/src/core/menu/menu.mjs +++ b/src/core/menu/menu.mjs @@ -171,8 +171,9 @@ window.addEventListener("message", (event) => { }); useState(["hotkey"], ([hotkey]) => { const { addKeyListener } = globalThis.__enhancerApi ?? {}, - [hotkeyRegistered] = useState(["hotkeyRegistered"]); - if (!hotkey || !addKeyListener || hotkeyRegistered) return; + [hotkeyRegistered] = useState(["hotkeyRegistered"]), + [renderStarted] = useState(["renderStarted"]); + if (!hotkey || !addKeyListener || hotkeyRegistered || !renderStarted) return; setState({ hotkeyRegistered: true }); addKeyListener(hotkey, (event) => { event.preventDefault(); @@ -195,16 +196,7 @@ useState(["theme"], ([theme]) => { useState(["rerender"], async () => { const [theme, icon] = useState(["theme", "icon"]); if (!theme || !icon) return; - // chrome extensions run in an isolated execution context - // but extension:// pages can access chrome apis - // => notion-enhancer api is imported directly - if (typeof globalThis.__enhancerApi === "undefined") { + if (typeof globalThis.__enhancerApi === "undefined") await import("../../shared/system.js"); - // in electron this isn't necessary, as a) scripts are - // not running in an isolated execution context and b) - // the notion:// protocol csp bypass allows scripts to - // set iframe globals via $iframe.contentWindow - } - // load stylesheets and api globals (await import("../../load.mjs")).default.then(render); }); diff --git a/src/load.mjs b/src/load.mjs index 81efb4e..4789f21 100644 --- a/src/load.mjs +++ b/src/load.mjs @@ -8,30 +8,53 @@ export default (async () => { // prettier-ignore - const { enhancerUrl } = globalThis.__enhancerApi, - isMenu = location.href.startsWith(enhancerUrl("core/menu/index.html")), + const { enhancerUrl, platform } = globalThis.__enhancerApi, + signedIn = localStorage["LRU:KeyValueStore2:current-user-id"], pageLoaded = /(^\/$)|((-|\/)[0-9a-f]{32}((\?.+)|$))/.test(location.pathname), - signedIn = localStorage["LRU:KeyValueStore2:current-user-id"]; - if (!isMenu && (!signedIn || !pageLoaded)) return; - if (!isMenu) console.log("notion-enhancer: loading..."); + IS_MENU = location.href.startsWith(enhancerUrl("core/menu/index.html")), + IS_ELECTRON = ['linux', 'win32', 'darwin'].includes(platform); + + if (!IS_MENU) { + if (!signedIn || !pageLoaded) return; + console.log("notion-enhancer: loading..."); + } + + // in electron, iframes cannot access node + // => relevant functionality can be provided + // by setting contentWindow.__enhancerApi from + // the preload.js parent script thanks to the + // notion:// protocol csp bypass + + // in browser, extensions run in an isolated + // execution context => __enhancerApi modules + // can't be passed from the parent script and + // must be re-imported. this is fine, since + // extension:// pages can access chrome apis + + // in both situations, modules that attach to + // the dom must be re-imported, and should not + // be used until import is complete, otherwise + // their local states will be cleared (e.g., + // references to registered hotkeys) await Promise.all([ - import(enhancerUrl("assets/icons.svg.js")), + // i.e. if (not_menu) or (is_menu && not_electron), then import + !(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("assets/icons.svg.js")), import(enhancerUrl("vendor/twind.min.js")), import(enhancerUrl("vendor/lucide.min.js")), import(enhancerUrl("vendor/htm.min.js")), ]); await Promise.all([ + !(!IS_MENU || !IS_ELECTRON) || import(enhancerUrl("shared/registry.js")), import(enhancerUrl("shared/events.js")), - import(enhancerUrl("shared/registry.js")), import(enhancerUrl("shared/markup.js")), ]); - const { getMods, isEnabled, modDatabase } = globalThis.__enhancerApi; + const { getMods, isEnabled, modDatabase } = globalThis.__enhancerApi; for (const mod of await getMods()) { if (!(await isEnabled(mod.id))) continue; const isTheme = mod._src.startsWith("themes/"); - if (isMenu && !(mod._src === "core" || isTheme)) continue; + if (IS_MENU && !(mod._src === "core" || isTheme)) continue; // clientStyles for (let stylesheet of mod.clientStyles ?? []) { @@ -42,7 +65,7 @@ export default (async () => { } // clientScripts - if (isMenu) continue; + if (IS_MENU) continue; const db = await modDatabase(mod.id); for (let script of mod.clientScripts ?? []) { script = await import(enhancerUrl(`${mod._src}/${script}`)); @@ -50,5 +73,5 @@ export default (async () => { } } - if (isMenu) console.log("notion-enhancer: ready"); + if (IS_MENU) console.log("notion-enhancer: ready"); })(); diff --git a/src/shared/markup.js b/src/shared/markup.js index b80814a..8d99c26 100644 --- a/src/shared/markup.js +++ b/src/shared/markup.js @@ -6,7 +6,7 @@ "use strict"; -const { twind, htm } = globalThis, +const { twind, htm, lucide } = globalThis, { readFile, iconColour, iconMonochrome } = globalThis.__enhancerApi; const kebabToPascalCase = (string) => @@ -45,8 +45,8 @@ const encodeSvg = (svg) => svg = mode === "mask" ? iconMonochrome : iconColour; } else { icon = kebabToPascalCase(icon); - if (!globalThis.lucide[icon]) return; - const [type, props, children] = globalThis.lucide[icon]; + if (!lucide[icon]) return; + const [type, props, children] = lucide[icon]; svg = hToString(type, props, ...children); } // https://antfu.me/posts/icons-in-pure-css