Browse Source

fix: list selector not working in `same-origin` iframe

Ahmad Kholid 3 years ago
parent
commit
707d2e56b6

+ 14 - 2
src/components/content/shared/SharedElementHighlighter.vue

@@ -1,9 +1,13 @@
 <template>
+  {{ items }}
   <rect
     v-for="(item, index) in items"
     v-bind="{
-      ...item,
-      'stroke-dasharray': item.outline ? '5,5' : null,
+      x: getNumber(item?.x),
+      y: getNumber(item?.y),
+      width: getNumber(item?.width),
+      height: getNumber(item?.height),
+      'stroke-dasharray': item?.outline ? '5,5' : null,
       fill: getFillColor(item),
       stroke: getStrokeColor(item),
     }"
@@ -35,12 +39,20 @@ const props = defineProps({
   },
 });
 
+function getNumber(num) {
+  if (Number.isNaN(num) || !num) return 0;
+
+  return num;
+}
 function getFillColor(item) {
+  if (!item) return null;
   if (item.outline) return null;
 
   return item.highlight ? props.fill : props.activeFill || props.fill;
 }
 function getStrokeColor(item) {
+  if (!item) return null;
+
   return item.highlight ? props.stroke : props.activeStroke || props.stroke;
 }
 </script>

+ 54 - 23
src/components/content/shared/SharedElementSelector.vue

@@ -64,6 +64,7 @@ const props = defineProps({
 const emit = defineEmits(['selected']);
 
 let frameElement = null;
+let frameElementRect = null;
 let lastScrollPosY = window.scrollY;
 let lastScrollPosX = window.scrollX;
 
@@ -74,22 +75,39 @@ const elementsState = reactive({
 });
 
 const onScroll = debounce(() => {
-  if (state.hide) return;
+  if (props.disabled) return;
 
   hoveredElements = [];
-  elementsState.selected = [];
+  elementsState.hovered = [];
 
   const yPos = window.scrollY - lastScrollPosY;
   const xPos = window.scrollX - lastScrollPosX;
 
-  state.selected.forEach((_, index) => {
-    state.selected[index].x -= xPos;
-    state.selected[index].y -= yPos;
+  elementsState.selected.forEach((_, index) => {
+    elementsState.selected[index].x -= xPos;
+    elementsState.selected[index].y -= yPos;
   });
 
   lastScrollPosX = window.scrollX;
   lastScrollPosY = window.scrollY;
 }, 100);
+
+function getElementRectWithOffset(element, withAttribute) {
+  const rect = getElementRect(element, withAttribute);
+
+  if (frameElementRect) {
+    rect.y += frameElementRect.top;
+    rect.x += frameElementRect.left;
+  }
+
+  return rect;
+}
+function removeElementsList() {
+  const prevSelectedList = document.querySelectorAll('[automa-el-list]');
+  prevSelectedList.forEach((el) => {
+    el.removeAttribute('automa-el-list');
+  });
+}
 function resetFramesElements(options = {}) {
   const elements = document.querySelectorAll('iframe, frame');
 
@@ -112,16 +130,19 @@ function retrieveElementsRect({ clientX, clientY, target: eventTarget }, type) {
   const isSelectList = props.list && props.selectorType === 'css';
 
   let { 1: target } = document.elementsFromPoint(clientX, clientY);
+  if (!target) return;
+
   if (target.tagName === 'IFRAME' || target.tagName === 'FRAME') {
-    if (type === 'selected') {
-      const prevSelectedList = document.querySelectorAll('[automa-el-list]');
-      prevSelectedList.forEach((el) => {
-        el.removeAttribute('automa-el-list');
-      });
-    }
+    if (type === 'selected') removeElementsList();
 
     if (target.contentDocument) {
-      target = target.contentDocument.elementsFromPoint(clientX, clientY);
+      frameElement = target;
+      frameElementRect = target.getBoundingClientRect();
+
+      const yPos = clientY - frameElementRect.top;
+      const xPos = clientX - frameElementRect.left;
+
+      target = target.contentDocument.elementFromPoint(xPos, yPos);
     } else {
       const { top, left } = target.getBoundingClientRect();
       const payload = {
@@ -141,41 +162,50 @@ function retrieveElementsRect({ clientX, clientY, target: eventTarget }, type) {
         });
 
       target.contentWindow.postMessage(payload, '*');
+      frameElement = target;
+      frameElementRect = target.getBoundingClientRect();
+      return;
     }
-
-    frameElement = target;
-
-    return;
+  } else {
+    frameElement = null;
+    frameElementRect = null;
   }
 
-  frameElement = null;
-
   let elementsRect = [];
   const withAttribute = props.withAttributes && type === 'selected';
 
   if (isSelectList) {
-    const elements = findElementList(target) || [];
+    const elements = findElementList(target, frameElement) || [];
 
     if (type === 'hovered') hoveredElements = elements;
 
-    elementsRect = elements.map((el) => getElementRect(el, withAttribute));
+    elementsRect = elements.map((el) =>
+      getElementRectWithOffset(el, withAttribute)
+    );
   } else {
     if (type === 'hovered') hoveredElements = [target];
 
-    elementsRect = [getElementRect(target, withAttribute)];
+    elementsRect = [getElementRectWithOffset(target, withAttribute)];
   }
 
   elementsState[type] = elementsRect;
 
   if (type === 'selected') {
-    resetFramesElements();
+    if (!frameElement) resetFramesElements();
 
-    const selector = generateElementsSelector({
+    let selector = generateElementsSelector({
       target,
+      frameElement,
       hoveredElements,
       list: isSelectList,
       selectorType: props.selectorType,
     });
+
+    if (frameElement) {
+      const frameSelector = finder(frameElement);
+      selector = `${frameSelector} |> ${selector}`;
+    }
+
     emit('selected', {
       selector,
       elements: elementsRect,
@@ -223,6 +253,7 @@ function detachListeners() {
 watch(
   () => [props.list, props.disabled],
   () => {
+    removeElementsList();
     resetFramesElements({ clearCache: true });
   }
 );

+ 22 - 6
src/content/elementSelector/generateElementsSelector.js

@@ -1,9 +1,23 @@
 import { finder } from '@medv/finder';
 import { generateXPath } from '../utils';
 
-export default function ({ list, target, selectorType, hoveredElements }) {
+export default function ({
+  list,
+  target,
+  selectorType,
+  hoveredElements,
+  frameElement,
+}) {
   let selector = '';
+
   const [selectedElement] = hoveredElements;
+  const finderOptions = {};
+  let documentCtx = document;
+
+  if (frameElement) {
+    documentCtx = frameElement.contentDocument.body;
+    finderOptions.root = documentCtx;
+  }
 
   if (list) {
     const isInList = target.closest('[automa-el-list]');
@@ -17,11 +31,13 @@ export default function ({ list, target, selectorType, hoveredElements }) {
 
       selector = `${listSelector} ${childSelector}`;
     } else {
-      selector = `${finder(
-        selectedElement.parentElement
-      )} > ${selectedElement.tagName.toLowerCase()}`;
+      const parentSelector = finder(
+        selectedElement.parentElement,
+        finderOptions
+      );
+      selector = `${parentSelector} > ${selectedElement.tagName.toLowerCase()}`;
 
-      const prevSelectedList = document.querySelectorAll('[automa-el-list]');
+      const prevSelectedList = documentCtx.querySelectorAll('[automa-el-list]');
       prevSelectedList.forEach((el) => {
         el.removeAttribute('automa-el-list');
       });
@@ -33,7 +49,7 @@ export default function ({ list, target, selectorType, hoveredElements }) {
   } else {
     selector =
       selectorType === 'css'
-        ? finder(selectedElement)
+        ? finder(selectedElement, finderOptions)
         : generateXPath(selectedElement);
   }
 

+ 3 - 1
src/content/elementSelector/index.js

@@ -29,7 +29,9 @@ function elementSelectorInstance() {
   });
 
   try {
-    if (window.self === window.top) {
+    const isMainFrame = window.self === window.top;
+
+    if (isMainFrame) {
       const isAppExists = elementSelectorInstance();
 
       if (isAppExists) return;

+ 11 - 4
src/content/elementSelector/listSelector.js

@@ -35,7 +35,7 @@ export function getAllSiblings(el, selector) {
 }
 
 export function getCssPath(el, root = document.body) {
-  if (!(el instanceof Element)) return null;
+  if (!el) return null;
 
   const path = [];
 
@@ -66,7 +66,7 @@ export function getCssPath(el, root = document.body) {
 }
 
 export function getElementList(el, maxDepth = 50, paths = []) {
-  if (maxDepth === 0 || el.tagName === 'BODY') return null;
+  if (maxDepth === 0 || !el || el.tagName === 'BODY') return null;
 
   let selector = el.tagName.toLowerCase();
   const { elements, index } = getAllSiblings(el, paths.join(' > '));
@@ -83,8 +83,15 @@ export function getElementList(el, maxDepth = 50, paths = []) {
   return siblings;
 }
 
-export default function (target) {
+export default function (target, frameElement) {
+  if (!target) return [];
+
   const automaListEl = target.closest('[automa-el-list]');
+  let documentCtx = document;
+
+  if (frameElement) {
+    documentCtx = frameElement.contentDocument;
+  }
 
   if (automaListEl) {
     if (target.hasAttribute('automa-el-list')) return [];
@@ -93,7 +100,7 @@ export default function (target) {
       root: automaListEl,
       idName: () => false,
     });
-    const elements = document.querySelectorAll(
+    const elements = documentCtx.querySelectorAll(
       `[automa-el-list] ${childSelector}`
     );
 

+ 2 - 2
src/content/utils.js

@@ -35,10 +35,10 @@ export function getElementPath(el, root = document.documentElement) {
   return path;
 }
 
-export function generateXPath(element) {
+export function generateXPath(element, root = document.body) {
   if (!element) return null;
   if (element.id !== '') return `id("${element.id}")`;
-  if (element === document.body) return `//${element.tagName}`;
+  if (element === root) return `//${element.tagName}`;
 
   let ix = 0;
   const siblings = element.parentNode.childNodes;

+ 5 - 2
src/newtab/pages/Workflows.vue

@@ -64,6 +64,7 @@
               <ui-list-item
                 tag="button"
                 :active="state.activeTab === 'local'"
+                color="bg-box-transparent font-semibold"
                 class="pl-14"
                 @click="state.activeTab = 'local'"
               >
@@ -73,8 +74,9 @@
               </ui-list-item>
               <ui-list-item
                 v-if="store.state.user"
-                tag="button"
                 :active="state.activeTab === 'shared'"
+                tag="button"
+                color="bg-box-transparent font-semibold"
                 class="pl-14"
                 @click="state.activeTab = 'shared'"
               >
@@ -84,8 +86,9 @@
               </ui-list-item>
               <ui-list-item
                 v-if="workflowHosts.length > 0"
-                tag="button"
                 :active="state.activeTab === 'host'"
+                color="bg-box-transparent font-semibold"
+                tag="button"
                 class="pl-14"
                 @click="state.activeTab = 'host'"
               >