Browse Source

fix: clear preinject cache for more db changes

tophf 5 years ago
parent
commit
e03e28b29a
3 changed files with 56 additions and 32 deletions
  1. 3 19
      src/background/index.js
  2. 12 6
      src/background/utils/db.js
  3. 41 7
      src/background/utils/preinject.js

+ 3 - 19
src/background/index.js

@@ -9,7 +9,7 @@ import { getData, checkRemove } from './utils/db';
 import { setBadge } from './utils/icon';
 import { initialize } from './utils/init';
 import { getOption, hookOptions } from './utils/options';
-import { clearPreinjectData, getInjectedScripts } from './utils/preinject';
+import { getInjectedScripts } from './utils/preinject';
 import { SCRIPT_TEMPLATE, resetScriptTemplate } from './utils/template-hook';
 import { resetValueOpener, addValueOpener } from './utils/values';
 import './utils/clipboard';
@@ -83,19 +83,6 @@ Object.assign(commands, {
     }
     return res;
   },
-  InjectionFeedback(feedback, { tab, frameId }) {
-    feedback.forEach(([key, needsInjection]) => {
-      const code = cache.pop(key);
-      // see TIME_KEEP_DATA comment
-      if (needsInjection && code) {
-        browser.tabs.executeScript(tab.id, {
-          code,
-          frameId,
-          runAt: 'document_start',
-        });
-      }
-    });
-  },
   /**
    * Timers in content scripts are shared with the web page so it can clear them.
    * await sendCmd('SetTimeout', 100) in injected/content
@@ -124,13 +111,10 @@ const commandsToSyncIfTruthy = [
 async function handleCommandMessage(req, src) {
   const { cmd } = req;
   const res = await commands[cmd]?.(req.data, src);
-  const maybeChanged = commandsToSync.includes(cmd);
-  if (maybeChanged || res && commandsToSyncIfTruthy.includes(cmd)) {
+  if (commandsToSync.includes(cmd)
+  || res && commandsToSyncIfTruthy.includes(cmd)) {
     sync.sync();
   }
-  if (maybeChanged) {
-    clearPreinjectData();
-  }
   // `undefined` is not transferable, but `null` is
   return res ?? null;
 }

+ 12 - 6
src/background/utils/db.js

@@ -276,17 +276,17 @@ export async function getScriptsByURL(url, isTop) {
       && (isTop || !script.meta.noframes)
       && testScript(url, script)
     ));
-  const reqKeys = {};
-  const cacheKeys = {};
+  const reqKeys = [];
+  const cacheKeys = [];
   const scripts = allScripts.filter(script => script.config.enabled);
   scripts.forEach((script) => {
     const { meta, custom } = script;
     const { pathMap = buildPathMap(script) } = custom;
     meta.require.forEach((key) => {
-      reqKeys[pathMap[key] || key] = 1;
+      pushUnique(reqKeys, pathMap[key] || key);
     });
     meta.resources::forEachValue((key) => {
-      cacheKeys[pathMap[key] || key] = 1;
+      pushUnique(cacheKeys, pathMap[key] || key);
     });
   });
   const ids = allScripts.map(getPropsId);
@@ -295,8 +295,8 @@ export async function getScriptsByURL(url, isTop) {
   .filter(script => script.meta.grant?.some(gm => gmValues.includes(gm)))
   .map(getPropsId);
   const [require, cache, values, code] = await Promise.all([
-    storage.require.getMulti(Object.keys(reqKeys)),
-    storage.cache.getMulti(Object.keys(cacheKeys)),
+    storage.require.getMulti(reqKeys),
+    storage.cache.getMulti(cacheKeys),
     storage.value.getMulti(withValueIds, {}),
     storage.code.getMulti(enabledIds),
   ]);
@@ -308,14 +308,20 @@ export async function getScriptsByURL(url, isTop) {
       scripts,
     },
     // these will be used only by bg/* and to augment the data above
+    cacheKeys,
     code,
     enabledIds,
+    reqKeys,
     require,
     values,
     withValueIds,
   };
 }
 
+function pushUnique(arr, elem) {
+  if (!arr.includes(elem)) arr.push(elem);
+}
+
 /** @return {string[]} */
 function getIconUrls() {
   return store.scripts.reduce((res, script) => {

+ 41 - 7
src/background/utils/preinject.js

@@ -1,9 +1,11 @@
 import { getUniqId } from '#/common';
 import { INJECT_CONTENT, INJECTABLE_TAB_URL_RE, METABLOCK_RE } from '#/common/consts';
 import initCache from '#/common/cache';
+import storage from '#/common/storage';
 import ua from '#/common/ua';
 import { getScriptsByURL } from './db';
 import { extensionRoot, postInitialize } from './init';
+import { commands } from './message';
 import { getOption, hookOptions } from './options';
 import { popupTabs } from './popup-tracker';
 
@@ -25,14 +27,46 @@ postInitialize.push(() => {
   togglePreinject(getOption('isApplied'));
 });
 
-export function clearPreinjectData() {
-  if (ua.isFirefox) {
-    for (const data of cache.getValues()) {
-      data.registration?.then(r => r.unregister());
-    }
+Object.assign(commands, {
+  InjectionFeedback(feedback, { tab, frameId }) {
+    feedback.forEach(([key, needsInjection]) => {
+      const code = cache.pop(key);
+      // see TIME_KEEP_DATA comment
+      if (needsInjection && code) {
+        browser.tabs.executeScript(tab.id, {
+          code,
+          frameId,
+          runAt: 'document_start',
+        });
+      }
+    });
+  },
+});
+
+// Keys of the object returned by getScriptsByURL()
+const propsToClear = {
+  [storage.cache.prefix]: 'cacheKeys',
+  [storage.code.prefix]: true,
+  [storage.require.prefix]: 'reqKeys',
+  [storage.script.prefix]: true,
+  [storage.value.prefix]: 'withValueIds',
+};
+browser.storage.onChanged.addListener(changes => {
+  const dbKeys = Object.keys(changes);
+  const cacheValues = cache.getValues();
+  const dirty = cacheValues.some(data => data.inject
+    && dbKeys.some((key) => {
+      const prefix = key.slice(0, key.indexOf(':') + 1);
+      const prop = propsToClear[prefix];
+      key = key.slice(prefix.length);
+      return prop === true
+        || data[prop]?.includes(prefix === storage.value.prefix ? +key : key);
+    }));
+  if (dirty) {
+    cacheValues.forEach(data => data.registration?.then(r => r.unregister()));
+    cache.destroy();
   }
-  cache.destroy();
-}
+});
 
 /** @return {Promise<Object>} */
 export function getInjectedScripts(url, tabId, frameId) {