Browse Source

feat: mimic jQuery when adding DOM scripts

tophf 4 years ago
parent
commit
07c42111e2
1 changed files with 43 additions and 41 deletions
  1. 43 41
      src/injected/content/inject.js

+ 43 - 41
src/injected/content/inject.js

@@ -14,6 +14,7 @@ const VM_UUID = browser.runtime.getURL('');
 const VAULT_WRITER = `${IS_FIREFOX ? VM_UUID : INIT_FUNC_NAME}VW`;
 const VAULT_WRITER_ACK = `${VAULT_WRITER}+`;
 const DISPLAY_NONE = 'display:none!important';
+const safeDateNow = Date.now;
 let contLists;
 let pgLists;
 /** @type {Object<string,VMInjectionRealm>} */
@@ -21,8 +22,8 @@ let realms;
 /** @type boolean */
 let pageInjectable;
 let frameEventWnd;
-let elShadow;
-let elShadowRoot;
+let scriptDiv;
+let scriptDivRoot;
 
 // https://bugzil.la/1408996
 let VMInitInjection = window[INIT_FUNC_NAME];
@@ -228,9 +229,8 @@ async function injectDelayedScripts(contentId, webId, { cache, scripts }) {
 function inject(item, iframeCb) {
   const root = elemByTag('*');
   // In Chrome injectPageSandbox calls inject() another time while the first one still runs
-  const isAdded = root && elShadow && (root === elShadow || root::elemByTag('*') === elShadow);
-  const realScript = makeElem('script', item.code);
-  let el = realScript;
+  const isAdded = root && (root === scriptDiv || root::elemByTag('*') === scriptDiv);
+  const script = makeElem('script', item.code);
   let onError;
   let iframe;
   let iframeLoader;
@@ -245,49 +245,51 @@ function inject(item, iframeCb) {
     };
     window::on('error', onError);
   }
-  // Hiding the script's code from mutation events like DOMNodeInserted or DOMNodeRemoved
-  if (attachShadow) {
-    if (!elShadow) {
-      elShadow = makeElem('div');
-      elShadowRoot = elShadow::attachShadow({ mode: 'closed' });
-      elShadowRoot::appendChild(makeElem('style', `:host { ${DISPLAY_NONE} }`));
-      if (iframeCb) {
-        iframe = makeElem('iframe', {
-          /* Preventing other content scripts in Chrome */// eslint-disable-next-line no-script-url
-          src: 'javascript:void 0',
-          sandbox: 'allow-scripts allow-same-origin',
-          style: DISPLAY_NONE,
-        });
-        iframeLoader = () => {
-          iframeLoader = null;
-          iframe.contentDocument::getElementsByTagName('*')[0]::appendChild(realScript);
-          iframeCb();
-          realScript::remove();
-          iframe::remove();
-        };
-        /* In Chrome, when an empty iframe is inserted into document, `load` is fired synchronously,
-         * then `DOMNodeInserted`, also synchronously, then appendChild finishes, then our
-         * subsequent code runs. So, we have to use `load` event to run our script,
-         * otherwise it can be easily intercepted via DOMNodeInserted. */
-        if (!IS_FIREFOX) {
-          iframe::on('load', iframeLoader, { once: true });
-        }
-        elShadowRoot::appendChild(iframe);
-      }
+  if (!scriptDiv) {
+    scriptDiv = makeElem('div');
+    if (attachShadow) {
+      // Hiding the script's code from mutation events like DOMNodeInserted or DOMNodeRemoved
+      scriptDivRoot = scriptDiv::attachShadow({ mode: 'closed' });
+      scriptDivRoot::appendChild(makeElem('style', `:host { ${DISPLAY_NONE} }`));
+    } else {
+      scriptDivRoot = scriptDiv;
     }
-    if (!iframe) {
-      elShadowRoot::appendChild(realScript);
+  }
+  if (iframeCb) {
+    iframe = makeElem('iframe', {
+      /* Preventing other content scripts in Chrome */// eslint-disable-next-line no-script-url
+      src: 'javascript:void 0',
+      sandbox: 'allow-scripts allow-same-origin',
+      style: DISPLAY_NONE,
+    });
+    iframeLoader = () => {
+      iframeLoader = null;
+      iframe.contentDocument::getElementsByTagName('*')[0]::appendChild(script);
+      iframeCb();
+      script::remove();
+      iframe::remove();
+    };
+    /* In Chrome, when an empty iframe is inserted into document, `load` is fired synchronously,
+     * then `DOMNodeInserted`, also synchronously, then appendChild finishes, then our
+     * subsequent code runs. So, we have to use `load` event to run our script,
+     * otherwise it can be easily intercepted via DOMNodeInserted. */
+    if (!IS_FIREFOX) {
+      iframe::on('load', iframeLoader, { once: true });
     }
-    el = elShadow;
+    scriptDivRoot::appendChild(iframe);
+  }
+  if (!isAdded) {
+    // When using declarativeContent there's no documentElement so we'll append to `document`
+    (root || document)::appendChild(scriptDiv);
+    // Mimic jQuery's sequence: add a div, then set an id
+    scriptDiv::setAttribute('id', `sizzle${safeDateNow()}`);
   }
-  // When using declarativeContent there's no documentElement so we'll append to `document`
-  if (!isAdded) (root || document)::appendChild(el);
   if (iframeLoader) iframeLoader();
   if (onError) window::off('error', onError);
   // Clean up in case something didn't load
   if (iframe) iframe::remove();
-  if (attachShadow) realScript::remove();
-  el::remove();
+  script::remove();
+  scriptDiv::remove();
 }
 
 function injectAll(runAt) {