Bug 847518 - window.open(url, "_blank") should open in default browser. r=myk, r=bz
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Thu, 27 Feb 2014 11:56:48 -0500
changeset 189255 8693eace6c33818c0a31f5db65e846ecdf64bae5
parent 189254 1840466020a228dd2e78065f890d889d39d373de
child 189256 9bdb998af6c17d749c80056e879915c2eeaaabf1
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk, bz
bugs847518
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 847518 - window.open(url, "_blank") should open in default browser. r=myk, r=bz
dom/browser-element/BrowserElementParent.cpp
webapprt/Startup.jsm
webapprt/WebappRT.jsm
webapprt/content/webapp.js
webapprt/test/chrome/browser_window-open-blank.js
webapprt/test/chrome/browser_window-open-self.js
webapprt/test/chrome/browser_window-open.js
webapprt/test/chrome/webapprt.ini
webapprt/test/chrome/window-open-blank.html
webapprt/test/chrome/window-open-blank.webapp
webapprt/test/chrome/window-open-blank.webapp^headers^
webapprt/test/chrome/window-open-self.html
webapprt/test/chrome/window-open-self.webapp
webapprt/test/chrome/window-open-self.webapp^headers^
webapprt/test/chrome/window-open.html
webapprt/test/chrome/window-open.webapp
webapprt/test/chrome/window-open.webapp^headers^
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -234,22 +234,21 @@ BrowserElementParent::OpenWindowInProces
   // out-of-process, so we couldn't touch it if we tried.)
   //
   // GetScriptableTop gets us the <iframe mozbrowser>'s window; we'll use its
   // frame element, rather than aOpenerWindow's frame element, as our "opener
   // frame element" below.
   nsCOMPtr<nsIDOMWindow> topWindow;
   aOpenerWindow->GetScriptableTop(getter_AddRefs(topWindow));
 
-  nsCOMPtr<nsIDOMElement> openerFrameDOMElement;
-  topWindow->GetFrameElement(getter_AddRefs(openerFrameDOMElement));
-  NS_ENSURE_TRUE(openerFrameDOMElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(topWindow);
 
-  nsCOMPtr<Element> openerFrameElement =
-    do_QueryInterface(openerFrameDOMElement);
+  nsCOMPtr<Element> openerFrameElement = win->GetFrameElementInternal();
+  NS_ENSURE_TRUE(openerFrameElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
+
 
   nsRefPtr<HTMLIFrameElement> popupFrameElement =
     CreateIframe(openerFrameElement, aName, /* aRemote = */ false);
   NS_ENSURE_TRUE(popupFrameElement, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsAutoCString spec;
   if (aURI) {
     aURI->GetSpec(spec);
--- a/webapprt/Startup.jsm
+++ b/webapprt/Startup.jsm
@@ -82,31 +82,27 @@ function createBrandingFiles() {
 this.startup = function(window) {
   return Task.spawn(function () {
     // Observe XUL window loading.
     // For tests, it could be already loaded.
     let deferredWindowLoad = Promise.defer();
     if (window.document && window.document.getElementById("content")) {
       deferredWindowLoad.resolve();
     } else {
-      window.addEventListener("load", function onLoad() {
-        window.removeEventListener("load", onLoad, false);
+      window.addEventListener("DOMContentLoaded", function onLoad() {
+        window.removeEventListener("DOMContentLoaded", onLoad, false);
         deferredWindowLoad.resolve();
       });
     }
 
     // Wait for webapps registry loading.
     yield DOMApplicationRegistry.registryStarted;
 
-    // Install/update permissions and get the appID from the webapps registry.
-    let appID = Ci.nsIScriptSecurityManager.NO_APP_ID;
     let manifestURL = WebappRT.config.app.manifestURL;
     if (manifestURL) {
-      appID = DOMApplicationRegistry.getAppLocalIdByManifestURL(manifestURL);
-
       // On firstrun, set permissions to their default values.
       // When the webapp runtime is updated, update the permissions.
       // TODO: Update the permissions when the application is updated.
       if (isFirstRunOrUpdate(Services.prefs)) {
         PermissionsInstaller.installPermissions(WebappRT.config.app, true);
         yield createBrandingFiles();
       }
     }
@@ -124,12 +120,20 @@ this.startup = function(window) {
 
     // Wait for XUL window loading
     yield deferredWindowLoad.promise;
 
     // Get the <browser> element in the webapp.xul window.
     let appBrowser = window.document.getElementById("content");
 
     // Set the principal to the correct appID and launch the application.
-    appBrowser.docShell.setIsApp(appID);
+    appBrowser.docShell.setIsApp(WebappRT.appID);
     appBrowser.setAttribute("src", WebappRT.launchURI);
+
+    if (WebappRT.config.app.manifest.fullscreen) {
+      appBrowser.addEventListener("load", function onLoad() {
+        appBrowser.removeEventListener("load", onLoad, true);
+        appBrowser.contentDocument.
+          documentElement.mozRequestFullScreen();
+      }, true);
+    }
   }).then(null, Cu.reportError.bind(Cu));
 }
--- a/webapprt/WebappRT.jsm
+++ b/webapprt/WebappRT.jsm
@@ -7,20 +7,22 @@ this.EXPORTED_SYMBOLS = ["WebappRT"];
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "FileUtils", function() {
-  Cu.import("resource://gre/modules/FileUtils.jsm");
-  return FileUtils;
-});
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+  "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+                                  "@mozilla.org/AppsService;1",
+                                  "nsIAppsService");
 
 this.WebappRT = {
   _config: null,
 
   get config() {
     if (this._config)
       return this._config;
 
@@ -49,9 +51,18 @@ this.WebappRT = {
     let manifest = this.localeManifest;
     return manifest.fullLaunchPath();
   },
 
   get localeManifest() {
     return new ManifestHelper(this.config.app.manifest,
                               this.config.app.origin);
   },
+
+  get appID() {
+    let manifestURL = WebappRT.config.app.manifestURL;
+    if (!manifestURL) {
+      return Ci.nsIScriptSecurityManager.NO_APP_ID;
+    }
+
+    return appsService.getAppLocalIdByManifestURL(manifestURL);
+  },
 };
--- a/webapprt/content/webapp.js
+++ b/webapprt/content/webapp.js
@@ -14,16 +14,21 @@ XPCOMUtils.defineLazyGetter(this, "gAppB
                             function() document.getElementById("content"));
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                    "@mozilla.org/toolkit/crash-reporter;1",
                                    "nsICrashReporter");
 #endif
 
+function isSameOrigin(url) {
+  let origin = Services.io.newURI(url, null, null).prePath;
+  return (origin == WebappRT.config.app.origin);
+}
+
 let progressListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
   onLocationChange: function onLocationChange(progress, request, location,
                                               flags) {
 
     // Close tooltip (code adapted from /browser/base/content/browser.js)
     let pageTooltip = document.getElementById("contentAreaTooltip");
@@ -45,120 +50,100 @@ let progressListener = {
       }
     }
 
     // Set the title of the window to the name of the webapp, adding the origin
     // of the page being loaded if it's from a different origin than the app
     // (per security bug 741955, which specifies that other-origin pages loaded
     // in runtime windows must be identified in chrome).
     let title = WebappRT.config.app.manifest.name;
-    let origin = location.prePath;
-    if (origin != WebappRT.config.app.origin) {
-      title = origin + " - " + title;
-
-      // We should exit fullscreen mode if the user navigates off the app
-      // origin.
-      document.mozCancelFullScreen();
+    if (!isSameOrigin(location.spec)) {
+      title = location.prePath + " - " + title;
     }
     document.documentElement.setAttribute("title", title);
   },
 
   onStateChange: function onStateChange(aProgress, aRequest, aFlags, aStatus) {
     if (aRequest instanceof Ci.nsIChannel &&
         aFlags & Ci.nsIWebProgressListener.STATE_START &&
         aFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) {
       updateCrashReportURL(aRequest.URI);
     }
   }
 };
 
+function onOpenWindow(event) {
+  let name = event.detail.name;
+
+  if (name == "_blank") {
+    let uri = Services.io.newURI(event.detail.url, null, null);
+
+    // Direct the URL to the browser.
+    Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+    getService(Ci.nsIExternalProtocolService).
+    getProtocolHandlerInfo(uri.scheme).
+    launchWithURI(uri);
+  } else {
+    let win = window.openDialog("chrome://webapprt/content/webapp.xul",
+                                name,
+                                "chrome,dialog=no,resizable," + event.detail.features);
+
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad, false);
+
+#ifndef XP_WIN
+#ifndef XP_MACOSX
+      if (isSameOrigin(event.detail.url)) {
+        // On non-Windows platforms, we open new windows in fullscreen mode
+        // if the opener window is in fullscreen mode, so we hide the menubar;
+        // but on Mac we don't need to hide the menubar.
+        if (document.mozFullScreenElement) {
+          win.document.getElementById("main-menubar").style.display = "none";
+        }
+      }
+#endif
+#endif
+
+      win.document.getElementById("content").docShell.setIsApp(WebappRT.appID);
+      win.document.getElementById("content").setAttribute("src", event.detail.url);
+    }, false);
+  }
+}
+
 function onLoad() {
   window.removeEventListener("load", onLoad, false);
 
   gAppBrowser.addProgressListener(progressListener,
                                   Ci.nsIWebProgress.NOTIFY_LOCATION |
                                   Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 
   updateMenuItems();
 
-  // Listen for clicks to redirect <a target="_blank"> to the browser.
-  // This doesn't capture clicks so content can capture them itself and do
-  // something different if it doesn't want the default behavior.
-  gAppBrowser.addEventListener("click", onContentClick, false, true);
-
-  if (WebappRT.config.app.manifest.fullscreen) {
-    enterFullScreen();
-  }
+  gAppBrowser.addEventListener("mozbrowseropenwindow", onOpenWindow);
 }
 window.addEventListener("load", onLoad, false);
 
 function onUnload() {
   gAppBrowser.removeProgressListener(progressListener);
+  gAppBrowser.removeEventListener("mozbrowseropenwindow", onOpenWindow);
 }
 window.addEventListener("unload", onUnload, false);
 
 // Fullscreen handling.
 
-function enterFullScreen() {
-  // We call mozRequestFullScreen here so that the app window goes in
-  // fullscreen mode as soon as it's loaded and not after the <browser>
-  // content is loaded.
-  gAppBrowser.mozRequestFullScreen();
-
-  // We need to call mozRequestFullScreen on the document element too,
-  // otherwise the app isn't aware of the fullscreen status.
-  gAppBrowser.addEventListener("load", function onLoad() {
-    gAppBrowser.removeEventListener("load", onLoad, true);
-    gAppBrowser.contentDocument.
-      documentElement.wrappedJSObject.mozRequestFullScreen();
-  }, true);
-}
-
 #ifndef XP_MACOSX
 document.addEventListener('mozfullscreenchange', function() {
   if (document.mozFullScreenElement) {
     document.getElementById("main-menubar").style.display = "none";
   } else {
     document.getElementById("main-menubar").style.display = "";
   }
 }, false);
 #endif
 
-/**
- * Direct a click on <a target="_blank"> to the user's default browser.
- *
- * In the long run, it might be cleaner to move this to an extension of
- * nsIWebBrowserChrome3::onBeforeLinkTraversal.
- *
- * @param {DOMEvent} event the DOM event
- **/
-function onContentClick(event) {
-  let target = event.target;
-
-  if (!(target instanceof HTMLAnchorElement) ||
-      target.getAttribute("target") != "_blank") {
-    return;
-  }
-
-  let uri = Services.io.newURI(target.href,
-                               target.ownerDocument.characterSet,
-                               null);
-
-  // Direct the URL to the browser.
-  Cc["@mozilla.org/uriloader/external-protocol-service;1"].
-    getService(Ci.nsIExternalProtocolService).
-    getProtocolHandlerInfo(uri.scheme).
-    launchWithURI(uri);
-
-  // Prevent the runtime from loading the URL.  We do this after directing it
-  // to the browser to give the runtime a shot at handling the URL if we fail
-  // to direct it to the browser for some reason.
-  event.preventDefault();
-}
-
 // On Mac, we dynamically create the label for the Quit menuitem, using
 // a string property to inject the name of the webapp into it.
 function updateMenuItems() {
 #ifdef XP_MACOSX
   let installRecord = WebappRT.config.app;
   let manifest = WebappRT.config.app.manifest;
   let bundle =
     Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/browser_window-open-blank.js
@@ -0,0 +1,75 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let HandlerService = {
+  classID: Components.ID("{b4ed9fab-fd39-435a-8e3e-edc3e689e72e}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory,
+                                         Ci.nsIExternalProtocolService]),
+
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+
+    return this.QueryInterface(aIID);
+  },
+
+  init: function() {
+    Components.manager.nsIComponentRegistrar.registerFactory(this.classID,
+      "Test Protocol Handler Service",
+      "@mozilla.org/uriloader/external-protocol-service;1",
+      this);
+  },
+
+  getProtocolHandlerInfo: function(aProtocolScheme) {
+    let handlerInfoObj = {
+      launchWithURI: function(aURI) {
+        is(aURI.spec,
+           "http://test/webapprtChrome/webapprt/test/chrome/sample.html",
+           "The app tried to open the link in the default browser");
+
+        finish();
+      }
+    };
+
+    return handlerInfoObj;
+  }
+};
+
+HandlerService.init();
+
+function test() {
+  waitForExplicitFinish();
+
+  let progressListener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                           Ci.nsISupportsWeakReference]),
+    onLocationChange: function(progress, request, location, flags) {
+      ok(false, "Location changed");
+      finish();
+    }
+  };
+
+  let winObserver = function(win, topic) {
+    if (topic == "domwindowopened") {
+      win.addEventListener("load", function onLoadWindow() {
+        win.removeEventListener("load", onLoadWindow, false);
+
+        if (win.location == "chrome://webapprt/content/webapp.xul") {
+          ok(false, "New app window opened");
+          finish();
+        }
+      }, false);
+    }
+  }
+
+  loadWebapp("window-open-blank.webapp", undefined, function() {
+    gAppBrowser.addProgressListener(progressListener,
+                                    Ci.nsIWebProgress.NOTIFY_LOCATION);
+  });
+
+  registerCleanupFunction(function() {
+    Services.ww.unregisterNotification(winObserver);
+    gAppBrowser.removeProgressListener(progressListener);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/browser_window-open-self.js
@@ -0,0 +1,55 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://webapprt/modules/WebappRT.jsm");
+let { DOMApplicationRegistry } =
+  Cu.import("resource://gre/modules/Webapps.jsm", {});
+
+function test() {
+  waitForExplicitFinish();
+
+  let appID = Ci.nsIScriptSecurityManager.NO_APP_ID;
+
+  let progressListener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                           Ci.nsISupportsWeakReference]),
+    onLocationChange: function(progress, request, location, flags) {
+      gAppBrowser.addEventListener("load", function onLoad() {
+        gAppBrowser.removeEventListener("load", onLoad, true);
+
+        is(DOMApplicationRegistry.getAppLocalIdByManifestURL(WebappRT.config.app.manifestURL),
+           appID,
+           "Principal app ID hasn't changed");
+
+        finish();
+      }, true);
+    }
+  };
+
+  let winObserver = function(win, topic) {
+    if (topic == "domwindowopened") {
+      win.addEventListener("load", function onLoadWindow() {
+        win.removeEventListener("load", onLoadWindow, false);
+
+        if (win.location == "chrome://webapprt/content/webapp.xul") {
+          ok(false, "New app window opened");
+          finish();
+        }
+      }, false);
+    }
+  }
+
+  loadWebapp("window-open-self.webapp", undefined, function() {
+    appID = gAppBrowser.contentDocument.defaultView.document.nodePrincipal.appId;
+
+    is(DOMApplicationRegistry.getAppLocalIdByManifestURL(WebappRT.config.app.manifestURL),
+       appID,
+       "Principal app ID correct");
+
+    gAppBrowser.addProgressListener(progressListener,
+                                    Ci.nsIWebProgress.NOTIFY_LOCATION);
+  });
+
+  registerCleanupFunction(function() {
+    Services.ww.unregisterNotification(winObserver);
+    gAppBrowser.removeProgressListener(progressListener);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/browser_window-open.js
@@ -0,0 +1,64 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://webapprt/modules/WebappRT.jsm");
+let { DOMApplicationRegistry } =
+  Cu.import("resource://gre/modules/Webapps.jsm", {});
+
+function test() {
+  waitForExplicitFinish();
+
+  let appID = Ci.nsIScriptSecurityManager.NO_APP_ID;
+
+  let progressListener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                           Ci.nsISupportsWeakReference]),
+    onLocationChange: function(progress, request, location, flags) {
+      ok(false, "Content redirected")
+      finish();
+    }
+  };
+
+  let winObserver = function(win, topic) {
+    if (topic == "domwindowopened") {
+      win.addEventListener("load", function onLoadWindow() {
+        win.removeEventListener("load", onLoadWindow, false);
+
+        if (win.location == "chrome://webapprt/content/webapp.xul") {
+          let winAppBrowser = win.document.getElementById("content");
+          winAppBrowser.addEventListener("load", function onLoadBrowser() {
+            winAppBrowser.removeEventListener("load", onLoadBrowser, true);
+
+            is(winAppBrowser.getAttribute("src"),
+               "http://test/webapprtChrome/webapprt/test/chrome/sample.html",
+               "New window browser has correct src");
+
+            is(winAppBrowser.contentDocument.defaultView.document.nodePrincipal.appId,
+               appID,
+               "New window principal app ID correct");
+
+            win.close();
+
+            finish();
+          }, true);
+        }
+      }, false);
+    }
+  }
+
+  Services.ww.registerNotification(winObserver);
+
+  loadWebapp("window-open.webapp", undefined, function() {
+    appID = gAppBrowser.contentDocument.defaultView.document.nodePrincipal.appId;
+
+    is(DOMApplicationRegistry.getAppLocalIdByManifestURL(WebappRT.config.app.manifestURL),
+       appID,
+       "Principal app ID correct");
+
+    gAppBrowser.addProgressListener(progressListener,
+                                    Ci.nsIWebProgress.NOTIFY_LOCATION);
+  });
+
+  registerCleanupFunction(function() {
+    Services.ww.unregisterNotification(winObserver);
+    gAppBrowser.removeProgressListener(progressListener);
+  });
+}
--- a/webapprt/test/chrome/webapprt.ini
+++ b/webapprt/test/chrome/webapprt.ini
@@ -24,20 +24,32 @@ support-files =
   debugger.html
   mozpay.webapp
   mozpay.webapp^headers^
   mozpay.html
   mozpay-success.html
   getUserMedia.webapp
   getUserMedia.webapp^headers^
   getUserMedia.html
+  window-open-self.webapp
+  window-open-self.webapp^headers^
+  window-open-self.html
+  window-open.webapp
+  window-open.webapp^headers^
+  window-open.html
+  window-open-blank.webapp
+  window-open-blank.webapp^headers^
+  window-open-blank.html
 
 
 [browser_sample.js]
 [browser_window-title.js]
 [browser_webperm.js]
 [browser_noperm.js]
 [browser_geolocation-prompt-perm.js]
 [browser_geolocation-prompt-noperm.js]
 [browser_debugger.js]
 [browser_mozpay.js]
 [browser_getUserMedia.js]
 skip-if = true
+[browser_window-open-self.js]
+[browser_window-open.js]
+[browser_window-open-blank.js]
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-blank.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Window Open Blank Test App</title>
+    <meta charset="utf-8">
+    <script>
+      function onLoad() {
+        window.open("sample.html", "_blank");
+      }
+    </script>
+  </head>
+  <body onload="onLoad()">
+    <h1>Window Open Blank Test App</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-blank.webapp
@@ -0,0 +1,5 @@
+{
+  "name": "Window Open Blank Test App",
+  "description": "an app for testing window.open(url, '_blank')",
+  "launch_path": "/webapprtChrome/webapprt/test/chrome/window-open-blank.html"
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-blank.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-self.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Window Open Self Test App</title>
+    <meta charset="utf-8">
+    <script>
+      function onLoad() {
+        window.open("sample.html", "_self");
+      }
+    </script>
+  </head>
+  <body onload="onLoad()">
+    <h1>Window Open Self Test App</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-self.webapp
@@ -0,0 +1,5 @@
+{
+  "name": "Window Open Self Test App",
+  "description": "an app for testing window.open(url, '_self')",
+  "launch_path": "/webapprtChrome/webapprt/test/chrome/window-open-self.html"
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open-self.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Window Open Test App</title>
+    <meta charset="utf-8">
+    <script>
+      function onLoad() {
+        window.open("sample.html");
+      }
+    </script>
+  </head>
+  <body onload="onLoad()">
+    <h1>Window Open Test App</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open.webapp
@@ -0,0 +1,5 @@
+{
+  "name": "Window Open Test App",
+  "description": "an app for testing window.open",
+  "launch_path": "/webapprtChrome/webapprt/test/chrome/window-open.html"
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/window-open.webapp^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/x-web-app-manifest+json