|
@@ -1,63 +1,140 @@
|
|
|
import browser from 'webextension-polyfill';
|
|
|
-import { nanoid } from 'nanoid';
|
|
|
import { toCamelCase } from '@/utils/helper';
|
|
|
-import FindElement from '@/utils/FindElement';
|
|
|
-import { getDocumentCtx } from './handleSelector';
|
|
|
-import executedBlock from './executedBlock';
|
|
|
import blocksHandler from './blocksHandler';
|
|
|
+import showExecutedBlock from './showExecutedBlock';
|
|
|
import handleTestCondition from './handleTestCondition';
|
|
|
|
|
|
-function messageListener({ data, source }) {
|
|
|
- if (data !== 'automa:get-frame') return;
|
|
|
+const isMainFrame = window.self === window.top;
|
|
|
+
|
|
|
+function messageToFrame(frameElement, blockData) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ function onMessage({ data }) {
|
|
|
+ if (data.type !== 'automa:block-execute-result') return;
|
|
|
|
|
|
- let frameRect = { x: 0, y: 0 };
|
|
|
+ if (data.result?.$isError) {
|
|
|
+ const error = new Error(data.result.message);
|
|
|
+ error.data = data.result.data;
|
|
|
|
|
|
- document.querySelectorAll('iframe').forEach((iframe) => {
|
|
|
- if (iframe.contentWindow !== source) return;
|
|
|
+ reject(error);
|
|
|
+ } else {
|
|
|
+ resolve(data.result);
|
|
|
+ }
|
|
|
|
|
|
- frameRect = iframe.getBoundingClientRect();
|
|
|
+ window.removeEventListener('message', onMessage);
|
|
|
+ }
|
|
|
+ window.addEventListener('message', onMessage);
|
|
|
+
|
|
|
+ frameElement.contentWindow.postMessage(
|
|
|
+ {
|
|
|
+ type: 'automa:execute-block',
|
|
|
+ blockData: { ...blockData, frameSelector: '' },
|
|
|
+ },
|
|
|
+ '*'
|
|
|
+ );
|
|
|
});
|
|
|
+}
|
|
|
+async function executeBlock(data) {
|
|
|
+ const removeExecutedBlock = showExecutedBlock(data, data.executedBlockOnWeb);
|
|
|
+
|
|
|
+ if (data.data.selector?.includes('|>') && isMainFrame) {
|
|
|
+ const [frameSelector, selector] = data.data.selector.split(/\|>(.+)/);
|
|
|
+ const frameElement = document.querySelector(frameSelector);
|
|
|
+ const frameError = (message) => {
|
|
|
+ const error = new Error(message);
|
|
|
+ error.data = { selector: frameSelector };
|
|
|
+
|
|
|
+ return error;
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!frameElement) throw frameError('iframe-not-found');
|
|
|
+
|
|
|
+ const isFrameEelement = ['IFRAME', 'FRAME'].includes(frameElement.tagName);
|
|
|
+ if (!isFrameEelement) throw frameError('not-iframe');
|
|
|
+
|
|
|
+ data.data.selector = selector;
|
|
|
+ data.data.$frameSelector = frameSelector;
|
|
|
+
|
|
|
+ if (frameElement.contentDocument) {
|
|
|
+ data.frameSelector = frameSelector;
|
|
|
+ } else {
|
|
|
+ const result = await messageToFrame(frameElement, data);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- source.postMessage(
|
|
|
- {
|
|
|
- frameRect,
|
|
|
- type: 'automa:the-frame-rect',
|
|
|
- },
|
|
|
- '*'
|
|
|
- );
|
|
|
+ const handler = blocksHandler[toCamelCase(data.name)];
|
|
|
+
|
|
|
+ if (handler) {
|
|
|
+ const result = await handler(data);
|
|
|
+ removeExecutedBlock();
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ const error = new Error(`"${data.name}" doesn't have a handler`);
|
|
|
+ console.error(error);
|
|
|
+
|
|
|
+ throw error;
|
|
|
}
|
|
|
+function messageListener({ data, source }) {
|
|
|
+ if (data.type === 'automa:get-frame' && isMainFrame) {
|
|
|
+ let frameRect = { x: 0, y: 0 };
|
|
|
|
|
|
-(() => {
|
|
|
- if (window.isAutomaInjected) return;
|
|
|
- window.isAutomaInjected = true;
|
|
|
+ document.querySelectorAll('iframe').forEach((iframe) => {
|
|
|
+ if (iframe.contentWindow !== source) return;
|
|
|
+
|
|
|
+ frameRect = iframe.getBoundingClientRect();
|
|
|
+ });
|
|
|
|
|
|
- if (window.self === window.top) {
|
|
|
- window.addEventListener('message', messageListener);
|
|
|
+ source.postMessage(
|
|
|
+ {
|
|
|
+ frameRect,
|
|
|
+ type: 'automa:the-frame-rect',
|
|
|
+ },
|
|
|
+ '*'
|
|
|
+ );
|
|
|
+
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- browser.runtime.onMessage.addListener((data) => {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- if (data.isBlock) {
|
|
|
- const removeExecutedBlock = executedBlock(
|
|
|
- data,
|
|
|
- data.executedBlockOnWeb
|
|
|
+ if (data.type === 'automa:execute-block') {
|
|
|
+ executeBlock(data.blockData)
|
|
|
+ .then((result) => {
|
|
|
+ window.top.postMessage(
|
|
|
+ {
|
|
|
+ result,
|
|
|
+ type: 'automa:block-execute-result',
|
|
|
+ },
|
|
|
+ '*'
|
|
|
);
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error(error);
|
|
|
+ window.top.postMessage(
|
|
|
+ {
|
|
|
+ result: {
|
|
|
+ $isError: true,
|
|
|
+ message: error.message,
|
|
|
+ data: error.data || {},
|
|
|
+ },
|
|
|
+ type: 'automa:block-execute-result',
|
|
|
+ },
|
|
|
+ '*'
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- const handler = blocksHandler[toCamelCase(data.name)];
|
|
|
-
|
|
|
- if (handler) {
|
|
|
- handler(data)
|
|
|
- .then((result) => {
|
|
|
- removeExecutedBlock();
|
|
|
- resolve(result);
|
|
|
- })
|
|
|
- .catch(reject);
|
|
|
+(() => {
|
|
|
+ if (window.isAutomaInjected) return;
|
|
|
|
|
|
- return;
|
|
|
- }
|
|
|
- console.error(`"${data.name}" doesn't have a handler`);
|
|
|
+ window.isAutomaInjected = true;
|
|
|
+ window.addEventListener('message', messageListener);
|
|
|
|
|
|
- resolve('');
|
|
|
+ browser.runtime.onMessage.addListener((data) => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ if (data.isBlock) {
|
|
|
+ executeBlock(data).then(resolve).catch(reject);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -70,38 +147,6 @@ function messageListener({ data, source }) {
|
|
|
case 'content-script-exists':
|
|
|
resolve(true);
|
|
|
break;
|
|
|
- case 'loop-elements': {
|
|
|
- const selectors = [];
|
|
|
- const attrId = nanoid(5);
|
|
|
-
|
|
|
- const documentCtx = getDocumentCtx(data.frameSelector);
|
|
|
- const selectorType = data.selector.startsWith('/')
|
|
|
- ? 'xpath'
|
|
|
- : 'cssSelector';
|
|
|
- const elements = FindElement[selectorType](
|
|
|
- { selector: data.selector, multiple: true },
|
|
|
- documentCtx
|
|
|
- );
|
|
|
-
|
|
|
- if (!elements || elements?.length === 0) {
|
|
|
- reject(new Error('element-not-found'));
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- elements.forEach((el, index) => {
|
|
|
- if (data.max > 0 && selectors.length - 1 > data.max) return;
|
|
|
-
|
|
|
- const attrName = 'automa-loop';
|
|
|
- const attrValue = `${attrId}--${index}`;
|
|
|
-
|
|
|
- el.setAttribute(attrName, attrValue);
|
|
|
- selectors.push(`[${attrName}="${attrValue}"]`);
|
|
|
- });
|
|
|
-
|
|
|
- resolve(selectors);
|
|
|
- break;
|
|
|
- }
|
|
|
default:
|
|
|
}
|
|
|
});
|