Browse Source

fix: injection raciness

* properly fixes #1739
* supersedes and partially reverts:
  a3d94e37, c5f5fe0c, 08ecd596
tophf 2 years ago
parent
commit
f927d72bee
2 changed files with 18 additions and 15 deletions
  1. 4 3
      src/background/utils/db.js
  2. 14 12
      src/background/utils/preinject.js

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

@@ -273,8 +273,7 @@ export function getScriptsByURL(url, isTop, errors) {
   const envStart = makeEnv();
   /** @type {VMInjection.EnvDelayed} */
   const envDelayed = makeEnv();
-  for (const [areaName, listName] of STORAGE_ROUTES_ENTRIES) {
-    envStart[areaName] = {}; envDelayed[areaName] = {};
+  for (const [, listName] of STORAGE_ROUTES_ENTRIES) {
     envStart[listName] = []; envDelayed[listName] = [];
   }
   allScripts.forEach((script) => {
@@ -318,7 +317,8 @@ export function getScriptsByURL(url, isTop, errors) {
     env[ENV_SCRIPTS].push(script);
   });
   if (!errors) {
-    return readEnvironmentData(envDelayed);
+    envDelayed.promise = readEnvironmentData(envDelayed);
+    return envDelayed;
   }
   if (envStart.ids.length) {
     envStart.promise = readEnvironmentData(envStart);
@@ -339,6 +339,7 @@ async function readEnvironmentData(env) {
   const data = await storage.base.getMulti(keys);
   const badScripts = new Set();
   for (const [area, listName] of STORAGE_ROUTES_ENTRIES) {
+    env[area] = {}; // presence of the area object is used to check that `env.promise` is resolved
     for (const id of env[listName]) {
       let val = data[storage[area].toKey(id)];
       if (!val && area === S_VALUE) val = {};

+ 14 - 12
src/background/utils/preinject.js

@@ -111,7 +111,7 @@ addPublicCommands({
     if (!url) url = src.url || tab.url;
     clearFrameData(tabId, frameId);
     const bagKey = getKey(url, isTop);
-    const bagP = cache.get(bagKey) || await prepare(bagKey, url, isTop);
+    const bagP = cache.get(bagKey) || prepare(bagKey, url, isTop);
     const bag = bagP[INJECT] ? bagP : await bagP.promise;
     /** @type {VMInjection} */
     const inject = bag[INJECT];
@@ -136,14 +136,10 @@ addPublicCommands({
     injectContentRealm(items, tabId, frameId);
     if (!moreKey) return;
     if (!url) url = src.url || tab.url;
-    let more = cache.get(moreKey);
-    if (!more || more.ids.some(id => !more[S_CODE][id])) {
-      // Workaround for FF bug(?): the `code` object is mysteriously emptied
-      more = cache.put(moreKey, getScriptsByURL(url, !frameId));
-    }
-    // Caching as Promise to be awaited by other tabs with this moreKey
+    let more = cache.get(moreKey)
+      || cache.put(moreKey, getScriptsByURL(url, !frameId));
     const envCache = more[S_CACHE]
-      || cache.put(moreKey, more = await more)[S_CACHE];
+      || cache.put(moreKey, more = await more.promise)[S_CACHE];
     const scripts = prepareScripts(more);
     triageRealms(scripts, forceContent, tabId, frameId);
     addValueOpener(scripts, tabId, frameId);
@@ -284,12 +280,17 @@ async function prepare(cacheKey, url, isTop) {
   const errors = [];
   // TODO: teach `getScriptEnv` to skip prepared scripts in cache
   const env = getScriptsByURL(url, isTop, errors);
-  if (!env) return cache.put(cacheKey, bagNoOp);
-  const inject = shouldExpose ? { expose: true } : {};
-  const bag = { [INJECT]: inject };
-  cache.put(cacheKey, bag); // synchronous onHeadersReceived needs plain object not a Promise
+  if (env) {
+    env.promise = prepareBag(cacheKey, url, isTop,
+      env, shouldExpose ? { expose: true } : {}, errors);
+  }
+  return cache.put(cacheKey, env || bagNoOp);
+}
+
+async function prepareBag(cacheKey, url, isTop, env, inject, errors) {
   await env.promise;
   cache.batch(true);
+  const bag = { [INJECT]: inject };
   const { allIds, [INJECT_MORE]: envDelayed } = env;
   const moreKey = envDelayed.promise && getUniqId('more');
   Object.assign(inject, {
@@ -315,6 +316,7 @@ async function prepare(cacheKey, url, isTop) {
     cache.put(moreKey, envDelayed);
     envDelayed[INJECT_MORE] = cacheKey;
   }
+  cache.put(cacheKey, bag); // synchronous onHeadersReceived needs plain object not a Promise
   cache.batch(false);
   return bag;
 }