External Content

The Chrome Apps security model disallows external content in iframes and the use of inline scripting and eval(). You can override these restrictions, but your external content must be isolated from the app.

Isolated content cannot directly access the app's data or any of the APIs. Use cross-origin XMLHttpRequests and post-messaging to communicate between the event page and sandboxed content and indirectly access the APIs.

Referencing external resources

The Content Security Policy used by apps disallows the use of many kinds of remote URLs, so you can't directly reference external images, stylesheets, or fonts from an app page. Instead, you can use use cross-origin XMLHttpRequests to fetch these resources, and then serve them via blob: URLs.

Manifest requirement

To be able to do cross-origin XMLHttpRequests, you'll need to add a permission for the remote URL's host:

"permissions": [
    "...",
    "https://supersweetdomainbutnotcspfriendly.com/"
  ]

Cross-origin XMLHttpRequest

Fetch the remote URL into the app and serve its contents as a blob: URL:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://supersweetdomainbutnotcspfriendly.com/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
  var img = document.createElement('img');
  img.src = window.URL.createObjectURL(this.response);
  document.body.appendChild(img);
};

xhr.send();

You may want to save these resources locally, so that they are available offline.

Embed external web pages

The webview tag allows you to embed external web content in your app, for example, a web page. It replaces iframes that point to remote URLs, which are disabled inside Chrome Apps. Unlike iframes, the webview tag runs in a separate process. This means that an exploit inside of it will still be isolated and won't be able to gain elevated privileges. Further, since its storage (cookies, etc.) is isolated from the app, there is no way for the web content to access any of the app's data.

Add webview element

Your webview element must include the URL to the source content and specify its dimensions.

<webview src="http://news.google.com/" width="640" height="480"></webview>

Update properties

To dynamically change the src, width and height properties of a webview tag, you can either set those properties directly on the JavaScript object, or use the setAttribute DOM function.

document.querySelector('#mywebview').src =
    'http://blog.chromium.org/';
// or
document.querySelector('#mywebview').setAttribute(
    'src', 'http://blog.chromium.org/');

Sandbox local content

Sandboxing allows specified pages to be served in a sandboxed, unique origin. These pages are then exempt from their Content Security Policy. Sandboxed pages can use iframes, inline scripting, and eval(). Check out the manifest field description for sandbox.

It's a trade-off though: sandboxed pages can't use the chrome.* APIs. If you need to do things like eval(), go this route to be exempt from CSP, but you won't be able to use the cool new stuff.

Use inline scripts in sandbox

Here's a sample sandboxed page which uses an inline script and eval():

<html>
  <body>
    <h1>Woot</h1>
    <script>
      eval('console.log(\'I am an eval-ed inline script.\')');
    </script>
  </body>
</html>

Include sandbox in manifest

You need to include the sandbox field in the manifest and list the app pages to be served in a sandbox:

"sandbox": {
  "pages": ["sandboxed.html"]
}

Opening a sandboxed page in a window

Just like any other app pages, you can create a window that the sandboxed page opens in. Here's a sample that creates two windows, one for the main app window that isn't sandboxed, and one for the sandboxed page:

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create('window.html', {
    'bounds': {
      'width': 400,
      'height': 400,
      'left': 0,
      'top': 0
    }
  });

  chrome.app.window.create('sandboxed.html', {
    'bounds': {
      'width': 400,
      'height': 400,
      'left': 400,
      'top': 0
    }
  });
});

Embedding a sandboxed page in an app page

Sandboxed pages can also be embedded within another app page using an iframe:

<!DOCTYPE html>
<html>
<head>
</head>
  <body>
    <p>I am normal app window.</p>

    <iframe src="sandboxed.html" width="300" height="200"></iframe>
  </body>
</html>

Sending messages to sandboxed pages

There are two parts to sending a message: you need to post a message from the sender page/window, and listen for messages on the receiving page/window.

Post message

You can use postMessage to communicate between your app and sandboxed content. Here's a sample background script that posts a message to the sandboxed page it opens:

var myWin = null;

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create('sandboxed.html', {
    'bounds': {
      'width': 400,
      'height': 400
    }
  }, function(win) {
       myWin = win;
       myWin.contentWindow.postMessage('Just wanted to say hey.', '*');
     });
});

Generally speaking on the web, you want to specify the exact origin from where the message is sent. Chrome Apps have no access to the unique origin of sandboxed content, so you can only allowlist all origins as acceptable origins ('*'). On the receiving end, you generally want to check the origin; but since Chrome Apps content is contained, it isn't necessary. To find out more, see window.postMessage.

Listen for message and reply

Here's a sample message receiver that gets added to your sandboxed page:

var messageHandler = function(event) {
  console.log('Background script says hello.', event.data);

  // Send a reply
  event.source.postMessage(
      {'reply': 'Sandbox received: ' + event.data}, event.origin);
};

window.addEventListener('message', messageHandler);

For more details, check out the sandbox sample.