【问题标题】:Injected script doesn't run correctly unless cache is cleared除非清除缓存,否则注入的脚本无法正确运行
【发布时间】:2022-01-03 13:30:52
【问题描述】:

我正在重写一个 Chrome 扩展程序以显示 v3(该死,谷歌垄断!)

它针对某个网站,添加了一堆很酷的功能。这意味着我必须注入可以访问页面上下文的脚本。该网站使用 PageSpeed Insights(可能是旧版本,因为我怀疑他们现在使用 eval),它获取字符串化代码并对其进行评估:

<script src="https://cdn.tanktrouble.com/RELEASE-2021-07-06-01/js/red_infiltration.js+messages.js.pagespeed.jc.vNQfCE2Wzx.js"></script>
<!-- ^ Contains the variables `mod_pagespeed_nEcHBi$9_H = 'function foo(){...}'` and `mod_pagespeed_gxZ81E5yX8 = 'function bar(){...}'` and the scripts below evaluate them -->
<script>eval(mod_pagespeed_nEcHBi$9_H);</script>
<script>eval(mod_pagespeed_gxZ81E5yX8);</script>

现在,我想出了一个方法,可以覆盖原生 eval 函数,获取它正在评估的字符串,生成一个散列并将其与一些存储的散列进行比较。如果它们匹配,我会停止评估脚本并改为注入我的脚本。理论上效果很好,并且在站点更新任何代码时充当故障保护。

问题在于,有 50% 的时间,评估发生在我覆盖 eval 之前,它似乎与缓存有关。如果我进行硬重置(Ctrl+Shift+R),它大部分时间都可以工作,并按预期注入我的脚本。但是,正常的重新加载/站点加载,它不会起作用。

manifest.json

    ...
    "content_scripts": [{
            "run_at": "document_start",
            "js": ["js/content.js"],
            "matches": [ "*://*.tanktrouble.com/*" ]
        }
    ], ...

content.js(内容脚本)

class GameLoader {
    constructor() {
        // Preload scripts, why not?
        this.hasherScript = Object.assign(document.createElement('script'), {
            'src': chrome.runtime.getURL('js/hasher.js'),
            'type': 'module' // So we can import some utils and data later on
        });
        // These custom elements are a hacky method to get my chrome runtime URL into the site with element datasets.
        this.extensionURL = document.createElement('tanktroubleaddons-url');
        this.extensionURL.dataset.url = chrome.runtime.getURL('');
    }

    observe() {
        return new Promise(resolve => {
            const observer = new MutationObserver((mutations, observer) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.tagName === 'HEAD') {
                            node.insertBefore(this.extensionURL, node.firstChild); // extensionURL is used in hasherScript.js.
                            node.insertBefore(this.hasherScript, node.firstChild); // Inject the hash module

                            resolve(), observer.disconnect();
                        }
                    }
                }
            })
            .observe(document.documentElement, { childList: true, subtree: true });
        });
    }
}

const loader = new GameLoader();
loader.observe();

hasher.js (this.hasherScript)

import ScriptHashes from '/config/ScriptHashes.js'; // The script hashes previously mentioned. The format is { '7pqp95akl2s': 'game/gamemanager.js' ... }
import Hasher from '/js/utils/HashAlgorithm.js'; // Quick hash algorithm

/**
Here we implement the aforementioned hacky method for the extension URL.
 */
const nodeData = document.querySelector('tanktroubleaddons-url'),
extensionURL = nodeData.dataset.url;

window.t_url = function(url) {
    return extensionURL + url;
}

// Change the native eval function and generate a hash of the script being evaluated.
const proxied = eval;
const hashLength = ScriptHashes.length;
window.eval = function(code) {
    if (typeof code === 'string') {
        const codeHash = Hasher(code),
        match = ScriptHashes.hashes[codeHash]; // Check the newly generated hash against my list of hashes. If match, it should return a path to use be used in script fetching.

        if (match) {
            // Synchronous script fetching with jQuery. When done, return null so original script won't be evaluated.
            $.getScript(t_url('js/injects/' + match), () => {
                return null;
            });
        }
    }
    return proxied.apply(this, arguments);
}

奇怪的行为

我注意到将 hasherScript 更改为常规脚本而不是模块时的奇怪行为。如果我排除 type 参数并将导入直接粘贴到 hasher.js 中,事情就会加载并正常工作。脚本每次都会被评估等等。这让我想知道这是否是一个同步/异步问题。 不幸的是,我无法找到任何关于此的内容。

提前致谢! :)

【问题讨论】:

  • 这与您的 DOM 状态没有完全加载有关,并且操作顺序仍然领先于站点。这可能经常发生。我会尝试延迟注入的开始或等待页面加载 100%。
  • 一般来说,为了轻松和完全控制域,我所做的是在预加载时删除所有 HTML/CSS/JS 并插入我自己的,使其保持静态并由我注入。跨度>
  • @BGPHiJACK 请原谅我的无知,但我该怎么做呢?我不喜欢清除网站内容,因为我没有覆盖所有内容,它本身就是一团糟。我尝试过将 window.onload 和文档 DOMContentLoaded 写入 loader.observe(),并且我还尝试使用 onload 和 DOMContentLoaded 将 node.insertBefore 包装在 MutationObserver 中
  • 完美,所有正确的工具。我举了一个小例子,这对你来说是一个很好的主食。如果需要,您可以通过包装它们来私下限定您的函数。 (function () {/*code*/})();
  • MV3 在 document_start 覆盖 page context 代码时本质上是不可靠的,因为它仅支持异步加载的 web_accessible_resources 脚本,因此它会随机延迟。在crbug.com/1054624crbug.com/1207006 中实现world 参数之前,您必须使用MV2。

标签: javascript google-chrome-extension chrome-extension-manifest-v3


【解决方案1】:

我会尝试等待 DOM 的加载状态,如果这还不够,并且有脚本进一步渲染页面,您可以在完全插入之前等待特定元素/属性,但这可能还不够,因此添加了 CheckState。

当你以你的方式插入它时,它主要是在 DOM 完全加载之前完成的,所以可能是你的问题。

/*
The DOM state should be loaded before injecting code, however this depends on the site. Calling this makes sure that all HTML/JavaScript/CSS is loaded. 
*/
window.onload = async() => {
  console.log("HTML/JavaScript/CSS are considered to be loaded now");
  console.log("Page may be running scripts, wait for finish!");
  CheckState();
  // Above is optional or may be needed. 
};
let fullyloaded;

function CheckState() {
  console.log("checksite");
  fullyloaded = setTimeout(() => {
    /*
    Sometimes the site is loaded 100% but nothing is on the page, possibly loading the rest of the site through script. In this case you can run a setTimeout
    */
    if (document.querySelector('.SelectionThatTellsUsPageIsLoadedProperly')) {
      InjectCode();
    } else {
      CheckState();
    }

  }, 10000);
}

function InjectCode() {
  console.log("We're loaded, let's insert the code now no issues");
}

【讨论】:

  • 只是一个例子,但经常与脚本注入一起使用。您将需要确定是否需要在加载之前、期间或之后加载脚本。如果是之前,路线有点不同;总是会在加载后注入以获得最安全的结果(ETags/etc 可能存在)。此脚本将在加载之前插入,但如果有意义,则在加载时运行。
  • 感谢您的回答!不过,恐怕你误会了。在加载任何站点脚本之前,我必须注入并运行我的模块脚本。如果我需要在加载完所有内容后运行我的脚本,我会将清单设置为使用 document_idle 而不是 document_start。我将我的模块放在任何其他脚本之前,并希望它运行这样它会影响它之后的脚本站点。
  • 是的,所以这实际上可以用于两者。因为您在 document_start 上插入,脚本最初会加载,如果任何 code-sn-p 必须等待,它可以插入到上面的块中。因此,您最初也想加载的任何内容都不会在 onload 函数内而是在它之外。 :)
  • 啊,这样!这更有意义。谢谢!
猜你喜欢
  • 1970-01-01
  • 2013-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-21
  • 2019-11-09
相关资源
最近更新 更多