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 171333 8693eace6c33818c0a31f5db65e846ecdf64bae5
parent 171332 1840466020a228dd2e78065f890d889d39d373de
child 171334 9bdb998af6c17d749c80056e879915c2eeaaabf1
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersmyk, bz
bugs847518
milestone30.0a1
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