Browse Source

feat: add switch frame block

Ahmad Kholid 3 years ago
parent
commit
3b97298d1c

+ 81 - 21
src/background/workflow-engine/blocks-handler.js

@@ -34,6 +34,45 @@ function generateBlockError(block, code) {
 
   return error;
 }
+function executeContentScript(tabId) {
+  return new Promise((resolve, reject) => {
+    let frameTimeout;
+    let timeout;
+    const frames = {};
+
+    const onMessageListener = (_, sender) => {
+      if (sender.frameId !== 0) frames[sender.url] = sender.frameId;
+
+      clearTimeout(frameTimeout);
+      frameTimeout = setTimeout(() => {
+        clearTimeout(timeout);
+        browser.runtime.onMessage.removeListener(onMessageListener);
+        resolve(frames);
+      }, 250);
+    };
+
+    browser.tabs
+      .executeScript(tabId, {
+        file: './contentScript.bundle.js',
+        allFrames: true,
+      })
+      .then(() => {
+        browser.tabs.sendMessage(tabId, {
+          type: 'give-me-the-frame-id',
+        });
+        browser.runtime.onMessage.addListener(onMessageListener);
+
+        timeout = setTimeout(() => {
+          clearTimeout(frameTimeout);
+          resolve(frames);
+        }, 5000);
+      })
+      .catch((error) => {
+        console.error(error);
+        reject(error);
+      });
+  });
+}
 
 export async function closeTab(block) {
   const nextBlockId = getBlockConnection(block);
@@ -67,9 +106,7 @@ export async function trigger(block) {
   const nextBlockId = getBlockConnection(block);
   try {
     if (block.data.type === 'visit-web' && this.tabId) {
-      await browser.tabs.executeScript(this.tabId, {
-        file: './contentScript.bundle.js',
-      });
+      this.frames = executeContentScript(this.tabId);
     }
 
     return { nextBlockId, data: '' };
@@ -190,22 +227,12 @@ function tabUpdatedListener(tab) {
     this._listener({
       name: 'tab-updated',
       id: tab.id,
-      once: true,
-      callback: async (tabId, changeInfo, deleteListener) => {
+      callback: (tabId, changeInfo, deleteListener) => {
         if (changeInfo.status !== 'complete') return;
 
-        try {
-          await browser.tabs.executeScript(tabId, {
-            file: './contentScript.bundle.js',
-          });
-
-          deleteListener();
+        deleteListener();
 
-          resolve();
-        } catch (error) {
-          console.error(error);
-          reject(error);
-        }
+        executeContentScript(tabId).then(resolve, reject);
       },
     });
   });
@@ -223,7 +250,8 @@ export async function newTab(block) {
       this.windowId = windowId;
     }
 
-    await tabUpdatedListener.call(this, { id: this.tabId });
+    this.frameId = 0;
+    this.frames = await tabUpdatedListener.call(this, { id: this.tabId });
 
     return {
       data: url,
@@ -255,10 +283,9 @@ export async function activeTab(block) {
       currentWindow: true,
     });
 
-    await browser.tabs.executeScript(tab.id, {
-      file: './contentScript.bundle.js',
-    });
+    this.frames = await executeContentScript(tab.id);
 
+    this.frameId = 0;
     this.tabId = tab.id;
     this.windowId = tab.windowId;
 
@@ -338,11 +365,44 @@ export async function takeScreenshot(block) {
   }
 }
 
+export async function switchTo(block) {
+  const nextBlockId = getBlockConnection(block);
+
+  try {
+    if (block.data.windowType === 'main-window') {
+      this.frameId = 0;
+
+      return {
+        data: '',
+        nextBlockId,
+      };
+    }
+
+    const { url } = await this._sendMessageToTab(block, { frameId: 0 });
+
+    if (objectHasKey(this.frames, url)) {
+      this.frameId = this.frames[url];
+
+      return {
+        data: this.frameId,
+        nextBlockId,
+      };
+    }
+    throw new Error(errorMessage('no-iframe-id', block.data));
+  } catch (error) {
+    error.nextBlockId = nextBlockId;
+
+    throw error;
+  }
+}
+
 export async function interactionHandler(block) {
   const nextBlockId = getBlockConnection(block);
 
   try {
-    const data = await this._sendMessageToTab(block);
+    const data = await this._sendMessageToTab(block, {
+      frameId: this.frameId || 0,
+    });
 
     if (block.name === 'link')
       await new Promise((resolve) => setTimeout(resolve, 5000));

+ 6 - 3
src/background/workflow-engine/error-message.js

@@ -1,8 +1,11 @@
-import { objectHasKey, replaceMustache } from '@/utils/helper';
+import { get } from 'object-path-immutable';
+import { replaceMustache } from '@/utils/helper';
 
 const messages = {
   'no-trigger-block': '"{{name}}"" workflow doesn\'t have a trigger block.',
   'no-block': '"{{name}}" workflow doesn\'t have any blocks.',
+  'no-iframe-id':
+    'Can\'t find Frame ID for the frame element with "{{selector}}" selector',
   'no-tab':
     'Can\'t connect to a tab, use "New tab" or "Active tab" block before using the "{{name}}" block.',
 };
@@ -12,11 +15,11 @@ export default function (errorId, data) {
 
   if (!message) return `Can't find message for this error (${errorId})`;
 
-  /* eslint-disable-next-line */
   const resultMessage = replaceMustache(message, (match) => {
     const key = match.slice(2, -2);
+    const result = get(data, key);
 
-    return objectHasKey(data, key) ? data[key] : key;
+    return result ?? key;
   });
 
   return resultMessage;

+ 10 - 13
src/background/workflow-engine/index.js

@@ -66,18 +66,19 @@ class WorkflowEngine {
     this.isInCollection = isInCollection;
     this.collectionLogId = collectionLogId;
     this.data = {};
+    this.logs = [];
     this.blocks = {};
     this.frames = {};
-    this.eventListeners = {};
-    this.repeatedTasks = {};
     this.loopList = {};
     this.loopData = {};
-    this.logs = [];
+    this.repeatedTasks = {};
+    this.eventListeners = {};
     this.isPaused = false;
     this.isDestroyed = false;
+    this.frameId = null;
+    this.windowId = null;
     this.currentBlock = null;
     this.workflowTimeout = null;
-    this.windowId = null;
 
     this.tabUpdatedListeners = {};
     this.tabUpdatedHandler = tabUpdatedHandler.bind(this);
@@ -251,11 +252,11 @@ class WorkflowEngine {
     this.dispatchEvent('update', this.state);
 
     const started = Date.now();
-    const isInteraction = tasks[block.name].category === 'interaction';
-    const handlerName = isInteraction
-      ? 'interactionHandler'
-      : toCamelCase(block?.name);
-    const handler = blocksHandler[handlerName];
+    const blockHandler = blocksHandler[toCamelCase(block?.name)];
+    const handler =
+      !blockHandler && tasks[block.name].category === 'interaction'
+        ? blocksHandler.interactionHandler
+        : blockHandler;
 
     if (handler) {
       const replacedBlock = referenceData(block, {
@@ -339,10 +340,6 @@ class WorkflowEngine {
       'tab-updated': 'tabUpdatedListeners',
     };
     this[listenerNames[name]][id] = { callback, once, ...options };
-
-    return () => {
-      delete this.tabMessageListeners[id];
-    };
   }
 }
 

+ 39 - 0
src/components/newtab/workflow/edit/EditSwitchTo.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="space-y-2">
+    <ui-textarea
+      :model-value="data.description"
+      autoresize
+      placeholder="Description"
+      class="w-full"
+      @change="updateData({ description: $event })"
+    />
+    <ui-select
+      :model-value="data.windowType"
+      class="w-full"
+      @change="updateData({ windowType: $event })"
+    >
+      <option value="main-window">Main window</option>
+      <option value="iframe">Iframe</option>
+    </ui-select>
+    <ui-input
+      v-if="data.windowType === 'iframe'"
+      :model-value="data.selector"
+      placeholder="Iframe element selector"
+      class="mb-1 w-full"
+      @change="updateData({ selector: $event })"
+    />
+  </div>
+</template>
+<script setup>
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['update:data']);
+
+function updateData(value) {
+  emit('update:data', { ...props.data, ...value });
+}
+</script>

+ 17 - 3
src/content/blocks-handler.js

@@ -7,7 +7,7 @@ function markElement(el, { id, data }) {
     el.setAttribute(`block--${id}`, '');
   }
 }
-function handleElement({ data, id }, callback) {
+function handleElement({ data, id }, callback, errCallback) {
   if (!data || !data.selector) return null;
 
   try {
@@ -30,6 +30,8 @@ function handleElement({ data, id }, callback) {
     } else if (element) {
       markElement(element, { id, data });
       callback(element);
+    } else if (errCallback) {
+      errCallback();
     }
   } catch (error) {
     console.error(error);
@@ -38,8 +40,20 @@ function handleElement({ data, id }, callback) {
 
 export function switchTo(block) {
   return new Promise((resolve) => {
-    console.log(block);
-    resolve('');
+    handleElement(
+      block,
+      (element) => {
+        if (element.tagName !== 'IFRAME') {
+          resolve('');
+          return;
+        }
+
+        resolve({ url: element.src });
+      },
+      () => {
+        resolve('');
+      }
+    );
   });
 }
 

+ 4 - 8
src/content/element-selector/ElementSelector.ce.vue

@@ -157,8 +157,10 @@ function selectParentElement() {
 function handleClick(event) {
   if (event.target === root || element.hide) return;
 
-  event.preventDefault();
-  event.stopPropagation();
+  if (!element.hide) {
+    event.preventDefault();
+    event.stopPropagation();
+  }
 
   selectedPath = event.path;
   element.selected = getElementRect(targetEl);
@@ -198,12 +200,6 @@ window.addEventListener('keyup', handleKeyup);
 window.addEventListener('scroll', handleScroll);
 document.addEventListener('click', handleClick, true);
 window.addEventListener('mousemove', handleMouseMove);
-
-// chrome.runtime.sendMessage({
-//   name: 'background--get:sender',
-// }, (result) => {
-//   console.log(result, 'exe');
-// });
 </script>
 <style>
 :host {

+ 4 - 0
src/content/index.js

@@ -21,6 +21,10 @@ browser.runtime.onMessage.addListener((data) => {
     } else if (data.type === 'select-element') {
       elementSelector();
       resolve(true);
+    } else if (data.type === 'give-me-the-frame-id') {
+      browser.runtime.sendMessage({
+        type: 'this-is-the-frame-id',
+      });
     }
   });
 });

File diff suppressed because it is too large
+ 1 - 1
src/utils/shared.js


+ 3 - 0
src/utils/simulate-event.js

@@ -20,6 +20,9 @@ export function getEventObj(name, params) {
     case 'wheel-event':
       event = new WheelEvent(name, params);
       break;
+    case 'input':
+      event = new InputEvent(name, params);
+      break;
     default:
       event = new Event(name, params);
   }

+ 7 - 8
src/utils/webhookUtil.js

@@ -8,14 +8,13 @@ const renderContent = (content, contentType) => {
 
   if (contentType === 'form') {
     return Object.keys(renderedJson)
-      .map(
-        (key) =>
-          `${key}=${
-            isObject(renderedJson[key])
-              ? JSON.stringify(renderedJson[key])
-              : renderedJson[key]
-          }`
-      )
+      .map((key) => {
+        const value = isObject(renderedJson[key])
+          ? JSON.stringify(renderedJson[key])
+          : renderedJson[key];
+
+        return `${key}=${value}`;
+      })
       .join('&');
   }
 

Some files were not shown because too many files changed in this diff