Chrome extension 升级到 manifest version 2 的问题

最近在折腾一个 chrome extension, 虽然之前也折腾过,但是了解很浅,这次就顺带系统地了解了下。开始的时候一切都很顺利,对一个前端工程师来说,学习的成本并不是很高。

问题

在某天 chrome 提示升级的时候,就果断升级了(当前版本为 Version 21.0.1180.57)。升级之后,发现在 extensions 页面,有个提示:

There were warnings when trying to install this extension:
Support for manifest version 1 is being phased out. Please upgrade to version 2.

一开始并没有想到这个版本号的升级会有多大问题,因为至少看起来并没有特别多的改动。但是实际操作起来,遇到了不少问题,还挺让人头疼的。

升级之后整个插件就崩溃了。经查看 console 里面的提示信息为:

Uncaught Error: Code generation from strings disallowed for this context

一番查询之后,发现这次版本号升级里面,实际在 extension 的安全策略方面做了较大改动.改变最大的是 Content Security Policy (CSP) 。这个改变包括,不再允许执行 inline script;不再允许使用 eval() 或者 new Function() 来进行字符串处理。

对 eval() 和 new Function() 的限制很悲剧,因为大多数的 javascript 模板库都用到了这两个方法。我在用的 jQuery templates 就因为用到了 new Function() 而悲剧了。

而且没有任何方式可以改变默认的安全策略来使用这些被 CSP 禁用的方法。

There is no mechanism for relaxing the restriction against executing inline JavaScript. In particular, setting a script policy that includes unsafe-inline will have no effect. This is intentional.

方案

  1. chrome extension team 提供了一个官方的解决方案。可以使用 sandboxed page来继续使用用到了 eval() new Function() 的 javascript template library 。通过 iframe 加载它并与之通信以渲染模板。参考 - Using eval in Chrome Extensions. Safely.
  2. 官方实例
  3. 另外可用的方案是使用可以预编译模板的 javascript template library,目前我使用了 handlebarsjs,其他可用的还有 dust eco 。这几个模板库都可以通过预编译模板文件来规避 eval() new Function 与 CSP 的冲突。
  4. 还有一个需要注意的地方是,我在插件中还用到了 underscore 的一些函数。因为 underscore 里面的 template() 也使用了 new Function(),所以我只能把相关的函数单独拆分出来,以避免和 CSP 的冲突。

关于 handlebarsjs

  1. 安装通过 npm 安装来使用 handlebars 的预编译 $ npm install handlebars -g
  2. 把之前的模板转为 handlebars 模板以我之前使用的 jQuery template 为例,先把之前写在 html 里面以 <script type="text/x-jquery-tmpl"> 标记的模板拆分成单个后缀为 handlebars 的文件。然后根据 handlebars 的语法要求,把 {{= }} {{if}} {{each}} 分别替换为 {{}} {{#if}} {{#each}}
  3. 预编译 $ handlebars <input> -f <output>

handlebars 内置的那些 helper 相对来说过于简单了。比如 jQuery template 里面的 {{each(i, v) items}} 都需要自己写 helper 来实现。官方的说法是鼓励自己写 helper 来实现这些用法,而且 “that’s on purpose.” 。

可以把这些 hepler 单独写在一个 helpler.js 里面,预编译的时候,通过 -k 指定这个 helper.js 进行编译。

官方文档里面有提到

Because you are precompiling templates, you can also apply several optimization to the compiler. The first allows you to specify a list of the known helpers to the compiler

handlebars <input> -f <output> -k each -k if -k unless

The Handlebars compiler will optimize accesses to those helpers for performance. 4. 使用模板模板预编译完成之后,会得到一个如 templates.js 的模板文件。只需要把 handlebars runtime 文件和这个模板文件通过 <script>方式引入到 html 中就可以使用了。handlebars 把所有的模板挂载到全局的 Handlbars.templates 对象之下。每个模板都是安装模板文件的名称来命名的。如果模板为 cinema.handlebars 则可以通过 Handlbars.templates.cinema 来获取到这个模板。 var _cinema = { 'title': '', 'id': '', 'total': '', 'timelines': [] }; var _render = Handlbars.templates.cinema(_cinema)

_render 就是渲染后的模板。 5. handlebars 参考资料 - ArticleGetting Started with Handlebars.js
- ArticleHandlebars.js Part 2: Partials and Helpers - ArticleHandlebars.js Part 3: Tips and Tricks

以安全之名,各位 chrome extension 开发者,开始折腾吧。

reference

在解决问题的查找过程中,发现中文资料还是太少,幸亏发现了 Javascript Templates and Chrome’s Content Security Policy 这篇文章,从问题到解决方案,一应俱全,因此我这篇只是在他的基础上做了一些补充。

更新

关于 javascript template library,今天同事分享了一个看上去更简单的 t.js。暂时先补充上,之后或许会试用下。