基于安全考虑,Chrome 扩展运行在一个独立的沙箱里,不能直接接触页面里的变量和实例。但有些时候我们必须在当前页面的环境里执行 JS,比如近期开发 Navlang 时遇到一个需求:修改浏览器环境里语言信息,实现语言切换。
修改 navigator.language
语言切换可以通过修改 navigator.language
和 navigator.languages
实现。这里大家可能会遇到另一个问题:当我们执行 navigator.language = 'en'
后,再次访问 navigator.language
,返回的仍然是赋值前的结果。这是因为 navigator
里的值,包括常用的 userAgent
在内,其实都是通过 getter
函数返回的,直接赋值无效。所以这里我们需要用 ES5 里新增的 Object.defineProperty
来操作:
Object.defineProperty(navigator, "language", {
get: () => $lang,
});
Object.defineProperty(navigator, "languages", {
get: () => ([$lang]),
});
这里还用到 ES6 中的箭头函数,尤其返回 languages
的时候,因为是数组,还用到一些 ES6 中的解构语法,如果不太清楚的话可以找一些资料来看看,或者留言。
在页面里执行 JS
接下来,插件运行环境里也有 window
实例,提供完整的 BOM 实现,包括 navigator
等对象,所以我一开始没反应过来,直接就在插件里跑,执行也不报错,但是效果出不来。
来回试了几次,终于想起来,它们环境不一样,我在插件里改变了的 navigator.language
,和网页里的不是一个。所以虽然程序执行都正常,但是没有实际生效。
经过一番 Google,终于找到解决方案。因为执行环境不同,所以直接访问肯定不行,目前可行的方案则是操作 DOM,注入一个 <script>
,把 JS 放进去,这样就可以在页面的环境里执行 JS 了。
function injectScript(content) {
const script = document.createElement('script');
script.textContent = `(${asyncFunction})()`;
document.documentElement.appendChild(script);
script.remove();
}
欢迎吐槽,共同进步