From 97167e571f3d387f2ad0d06c52b773c2ae36a9c6 Mon Sep 17 00:00:00 2001 From: Federico Date: Sat, 25 Sep 2021 00:47:44 +0700 Subject: [PATCH] Use Messenger in more places (Native Editor, Dev Tools) --- package-lock.json | 60 ++++++- package.json | 2 +- src/background/contextMenus.ts | 18 +-- src/background/devtools/internal.ts | 7 +- src/background/devtools/protocol.ts | 39 +++-- src/background/navigation.ts | 8 +- src/common.ts | 8 +- src/contentScript.ts | 6 +- src/contentScript/devTools.ts | 166 ++++++++------------ src/contentScript/lifecycle.ts | 15 +- src/contentScript/messenger/api.ts | 27 ++++ src/contentScript/messenger/registration.ts | 71 ++++++++- src/contentScript/notify.ts | 17 +- src/contentScript/script.ts | 25 --- src/contentScript/uipath.ts | 23 +-- src/nativeEditor/dynamic.ts | 93 +++++------ src/nativeEditor/index.ts | 4 +- src/nativeEditor/insertButton.tsx | 7 +- src/nativeEditor/insertPanel.tsx | 5 +- 19 files changed, 312 insertions(+), 289 deletions(-) delete mode 100644 src/contentScript/script.ts diff --git a/package-lock.json b/package-lock.json index 67cd244d8e..9b1d2bf532 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "webext-content-scripts": "^0.9.0", "webext-detect-page": "^3.0.2", "webext-dynamic-content-scripts": "^8.0.0", - "webext-messenger": "^0.7.1", + "webext-messenger": "^0.8.0", "webext-patterns": "^1.1.1", "webext-polyfill-kinda": "^0.1.0", "webextension-polyfill-ts": "^0.26.0" @@ -18532,6 +18532,11 @@ "@types/node": "*" } }, + "node_modules/@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -36040,6 +36045,18 @@ "node": ">=8" } }, + "node_modules/p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "dependencies": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-timeout": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.0.tgz", @@ -39273,6 +39290,14 @@ "node": ">=0.12" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -43815,10 +43840,11 @@ } }, "node_modules/webext-messenger": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/webext-messenger/-/webext-messenger-0.7.1.tgz", - "integrity": "sha512-cnMW7JZLsvHLX1KUryJfarPk4GtzVo3YfSvDoBC7Wk4BKaMBqXq2RWYp4oPzAp+TMXWEGBwwSWVI9uFaCUKUQg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/webext-messenger/-/webext-messenger-0.8.0.tgz", + "integrity": "sha512-tN8ltMd6WMoVgFebimMz+S9cQXD8Zvryk0PQCJItqMZZAYPjgffcM+3manOp9zGbsStg0Faie+6c973Bt9HKwg==", "dependencies": { + "p-retry": "^4.6.1", "serialize-error": "^8.1.0", "type-fest": "^2.3.4", "webext-detect-page": "^3.0.2", @@ -58986,6 +59012,11 @@ "@types/node": "*" } }, + "@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -72662,6 +72693,15 @@ "aggregate-error": "^3.0.0" } }, + "p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "requires": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + } + }, "p-timeout": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.0.0.tgz", @@ -75208,6 +75248,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -78833,10 +78878,11 @@ } }, "webext-messenger": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/webext-messenger/-/webext-messenger-0.7.1.tgz", - "integrity": "sha512-cnMW7JZLsvHLX1KUryJfarPk4GtzVo3YfSvDoBC7Wk4BKaMBqXq2RWYp4oPzAp+TMXWEGBwwSWVI9uFaCUKUQg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/webext-messenger/-/webext-messenger-0.8.0.tgz", + "integrity": "sha512-tN8ltMd6WMoVgFebimMz+S9cQXD8Zvryk0PQCJItqMZZAYPjgffcM+3manOp9zGbsStg0Faie+6c973Bt9HKwg==", "requires": { + "p-retry": "^4.6.1", "serialize-error": "^8.1.0", "type-fest": "^2.3.4", "webext-detect-page": "^3.0.2", diff --git a/package.json b/package.json index a196f26156..202d0fa9b5 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "webext-content-scripts": "^0.9.0", "webext-detect-page": "^3.0.2", "webext-dynamic-content-scripts": "^8.0.0", - "webext-messenger": "^0.7.1", + "webext-messenger": "^0.8.0", "webext-patterns": "^1.1.1", "webext-polyfill-kinda": "^0.1.0", "webextension-polyfill-ts": "^0.26.0" diff --git a/src/background/contextMenus.ts b/src/background/contextMenus.ts index 9753f37634..b6b0427e92 100644 --- a/src/background/contextMenus.ts +++ b/src/background/contextMenus.ts @@ -20,8 +20,10 @@ import { browser, Menus, Tabs } from "webextension-polyfill-ts"; import { isBackgroundPage } from "webext-detect-page"; import { reportError } from "@/telemetry/logging"; import { noop } from "lodash"; -import { handleMenuAction } from "@/contentScript/messenger/api"; -import { showNotification } from "@/contentScript/notify"; +import { + handleMenuAction, + showNotification, +} from "@/contentScript/messenger/api"; import { ensureContentScript } from "@/background/util"; import { reportEvent } from "@/telemetry/events"; import { getErrorMessage, hasCancelRootCause } from "@/errors"; @@ -75,22 +77,16 @@ async function dispatchMenu( args: info, maxWaitMillis: CONTEXT_MENU_INSTALL_MS, }); - void showNotification(target, { - message: "Ran content menu item action", - className: "success", - }); + void showNotification(target, "Ran content menu item action", "success"); } catch (error: unknown) { if (hasCancelRootCause(error)) { - void showNotification(target, { - message: "The action was cancelled", - className: "info", - }); + void showNotification(target, "The action was cancelled", "info"); } else { const message = `Error processing context menu action: ${getErrorMessage( error )}`; reportError(new Error(message)); - void showNotification(target, { message, className: "error" }); + void showNotification(target, message, "error"); } } diff --git a/src/background/devtools/internal.ts b/src/background/devtools/internal.ts index 11e436b00a..c923c9d6cb 100644 --- a/src/background/devtools/internal.ts +++ b/src/background/devtools/internal.ts @@ -39,10 +39,10 @@ import { isBackgroundPage } from "webext-detect-page"; import { uuidv4 } from "@/types/helpers"; import { callBackground } from "@/background/devtools/external"; import { ensureContentScript } from "@/background/util"; -import * as nativeEditorProtocol from "@/nativeEditor"; import { reactivate } from "@/background/navigation"; import { expectContext, forbidContext } from "@/utils/expectContext"; import { getErrorMessage, isPrivatePageError } from "@/errors"; +import { clearDynamicElements } from "@/contentScript/messenger/api"; const TOP_LEVEL_FRAME_ID = 0; @@ -184,10 +184,7 @@ export function liftBackground< async function resetTab(tabId: number): Promise { try { - await nativeEditorProtocol.clear( - { tabId, frameId: TOP_LEVEL_FRAME_ID }, - {} - ); + await clearDynamicElements({ tabId, frameId: TOP_LEVEL_FRAME_ID }, {}); } catch (error: unknown) { console.warn("Error clearing dynamic elements for tab: %d", tabId, { error, diff --git a/src/background/devtools/protocol.ts b/src/background/devtools/protocol.ts index fe1bf0d8b5..a51c6998be 100644 --- a/src/background/devtools/protocol.ts +++ b/src/background/devtools/protocol.ts @@ -17,10 +17,8 @@ import { browser, Runtime } from "webextension-polyfill-ts"; import * as contentScriptProtocol from "@/contentScript/devTools"; -import * as robotProtocol from "@/contentScript/uipath"; import { Framework, FrameworkMeta } from "@/messaging/constants"; import * as nativeSelectionProtocol from "@/nativeEditor/selector"; -import * as nativeEditorProtocol from "@/nativeEditor"; import { PanelSelectionResult } from "@/nativeEditor/insertPanel"; import { Availability } from "@/blocks/types"; import { ReaderTypeConfig } from "@/blocks/readers/factory"; @@ -37,6 +35,7 @@ import { removeActionPanel, showActionPanel, } from "@/contentScript/messenger/api"; +import * as contentScript from "@/contentScript/messenger/api"; export const registerPort = liftBackground( "REGISTER_PORT", @@ -67,15 +66,14 @@ export const ensureScript = liftBackground( export const readSelectedElement = liftBackground( "READ_ELEMENT", - (target: Target) => async () => contentScriptProtocol.readSelected(target) + (target: Target) => async () => contentScript.readSelected(target) ); export const detectFrameworks: ( port: Runtime.Port ) => Promise = liftBackground( "DETECT_FRAMEWORKS", - (target: Target) => async () => - contentScriptProtocol.detectFrameworks(target) as Promise + (target: Target) => async () => contentScript.detectFrameworks(target, null) ); export const cancelSelectElement = liftBackground( @@ -113,14 +111,14 @@ export const selectElement = liftBackground( export const insertButton = liftBackground( "INSERT_BUTTON", - (target: Target) => async () => nativeEditorProtocol.insertButton(target) + (target: Target) => async () => contentScript.insertButton(target) ); export const insertPanel: ( port: Runtime.Port ) => Promise = liftBackground( "INSERT_PANEL", - (target: Target) => async () => nativeEditorProtocol.insertPanel(target) + (target: Target) => async () => contentScript.insertPanel(target) ); export const showBrowserActionPanel = liftBackground( @@ -131,42 +129,42 @@ export const showBrowserActionPanel = liftBackground( export const updateDynamicElement = liftBackground( "UPDATE_DYNAMIC_ELEMENT", (target: Target) => async (element: DynamicDefinition) => - nativeEditorProtocol.updateDynamicElement(target, element) + contentScript.updateDynamicElement(target, element) ); export const clearDynamicElements = liftBackground( "CLEAR_DYNAMIC", (target: Target) => async ({ uuid }: { uuid?: UUID }) => - nativeEditorProtocol.clear(target, { uuid }) + contentScript.clearDynamicElements(target, { uuid }) ); export const enableDataOverlay = liftBackground( "ENABLE_ELEMENT", (target: Target) => async (uuid: UUID) => - nativeEditorProtocol.enableOverlay(target, `[data-uuid="${uuid}"]`) + contentScript.enableOverlay(target, `[data-uuid="${uuid}"]`) ); export const enableSelectorOverlay = liftBackground( "ENABLE_SELECTOR", (target: Target) => async (selector: string) => - nativeEditorProtocol.enableOverlay(target, selector) + contentScript.enableOverlay(target, selector) ); export const disableOverlay = liftBackground( "DISABLE_ELEMENT", - (target: Target) => async () => nativeEditorProtocol.disableOverlay(target) + (target: Target) => async () => contentScript.disableOverlay(target) ); export const getInstalledExtensionPointIds = liftBackground( "INSTALLED_EXTENSION_POINT_IDS", (target: Target) => async () => - nativeEditorProtocol.getInstalledExtensionPointIds(target) + contentScript.getInstalledExtensionPointIds(target) ); export const checkAvailable = liftBackground( "CHECK_AVAILABLE", (target: Target) => async (availability: Availability) => - nativeEditorProtocol.checkAvailable(target, availability) + contentScript.checkAvailable(target, availability) ); export const searchWindow: ( @@ -175,13 +173,13 @@ export const searchWindow: ( ) => Promise<{ results: unknown[] }> = liftBackground( "SEARCH_WINDOW", (target: Target) => async (query: string) => - contentScriptProtocol.searchWindow(target, query) + contentScript.searchWindow(target, query) ); export const runBlock = liftBackground( "RUN_BLOCK", (target: Target) => async (args: contentScriptProtocol.RunBlockArgs) => - contentScriptProtocol.runBlock(target, args) + contentScript.runBlock(target, args) ); export const runReaderBlock = liftBackground( @@ -193,7 +191,7 @@ export const runReaderBlock = liftBackground( id: RegistryId; rootSelector?: string; }) => - contentScriptProtocol.runReaderBlock(target, { + contentScript.runReaderBlock(target, { id, rootSelector, }) @@ -208,7 +206,7 @@ export const runReader = liftBackground( config: ReaderTypeConfig; rootSelector?: string; }) => - contentScriptProtocol.runReader(target, { + contentScript.runReader(target, { config, rootSelector, }) @@ -216,17 +214,16 @@ export const runReader = liftBackground( export const uninstallActionPanelPanel = liftBackground( "UNINSTALL_ACTION_PANEL_PANEL", - // False positive - it's the inner method that should be async (target) => async ({ extensionId }: { extensionId: UUID }) => removeActionPanel(target, extensionId) ); export const initUiPathRobot = liftBackground( "UIPATH_INIT", - (target: Target) => async () => robotProtocol.initRobot(target) + (target: Target) => async () => contentScript.initRobot(target) ); export const getUiPathProcesses = liftBackground( "UIPATH_GET_PROCESSES", - (target: Target) => async () => robotProtocol.getProcesses(target) + (target: Target) => async () => contentScript.getProcesses(target) ); diff --git a/src/background/navigation.ts b/src/background/navigation.ts index 00c49b9de0..23d3295946 100644 --- a/src/background/navigation.ts +++ b/src/background/navigation.ts @@ -16,20 +16,16 @@ * along with this program. If not, see . */ -import * as contentScript from "@/contentScript/lifecycle"; import { liftBackground } from "@/background/protocol"; import { browser, WebNavigation } from "webextension-polyfill-ts"; -import { reactivateTab } from "@/contentScript/messenger/api"; +import { handleNavigate, reactivateTab } from "@/contentScript/messenger/api"; import { notifyTabs } from "@/background/util"; async function historyListener( details: WebNavigation.OnHistoryStateUpdatedDetailsType ) { try { - await contentScript.notifyNavigation( - { tabId: details.tabId, frameId: details.frameId }, - {} - ); + await handleNavigate(details); } catch (error: unknown) { console.warn("Error notifying page navigation", error); } diff --git a/src/common.ts b/src/common.ts index c204ff3e8e..bf6d3da71e 100644 --- a/src/common.ts +++ b/src/common.ts @@ -18,10 +18,10 @@ import { createSendScriptMessage } from "./messaging/chrome"; import { DETECT_FRAMEWORK_VERSIONS, + FrameworkMeta, READ_WINDOW, SEARCH_WINDOW, } from "./messaging/constants"; -import type { Library } from "@/vendors/libraryDetector/detect"; type ReadSpec = >(arg: { pathSpec: T; @@ -36,6 +36,6 @@ export const withSearchWindow = createSendScriptMessage<{ results: unknown[] }>( SEARCH_WINDOW ); -export const withDetectFrameworkVersions = createSendScriptMessage( - DETECT_FRAMEWORK_VERSIONS -); +export const withDetectFrameworkVersions = createSendScriptMessage< + FrameworkMeta[] +>(DETECT_FRAMEWORK_VERSIONS); diff --git a/src/contentScript.ts b/src/contentScript.ts index 168a279b89..be6741f763 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -24,19 +24,16 @@ import "@/contentScript/messenger/registration"; import addErrorListeners from "@/contentScript/errors"; import registerBuiltinBlocks from "@/blocks/registerBuiltinBlocks"; import registerContribBlocks from "@/contrib/registerContribBlocks"; -import "@/contentScript/devTools"; -import "@/contentScript/contextMenus"; import addContentScriptListener from "@/contentScript/backgroundProtocol"; import { handleNavigate } from "@/contentScript/lifecycle"; import addExecutorListener from "@/contentScript/executor"; import "@/messaging/external"; -import "@/contentScript/script"; import "@/vendors/notify"; import { markReady, updateTabInfo } from "@/contentScript/context"; import { initTelemetry } from "@/telemetry/events"; -import "@/contentScript/uipath"; import { markTabAsReady, whoAmI } from "@/background/messenger/api"; import { ENSURE_CONTENT_SCRIPT_READY } from "./messaging/constants"; +import { addListenerForUpdateSelectedElement } from "./devTools/getSelectedElement"; const PIXIEBRIX_SYMBOL = Symbol.for("pixiebrix-content-script"); const uuid = uuidv4(); @@ -56,6 +53,7 @@ async function init(): Promise { addContentScriptListener(); addExecutorListener(); + addListenerForUpdateSelectedElement(); initTelemetry(); const sender = await whoAmI(); diff --git a/src/contentScript/devTools.ts b/src/contentScript/devTools.ts index 46170948e8..bc937fdd58 100644 --- a/src/contentScript/devTools.ts +++ b/src/contentScript/devTools.ts @@ -15,10 +15,7 @@ * along with this program. If not, see . */ -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import { deserializeError } from "serialize-error"; -import { isContentScript } from "webext-detect-page"; -import { withDetectFrameworkVersions, withSearchWindow } from "@/common"; import { makeRead, ReaderTypeConfig } from "@/blocks/readers/factory"; import FRAMEWORK_ADAPTERS from "@/frameworks/adapters"; import { getComponentData } from "@/pageScript/protocol"; @@ -26,26 +23,13 @@ import blockRegistry from "@/blocks/registry"; import { getCssSelector } from "css-selector-generator"; import { runStage } from "@/blocks/combinators"; import { IReader, RegistryId } from "@/core"; -import { - addListenerForUpdateSelectedElement, - selectedElement, -} from "@/devTools/getSelectedElement"; - -// Install handlers -import "@/nativeEditor/insertButton"; -import "@/nativeEditor/insertPanel"; -import "@/nativeEditor/dynamic"; +import { selectedElement } from "@/devTools/getSelectedElement"; import { isNullOrBlank, resolveObj } from "@/utils"; -import type { Target } from "@/types"; import { BlockConfig } from "@/blocks/types"; import { cloneDeep } from "lodash"; import ConsoleLogger from "@/tests/ConsoleLogger"; import { SerializableResponse } from "@/messaging/protocol"; -if (isContentScript()) { - addListenerForUpdateSelectedElement(); -} - async function read(factory: () => Promise): Promise { try { return await factory(); @@ -58,19 +42,6 @@ async function read(factory: () => Promise): Promise { } } -export const detectFrameworks = liftContentScript( - "DETECT_FRAMEWORKS", - async () => withDetectFrameworkVersions(null) -); - -export const searchWindow: ( - target: Target, - query: string -) => Promise<{ results: unknown[] }> = liftContentScript( - "SEARCH_WINDOW", - async (query: string) => withSearchWindow({ query }) -); - export type RunBlockArgs = { blockConfig: BlockConfig; args: Record; @@ -79,82 +50,79 @@ export type RunBlockArgs = { /** * Run a single block (e.g., for generating output previews) */ -export const runBlock = liftContentScript( - "RUN_SINGLE_BLOCK", - async ({ blockConfig, args }: RunBlockArgs) => { - const block = await blockRegistry.lookup(blockConfig.id); - - const result = await runStage(block, blockConfig, args, { - context: args, - logger: new ConsoleLogger(), - headless: true, - validate: true, - logValues: false, - // TODO: need to support other roots for triggers. Or we at least need to throw an error so we can show a message - // in the UX that non-root contexts aren't supported - root: document, - }); - - return cloneDeep(result) as SerializableResponse; - } -); - -export const runReaderBlock = liftContentScript( - "RUN_READER_BLOCK", - async ({ id, rootSelector }: { id: RegistryId; rootSelector?: string }) => { - const root = isNullOrBlank(rootSelector) - ? document - : // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive for jquery find method - $(document).find(rootSelector).get(0); - - if (id === "@pixiebrix/context-menu-data") { - // HACK: special handling for context menu built-in - if (root instanceof HTMLElement) { - return { - // TODO: extract the media type - mediaType: null, - // Use `innerText` because only want human readable elements - // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#differences_from_innertext - // eslint-disable-next-line unicorn/prefer-dom-node-text-content - linkText: root.tagName === "A" ? root.innerText : null, - linkUrl: root.tagName === "A" ? root.getAttribute("href") : null, - srcUrl: root.getAttribute("src"), - documentUrl: document.location.href, - }; - } +export async function runBlock({ blockConfig, args }: RunBlockArgs) { + const block = await blockRegistry.lookup(blockConfig.id); + + const result = await runStage(block, blockConfig, args, { + context: args, + logger: new ConsoleLogger(), + headless: true, + validate: true, + logValues: false, + // TODO: need to support other roots for triggers. Or we at least need to throw an error so we can show a message + // in the UX that non-root contexts aren't supported + root: document, + }); + + return cloneDeep(result) as SerializableResponse; +} +export async function runReaderBlock({ + id, + rootSelector, +}: { + id: RegistryId; + rootSelector?: string; +}) { + const root = isNullOrBlank(rootSelector) + ? document + : // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive for jquery find method + $(document).find(rootSelector).get(0); + + if (id === "@pixiebrix/context-menu-data") { + // HACK: special handling for context menu built-in + if (root instanceof HTMLElement) { return { - selectionText: window.getSelection().toString(), + // TODO: extract the media type + mediaType: null, + // Use `innerText` because only want human readable elements + // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#differences_from_innertext + // eslint-disable-next-line unicorn/prefer-dom-node-text-content + linkText: root.tagName === "A" ? root.innerText : null, + linkUrl: root.tagName === "A" ? root.getAttribute("href") : null, + srcUrl: root.getAttribute("src"), documentUrl: document.location.href, }; } - const reader = (await blockRegistry.lookup(id)) as IReader; - return reader.read(root); - } -); - -export const runReader = liftContentScript( - "RUN_READER", - async ({ - config, - rootSelector, - }: { - config: ReaderTypeConfig; - rootSelector?: string; - }) => { - console.debug("runReader", { config, rootSelector }); - - const root = isNullOrBlank(rootSelector) - ? document - : // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive for JQuery - $(document).find(rootSelector).get(0); - - return makeRead(config)(root); + return { + selectionText: window.getSelection().toString(), + documentUrl: document.location.href, + }; } -); -export const readSelected = liftContentScript("READ_SELECTED", async () => { + const reader = (await blockRegistry.lookup(id)) as IReader; + return reader.read(root); +} + +export async function runReader({ + config, + rootSelector, +}: { + config: ReaderTypeConfig; + rootSelector?: string; +}) { + console.debug("runReader", { config, rootSelector }); + + const root = isNullOrBlank(rootSelector) + ? document + : // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive for JQuery + $(document).find(rootSelector).get(0); + + return makeRead(config)(root); +} + +export async function readSelected() { if (selectedElement) { const selector = getCssSelector(selectedElement); console.debug(`Generated selector: ${selector}`); @@ -179,4 +147,4 @@ export const readSelected = liftContentScript("READ_SELECTED", async () => { return { error: "No element selected", }; -}); +} diff --git a/src/contentScript/lifecycle.ts b/src/contentScript/lifecycle.ts index 23c5466e2d..1768f0dbcc 100644 --- a/src/contentScript/lifecycle.ts +++ b/src/contentScript/lifecycle.ts @@ -18,7 +18,6 @@ import { loadOptions } from "@/options/loader"; import extensionPointRegistry from "@/extensionPoints/registry"; import { ResolvedExtension, IExtensionPoint, RegistryId, UUID } from "@/core"; -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import * as context from "@/contentScript/context"; import * as actionPanel from "@/actionPanel/native"; import { PromiseCancelled, sleep } from "@/utils"; @@ -295,9 +294,8 @@ async function waitLoaded(cancel: () => boolean): Promise { * Handle a website navigation, e.g., page load or a URL change in an SPA. */ export async function handleNavigate({ - openerTabId, force, -}: { openerTabId?: number; force?: boolean } = {}): Promise { +}: { force?: boolean } = {}): Promise { if (context.frameId == null) { console.debug( "Ignoring handleNavigate because context.frameId is not set yet" @@ -326,10 +324,6 @@ export async function handleNavigate({ const extensionPoints = await loadExtensionsOnce(); - if (openerTabId != null) { - console.debug(`Setting opener tabId: ${openerTabId}`); - } - if (extensionPoints.length > 0) { _navSequence++; @@ -358,13 +352,6 @@ export async function handleNavigate({ } } -export const notifyNavigation = liftContentScript( - "NAVIGATE", - async ({ openerTabId }: { openerTabId?: number; frameId?: number }) => - handleNavigate({ openerTabId }), - { asyncResponse: false } -); - export async function queueReactivateTab() { console.debug("contentScript will reload extensions on next navigation"); _reloadOnNextNavigate = true; diff --git a/src/contentScript/messenger/api.ts b/src/contentScript/messenger/api.ts index 4400d088c2..568aa879a9 100644 --- a/src/contentScript/messenger/api.ts +++ b/src/contentScript/messenger/api.ts @@ -39,6 +39,33 @@ export const toggleActionPanel = getContentScriptMethod("TOGGLE_ACTION_PANEL"); export const showActionPanel = getContentScriptMethod("SHOW_ACTION_PANEL"); export const hideActionPanel = getContentScriptMethod("HIDE_ACTION_PANEL"); export const removeActionPanel = getContentScriptMethod("REMOVE_ACTION_PANEL"); +export const insertPanel = getContentScriptMethod("INSERT_PANEL"); +export const insertButton = getContentScriptMethod("INSERT_BUTTON"); + +export const initRobot = getContentScriptMethod("UIPATH_INIT"); +export const getProcesses = getContentScriptMethod("UIPATH_GET_PROCESSES"); +export const searchWindow = getContentScriptMethod("SEARCH_WINDOW"); +export const detectFrameworks = getContentScriptMethod("DETECT_FRAMEWORKS"); + +export const runBlock = getContentScriptMethod("RUN_SINGLE_BLOCK"); +export const runReaderBlock = getContentScriptMethod("RUN_READER_BLOCK"); +export const runReader = getContentScriptMethod("RUN_READER"); +export const readSelected = getContentScriptMethod("READ_SELECTED"); + +export const clearDynamicElements = getContentScriptMethod( + "CLEAR_DYNAMIC_ELEMENTS" +); +export const updateDynamicElement = getContentScriptMethod( + "UPDATE_DYNAMIC_ELEMENT" +); +export const enableOverlay = getContentScriptMethod("ENABLE_OVERLAY"); +export const disableOverlay = getContentScriptMethod("DISABLE_OVERLAY"); +export const getInstalledExtensionPointIds = getContentScriptMethod( + "INSTALLED_EXTENSIONS" +); +export const checkAvailable = getContentScriptMethod("CHECK_AVAILABLE"); +export const handleNavigate = getContentScriptMethod("HANDLE_NAVIGATE"); +export const showNotification = getContentScriptMethod("SHOW_NOTIFICATION"); // Temporary, webext-messenger depends on this global (globalThis as any).browser = browser; diff --git a/src/contentScript/messenger/registration.ts b/src/contentScript/messenger/registration.ts index 9fd619c32a..a6793cf1e9 100644 --- a/src/contentScript/messenger/registration.ts +++ b/src/contentScript/messenger/registration.ts @@ -20,7 +20,12 @@ import { registerMethods } from "webext-messenger"; import { browser } from "webextension-polyfill-ts"; import { expectContext } from "@/utils/expectContext"; import { handleMenuAction } from "@/contentScript/contextMenus"; -import { queueReactivateTab, reactivateTab } from "@/contentScript/lifecycle"; +import { + getInstalledIds, + handleNavigate, + queueReactivateTab, + reactivateTab, +} from "@/contentScript/lifecycle"; import { getFormDefinition, resolveForm, @@ -32,6 +37,24 @@ import { toggleActionPanel, removeExtension, } from "@/actionPanel/native"; +import { + clearDynamicElements, + disableOverlay, + enableOverlay, + insertButton, + insertPanel, + updateDynamicElement, +} from "@/nativeEditor"; +import { getProcesses, initRobot } from "@/contentScript/uipath"; +import { withDetectFrameworkVersions, withSearchWindow } from "@/common"; +import { + runBlock, + runReaderBlock, + runReader, + readSelected, +} from "@/contentScript/devTools"; +import { checkAvailable } from "@/blocks/available"; +import { showNotification } from "@/contentScript/notify"; expectContext("contentScript"); @@ -43,13 +66,36 @@ declare global { FORM_GET_DEFINITION: typeof getFormDefinition; FORM_RESOLVE: typeof resolveForm; FORM_CANCEL: typeof cancelForm; + QUEUE_REACTIVATE_TAB: typeof queueReactivateTab; REACTIVATE_TAB: typeof reactivateTab; + HANDLE_MENU_ACTION: typeof handleMenuAction; TOGGLE_ACTION_PANEL: typeof toggleActionPanel; SHOW_ACTION_PANEL: typeof showActionPanel; HIDE_ACTION_PANEL: typeof hideActionPanel; REMOVE_ACTION_PANEL: typeof removeExtension; + INSERT_PANEL: typeof insertPanel; + INSERT_BUTTON: typeof insertButton; + + UIPATH_INIT: typeof initRobot; + UIPATH_GET_PROCESSES: typeof getProcesses; + + SEARCH_WINDOW: typeof withSearchWindow; + DETECT_FRAMEWORKS: typeof withDetectFrameworkVersions; + RUN_SINGLE_BLOCK: typeof runBlock; + RUN_READER_BLOCK: typeof runReaderBlock; + RUN_READER: typeof runReader; + READ_SELECTED: typeof readSelected; + + CLEAR_DYNAMIC_ELEMENTS: typeof clearDynamicElements; + UPDATE_DYNAMIC_ELEMENT: typeof updateDynamicElement; + ENABLE_OVERLAY: typeof enableOverlay; + DISABLE_OVERLAY: typeof disableOverlay; + INSTALLED_EXTENSIONS: typeof getInstalledIds; + CHECK_AVAILABLE: typeof checkAvailable; + HANDLE_NAVIGATE: typeof handleNavigate; + SHOW_NOTIFICATION: typeof showNotification; } } @@ -57,11 +103,34 @@ registerMethods({ FORM_GET_DEFINITION: getFormDefinition, FORM_RESOLVE: resolveForm, FORM_CANCEL: cancelForm, + QUEUE_REACTIVATE_TAB: queueReactivateTab, REACTIVATE_TAB: reactivateTab, + HANDLE_MENU_ACTION: handleMenuAction, TOGGLE_ACTION_PANEL: toggleActionPanel, SHOW_ACTION_PANEL: showActionPanel, HIDE_ACTION_PANEL: hideActionPanel, REMOVE_ACTION_PANEL: removeExtension, + INSERT_PANEL: insertPanel, + INSERT_BUTTON: insertButton, + + UIPATH_INIT: initRobot, + UIPATH_GET_PROCESSES: getProcesses, + + SEARCH_WINDOW: withSearchWindow, + DETECT_FRAMEWORKS: withDetectFrameworkVersions, + RUN_SINGLE_BLOCK: runBlock, + RUN_READER_BLOCK: runReaderBlock, + RUN_READER: runReader, + READ_SELECTED: readSelected, + + CLEAR_DYNAMIC_ELEMENTS: clearDynamicElements, + UPDATE_DYNAMIC_ELEMENT: updateDynamicElement, + ENABLE_OVERLAY: enableOverlay, + DISABLE_OVERLAY: disableOverlay, + INSTALLED_EXTENSIONS: getInstalledIds, + CHECK_AVAILABLE: checkAvailable, + HANDLE_NAVIGATE: handleNavigate, + SHOW_NOTIFICATION: showNotification, }); diff --git a/src/contentScript/notify.ts b/src/contentScript/notify.ts index 6f360a0c33..037f985dbe 100644 --- a/src/contentScript/notify.ts +++ b/src/contentScript/notify.ts @@ -15,17 +15,16 @@ * along with this program. If not, see . */ -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import { merge } from "lodash"; -export const showNotification = liftContentScript( - "SHOW_NOTIFICATION", - async ({ message, className }: { message: string; className: string }) => { - $.notify(message, { - className, - }); - } -); +export async function showNotification( + message: string, + className: "error" | "info" | "success" +): Promise { + $.notify(message, { + className, + }); +} export const DEFAULT_ACTION_RESULTS = { error: { diff --git a/src/contentScript/script.ts b/src/contentScript/script.ts deleted file mode 100644 index 4fc8b9f7a3..0000000000 --- a/src/contentScript/script.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2021 PixieBrix, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import { liftContentScript } from "@/contentScript/backgroundProtocol"; -import { withSearchWindow, withDetectFrameworkVersions } from "@/common"; - -export const searchWindow = liftContentScript("searchWindow", withSearchWindow); -export const detectFrameworks = liftContentScript( - "detectFrameworks", - withDetectFrameworkVersions -); diff --git a/src/contentScript/uipath.ts b/src/contentScript/uipath.ts index 192bc28697..ebe3fd93f5 100644 --- a/src/contentScript/uipath.ts +++ b/src/contentScript/uipath.ts @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import { IRobotSDK } from "@uipath/robot/dist/iRobotSDK"; import UiPathRobot from "@/contrib/uipath/UiPathRobot"; +import { RobotProcess } from "@uipath/robot/dist/models"; let _robot: IRobotSDK; @@ -27,7 +27,7 @@ type InitResponse = { consentCode?: string; }; -async function _initRobot(): Promise { +export async function initRobot(): Promise { if (_robot) { return { missingComponents: false, @@ -60,17 +60,10 @@ async function _initRobot(): Promise { }); } -export const initRobot = liftContentScript("UIPATH_INIT", async () => - _initRobot() -); - -export const getProcesses = liftContentScript( - "UIPATH_GET_PROCESSES", - async () => { - if (!_robot) { - throw new Error("UiPath not initialized"); - } - - return _robot.getProcesses(); +export async function getProcesses(): Promise { + if (!_robot) { + throw new Error("UiPath not initialized"); } -); + + return _robot.getProcesses(); +} diff --git a/src/nativeEditor/dynamic.ts b/src/nativeEditor/dynamic.ts index 530e13cdf2..14c8686dc0 100644 --- a/src/nativeEditor/dynamic.ts +++ b/src/nativeEditor/dynamic.ts @@ -16,15 +16,9 @@ */ import { EmptyConfig, IExtension, IExtensionPoint, UUID } from "@/core"; -import { liftContentScript } from "@/contentScript/backgroundProtocol"; -import { - clearDynamic, - getInstalledIds, - runDynamic, -} from "@/contentScript/lifecycle"; +import { clearDynamic, runDynamic } from "@/contentScript/lifecycle"; import { fromJS as extensionPointFactory } from "@/extensionPoints/factory"; import Overlay from "@/nativeEditor/Overlay"; -import { checkAvailable as _checkAvailable } from "@/blocks/available"; import { ExtensionPointConfig, ExtensionPointDefinition, @@ -44,68 +38,53 @@ export interface DynamicDefinition< let _overlay: Overlay | null = null; const _temporaryExtensions: Map = new Map(); -export const clear = liftContentScript( - "CLEAR_DYNAMIC", - async ({ uuid }: { uuid?: UUID }) => { - clearDynamic(uuid); - if (uuid) { - _temporaryExtensions.delete(uuid); - } else { - _temporaryExtensions.clear(); - } +export async function clearDynamicElements({ + uuid, +}: { + uuid?: UUID; +}): Promise { + clearDynamic(uuid); + if (uuid) { + _temporaryExtensions.delete(uuid); + } else { + _temporaryExtensions.clear(); } -); +} -export const getInstalledExtensionPointIds = liftContentScript( - "INSTALLED_EXTENSIONS", - async () => getInstalledIds() -); +export async function updateDynamicElement({ + extensionPoint: extensionPointConfig, + extension: extensionConfig, +}: DynamicDefinition): Promise { + const extensionPoint = extensionPointFactory(extensionPointConfig); -export const updateDynamicElement = liftContentScript( - "UPDATE_DYNAMIC_ELEMENT", - async ({ - extensionPoint: extensionPointConfig, - extension: extensionConfig, - }: DynamicDefinition) => { - const extensionPoint = extensionPointFactory(extensionPointConfig); + _temporaryExtensions.set(extensionConfig.id, extensionPoint); - _temporaryExtensions.set(extensionConfig.id, extensionPoint); + clearDynamic(extensionConfig.id, { clearTrace: false }); - clearDynamic(extensionConfig.id, { clearTrace: false }); + // In practice, should be a no-op because the page editor handles the extensionPoint + const resolved = await resolveDefinitions(extensionConfig); - // In practice, should be a no-op because the page editor handles the extensionPoint - const resolved = await resolveDefinitions(extensionConfig); + extensionPoint.addExtension(resolved); + await runDynamic(extensionConfig.id, extensionPoint); +} - extensionPoint.addExtension(resolved); - await runDynamic(extensionConfig.id, extensionPoint); +export async function enableOverlay(selector: string): Promise { + if (!selector) { + throw new Error(`Selector not found: ${selector}`); } -); - -export const enableOverlay = liftContentScript( - "ENABLE_OVERLAY", - async (selector: string) => { - if (!selector) { - throw new Error(`Selector not found: ${selector}`); - } - - if (_overlay == null) { - _overlay = new Overlay(); - } - // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive on JQuery method - const $elt = $(document).find(selector); - _overlay.inspect($elt.toArray(), null); + if (_overlay == null) { + _overlay = new Overlay(); } -); -export const disableOverlay = liftContentScript("DISABLE_OVERLAY", async () => { + // eslint-disable-next-line unicorn/no-array-callback-reference -- false positive on JQuery method + const $elt = $(document).find(selector); + _overlay.inspect($elt.toArray(), null); +} + +export async function disableOverlay(): Promise { if (_overlay != null) { _overlay.remove(); _overlay = null; } -}); - -export const checkAvailable = liftContentScript( - "CHECK_AVAILABLE", - _checkAvailable -); +} diff --git a/src/nativeEditor/index.ts b/src/nativeEditor/index.ts index f9dc8e63ee..2faeb3b6c8 100644 --- a/src/nativeEditor/index.ts +++ b/src/nativeEditor/index.ts @@ -16,12 +16,10 @@ */ export { - clear, + clearDynamicElements, enableOverlay, disableOverlay, updateDynamicElement, - checkAvailable, - getInstalledExtensionPointIds, } from "./dynamic"; export { insertButton } from "./insertButton"; export { insertPanel } from "./insertPanel"; diff --git a/src/nativeEditor/insertButton.tsx b/src/nativeEditor/insertButton.tsx index 9c3b5cde72..b98183d2cf 100644 --- a/src/nativeEditor/insertButton.tsx +++ b/src/nativeEditor/insertButton.tsx @@ -18,7 +18,6 @@ // https://github.com/facebook/react/blob/7559722a865e89992f75ff38c1015a865660c3cd/packages/react-devtools-shared/src/backend/views/Highlighter/index.js import { uuidv4 } from "@/types/helpers"; -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import { ElementInfo } from "./frameworks"; import { userSelectElement } from "./selector"; import * as pageScript from "@/pageScript/protocol"; @@ -28,7 +27,7 @@ import { MenuItemExtensionConfig, } from "@/extensionPoints/menuItemExtension"; import { html as beautifyHTML } from "js-beautify"; -import { DynamicDefinition } from "./dynamic"; +import type { DynamicDefinition } from "./dynamic"; import { Except } from "type-fest"; import { UUID } from "@/core"; @@ -46,7 +45,7 @@ export type ButtonSelectionResult = { containerInfo: ElementInfo; }; -export const insertButton = liftContentScript("INSERT_BUTTON", async () => { +export async function insertButton(): Promise { let selected = await userSelectElement(); // Anchor is an inline element, so if the structure in a > span, the user has no way of @@ -86,4 +85,4 @@ export const insertButton = liftContentScript("INSERT_BUTTON", async () => { }; return element; -}); +} diff --git a/src/nativeEditor/insertPanel.tsx b/src/nativeEditor/insertPanel.tsx index 2e0a8b1376..9b6e9d78e2 100644 --- a/src/nativeEditor/insertPanel.tsx +++ b/src/nativeEditor/insertPanel.tsx @@ -18,7 +18,6 @@ // https://github.com/facebook/react/blob/7559722a865e89992f75ff38c1015a865660c3cd/packages/react-devtools-shared/src/backend/views/Highlighter/index.js import { uuidv4 } from "@/types/helpers"; -import { liftContentScript } from "@/contentScript/backgroundProtocol"; import { ElementInfo } from "./frameworks"; import { userSelectElement } from "./selector"; import * as pageScript from "@/pageScript/protocol"; @@ -40,7 +39,7 @@ export type PanelSelectionResult = { containerInfo: ElementInfo; }; -export const insertPanel = liftContentScript("INSERT_PANEL", async () => { +export async function insertPanel(): Promise { const selected = await userSelectElement(); const { container, selectors } = findContainer(selected); @@ -65,4 +64,4 @@ export const insertPanel = liftContentScript("INSERT_PANEL", async () => { }; return element; -}); +}