【发布时间】: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/1054624 或crbug.com/1207006 中实现
world参数之前,您必须使用MV2。
标签: javascript google-chrome-extension chrome-extension-manifest-v3