我们知道,浏览器扩展(Chrome extension)可以通过 content script 在目标网页里执行代码。它和目标页面的 JS 处于不同的沙箱,但是共用一套 DOM。所以,content script 无法访问目标页面的 JS 变量,但是可以修改目标页面的 DOM、侦听 DOM 上的事件、或者触发 DOM 事件。
在这种情况下,要操作 CodeMirror 的时候会遇到一些问题。为了性能考虑,CodeMirror 不会把所有代码都渲染出来,而是会按需渲染。比如代码总共 1k 行,但是当前窗口里只能看到 40 行,那么 CodeMirror 就会渲染当前的 40 行,和前后大概各 20 行(方便快速滚屏),总计约 80 行。
换言之,如果无法接触到目标页面的 JS 环境,单纯从 DOM 里无法获取到全部代码,也很难修改 CM 里的代码。于是我们就需要一些特别的技巧——这些技巧在操作其它类似的类库时也会用到,所以写篇博客分享下。
解决方案:向目标网页注入 <script>
此时,我们可以通过创建 <script>
的方式向目标网页注入一段 JS。这个 JS 的执行环境也是目标网页,所以可以访问到目标网页 环境下的 JS 变量。
CodeMirror 会把实例添加到容器节点上,所以我们可以先找到容器节点,即 div.CodeMirror
;然后访问它的 CodeMirror
属性找到 CM 实例;最后进行操作。
为了方便 content script 和 CodeMirror 交互,我们必须搭建一个桥梁,也就是下面这段代码:
const cms = document.getElementsByClassName('CodeMirror')
for (const dom of cms) {
// 这个 textarea 被 CodeMirror 视图所取代,是隐藏的。我们利用它传递代码
const textarea = dom.previousElementSibling;
const cm = dom.CodeMirror;
// 给 CodeMirror 添加清空功能,可以通过在 dom 上广播事件触发
dom.addEventListener('clear', () => {
cm.setValue('');
});
// `changes` 是 CodeMirror 的事件,在代码改变时触发,这里我们通过侦听它,并且将代码同步到 `textarea` 的方式,方便 content script 访问
cm.on('changes', cm => {
if (texture.value === cm.getValue()) {
return;
}
textarea.value = cm.getValue();
});
// 为初始化
textarea.value = cm.getValue();
}
因为我执行 content script 的时机总在页面完成初始化之后,此时所有 CodeMirror 都已就位。如果你要在别的时间执行,可以做一些调整。
欢迎吐槽,共同进步