How to open a new window from a link in a webview in a chrome-packaged app

Alejandro Pedraza picture Alejandro Pedraza · Aug 25, 2013 · Viewed 10k times · Source

In order to show ads from a banner exchange, I'm loading a webview containing an iframe linking to the ads server. Through some javascript I can have the ad link target to be "_blank" so the ad would open in a separate browser window, otherwise it would open in the same small webview.

However it doesn't work and I get this in the console:

<webview>: A new window was blocked.

Any ideas how to do this?

Answer

Sergey Shevchenko picture Sergey Shevchenko · Aug 26, 2013

The thing is that by default a webview will not let the guest open arbitrary windows. Instead, it will issue a 'newwindow' event, which you can intercept and decide what to do with it. In a less scary form, this will look something like:

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create(
    'main.html',
    { 'width': 1000, 'height': 1000 },
    function(win) {
      win.contentWindow.onload = function() {
        var webview = win.contentWindow.document.querySelector('#my_webview');
        webview.addEventListener('newwindow', function(e) {
          e.preventDefault();
          // e.targetUrl contains the target URL of the original link click
          // or window.open() call: use it to open your own window to it.
          // Something to keep in mind: window.open() called from the
          // app's event page is currently (Nov 2013) handicapped and buggy
          // (e.g. it doesn't have access to local storage, including cookie
          // store). You can try to use it here and below, but be prepare that
          // it may sometimes produce bad results.
          chrome.app.window.create(e.targetUrl, ...);
        });
      };
    }
  );
});

I think this should work for you, given the conditions that you've described.

In a worse situation, certain websites may be opening new windows/tabs like this:

function open(href) {
    var w = window.open('', '_blank');
    w.opener = null;
    w.document.write(
      '<META HTTP-EQUIV="refresh" content="0; url=' + href + '">');
    w.document.close();
}

If such a website is wrapped in a webview, everything becomes more difficult: e.targetUrl in the 'newwindow' handler above will contain "about:blank", so without modification the code will open a blank window/tab. To intercept the subsequent redirect from the guest, the app will also have to use the chrome.webRequest API (the documentation appears to be for extensions only, but the API is already available for packaged apps in the stable channel, too):

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create(
    'main.html',
    { 'width': 2000, 'height': 1000 },
    function(win) {
      win.contentWindow.onload = function() {
        var webview = win.contentWindow.document.querySelector('#webview');
        webview.addEventListener('newwindow', function(e) {
          e.preventDefault();
          if (e.targetUrl !== 'about:blank') {
            // Easy case where the original link or window.open()
            // already contains a target URL.
            newWindow_openInTab(e.targetUrl);
          } else {
            // Harder case where the guest first opens a blank
            // window and then redirects it via a
            // 'META HTTP-EQUIV="refresh"'.
            newWindow_openInTabAndInterceptRedirect(e.window);
        });
      };
    }
  );
});

function newWindow_openInTab(url) {
  chrome.app.window.create(url, ...);
}

function newWindow_openInTabAndInterceptRedirect(newWindow) {
  // Create an invisible proxy webview to listen to redirect
  // requests from |newWindow| (the window that the guest is
  // trying to open). NOTE: The proxy webview currently has to
  // live somewhere in the DOM, so we append it to the body.
  // This requirement is in the process of being eliminated.
  var proxyWebview = document.createElement('webview');
  document.body.appendChild(proxyWebview);

  // Listen to onBeforeRequest event (chrome.webRequest API)
  // on proxyWebview in order to intercept newWindow's redirects.
  var onBeforeRequestListener = function(e) {
    // Only consider top-level non-blank redirects.
    if (e.type === "main_frame" && e.url !== 'about:blank') {
      chrome.app.window.create(e.url, ...);
      // Don't need proxyWebview anymore.
      document.body.removeChild(proxyWebview);
      // Handled this redirect: cancel further processing.
      return { cancel: true };
    } else {
      // Ignored this redirect: proceed with default processing.
      return { cancel: false };
    }
  };
  proxyWebview.onBeforeRequest.addListener(
    onBeforeRequestListener,
    { urls: [ "*://*/*" ] },
    [ 'blocking' ]
  );

  // Attach |newWindow| to proxyWebview. From the original
  // webview guest's point of view, the window is now opened
  // and ready to be redirected: when it does so, the redirect
  // will be intercepted by |onBeforeRequestListener|.
  newWindow.attach(proxyWebview);
}