我们在调试移动端的网页、或是使用 Stetho 等工具时,经常需要操作 chrome://inspect
,然而这玩意在远程 Chrome 版本低于本地 Chrome 时会走 Google 的服务器(不清楚为何要这样设计),导致异常卡顿,几乎无法使用:
而当 Chrome 认为自己的版本低于远程 Chrome 版本时,会增加一个 inspect fallback 的选项,通过 inspect fallback 进入的调试界面就正常很多:
所以我们会有一个很自然的想法:能否让所有远程调试都走 inspect fallback?好在 chrome://inspect
这个页面本身也是可以调试的,所以能够很轻易地找出它的逻辑:
const browserNeedsFallback = isVersionNewerThanHost(browser.adbBrowserVersion);
function isVersionNewerThanHost(version) {
if (!HOST_CHROME_VERSION) {
return false;
}
version = version.split('.').map(s => Number(s) || 0);
for (let i = 0; i < HOST_CHROME_VERSION.length; i++) {
if (i > version.length) {
return false;
}
if (HOST_CHROME_VERSION[i] > version[i]) {
return false;
}
if (HOST_CHROME_VERSION[i] < version[i]) {
return true;
}
}
return false;
}
这里的 HOST_CHROME_VERSION
是从 User-Agent 里拆出来的一个四元组:
const chromeMatch = navigator.userAgent.match(/(?:^|\W)Chrome\/(\S+)/);
if (chromeMatch && chromeMatch.length > 1) {
HOST_CHROME_VERSION = chromeMatch[1].split('.').map(s => Number(s) || 0);
}
打个断点再看看参数传进去的 version
又是什么东西:
也是四个数字,中间用点隔开的版本号,这样一来就有了一个很简单的解决办法:修改 UA 中的 Chrome 版本号为 0.0.0.0
:
(() => {
const backup = Object.getOwnPropertyDescriptor(Navigator.prototype, 'userAgent').get
Object.defineProperty(
Navigator.prototype, 'userAgent', {
get() {
return backup.apply(this).replace(/(?<=(?:^|\W)Chrome\/)\S+/, '0.0.0.0')
}
}
)
})()
(注意上方代码不是油猴脚本,具体用法会在最后的 Gist 中给出)
嗯,现在确实能够用 inspect fallback 调试了,但是看着…… 实在有点丑,无用的 inspect 按钮没去掉不说,上面还多了一条警告信息,而且把 UA 里的版本号强行设为 0 也可能会引入一些奇怪的问题
那么有没有办法把 inspect 按钮的行为直接替换成 inspect fallback 呢?既然能够注入脚本,办法显然是有的,再不济可以也套个 MutationObserver
,只要 DOM 一发生变化,就立马用脚本把它维护成新的样式……
但再仔细看看它的代码就能发现,其实完全不需要对 DOM 动手脚,它的这些按钮都是通过一个 addActionLink
函数来绑定的:
那么我们便可以来一手「偷天换日」,一旦发现参数为 inspect
,就替换为 inspect fallback 的逻辑:
(function () {
Function.prototype.bind = new Proxy(
Function.prototype.bind, {
apply(func, thisObj, args) {
if (thisObj.name == 'sendTargetCommand' && args[1] == 'inspect') {
args[1] = 'inspect-fallback'
}
return Reflect.apply(func, thisObj, args)
}
}
)
})()
注入代码的能力则由 Chrome 扩展程序提供,具体实现可以参考 这篇 Gist