瀏覽代碼

fix: retest CSP in Firefox at document-body too

tophf 5 天之前
父節點
當前提交
4b22b03a8f
共有 3 個文件被更改,包括 38 次插入25 次删除
  1. 4 3
      src/background/utils/preinject.js
  2. 32 20
      src/injected/content/inject.js
  3. 2 2
      src/types.d.ts

+ 4 - 3
src/background/utils/preinject.js

@@ -572,7 +572,7 @@ function prepareScript(script, env) {
 
 function triageRealms(scripts, forceContent, tabId, frameId, bag) {
   let code;
-  let wantsPage;
+  let wantsPage = 0;
   const toContent = [];
   for (const scr of scripts) {
     const metaStr = scr[META_STR];
@@ -586,12 +586,13 @@ function triageRealms(scripts, forceContent, tabId, frameId, bag) {
     } else {
       metaStr[0] = '';
       code = forceContent ? ID_BAD_REALM : scr[__CODE];
-      if (!forceContent) wantsPage = true;
+      if (!forceContent) wantsPage = 1;
     }
     scr.code = code;
   }
   if (bag) {
-    bag[INJECT][PAGE] = wantsPage || triagePageRealm(bag[MORE]);
+    bag[INJECT][PAGE] = wantsPage
+      + ((!wantsPage || IS_FIREFOX) && triagePageRealm(bag[MORE]) ? 2 : 0);
   }
   if (toContent[0]) {
     // Processing known feedback without waiting for InjectionFeedback message.

+ 32 - 20
src/injected/content/inject.js

@@ -14,6 +14,7 @@ let pageInjectable;
 let frameEventWnd;
 /** @type {ShadowRoot} */
 let injectedRoot;
+let invokeContent;
 let nonce;
 
 // https://bugzil.la/1408996
@@ -129,6 +130,7 @@ export function injectPageSandbox(data) {
  */
 export async function injectScripts(data, info, isXml) {
   const { errors, [MORE]: more } = data;
+  const BODY = 'body';
   const CACHE = 'cache';
   if (errors) {
     logging.warn(errors);
@@ -151,15 +153,26 @@ export async function injectScripts(data, info, isXml) {
   const moreData = (more || toContent.length)
     && sendFeedback(toContent, more);
   const getReadyState = more && describeProperty(Document[PROTO], 'readyState').get;
-  const hasInvoker = contLists;
-  if (hasInvoker) {
-    setupContentInvoker();
-  }
+  const wasInjectableFF = IS_FIREFOX && !nonce && pageInjectable;
+  const pageBodyScripts = pageLists?.[BODY];
   tardyQueue = createNullObj();
   // Using a callback to avoid a microtask tick when the root element exists or appears.
   await onElement('*', injectAll, 'start');
-  if (pageLists?.body || contLists?.body) {
-    await onElement('body', injectAll, 'body');
+  if (pageBodyScripts || contLists?.[BODY]) {
+    await onElement(BODY, !wasInjectableFF || !pageBodyScripts ? injectAll : arg => {
+      if (didPageLoseInjectability(toContent, pageBodyScripts)) {
+        pageLists = null;
+        contLists ??= createNullObj();
+        const arr = contLists[BODY];
+        if (arr) {
+          for (const scr of pageBodyScripts) safePush(arr, scr);
+        } else {
+          contLists[BODY] = pageBodyScripts;
+        }
+        sendFeedback(toContent);
+      }
+      injectAll(arg);
+    }, BODY);
   }
   if (more && (data = await moreData)) {
     assign(bridge[CACHE], data[CACHE]);
@@ -171,16 +184,12 @@ export async function injectScripts(data, info, isXml) {
       });
       await 0; // let the site's listeners on `window` run first
     }
-    if (IS_FIREFOX && !nonce && pageInjectable && didPageLoseInjectability(toContent, data)) {
-      pageInjectable = false;
+    if (wasInjectableFF && didPageLoseInjectability(toContent, data[SCRIPTS])) {
       sendFeedback(toContent);
     }
     for (const scr of data[SCRIPTS]) {
       triageScript(scr);
     }
-    if (contLists && !hasInvoker) {
-      setupContentInvoker();
-    }
     await injectAll('end');
     await injectAll('idle');
   }
@@ -188,9 +197,9 @@ export async function injectScripts(data, info, isXml) {
   bridgeInfo = contLists = pageLists = VMInitInjection = null;
 }
 
-function didPageLoseInjectability(toContent, data) {
+function didPageLoseInjectability(toContent, scripts) {
   toContent.length = 0;
-  for (const scr of data[SCRIPTS]) {
+  for (const scr of scripts) {
     const realm = scr[INJECT_INTO];
     if (realm === PAGE
     || realm === AUTO && bridge[INJECT_INTO] !== CONTENT) {
@@ -198,16 +207,16 @@ function didPageLoseInjectability(toContent, data) {
       safePush(toContent, [scr.id, scr.key.data]);
     }
   }
-  if (toContent.length) {
+  let res = toContent.length;
+  if (res && pageInjectable) { // may have been cleared when handling pageBodyScriptsFF
     const testId = safeGetUniqId();
     const obj = window[kWrappedJSObject];
     inject({ code: `window["${testId}"]=1` });
-    if (obj[testId]) {
-      delete obj[testId];
-    } else {
-      return true;
-    }
+    res = obj[testId] !== 1;
+    if (res) pageInjectable = false;
+    else delete obj[testId];
   }
+  return res;
 }
 
 function sendFeedback(toContent, more) {
@@ -307,6 +316,9 @@ function inject(item, iframeCb) {
 }
 
 function injectAll(runAt) {
+  if (contLists && !invokeContent) {
+    setupContentInvoker();
+  }
   let res;
   for (let inPage = 1; inPage >= 0; inPage--) {
     const realm = inPage ? PAGE : CONTENT;
@@ -340,7 +352,7 @@ async function injectPageList(runAt) {
 }
 
 function setupContentInvoker() {
-  const invokeContent = VMInitInjection(IS_FIREFOX)(bridge.onHandle, logging);
+  invokeContent = VMInitInjection(IS_FIREFOX)(bridge.onHandle, logging);
   const postViaBridge = bridge.post;
   bridge.post = (cmd, params, realm, node) => {
     const fn = realm === CONTENT

+ 2 - 2
src/types.d.ts

@@ -278,8 +278,8 @@ declare interface VMInjection extends VMInjectionDisabled, VMInjectionFlags {
   /** cache key for envDelayed, which also tells content bridge to expect envDelayed */
   more: string;
   nonce?: string;
-  /** `page` mode will be necessary */
-  page: boolean;
+  /** `page` mode will be necessary, bit 0: at start/body, bit 1: at end/idle */
+  page: number;
   scripts: VMInjection.Script[];
   sessionId: string;
 }