瀏覽代碼

refactor: simplify delayed content mode injection

...because only `document-body` needs it.

+ remove `process.env` from VAULT_ID, HANDSHAKE_ID as these are runtime vars
+ rename HANDSHAKE_ID to PAGE_MODE_HANDSHAKE and use it instead of bridge.mode
tophf 3 年之前
父節點
當前提交
628c7587c0

+ 5 - 1
.eslintrc.js

@@ -17,7 +17,11 @@ const GLOBALS_COMMON = {
   ...getGlobals('src/common/safe-globals.js'),
   re: false, // transform-modern-regexp with useRe option
 };
-const GLOBALS_INJECTED = getGlobals(`src/injected/safe-globals-injected.js`);
+const GLOBALS_INJECTED = {
+  ...getGlobals(`src/injected/safe-globals-injected.js`),
+  PAGE_MODE_HANDSHAKE: false,
+  VAULT_ID: false,
+};
 const GLOBALS_CONTENT = {
   INIT_FUNC_NAME: false,
   ...getGlobals(`src/injected/content/safe-globals-content.js`),

+ 3 - 6
scripts/webpack.conf.js

@@ -12,8 +12,8 @@ const mergedConfig = shallowMerge(defaultOptions, projectConfig);
 
 // Avoiding collisions with globals of a content-mode userscript
 const INIT_FUNC_NAME = `Violentmonkey:${getUniqIdB64()}`;
-const VAULT_ID = '__VAULT_ID__';
-const HANDSHAKE_ID = '__HANDSHAKE_ID__';
+const VAULT_ID = 'VAULT_ID';
+const PAGE_MODE_HANDSHAKE = 'PAGE_MODE_HANDSHAKE';
 const VM_VER = getVersion();
 const WEBPACK_OPTS = {
   node: {
@@ -77,9 +77,6 @@ const defsObj = {
     { key: 'SYNC_DROPBOX_CLIENT_ID' },
   ]),
   'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME),
-  'process.env.VAULT_ID': VAULT_ID,
-  'process.env.HANDSHAKE_ID': HANDSHAKE_ID,
-  'process.env.HANDSHAKE_ACK': '1',
   'process.env.CODEMIRROR_THEMES': JSON.stringify(getCodeMirrorThemes()),
   'process.env.DEV': JSON.stringify(!isProd),
 };
@@ -146,7 +143,7 @@ module.exports = Promise.all([
     config.plugins.push(new ProtectWebpackBootstrapPlugin());
     addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({
       header: () => `${skipReinjectionHeader}
-        window[INIT_FUNC_NAME] = function (IS_FIREFOX,${HANDSHAKE_ID},${VAULT_ID}) {
+        window[INIT_FUNC_NAME] = function (IS_FIREFOX,${PAGE_MODE_HANDSHAKE},${VAULT_ID}) {
           const module = { __proto__: null };
           ${getGlobals()}`,
       footer: `

+ 1 - 2
src/injected/content/index.js

@@ -5,7 +5,7 @@ import { injectPageSandbox, injectScripts } from './inject';
 import './notifications';
 import './requests';
 import './tabs';
-import { nextTask, sendCmd } from './util';
+import { sendCmd } from './util';
 import { isEmpty, INJECT_CONTENT } from '../util';
 import { Run } from './cmd-run';
 
@@ -67,7 +67,6 @@ bridge.addBackgroundHandlers({
 
 bridge.addHandlers({
   Run,
-  NextTask: nextTask,
   TabFocus: true,
   UpdateValue: true,
 });

+ 1 - 1
src/injected/content/inject.js

@@ -113,7 +113,7 @@ export function injectPageSandbox(contentId, webId) {
     pageInjectable = true;
     evt::stopImmediatePropagation();
     bindEvents(contentId, webId, bridge);
-    fireBridgeEvent(handshakeId + process.env.HANDSHAKE_ACK, [webId, contentId]);
+    fireBridgeEvent(`${handshakeId}*`, [webId, contentId]);
   }
 }
 

+ 2 - 4
src/injected/web/gm-global-wrapper.js

@@ -1,5 +1,3 @@
-import { INJECT_CONTENT } from '../util';
-import bridge from './bridge';
 import { FastLookup, safeConcat } from './util';
 
 const isFrameIndex = key => +key >= 0 && key < window::getWindowLength();
@@ -7,7 +5,7 @@ const scopeSym = SafeSymbol.unscopables;
 const globalKeysSet = FastLookup();
 const globalKeys = (function makeGlobalKeys() {
   const kWrappedJSObject = 'wrappedJSObject';
-  const isContentMode = !process.env.HANDSHAKE_ID;
+  const isContentMode = !PAGE_MODE_HANDSHAKE;
   const names = builtinGlobals[0]; // `window` keys
   const numFrames = window::getWindowLength();
   // True if `names` is usable as is, but FF is bugged: its names have duplicates
@@ -35,7 +33,7 @@ const globalKeys = (function makeGlobalKeys() {
   }
   // wrappedJSObject is not included in getOwnPropertyNames so we add it explicitly.
   if (IS_FIREFOX
-    && bridge.mode === INJECT_CONTENT
+    && !PAGE_MODE_HANDSHAKE
     && kWrappedJSObject in global
     && !globalKeysSet.has(kWrappedJSObject)) {
     globalKeysSet.add(kWrappedJSObject);

+ 18 - 45
src/injected/web/index.js

@@ -9,10 +9,9 @@ import { bindEvents, INJECT_PAGE, INJECT_CONTENT } from '../util';
 
 // Make sure to call safe::methods() in code that may run after userscripts
 
-const queueMacrotask = cb => bridge.call('NextTask', 0, bridge, 0, cb);
-// Waiting for injection of content mode scripts that don't run on document-start
-let runningQueues;
-let waitingQueues;
+/** document-body scripts in content mode are inserted via executeScript at document-start
+ * in inert state, then wait for content bridge to call RunAt('body'). */
+let runAtBodyQueue;
 
 export default function initialize(
   webId,
@@ -20,18 +19,16 @@ export default function initialize(
   invokeHost,
 ) {
   let invokeGuest;
-  if (process.env.HANDSHAKE_ID) {
-    window::on(process.env.HANDSHAKE_ID + process.env.HANDSHAKE_ACK, e => {
+  if (PAGE_MODE_HANDSHAKE) {
+    window::on(PAGE_MODE_HANDSHAKE + '*', e => {
       e = e::getDetail();
       webId = e[0];
       contentId = e[1];
     }, { __proto__: null, once: true, capture: true });
-    window::fire(new SafeCustomEvent(process.env.HANDSHAKE_ID));
+    window::fire(new SafeCustomEvent(PAGE_MODE_HANDSHAKE));
   }
   bridge.dataKey = contentId;
   if (invokeHost) {
-    runningQueues = [];
-    waitingQueues = createNullObj();
     bridge.mode = INJECT_CONTENT;
     bridge.post = (cmd, data, context, node) => {
       invokeHost({ cmd, data, node, dataKey: (context || bridge).dataKey }, INJECT_CONTENT);
@@ -43,7 +40,14 @@ export default function initialize(
     global.chrome = undefined;
     global.browser = undefined;
     bridge.addHandlers({
-      RunAt: doRunAt,
+      RunAt() {
+        // executeScript code may run after <body> appeared
+        if (runAtBodyQueue) {
+          for (const fn of runAtBodyQueue) fn();
+        }
+        // allowing the belated code to run immediately
+        runAtBodyQueue = false;
+      },
     });
   } else {
     bridge.mode = INJECT_PAGE;
@@ -75,12 +79,9 @@ bridge.addHandlers({
       assign(bridge, info);
     }
     if (items) {
-      if (waitingQueues && runAt !== 'start') {
-        waitingQueues[runAt] = [];
-      }
       items::forEach(createScriptData);
       // FF bug workaround to enable processing of sourceURL in injected page scripts
-      if (IS_FIREFOX && bridge.mode === INJECT_PAGE) {
+      if (IS_FIREFOX && PAGE_MODE_HANDSHAKE) {
         bridge.post('InjectList', runAt);
       }
     }
@@ -109,7 +110,7 @@ function createScriptData(item) {
 }
 
 async function onCodeSet(item, fn) {
-  const { dataKey, stage } = item;
+  const { dataKey } = item;
   // deleting now to prevent interception via DOMNodeRemoved on el::remove()
   delete window[dataKey];
   if (process.env.DEBUG) {
@@ -121,40 +122,12 @@ async function onCodeSet(item, fn) {
     (wrapper || global)::fn(gm, logging.error);
   };
   const el = document::getCurrentScript();
-  const queue = waitingQueues?.[stage];
   if (el) {
     el::remove();
   }
-  if (queue) {
-    if (stage === 'idle') safePush(queue, 1);
-    safePush(queue, run);
+  if (!PAGE_MODE_HANDSHAKE && runAtBodyQueue !== false && item.runAt === 'body') {
+    safePush(runAtBodyQueue || (runAtBodyQueue = []), run);
   } else {
     run();
   }
 }
-
-async function doRunAt(name) {
-  if (name) {
-    safePush(runningQueues, waitingQueues[name]);
-    if (runningQueues.length > 1) return;
-  }
-  for (let i = 0, queue; i < runningQueues.length; i += 1) {
-    if ((queue = waitingQueues[i])) {
-      for (let j = 0, fn; j < queue.length; j += 1) {
-        fn = queue[j];
-        if (fn) {
-          queue[j] = null;
-          if (fn === 1) {
-            queueMacrotask(doRunAt);
-            return;
-          }
-          await 0;
-          fn();
-        }
-      }
-      queue.length = 0;
-      runningQueues[i] = null;
-    }
-  }
-  runningQueues.length = 0;
-}

+ 4 - 4
src/injected/web/safe-globals-web.js

@@ -12,7 +12,7 @@ export const {
    * TODO: try reimplementing Promise in our sandbox wrapper if it can work with user code */
   Promise: UnsafePromise,
 } = global;
-export const cloneInto = process.env.HANDSHAKE_ID ? null : global.cloneInto;
+export const cloneInto = PAGE_MODE_HANDSHAKE ? null : global.cloneInto;
 export let
   // window
   SafeCustomEvent,
@@ -93,9 +93,9 @@ export const VAULT = (() => {
   let srcFF;
   let src = global; // FF defines some stuff only on `global` in content mode
   let srcWindow = window;
-  if (process.env.VAULT_ID) {
-    res = window[process.env.VAULT_ID];
-    delete window[process.env.VAULT_ID];
+  if (VAULT_ID) {
+    res = window[VAULT_ID];
+    delete window[VAULT_ID];
   }
   if (!res) {
     res = createNullObj();

+ 1 - 1
src/injected/web/util.js

@@ -97,7 +97,7 @@ export const FastLookup = (hubs = createNullObj()) => {
 export const makeComponentUtils = () => {
   const CREATE_OBJECT_IN = 'createObjectIn';
   const EXPORT_FUNCTION = 'exportFunction';
-  const src = IS_FIREFOX && !process.env.HANDSHAKE_ID && global;
+  const src = IS_FIREFOX && !PAGE_MODE_HANDSHAKE && global;
   const defineIn = !src && ((target, as, val) => {
     if (as && (as = getOwnProp(as, 'defineAs'))) {
       setOwnProp(target, as, val);

+ 2 - 1
test/mock/polyfill.js

@@ -32,7 +32,8 @@ for (const k of Object.keys(domProps)) {
 }
 Object.defineProperties(global, domProps);
 delete MessagePort.prototype.onmessage; // to avoid hanging
-global.__VAULT_ID__ = false;
+global.PAGE_MODE_HANDSHAKE = 123;
+global.VAULT_ID = false;
 Object.assign(global, require('@/common/safe-globals'));
 Object.assign(global, require('@/injected/safe-globals-injected'));
 Object.assign(global, require('@/injected/content/safe-globals-content'));