Google Chrome extensions using KnockoutJS

Attempting to use KnockoutJS within a Google Chrome extension will swiftly result in the following error message.

Uncaught Error: Unable to parse bindings.
Message: Error: Code generation from strings disallowed for this context;

This is due to Chrome's Content Security Policy for extensions. Essentially, it blocks the use of eval and new Function, which means features like Knockout's template engine and data-bindings won't work.

Sandboxing your application

The solution to using KnockoutJS in a Google Chrome extension is to sandbox the extension's HTML page and javascript. This isolates them from the rest of the extension and allows the use of previously restricted features.

To sandbox an HTML page, we need to mention it in the extension's manifest.json file. Here's an example manifest that sandboxes a file named app.html.

{
  "name": "Knockout Chrome Extension",
  "version": "0.1",
  "manifest_version": 2,
  "description": "An example extension that uses KnockoutJS",
  "sandbox": {
    "pages": ["app.html"]
  },
  ...
}

The app.html page can now reference knockout.js and your other scripts. These referenced files are then also sandboxed. However, it can no longer be loaded directly into your extension's browser action pop-up, or be displayed in a tab.

Loading the sandboxed application

A sandboxed HTML page needs to be loaded from a non-sandboxed page, using an <iframe>.

For example, the extension can have a browser action pop-up, specified by the following part of the manifest.json:

{
  ...
  "browser_action": {
    "default_popup": "popup.html",
    "default_title": "Knockout Chrome Extension",
    "default_icon": {
      "19": "icon19.png",
      "38": "icon38.png"
    }
  },
  ...
}

The popup.html page serves as a container for the sandboxed application page.

<!DOCTYPE html>
<html>
  <head>
    <script src="popup.js"></script>
  <head>
  <body style="padding: 0">
    <iframe src="app.html" id="app" seamless></iframe>
  <body>
<html>

Note the handy HTML5 seamless attribute on the <iframe> tag. This removes all borders and scrollbars.

Communicating with the application

Anything loaded by a sandboxed page, such as scripts, are also sandboxed. This means your application script won't be able to use any of the Chrome APIs.

Scripts loaded by the un-sandboxed popup.html do have access to the Chrome APIs. Therefore, if your application needs to interact with Chrome, it has to get the un-sandboxed script to do so.

To communicate between sandboxed and un-sandboxed windows, you have to use messages.

Here's a snippet from popup.js, loaded by popup.html, which calls a Chrome API and then posts a message to the application.

var appWindow = document.getElementById("app").contentWindow;
chrome.topSites.get(function(topSites) {
  appWindow.postMessage({ displayLinks: topSites }, "*");
});

MDN has great documentation about the window.postMessage method.

In the application script, we can listen for this message:

var app = {
  links: ko.observableArray()
};

window.addEventListener(
  "message",
  function(event) {
    if (event.data.displayLinks) {
      app.links(event.data.displayLinks);
    }
  },
  false
);

ko.applyBindings(app);

Similarly, the application can post messages to the un-sandboxed script via the parent window property. For example:

window.parent.postMessage({ example: true }, "*")

Full sample code

To get the full sample code for a simple extension that uses KnockoutJS, join my web development mailing list. I'll give you access to a GitHub repository containing code samples, which you can copy and use in your own projects.

Would you like access to the sample code repository?

Subscribe to my web development mailing list.

No spam, just occasional web development tips, posts and sample code. Unsubscribe at any time.

Comments
blog comments powered by Disqus