index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import browser from 'webextension-polyfill';
  2. import { finder } from '@medv/finder';
  3. import { toCamelCase } from '@/utils/helper';
  4. import blocksHandler from './blocksHandler';
  5. import showExecutedBlock from './showExecutedBlock';
  6. import handleTestCondition from './handleTestCondition';
  7. import shortcutListener from './services/shortcutListener';
  8. // import elementObserver from './elementObserver';
  9. import { elementSelectorInstance } from './utils';
  10. const isMainFrame = window.self === window.top;
  11. function messageToFrame(frameElement, blockData) {
  12. return new Promise((resolve, reject) => {
  13. function onMessage({ data }) {
  14. if (data.type !== 'automa:block-execute-result') return;
  15. if (data.result?.$isError) {
  16. const error = new Error(data.result.message);
  17. error.data = data.result.data;
  18. reject(error);
  19. } else {
  20. resolve(data.result);
  21. }
  22. window.removeEventListener('message', onMessage);
  23. }
  24. window.addEventListener('message', onMessage);
  25. frameElement.contentWindow.postMessage(
  26. {
  27. type: 'automa:execute-block',
  28. blockData: { ...blockData, frameSelector: '' },
  29. },
  30. '*'
  31. );
  32. });
  33. }
  34. async function executeBlock(data) {
  35. const removeExecutedBlock = showExecutedBlock(data, data.executedBlockOnWeb);
  36. if (data.data?.selector?.includes('|>') && isMainFrame) {
  37. const [frameSelector, selector] = data.data.selector.split(/\|>(.+)/);
  38. const frameElement = document.querySelector(frameSelector);
  39. const frameError = (message) => {
  40. const error = new Error(message);
  41. error.data = { selector: frameSelector };
  42. return error;
  43. };
  44. if (!frameElement) throw frameError('iframe-not-found');
  45. const isFrameEelement = ['IFRAME', 'FRAME'].includes(frameElement.tagName);
  46. if (!isFrameEelement) throw frameError('not-iframe');
  47. data.data.selector = selector;
  48. data.data.$frameSelector = frameSelector;
  49. if (frameElement.contentDocument) {
  50. data.frameSelector = frameSelector;
  51. } else {
  52. const result = await messageToFrame(frameElement, data);
  53. return result;
  54. }
  55. }
  56. const handler = blocksHandler[toCamelCase(data.name)];
  57. if (handler) {
  58. const result = await handler(data);
  59. removeExecutedBlock();
  60. return result;
  61. }
  62. const error = new Error(`"${data.name}" doesn't have a handler`);
  63. console.error(error);
  64. throw error;
  65. }
  66. function messageListener({ data, source }) {
  67. if (data.type === 'automa:get-frame' && isMainFrame) {
  68. let frameRect = { x: 0, y: 0 };
  69. document.querySelectorAll('iframe').forEach((iframe) => {
  70. if (iframe.contentWindow !== source) return;
  71. frameRect = iframe.getBoundingClientRect();
  72. });
  73. source.postMessage(
  74. {
  75. frameRect,
  76. type: 'automa:the-frame-rect',
  77. },
  78. '*'
  79. );
  80. return;
  81. }
  82. if (data.type === 'automa:execute-block') {
  83. executeBlock(data.blockData)
  84. .then((result) => {
  85. window.top.postMessage(
  86. {
  87. result,
  88. type: 'automa:block-execute-result',
  89. },
  90. '*'
  91. );
  92. })
  93. .catch((error) => {
  94. console.error(error);
  95. window.top.postMessage(
  96. {
  97. result: {
  98. $isError: true,
  99. message: error.message,
  100. data: error.data || {},
  101. },
  102. type: 'automa:block-execute-result',
  103. },
  104. '*'
  105. );
  106. });
  107. }
  108. }
  109. (() => {
  110. if (window.isAutomaInjected) return;
  111. window.isAutomaInjected = true;
  112. window.addEventListener('message', messageListener);
  113. let contextElement = null;
  114. let $ctxTextSelection = '';
  115. if (isMainFrame) {
  116. shortcutListener();
  117. window.addEventListener('contextmenu', ({ target }) => {
  118. contextElement = target;
  119. $ctxTextSelection = window.getSelection().toString();
  120. });
  121. // window.addEventListener('load', elementObserver);
  122. }
  123. browser.runtime.onMessage.addListener((data) => {
  124. return new Promise((resolve, reject) => {
  125. if (data.isBlock) {
  126. executeBlock(data).then(resolve).catch(reject);
  127. } else {
  128. switch (data.type) {
  129. case 'condition-builder':
  130. handleTestCondition(data.data)
  131. .then((result) => resolve(result))
  132. .catch((error) => reject(error));
  133. break;
  134. case 'content-script-exists':
  135. resolve(true);
  136. break;
  137. case 'automa-element-selector': {
  138. const selectorInstance = elementSelectorInstance();
  139. resolve(selectorInstance);
  140. break;
  141. }
  142. case 'context-element': {
  143. let $ctxElSelector = '';
  144. if (contextElement) {
  145. $ctxElSelector = finder(contextElement);
  146. contextElement = null;
  147. }
  148. resolve({ $ctxElSelector, $ctxTextSelection });
  149. break;
  150. }
  151. default:
  152. resolve(null);
  153. }
  154. }
  155. });
  156. });
  157. })();