Просмотр исходного кода

feat: inject earlier than document_start in Chrome

tophf 6 лет назад
Родитель
Сommit
414cfdfe2d
4 измененных файлов с 46 добавлено и 4 удалено
  1. 21 0
      src/background/index.js
  2. 22 3
      src/injected/content/inject.js
  3. 2 1
      src/injected/utils/helpers.js
  4. 1 0
      src/manifest.yml

+ 21 - 0
src/background/index.js

@@ -127,4 +127,25 @@ initialize(() => {
   sync.initialize();
   checkRemove();
   setInterval(checkRemove, TIMEOUT_24HOURS);
+  if (ua.isChrome) {
+    // Using declarativeContent to run content scripts earlier than document_start
+    const api = global.chrome.declarativeContent;
+    api.onPageChanged.getRules(['inject'], rules => {
+      if (rules.length) return;
+      api.onPageChanged.addRules([{
+        id: 'inject',
+        conditions: [
+          new api.PageStateMatcher({
+            pageUrl: { urlContains: '://' }, // essentially like <all_urls>
+          }),
+        ],
+        actions: [
+          new api.RequestContentScript({
+            js: browser.runtime.getManifest().content_scripts[0].js,
+            // Not using `allFrames:true` as there's no improvement in frames
+          }),
+        ],
+      }]);
+    });
+  }
 });

+ 22 - 3
src/injected/content/inject.js

@@ -8,7 +8,7 @@ import {
 import { sendCmd } from '#/common';
 
 import {
-  forEach, join, append, createElementNS, defineProperty, NS_HTML,
+  forEach, join, append, createElementNS, defineProperty, describeProperty, NS_HTML, DocProto,
   charCodeAt, fromCharCode, replace, remove,
 } from '../utils/helpers';
 import bridge from './bridge';
@@ -19,7 +19,11 @@ const VMInitInjection = window[Symbol.for(process.env.INIT_FUNC_NAME)];
 // (the symbol is undeletable so a userscript can't fool us on reinjection)
 defineProperty(window, Symbol.for(process.env.INIT_FUNC_NAME), { value: 1 });
 
-const { encodeURIComponent } = global;
+const { encodeURIComponent, document } = global;
+// Userscripts in content mode may redefine head and documentElement
+const { get: getHead } = describeProperty(DocProto, 'head');
+const { get: getDocElem } = describeProperty(DocProto, 'documentElement');
+const { appendChild } = DocProto; // same as Node.appendChild
 
 bridge.addHandlers({
   Inject: injectScript,
@@ -32,6 +36,8 @@ export function injectPageSandbox(contentId, webId) {
 }
 
 export function injectScripts(contentId, webId, data, isXml) {
+  // eslint-disable-next-line prefer-rest-params
+  if (!document::getDocElem()) return waitForDocElem(() => injectScripts(...arguments));
   const injectPage = [];
   const injectContent = [];
   const scriptLists = {
@@ -84,6 +90,16 @@ function checkInjectable() {
   return res;
 }
 
+function waitForDocElem(cb) {
+  const observer = new MutationObserver(() => {
+    if (document::getDocElem()) {
+      observer.disconnect();
+      cb();
+    }
+  });
+  observer.observe(document, { childList: true });
+}
+
 // fullwidth range starts at 0xFF00
 // normal range starts at space char code 0x20
 const replaceWithFullWidthForm = s => fromCharCode(s::charCodeAt(0) - 0x20 + 0xFF00);
@@ -109,6 +125,9 @@ function inject(code, sourceUrl) {
     ...typeof code === 'string' ? [code] : code,
     ...sourceUrl ? ['\n//# sourceURL=', sourceUrl] : [],
   );
-  document.documentElement::append(script);
+  const root = document::getHead() || document::getDocElem();
+  // When using declarativeContent there's no documentElement so we'll append to `document`
+  if (root) root::appendChild(script);
+  else document::appendChild(script);
   script::remove();
 }

+ 2 - 1
src/injected/utils/helpers.js

@@ -25,7 +25,8 @@ const { toString: numberToString } = Number.prototype;
 export const { fromCharCode } = String;
 export const { addEventListener, removeEventListener } = EventTarget.prototype;
 export const { append, remove, setAttribute } = Element.prototype;
-export const { createElementNS } = Document.prototype;
+export const DocProto = Document.prototype;
+export const { createElementNS } = DocProto;
 export const logging = assign({}, console);
 
 export const NS_HTML = 'http://www.w3.org/1999/xhtml';

+ 1 - 0
src/manifest.yml

@@ -43,6 +43,7 @@ permissions:
   - unlimitedStorage
   - clipboardWrite
   - cookies
+  - declarativeContent
 commands:
   _execute_browser_action: {}
   dashboard: